Use when adding a new benchmark image to the fleet — a Dockerized task plus verifier that evaluates an agent. Walks the directory layout, the five authored files, the two version knobs, required labels, and the three deployment surfaces. For packaging the AI system that runs *inside* a benchmark (install + entrypoint, LLM access), use add-agent instead.
Resources
1Install
npx skillscat add exgentic/eval-containers/add-benchmark Install via the SkillsCat registry.
Add a benchmark
A benchmark image contains everything needed to evaluate an agent on a task:
the task data, the test logic, and the entrypoints. This skill produces a new
benchmark that satisfies .agents/benchmarks/RULES.md. Read that RULES.md
before starting. A copyable starting point lives at
`assets/TEMPLATE.md` — the steps below tell you how to use
it and why each piece exists.
Before you start: pick the benchmark shape
Decide which of the three task models fits, because it changes how EVAL_TASK_ID
flows and how the image is built:
- Shared-env — one image, many tasks; all tasks share the same environment
and only the instruction differs (AIME, SimpleQA, GPQA).EVAL_TASK_IDis the
only required runtime input and selects the task at run time. - Per-task, prebuilt base — one image per task and a per-task upstream base
image already exists;EVAL_TASK_IDis a build-timeARGand a singleDockerfilebakes exactly one task (seebenchmarks/swe-bench/Dockerfile). - Per-task, built-from-source — one image per task, but no per-task
upstream image exists and each task ships its ownenvironment/Dockerfile
(heterogeneous bases + setup — the Harbor task format). Ship an executablebuild.sh <image> <task-id>that (1)
builds the task's ownenvironment/Dockerfileinto a task-env image, then (2)
overlays the eval pipeline via aFROM ${TASK_BASE}Dockerfile. Do NOT
improvise a shared base that clones the whole upstream repo — that is the
anti-pattern rule 24g exists to prevent. Copybenchmarks/terminal-bench/
(build.sh+ overlayDockerfile+ fetch-the-goldsolution.sh) and
substitute the repo + ref (.agents/benchmarks/RULES.md:24g). - Native harness — the upstream benchmark drives the model through its own
scaffold, so there is no agent to install. DeclareLABEL eval.benchmark.agent="native"and ship the harness at/harness.sh
(run as root withEVAL_TASK_ID; it drives the model through$OPENAI_BASE_URLand writes/logs/verifier/reward.txt); the benchmark
pairs only with thenativeagent. Never replace the launcher. Copybenchmarks/walle/(.agents/benchmarks/RULES.md:12a).
Either docker run (single-image) or docker compose up (multi-service) MUST
work with no Dock install and no internet, resolving task content, expected
answer, and any attached files from EVAL_TASK_ID alone
(.agents/benchmarks/RULES.md:1, .agents/benchmarks/RULES.md:2).
Steps
Create the benchmark directory. Make
benchmarks/<name>/and copy
`assets/TEMPLATE.md` as your scaffold. Every benchmark
ships three authored files:Dockerfile(builds the base image with
tasks + verifier), the per-benchmark deploy filecompose.yaml, andREADME.md. The single-container surface (the standalone bundle) renders
from the one genericcore/standalone.Dockerfile, and the k8s surface from the
shared chart selected with--set benchmark=<name>— neither needs a
per-benchmark file (author abenchmarks/_chart/presets/<name>.yamlonly for
bespoke topology). Why: the directory is the unit a CI test walks; a missingcompose.yamlmakes the benchmark incomplete
(.agents/benchmarks/RULES.md:24,.agents/benchmarks/RULES.md:29).Write the
Dockerfileto materialize tasks as flat files. Fetch the
dataset and write each task as/tasks/<id>/problem.txt+answer.txt
(+id.txt), thenchmod -R 600 /tasksso the agent UID cannot read them.
Why: tasks SHOULD be plain files, no databases or archives
(.agents/benchmarks/RULES.md:16); shared-env tasks MUST be addressable by
sequential integers with the upstream id preserved inid.txt
(.agents/benchmarks/RULES.md:15); and the agent MUST NOT be able to read
anything the test phase uses (.agents/benchmarks/RULES.md:5,.agents/benchmarks/RULES.md:28). If a task has attached files, copy them
into/app/(agent-readable) in the entrypoint — never loosen/tasks/
permissions (.agents/benchmarks/RULES.md:17). For a built-from-source
benchmark you do not materialize tasks here — the task's ownenvironment/Dockerfileprovides them and the overlayDockerfileadds only
the agent-readableinstruction.mdplus a root-only copy of the tests
(chmod 700, root-owned). Never bake the upstream repo into the image: it
carries every task's gold solution and tests, which the agent could then read
(.agents/benchmarks/RULES.md:5,.agents/benchmarks/RULES.md:9).Pin the dataset version (knob 1 of 2 — build-time default). Set
ARG DATA_REVISION=<sha>(or equivalent) as the default and record it in theeval.benchmark.data_revisionlabel. Why: the image MUST produce identical
task content on every build when no env vars are set
(.agents/benchmarks/RULES.md:3). Get the sha withcurl -s https://huggingface.co/api/datasets/<dataset> | jq .sha.Honor the runtime version override (knob 2 of 2 —
EVAL_BENCHMARK_VERSION).
The entrypoint MUST readEVAL_BENCHMARK_VERSIONand, when set, fetch and
materialize that dataset revision into/tasks/in place of the default,
writing the resolved revision to/output/task/version.jsonbefore the agent
runs. When unset, the build-time default applies unchanged. Why: this is
how a caller evaluates against a specific dataset revision without rebuilding
(.agents/benchmarks/RULES.md:4). NoteEVAL_BENCHMARK_TAGselects the
image tag to pull — that is Docker's job, not the entrypoint's.Reuse the shared entrypoint and verifier; do not reimplement them.
COPY
incore/entrypoint'sdock-entrypoint.shand, for exact-match scoring,core/test-exact-match. Your thin/entrypoint.shonly assemblesTASK
andEXPECTED_ANSWERfrom/tasks/$TASK_ID/, thenexecs the shared
entrypoint. Why: execution MUST follow the three-phase flow agent → test →
result via the shared entrypoint, and benchmarks MUST NOT bypass it or
reimplement shared logic (.agents/benchmarks/RULES.md:12,.agents/benchmarks/RULES.md:22). Do NOT bake agent-specific tools
(browsers, SDKs) into the benchmark image — the agent'sinstall.shbrings
its own (.agents/benchmarks/RULES.md:23).Keep the agent unprivileged, credential-free, and offline by default. The
agent process runs as a non-root user (the test phase MAY be root), cannot
read the LLM API key, and has no outbound internet unless the benchmark
explicitly needs it. Why:.agents/benchmarks/RULES.md:13(agent as
non-root),.agents/benchmarks/RULES.md:8(no agent access to credentials),.agents/benchmarks/RULES.md:9(no agent internet by default). Use the
simplest isolation that works — file permissions over extra containers
(.agents/benchmarks/RULES.md:6), and standard Docker features only
(.agents/benchmarks/RULES.md:11). OnlyTASK,EVAL_TASK_ID,OPENAI_BASE_URL, andANTHROPIC_BASE_URLSHOULD reach the agent; benchmark
internals MUST NOT leak (.agents/benchmarks/RULES.md:7). Agent execution
MUST be bounded byEVAL_TIMEOUT, which the shared entrypoint enforces — the
benchmark does not implement its own timeout (.agents/benchmarks/RULES.md:14).Wire the scorer to the reward contract. The test script MUST write a
float in[0.0, 1.0]to/logs/verifier/reward.txt(or-1for externally
graded benchmarks). Use the simplest correct scorer — exact match when
possible, code execution for programming, LLM-as-judge only when nothing
simpler works. Why:.agents/benchmarks/RULES.md:18(reward contract),.agents/benchmarks/RULES.md:19(simplest correct scorer). If grading needs
an outside service, still collect the agent's output, write-1, and do NOT
approximate the external grader (.agents/benchmarks/RULES.md:20). For a
custom scorer, replace thetest-exact-matchCOPY with your own/grade.sh. Then add the oracle that validates this scorer — abenchmarks/<name>/solution.sh, mounted read-only at oracle run time and neverCOPY'd into the image — that derives the gold (runs the upstream reference
solution, or fetches the dataset's canonical answer) and writes it where the
agent would; never hardcode a literal answer or copy the test's expected-output
file — a derived oracle stays valid as the data revision moves and proves the
task is solvable (.agents/benchmarks/RULES.md:20a). For a built-from-source benchmark thesolution.shfetches the per-task upstream gold fresh and MUST read the task
name from a bakedENV, notEVAL_TASK_ID(which the oracle overrides to0)
(.agents/benchmarks/RULES.md:24i). Validate both halves witheval-containers oracle <name> [--task-id <t>] --local: a correct gold MUST
score1.0and a no-op MUST score< 1.0.Set the required labels. Every benchmark image MUST carry
eval.type,eval.benchmark.name,eval.benchmark.description,eval.benchmark.tasks,eval.benchmark.env, andeval.benchmark.internet
(.agents/benchmarks/RULES.md:21). If theFROMpoints at a third-party
registry outside Dock's control, also declareeval.benchmark.upstream_base="<full image ref>"so the external dependency
is visible to audit tools (.agents/benchmarks/RULES.md:21b). Do NOT addeval.benchmark.released="true"yet — that label is earned at the release
gate (step 11).Author the three deployment surfaces with one shared env contract. The
surfaces MUST share the same env contract (EVAL_MODEL,EVAL_TASK_ID,
upstream credentials) and produce byte-equivalenttask/result.jsonfor the
same inputs (.agents/benchmarks/RULES.md:24):- single — no per-benchmark file. The single-container standalone bundle
renders from the one genericcore/standalone.Dockerfile(FROM eval-base+
the in-process gateway/otelcol/process-compose); the lean base is theeval-basebuild context (baketarget:eval/--build-context), and
per-task resolution lives in that context ref — so there is no per-benchmark
stub and noARG EVAL_TASK_IDindirection. Record the lean base's canonical build
args (BENCHMARK_IMAGE,AGENT_IMAGE,AGENT_VERSION) in theREADME.mdso
CI can rebuild viacore/combination.Dockerfile
(.agents/benchmarks/RULES.md:24a). compose.yaml(compose) — pull incompose/services.yamlviainclude:and only declare overrides; do NOT inline a service, network, or
volume that already exists there
(.agents/benchmarks/RULES.md:24b,.agents/benchmarks/RULES.md:25); a
native harness extendscompose/runner-native.yamlinstead.- k8s — the shared chart
benchmarks/_chart, selected with--set benchmark=<name>. A standard benchmark needs nothing here. One with
bespoke topology addsbenchmarks/_chart/presets/<name>.yamlto compose its
sidecars/Deployments/Services through the chart's hooks
(initContainers,runnerExtraEnv,extraManifests, …) — do NOT
redeclare the otelcol/gateway/runner Pod, and do NOT setrunnerArgs
(.agents/benchmarks/RULES.md:24b,.agents/benchmarks/RULES.md:25); a
native harness's preset pinsagent: native.
For a simple shared-env benchmark, copy
benchmarks/aime/and substitute the
name (no preset needed). Changes to the compose base (compose/services.yaml)
or the chart (benchmarks/_chart) MUST be reflected in the other in the same
commit.- single — no per-benchmark file. The single-container standalone bundle
Parameterize the task and enforce limits/isolation in every surface.
- Task: shared-env
compose.yamlMUST readTASK_ID: ${TASK_ID:-0}(never
hardcode a literal); the k8s task comes fromhelm --set task=(default
0), so a preset MUST NOT hardcode a task. Per-task benchmarks bakeEVAL_TASK_IDvia buildARGand the artifacts inherit it
(.agents/benchmarks/RULES.md:24c). - Resource limits: declare CPU and memory in BOTH
compose.yaml
(deploy.resources.limitson the runner) and the k8s runner — the chart
default, overridden per benchmark via its preset'sresources:—
matching modulo k8s unit syntax
(.agents/benchmarks/RULES.md:10,.agents/benchmarks/RULES.md:24e). - Network: enforce no-agent-internet per surface —
internal: truein
compose,iptables --uid-ownerin the standalone bundle (single mode),
credential isolation (rule 8) in k8s. If the benchmark requires internet, declareeval.benchmark.internet=trueand remove the isolation in every surface;
asymmetry is forbidden (.agents/benchmarks/RULES.md:24d).
- Task: shared-env
Add the tests, then earn the release label. Provide a build test
(Dockerfile builds + correcteval.*labels,.agents/benchmarks/RULES.md:26), a compose-config test
(.agents/benchmarks/RULES.md:27), and at least one end-to-end replay test
with a recorded fixture that verifiesresult.jsonschema
(.agents/benchmarks/RULES.md:28). The triple-mode CI gate
(.agents/benchmarks/RULES.md:29) checks the per-benchmark files exist and
parse, the chart renders for the benchmark, and all surfaces share one env
contract. Once the benchmark is proven end-to-end against
at least one agent with a replay fixture attests/fixtures/<benchmark>-<task>-<agent>.traces.jsonl, addLABEL eval.benchmark.released="true"(.agents/benchmarks/RULES.md:21a).
Rules served
.agents/benchmarks/RULES.md:1-29— the
benchmark contract this skill produces (self-contained, version knobs,
isolation, three-phase execution, task format, scoring, labels, the three
deployment surfaces, and testing).
References
- `assets/TEMPLATE.md` — copyable scaffold with the
Dockerfile, the per-benchmark deploy files, the blanks-to-fill table, and gotchas. .agents/benchmarks/RULES.md— the outcomes every benchmark MUST satisfy.benchmarks/aime/— canonical simple shared-env reference.benchmarks/terminal-bench/— canonical per-task built-from-source reference
(build.sh+FROM ${TASK_BASE}overlay + fetch-the-goldsolution.sh).benchmarks/_chart/— the shared k8s Helm chart (the canonical Pod, once).