Two provenance fixes in the same class as the release itself, plus the round-11/12 review findings that land in the same files. FIX A — an isolated benchmark container carries only the providers the run declared. `build_isolated_settings` copied EVERY provider credential present in the live settings file regardless of which providers the run's model slots declared, so a run pinned to OpenRouter still received direct ANTHROPIC / OPENAI / Cloud.ru / GigaChat keys. Two consequences: a routing fallback could spend outside the declared bucket while the manifest said otherwise, and the reachable provider set was a function of whatever happened to be in the live file at launch — a pinned seed that pins the code but not the environment is not reproducible. Provider credentials are now gated on the run's declared slots, derived from the routing SSOT (`provider_models.PROVIDER_PREFIXES` / `provider_for_model`, the same registry `llm._resolve_remote_target` routes on), and travel in whole groups so a key never arrives without the endpoint or auth fields it is useless without. Owner/control secrets were never copied and still are not. Ambiguity fails OPEN (carry a spare, disclose it) — never closed. `benchmark_run_manifest` now records `provider_credentials`: which credentials the container actually received, by fingerprint, never by value. FIX B — a task the cost rail truncated says so. `usage_accounting.reserve_- attempt` refuses on a worst-case reservation bound that reached a $6.00 rail at $0.45 of actual spend in the v6.81.0 OSWorld smoke, stopping two of three tasks at 13 and 22 rounds; the artefacts published `status=completed`, `reason_code=official_evaluate` and the string `budget_exhausted` appeared nowhere. `task_result_row` gains an always-present `runtime_outcome` projected by the new shared `runtime_terminal_disclosure`, and every writer holding a runtime task result now publishes it: OSWorld cu_bridge, SWE-bench and ProgramBench success rows (the failure rows already did), the Terminal-Bench in-container summary and disclosure ledger, the GAIA solver, the harness-bench wrapper, and the CL-Bench per-question writer. Reward, `official_eval_status` and adapter-stage `status` are untouched: disclosure ADDED, fact not subtracted. TB's ledger gains a `cost_truncated` bucket — `genuine` asserts a fair shot, which a rail-truncated trial did not get. Review findings folded in: - `_amend_manifest` emitted `output_paths.task_outcome` unconditionally, so the finalized attempt manifest kept pointing at an outcome whose write failed. The previous round fixed the ledger row and left the manifest lying; both sides now follow the same rule. - `ADAPTER_PATCH_MARKERS` keyed two of three detections on bare env-var names, which the unpatched adapter may mention in a comment or a `-e` passthrough list. That false positive OVERSTATES enforcement. Markers are now patch-unique tokens and the uniqueness requirement is recorded beside them. (One marker legitimately covers all three env knobs: they arrive in one loop in one hunk.) - CLB fidelity overstated enforcement on the DEFAULT `--path standard`: `_docker_launcher.submit()` hardcodes `disabled_tools: []` and never imports the patched bridge module, so the evidence is now entrypoint-specific. - `runtime_attested` renamed to `runtime_attestation_available`: it is a tree probe, and a definition existing is not evidence that it ran. - README version badge alt text said 6.80.0 while the URL said 6.81.0, which `version_carrier_desyncs` flags and the advisory preflight blocks on. Bug-pinning tests inverted, and said so in the test docstrings: - `test_dry_run_claims_attestation_only_when_the_patch_is_in_the_execution_- clone` asserted `runtime_attested is True` for a DRY RUN against a clone that merely contained an `_attest_runtime` definition — it demanded the false positive as the contract. - the CLB fidelity fixtures wrote bare env-var names as "the patch", which is precisely the marker weakness above. Docs corrected where the code falsified them: OSWorld METHODOLOGY §7.4 claimed `OUROBOROS_MAX_ROUNDS` plus the timeout were the only per-task caps (the USD rail binds first), §6 now says scoring reads `official_eval_status` / `details.outcome_status` rather than filtering on `status == "completed"` and that `output_paths.task_outcome` may be absent; CLB METHODOLOGY §3 documents when an exported runtime mode overrides the adapter's hard-set `advanced`, and §6 distinguishes the `--runner-path` adapter checkout from the `--ouroboros-clone` execution seed and points at the field the code actually writes. Known ordering debt noted in place for the v6.82 backlog, deliberately not restructured here: `_auto_sync_release_metadata_if_needed` runs ~87 lines after the `_release_metadata_preflight` gate it would satisfy. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> |
||
|---|---|---|
| .. | ||
| __init__.py | ||
| export_programbench_submissions.py | ||
| instruction_template.md | ||
| programbench_adapter.py | ||
| README.md | ||
| run_programbench.py | ||
| run_programbench_e2e.py | ||
| schemas.py | ||
| settings_base.json | ||
ProgramBench
Ouroboros adapter for official ProgramBench
cleanroom execution. Scoring always goes through the official programbench CLI.
Modes
End-to-end (run_programbench_e2e.py)
Full harness: pull task_cleanroom_v6 image, seed a host workspace, start a no-network
Docker backend, submit an Ouroboros task with executor_ref + protected_artifacts,
wait for completion, write submission.tar.gz, optionally run programbench eval.
cd <workspace>/repo
source .venv/bin/activate
export OUROBOROS_REPO_DIR=<workspace>/repo
export OUROBOROS_DATA_DIR=<workspace>/data
export OUROBOROS_BENCH_RUNS_ROOT=<workspace>/bench_runs
# Model slots: the server reads OUROBOROS_DATA_DIR/settings.json. For a pinned
# ProgramBench profile merge devtools/benchmarks/programbench/settings_base.json
# into data/settings.json before starting the server (secrets are blanked in the
# template; add the real provider key yourself, never commit it).
#
# settings_base decisions:
# - OUROBOROS_TASK_REVIEW_MODE=required so adaptive multi-pass improvement after
# task_acceptance_review actually runs on headless bench tasks.
# - OUROBOROS_RUNTIME_MODE=pro (container bench), OUROBOROS_MAX_WORKERS=4,
# OUROBOROS_SAFETY_MODE=light.
# - The solve task itself disables claude_code_edit (house rule: benches measure
# the single-model Ouroboros harness, not an external coding agent).
# - Model slots pin openai/gpt-5.5: the id must exist in the OpenRouter catalog
# (there is no openai/gpt-5.5-mini there — it 400s on every task).
# Terminal 1 — source server (not the desktop app; the sandboxed desktop cannot
# see bench_runs/ workspaces)
python server.py --host 127.0.0.1 --port 8770
# Terminal 2 — smoke (3 easy tasks)
python devtools/benchmarks/programbench/run_programbench_e2e.py \
--difficulty easy \
--slice 0:3 \
--solve-model openai/gpt-5.5 \
--eval \
--ouroboros-url http://127.0.0.1:8770
Useful flags:
--instance-id <id>— single task--solve-model <id>— expected OUROBOROS_MODEL; validated against--settings-path--dry-run— docker +ouroboros_task_body.jsononly (no solve)--skip-pull— reuse already-pulled images--redo-existing— rerun even whensubmission.tar.gzexists (clears the task checkpoint)--timeout-sec 21600— per-task wall clock (default; matches official 6h budget)--cpus 4 --memory 16g— container resources (lower than mini-swe baseline for Mac)
Prepare-only (run_programbench.py)
Writes ouroboros_task_body.json and packages an existing workspace when you already
have a running cleanroom container. Does not submit to the gateway.
Export (export_programbench_submissions.py)
Copies submission.tar.gz files into a minimal eval-compatible tree for targeted
programbench eval runs.
Budget and pacing
The task body carries metadata.budget_profile (see schemas.programbench_budget_profile);
POST /api/tasks normalizes it additively into task_contract.budget_profile
(there is deliberately no top-level task_contract field on the gateway):
improvement_policy=until_deadline,max_improvement_passes=6(the explicit cap remains authoritative under every policy),reserve_finalization_pct=15(0–100 pct),stall_rounds_threshold=12.- The 6h official budget flows through the body's
timeout_sec(gateway →deadline_at); round caps come from settings (OUROBOROS_MAX_ROUNDS).
Reliability (lessons from the 0/5 debug run)
- Model-id preflight: direct-provider routes reject legacy
provider/modelids (they needprovider::model). The runner validates every configured model slot via the runtime'smigrate_model_valuebefore any task burns and fails fast with the exact remediation. With an OpenRouter key,provider/modelis canonical and left untouched. - Checkpoints:
submit_and_waitpersists the latest task result to<instance>/ouroboros_task_checkpoint.jsonatomically on every poll; a restarted harness re-attaches to the recorded task_id instead of re-submitting, so a crash or client timeout does not discard hours of in-flight agent work. - Status inference: terminal detection reads the payload's explicit
statusagainst the settled set (completed/failed/cancelled/rejected_duplicate) and infra failures are classified fromreason_code/outcome_axes.execution(harbor-adapter pattern) — never heuristics over error text.
Invariants
- Use official
programbenchCLI for evaluation and summaries. - Use
task_cleanroomtask images; do not score locally. - Tool execution for the benchmark workspace runs in a no-network Docker backend.
- Reference binaries are declared through
resource_policy.protected_artifacts: execute is allowed; byte reads, copy/hash/static introspection/tracing/debugging are denied. - Submission artifact is
<run>/<instance_id>/submission.tar.gz. - Sidecars (
run_manifest.json,result_index.jsonl) are audit artifacts only. result_index.jsonlis APPEND-ONLY (v6.75.0). EVERY row — processed and skipped alike — is appended the moment it exists, at the run root AND in the instance dir, so a resumed instance's own history contains the skip event that explains the gap. Before this the run root ledger was one whole-file rewrite at the very end, so a crash discarded every completed instance and a resume silently replaced the previous run's history. Consequences for readers: a resumed or re-done instance appears MORE THAN ONCE, so dedup byinstance_idand take the LAST row; the file is run history, not a set. The denominator still comes from the requested selection (run_manifest.json→requested_task_ids), never from the line count.run_manifest.jsonis written FIRST (v6.75.0), right after selection and before the first instance — it carries the shared seed-provenance gate (benchmark_run_manifest(require_clean=True), escape--allow-dirty-seed) plus the runtime attestation of the server named by--ouroboros-url(the HTTPruntime_versionand the local HEAD of--repo-dir; a skew hard-stops unlessOBO_ALLOW_EVOLVED_VOLUME=1). It is written to disk as soon as it is built, so a refusal after admission still leaves a durable record of what was refused, and it is rewritten at the end with the eval status and the completed/failed counts. Previously it was written only after every instance AND the official eval, i.e. after all the spend, so the gate could not stop an unreproducible run.
Mac notes
ProgramBench images are linux/amd64 only. On Apple Silicon use Docker Desktop or
Colima (colima start --arch x86_64 --cpu 4 --memory 16) with
DOCKER_HOST=unix://$HOME/.colima/default/docker.sock if needed. The e2e runner
auto-sets DOCKER_HOST when a Colima socket is present.
Use the source Ouroboros server on port 8770 for benchmarks. The packaged
desktop app on 8765 is sandboxed and cannot access bench_runs/ workspaces.
Official programbench eval needs Python 3.11+ (typing.Self). The adapter
auto-picks python3.12/python3.11 with programbench installed, or set
PROGRAMBENCH_PYTHON=/path/to/python3.11. Eval defaults to --docker-cpus 4
(override with PROGRAMBENCH_DOCKER_CPUS).
Run dirs default under bench_runs/; set OUROBOROS_BENCH_RUNS_ROOT to redirect
(the test suite pins it to a temp dir so runs never leak into a developer's
bench_runs/).
Full-run gate (gate-20, v6.56.0) — operator methodology
This is an operator procedure, not an automatic runner feature: the
ProgramBench runner has no built-in gate, and the steps below are enforced by
the operator (and the campaign watcher) around a normal run, not by
run_programbench_e2e.py itself.
Begin a full 200-task run with a 20-task calibration gate: the 10 smoke tasks plus 10 unseen tasks. Score PAIRED per-task against the official leaderboard's per-task results (matched by instance id), not aggregate-vs-aggregate. Pass criteria: paired mean on these 20 >= the reference harness's paired mean on the same 20, AND not worse than the Codex baseline on the shared subset. On PASS, continue to the full 200; on FAIL, the operator stops the run and reports (on an overloaded host, pause instead of failing — attach load data — and re-run when contention clears). Record the gate result, task list, and per-task pairs alongside the run so a published 200-task number can always be traced back through its gate.