cbrun is a Harbor-independent benchmark runner. It evaluates an autonomous
coding agent by giving it only the public task specification and letting it build
the project from scratch, then scoring the result against hidden acceptance
tests.
The agent's job is to write the implementation. The test code is a hidden benchmark asset, never an agent output.
- Input the agent sees: the PRD (
/environment/prd/Full_PRD.md) and the Interface Contract (/environment/Interface_Contract.md), both also embedded in the instruction prompt. The agent's workspace/appstarts empty. - Output the agent produces: the implementation in
/app, importable / installable from the workspace root. - Scoring: after the agent finishes, the runner injects the hidden acceptance test suite and runs it; the reward is binary (1 = all pass, 0 = otherwise).
The guiding invariant is information completeness: the provided environment
(empty /app + PRD + Interface Contract) must be theoretically sufficient for an
ideal agent to pass the hidden tests. If a case's test_command depends on build
scaffolding the spec never declares, that is a case spec defect to fix in the case assets — never patched in the runner.
Fairness: visible checks vs hidden gates
- Visible / dev checks: the agent may write and run its own tests and any public sample checks as many times as it wants and read their full output. A real develop → test → debug loop is expected.
- Hidden gates: the hidden acceptance tests are physically absent from the
solve container. cbrun derives a per-case
:agentimage from the published:deliverableimage and removes/tests/finalin a new image layer (the tests are extracted to a host-side cache first). They are re-injected only for the judge phase, and judge output is not fed back to the agent.
There is no limit on steps, turns, edits or cost. The only hard ceilings are wall-clock budgets:
max_agent_timeout_sec— solve phase. Final-stage cases use a uniform 2h (7200s).max_test_timeout_sec— judge phase. Default 10min (600s), per-case overridable.--timeout-multiplierscales both wall clocks (mirrors Terminal-Bench'sglobal_timeout_multiplier).
A conservative stall watchdog stops a wedged CLI without misjudging a long compile or long model turn as a hang.
The judge always runs against the final workspace state regardless of how the solve ended, so the reward never depends on an agent self-report. The solve terminal status is recorded as one of:
completed— the CLI process exited 0 on its own (the implicit "submit").timeout— the wall clock was hit.error— stall-killed or a non-zero CLI exit (including agent setup failure).
Three backends ship as built-in AgentSpec records:
| Backend | CLI | Notes |
|---|---|---|
codex |
@openai/codex |
Writes ~/.codex/auth.json + config.toml from OPENAI_* env before solve. Default model_prefix=keep for OpenAI-compatible gateways. |
opencode |
opencode-ai |
Forwards provider env based on provider/model id. |
claude-code |
@anthropic-ai/claude-code |
Runs as non-root user cbagent with bypassPermissions (Claude Code rejects root). Judge still runs as root. |
Auth/provider env is selected centrally and forwarded into the container; only
present keys are injected (key names are recorded in results, never values).
CLI versions default to @latest but should be pinned for reproducibility via
CBRUN_CODEX_VERSION / CBRUN_OPENCODE_VERSION / CBRUN_CLAUDE_VERSION.
On a shared Docker daemon, set CBRUN_IMAGE_NAMESPACE to keep this release's
caseNNN:{deliverable,agent} tags separate from older case-number mappings. The
default remains codingbench-benchmark.
Point standard env vars at any OpenAI-compatible endpoint:
export OPENAI_API_KEY=your-key
export OPENAI_BASE_URL=https://your-gateway.example/v1
cbrun --case case010 --backend codex --model openai/gpt-4o-minicbrun writes a dedicated model provider into ~/.codex/config.toml during
setup. It uses the Responses API over HTTPS and disables WebSocket transport,
because OpenAI-compatible gateways do not necessarily implement WebSockets.
Use model_prefix: strip in a custom spec if your Codex install expects leaf
model names only.
- Agent deliverables must land in
/app(hard contract; not overridable). - Solve runs as
cbagent; judge reads/appas root. - Agent HOME (
/home/cbagent) holds CLI caches only; it is not scored.
Pass a local AgentSpec file instead of --backend:
cbrun --case case010 --agent-spec ./my-agent.json --model my/modelExample my-agent.json:
{
"name": "my-agent",
"env_passthrough": ["MY_API_KEY"],
"setup_script": "mkdir -p \"$HOME/.myagent\" && echo ok > \"$HOME/.myagent/ready\"",
"command": "my-cli --model {model_quoted} --workdir {workdir_quoted} \"$(cat {instruction_quoted})\" 2>&1 | tee {log_quoted}",
"run_as": "root",
"model_prefix": "keep",
"setup_timeout_sec": 120
}| Field | Required | Description |
|---|---|---|
name |
yes | Identifier recorded in results. |
command |
yes | Shell command template for the solve phase. Workdir is always /app. |
env_passthrough |
no | Env var names to forward when present on the host. |
setup_script |
no | Shell run before solve (auth files, config). Do not use set -x. |
install_script |
no | Optional CLI install when not baked into the :agent image. |
run_as |
no | root (default) or nonroot (cbagent via docker exec -u). |
model_prefix |
no | keep (default) or strip the provider/ prefix from --model. |
home |
no | Writable HOME/config directory (defaults by run_as). |
setup_timeout_sec |
no | Setup phase timeout (default 120s). |
python_hook |
no | Built-in hooks only (cbrun.agent_hooks:*); custom specs should use declarative fields. |
Placeholders in command / setup_script: {model}, {model_quoted},
{instruction_quoted}, {log_quoted}, {workdir} (/app), {workdir_quoted},
{home}.
- AgentSpec files are local trusted configuration. They can execute shell
(
setup_script,install_script,command). Do not run specs from untrusted sources. - Specs are loaded from
--agent-spec PATHonly; cbrun does not fetch and execute remote spec URLs. - The solve container never mounts the Docker socket.
- Hidden tests are absent during solve; logs must not contain secret values.
- During solve, GitHub hostnames are blocked via container
/etc/hosts(--add-host …:0.0.0.0). Model API calls and PyPI/npm remain reachable. Disable with--no-block-github. - Each case may ship
source/denylist.jsonlisting upstream package/module names for the target product. pip/conda install shims in the:agentimage reject those packages inline (warning only, no scoring). - After solve, cbrun statically scans
/appfor banned imports/symbols. If found, the agent gets one fix retry (--denylist-fix-retries, default 1); if violations remain, the trial scoresreward=0. Disable with--no-enforce-denylist.
# One case, one backend
cbrun --case case010 --backend codex --model openai/gpt-5.5
# Several backends, reward matrix
cbrun --case case010 \
--backend codex --backend opencode --backend claude-code \
--model openai/gpt-5.5
# Custom agent spec
cbrun --case case010 --agent-spec ./agents/echo.json --model dummy/model
# Every case under the cases root
cbrun --all --backend codex --model openai/gpt-5.5Outputs go to --out (default benchmark/output/cbrun/): per-trial agent.log,
agent_setup.log (when setup runs), judge.log, final_report.json, plus an
aggregate summary.json and a printed reward matrix.
Each trial records reproducibility metadata: agent spec name/hash, resolved model,
run_as, model_prefix, setup status, forwarded env key list, and CLI
version when available.
- Derive/reuse the
:agentimage (extract hidden tests to host cache, install pinned CLIs, createcbagentuser,rm -rf /tests/final). Idempotent. - Start a solve container (
--gpusonly when the case needs it, host network for model APIs, never the Docker socket). - Inject the instruction; setup agent auth/config; chown
/app(+ HOME for non-root agents); run the agent in/appunder the wall clock + stall watchdog, tee'ing output toagent.log. - Decide the terminal status; ensure no agent process lingers.
- Re-inject hidden tests + a synthesized
/task.toml, run the sharedfinal_judge.pyas root, parse the binary reward (distinguishingjudge_error).
coding_bench_harbor.adapter: case discovery,CaseAssets, the from-scratch_INSTRUCTION_PREAMBLE, runner normalization.coding_bench_harbor.final_judge: the scoring engine (single source of truth, shared with the Harbor adapter).
- Fast unit tests (no Docker):
tests/test_cbrun_agents.py,tests/test_cbrun_agent_spec.py,tests/test_cbrun_core.py,tests/test_cbrun_wiring.py. - Docker-gated integration (
@pytest.mark.slow,tests/test_cbrun_docker.py): oracle sanity (GT → reward 1), fairness invariant (:agentimage has no/tests/final, CLIs present,cbagentuser exists, Codex setup writes config). They skip automatically when Docker or the required images are unavailable. - Local agent smoke (your credentials, no source edits):
../local_agents/README.md— copy*.env.example, run./local_agents/run_smoke.sh.