Skip to content

AutoCode Arena (experimental)

Arena runs pinned repository issues through the existing public TaskRun interface, scores the candidate with evaluator-owned checks, and retains a SQLite attempt ledger. It never opens an issue or PR, pushes a patch, edits AutoCode itself, or promotes a version. The autocode-issue workflow remains the operator's route for an individual issue and eventual PR submission.

Install the checkout with pip install -e .. Commands can also be invoked as python tools/autocode_arena.py. The default store is .autocode/arena, ignored by Git; --arena PATH before the subcommand selects another evaluator directory. Use one operator at a time: concurrent catalog writers are not supported.

The planned reliability protocol pins tasks, repetitions, original limits and all-outcome reporting beside the catalogue. It contains no campaign results and is not an automatic runner.

Start with the included projects

The checkout includes eight original pinned upstream bug cases in arena/catalog.json: three starter Python cases and five hard cases across Python, TypeScript and Go. A ninth declaration is a separate corrected TypeScript successor; it preserves the original case and results. Problem descriptions and self-contained oracles live under arena/cases/; the successor has its own description and shares the original TypeScript oracle. The catalog pins the full baseline and reference commit IDs; preparation fetches those commits, verifies that the baseline fails and the reference passes, and only then adds the case to the local Arena. TypeScript also receives the disclosed test-only adapter described below; its upstream pins remain in the catalog.

Case ID Project and upstream report Work to solve Starter split
boltons-indexedset-update Boltons #474 Update an ordered set from several iterables while retaining order and tuple-valued members development
humanize-intcomma-large Humanize #392 Format large positive and negative integers, including ordinary integer subclasses, without losing digits regression
more-itertools-custom-exceptions More Itertools #1279 Preserve custom exceptions that evaluate to false and avoid formatting items when a custom exception is given holdout

From the checkout root, using its installed venv:

.venv/bin/python arena/prepare.py --list
.venv/bin/python arena/prepare.py --arena .autocode/arena \
  --case boltons-indexedset-update --case humanize-intcomma-large \
  --case more-itertools-custom-exceptions
.venv/bin/python tools/autocode_arena.py --arena .autocode/arena cases

Preparation requires GitHub network access but makes no model calls. It creates the Arena, so do not run init first. Use a fresh destination; preparation refuses to overwrite existing evaluator data. Add --case boltons-indexedset-update to prepare just one project. A failed preparation is retained for inspection; choose a new destination when retrying. Source repositories are in sources/, fixed reference trees in references/, and admitted cases in catalog.json and cases/ under that destination. Candidate attempts get their own frozen broken source tree without upstream history or remotes.

Omitting --case prepares all nine declarations. Install their dependencies first, including the external TypeScript build bundle and Go module cache below, and use --oracle-timeout 400 when including Go. Preparation does not install those dependencies or freeze their contents for you.

Run the development case with an authenticated native Codex CLI. The checkout's venv needs pytest for this project's test command; install it before starting an attempt. Keep the runner source and its dependencies fixed until the attempt finishes, because changes invalidate the recorded execution identity.

export PATH="$PWD/.venv/bin:$PATH"
.venv/bin/python tools/autocode_arena.py --arena .autocode/arena \
  run boltons-indexedset-update --cohort baseline-v1 \
  --i-authorize-live-model-spend --approve-benchmark-plans \
  --timeout 3600 --option=--engine=codex \
  "--option=--test-command=$PWD/.venv/bin/python -m pytest tests/test_setutils.py -q"
.venv/bin/python tools/autocode_arena.py --arena .autocode/arena report

This makes real model calls and approves the displayed benchmark plan through Arena's exact plan token. The one-hour deadline applies to each AutoCode CLI invocation, including a review and repair cycle. To use another configured provider, replace --option=--engine=codex with --option=--provider=NAME and add the model options that provider needs. --option forwards, exactly as typed, --engine, --provider, --single-model, the role model flags (--astra-model, --terra-model, --sol-model, --glm-model, --completion-model, --plan-reviewer-model, --requirements-model, --resolver-model, --investigator-model), the reasoning-effort flags (--reasoning-effort and each role's --*-reasoning-effort), --pin-model-role (repeatable), --test-command, --max-iterations and --joint-planning; Arena checks their choices and refuses any other flag, such as a workspace or run directory. The test command above is specific to Boltons; choose the corresponding project's test command for other cases.

These cases are public historical exercises. Their descriptions are explicitly marked evaluator-authored, problem-only adaptations of upstream issue and PR reports, not authenticated pre-fix issue snapshots. They omit solution text and comments. The checked-in source links and reference revisions remain evaluator material; this host-level setup cannot prevent a model from finding a public historical fix. The starter holdout is public and is not a secret or contamination-free benchmark. Use fresh private holdouts before drawing general improvement conclusions.

The starter oracles import the candidate source directly and use only Python's standard library. Humanize's generated version metadata is supplied in memory for source archive imports; its formatting implementation still comes from the candidate. These targeted checks do not replace each project's full upstream test suite. The catalog/preparation tool is distributed with the checkout, not the wheel.

For Humanize's native number tests, supply generated version metadata through the case-specific setup plugin:

PYTHONPATH="$PWD/arena/support/humanize" PYTEST_PLUGINS=arena_humanize_bootstrap \
PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 .venv/bin/python -m pytest -q \
  -p no:cacheprovider tests/test_number.py

Put those environment settings in the attempt environment or its configured test command. The helper supplies metadata only; number formatting comes from the candidate. The integer-subclass check was added after an earlier four-check candidate missed that boundary. Prepare a fresh store and cohort for the current five checks; earlier results retain their original score.

Hard cases

The harder group exercises interacting algorithms and lifecycle boundaries in larger projects. The listed module counts describe the upstream reference fixes; AutoCode may produce a different correct implementation.

Case ID Upstream report Behavior Production modules in reference fix Split
sympy-matrix-derivatives SymPy #25150 and linked reports Matrix gradients and Hessians, rectangular coordinates, noncommuting powers and tensor axes 11 development
django-filteredrelation-join-lifecycle Django #33766, PR #16786 Conditions that spawn joins, filtered aliases, cloning and nested subqueries 3 regression
pytest-stop-fixture-teardown pytest #11706, PR #11721 and PR #12048 Teardown and plugin reports after early stops, including multiple failing callbacks on one fixture 3 holdout
typescript-dependent-destructuring TypeScript #35283, PR #46266 and linked reports Correlated union tags and payloads after destructuring, with assignment and error-preservation boundaries 2 development
go-http2-hung-reset-health Go #59690 Canceled stream capacity, health acknowledgments, strict admission, healthy reuse and dead connection replacement 1 regression

The pytest case combines early-stop routing with same-fixture exception aggregation. Its reference is pinned to the early-stop reapplication, after both upstream fixes; the early-stop change was temporarily reverted between them. Three additional oracle checks cover same-fixture callbacks under maxfail, stepwise and setup failure, including callback counts and every plugin-visible exception. Earlier 9-check passes do not establish this stronger 12-check result: prepare a fresh Arena and cohort; never rescore or overwrite an existing attempt. The three-module count covers the two relevant fixes, not all intervening upstream changes.

These are public historical exercises. The holdout split controls which evidence an improvement proposal exposes; it does not make a public fix private. Fresh private tasks are needed for stronger generalization claims.

Prepare the three Python hard cases in a fresh destination:

.venv/bin/python arena/prepare.py --arena .autocode/arena-hard-v1 --oracle-timeout 120 \
  --case sympy-matrix-derivatives \
  --case django-filteredrelation-join-lifecycle \
  --case pytest-stop-fixture-teardown

Install runtime and test dependencies before freezing a cohort. The oracles need mpmath for SymPy and asgiref/sqlparse for Django. The tested upstream scopes also use pytest, numpy, hypothesis, attrs, py, pexpect and xmlschema. Keep every installed version fixed through all attempts. Disable third-party pytest plugin autoload with PYTEST_DISABLE_PLUGIN_AUTOLOAD=1; explicit setup plugins still load.

Use the checkout's absolute venv interpreter in --option=--test-command=.... These bounded upstream scopes cover the affected subsystems; they are not the entire SymPy, Django or pytest test suites:

Case Arguments after python -m pytest Per-process setup
SymPy -q -p no:cacheprovider sympy/core/tests/test_diff.py sympy/core/tests/test_function.py sympy/core/tests/test_args.py sympy/matrices/expressions/tests sympy/tensor/array/tests/test_array_derivatives.py sympy/tensor/array/expressions/tests PYTEST_DISABLE_PLUGIN_AUTOLOAD=1
Django -q -o 'python_files=test*.py' -p no:cacheprovider tests/filtered_relation tests/queries PYTHONPATH="$PWD/arena/support/django" PYTEST_PLUGINS=arena_django_bootstrap plus plugin autoload disabled
pytest -o minversion= testing/test_runner.py testing/test_session.py testing/test_stepwise.py -q PYTHONPATH="$PWD/arena/support/pytest" plus plugin autoload disabled

The Django setup plugin uses candidate-local source, its existing test apps and two in-memory SQLite databases. Other database engines are outside this scope. The pytest sitecustomize helper prefers candidate source and supplies generated version metadata only when absent; nested pytester processes inherit that source. Neither helper installs or substitutes a runtime implementation. Apply each helper only to its own case and keep its files frozen through the attempt. The environment is inherited by AutoCode's collected, derived and replayed checks.

For example, the Django case can be launched with:

export PATH="$PWD/.venv/bin:$PATH"
PYTHONDONTWRITEBYTECODE=1 PYTEST_DISABLE_PLUGIN_AUTOLOAD=1 \
PYTHONPATH="$PWD/arena/support/django" PYTEST_PLUGINS=arena_django_bootstrap \
.venv/bin/python tools/autocode_arena.py --arena .autocode/arena-hard-v1 \
  run django-filteredrelation-join-lifecycle --cohort hard-baseline-v1 \
  --i-authorize-live-model-spend --approve-benchmark-plans \
  --timeout 3600 --oracle-timeout 120 --option=--engine=codex \
  --option=--max-iterations=3 \
  "--option=--test-command=$PWD/.venv/bin/python -m pytest -q -o 'python_files=test*.py' -p no:cacheprovider tests/filtered_relation tests/queries"

Use an oracle deadline of 120 seconds for these Python cases; pytest's oracle starts several isolated child suites. Benchmark approval still covers only the exact plan token, never questions, human reviews, recovery or quota overrides.

TypeScript setup and native tests

The TypeScript controls used Node v25.9.0. Select and freeze the Node executable before preparation, and keep it unchanged through an attempt. Create the external build bundle from the checked-in lockfile:

arena_ts_deps="$PWD/.scenario-runs/arena-dependencies/typescript"
mkdir -p "$arena_ts_deps"
cp arena/support/typescript/package.json arena/support/typescript/package-lock.json "$arena_ts_deps/"
(cd "$arena_ts_deps" && npm ci --ignore-scripts --no-audit --no-fund)
export ARENA_TYPESCRIPT_DEPS="$arena_ts_deps"
export ARENA_TYPESCRIPT_HELPER="$PWD/arena/support/typescript/arena_typescript_compiler.cjs"
export ARENA_NODE="$(command -v node)"
.venv/bin/python arena/prepare.py --arena .autocode/arena-typescript-v1 \
  --case typescript-dependent-destructuring --oracle-timeout 120

The lockfile pins TypeScript 4.5.2, @types/node 14.18.2 and @types/microsoft__typescript-etw 0.1.1. Record the installed bundle's content identity, the helper and the Node runtime before freezing the attempt. The pinned TypeScript package bootstraps the build: both the helper and oracle compile the candidate's own source in a temporary directory and load that fresh compiler. Generated candidate build files are not reused. The diagnostic metadata generator and table also come from the candidate.

The pinned upstream tree lacks the selected native test file. Preparation adds only tests/cases/unittests/arenaDependentDestructuring.test.cjs, containing three preservation guards that pass on the broken baseline. It commits those new tests as an adapted baseline and adds the identical adapter to the reference tree. Upstream runtime source and existing tests are preserved. arena/catalog.json retains the upstream baseline/reference pins; the prepared case's preparation.json records upstream_base_commit, adapted_base_commit, upstream_reference_commit and per-file hashes under test_overlay_sha256.

Use this direct native command in --option=--test-command=..., with the same environment inherited by AutoCode and its regression replay:

node --test tests/cases/unittests/arenaDependentDestructuring.test.cjs

The task must extend that file with its own named regressions and retain the three guards. Passing only the initial preservation tests does not establish a fix. The independent oracle checks 15 diagnostic behaviors, including cases that must continue to report errors. This is a focused compiler scope using an in-memory declaration library; it does not run the full historic Gulp/Mocha, conformance, language-service, emit or platform suites. Its external oracle deadline is 120 seconds.

Corrected TypeScript case

Select typescript-dependent-destructuring-baselines-v2 to use the separately approved task scope. The original case's requirement to keep existing upstream expectations unchanged conflicts with the requested narrowing in tests/cases/conformance/controlFlow/controlFlowAliasing.ts: that existing test expects two errors and union payload types for an eligible destructured parameter. The corrected case permits necessary changes only to tests/baselines/reference/controlFlowAliasing.errors.txt and tests/baselines/reference/controlFlowAliasing.types. Preserve unrelated expectations, every upstream test input and definition, emitted JavaScript and symbol baselines, and the full original adapter prefix and all three guards.

.venv/bin/python arena/prepare.py --arena .autocode/arena-typescript-baselines-v2 \
  --case typescript-dependent-destructuring-baselines-v2 --oracle-timeout 120

This successor reuses the original oracle and adapter through fixture_case; its explicit preparation_note retains the approved successor instructions during normal preparation. Upstream commits and all 15 behavioral checks remain identical. Use the original adapter command for AutoCode's retained test gate, so candidate tests and the unchanged original guard overlay are independently replayed. Run the native compiler build and controlFlowAliasing conformance checks against the final candidate and its approved updated expectations. The original protected bundle remains retained; task permission does not authorize replacing it.

Keep original attempts and scores under typescript-dependent-destructuring. Record the corrected case's attempts under its separate ID and cohort. A corrected case pass does not retroactively qualify the original, and the declaration itself is no evidence that either case has passed.

Go setup and native tests

The HTTP/2 case uses Go 1.25.5 with GOTOOLCHAIN=local. Its independent oracle uses testing/synctest in an external evaluator module; the candidate's upstream go.mod is preserved. Select the frozen Go executable through PATH (the local controls used /opt/homebrew/bin/go). Before going offline, run go mod download from a separate checkout of the catalog's baseline into an external GOMODCACHE. The pinned dependencies are golang.org/x/crypto v0.28.0, x/sys v0.26.0, x/term v0.25.0 and x/text v0.19.0.

Freeze the toolchain and module-cache contents before preparation. Give each attempt separate writable build and temporary directories outside its source; set GOCACHE, GOPATH, GOTMPDIR and TMPDIR to those directories. Then inherit the following offline settings through preparation and every native replay:

export GOTOOLCHAIN=local GOMODCACHE=/absolute/frozen/go-module-cache
export GOPROXY=off GOSUMDB=off GOWORK=off GOFLAGS=-mod=readonly
export CGO_ENABLED=0 GODEBUG=asynctimerchan=0
.venv/bin/python arena/prepare.py --arena .autocode/arena-go-v1 \
  --case go-http2-hung-reset-health --oracle-timeout 400

Use the following literal native command in --option=--test-command=..., with PATH selecting the frozen Go executable:

go test ./http2 ./http2/hpack -count=1 -timeout=120s -parallel=2

The task must add its own native Go regression tests. The evaluator-owned oracle's six checks use a scripted net.Pipe wire peer, public connection state and actual HTTP/2 frames. They distinguish strict waiting and pool reservations, health ACKs from peer PINGs, repeated healthy cancellation/reuse, dead connections exhausted at several concurrency limits, and single-use preservation. It builds and tests only a disposable copy. A passing child must have actually run and passed its requested named test; skipped or missing results and real process timeouts are evaluation errors. Retained controls record two additional local source-integrity checks separately; these do not add scored behaviors.

Use --oracle-timeout 400 during both preparation and attempts: compilation is bounded at 180 seconds and each probe at 20 seconds in Go and 25 seconds by its supervisor. The native command covers HTTP/2 and HPACK; it does not establish the full x/net suite, TLS interoperability, real TCP/firewall failure handling or performance across platforms.

“Hard” describes the selected interacting behaviors, not a measured ranking of model difficulty. Positive/negative oracle controls and native setup checks establish case validity; they do not establish live AutoCode success, general reliability or improvement across languages. Freeze external dependencies and retain fresh per-attempt evidence before making those claims.

Declare a case

Choose an issue you have permission to evaluate, a local clone, and the full commit SHA before the fix. Supply a historical issue JSON snapshot for historical cases; fetching today's discussion may leak the fix. Arena does not infer the correct historical commit, scrub spoilers, or discover issues automatically.

Write a self-contained, trusted Python oracle, outside the candidate repository. It receives the candidate directory as its first argument and prints one JSON object with a nonempty checks list:

import json
import subprocess
import sys
from pathlib import Path

workspace = Path(sys.argv[1])
result = subprocess.run(
    [sys.executable, "-B", str(workspace / "greet.py"), "World"],
    capture_output=True, text=True, timeout=5,
) if (workspace / "greet.py").is_file() else None
ok = bool(result and result.returncode == 0 and result.stdout == "Hello, World\n")
print(json.dumps({"checks": [{"name": "greeting", "ok": ok}]}))

The oracle must exit 0 when it successfully evaluates either a passing or failing candidate. A crash, timeout, malformed result, duplicate/missing required check, or content mutation yields an evaluation error, never a passing result. Avoid writing into the candidate; use temporary directories and python -B for imports. The oracle is pinned as a single script. Its imported dependencies, services and environment are not transitively pinned; prefer self-contained offline checks.

Provide a separate known-good reference workspace. Ingestion actually runs the oracle against the frozen failing baseline and the reference. Both controls must behave as expected before the case enters the catalog:

autocode-arena init
autocode-arena ingest OSS-0001 \
  --repository /path/to/local/repo \
  --base FULL_COMMIT_SHA \
  --issue owner/repo#123 --issue-file /path/to/historical-issue.json \
  --oracle /path/to/oracle.py --reference /path/to/known-good-workspace \
  --check greeting --split development

Without --issue-file, ingestion uses the existing GitHub client to read the issue and its latest comments; GH_TOKEN / GITHUB_TOKEN work as in autocode-issue. The supplied snapshot is an evaluator declaration, not independently authenticated history. A control failure leaves its case directory for inspection; remove that new failed directory before retrying the same ID. Do not change a cataloged case; declare a new case instead.

Attempt and score

autocode-arena run OSS-0001 --cohort baseline-v1 \
  --runner /path/to/autocode-v1 \
  --i-authorize-live-model-spend \
  --option=--provider --option=YOUR_CONFIGURED_PROVIDER
autocode-arena report --cohort baseline-v1

Each attempt gets a fresh repository with only the frozen source tree and one local base commit. It has no upstream remotes, later commits or copied hooks. Issue and oracle snapshots remain outside it. The Arena layer never reads TaskRun's private state.json; status and approvals go through its public API.

By default the attempt stops at the operator's plan gate. For automated benchmark runs, explicitly add --approve-benchmark-plans; Arena saves the displayed plan and approves its exact current token. This is benchmark automation, not human acceptance. Questions, review criteria, recovery and quota overrides are never automatically answered. --timeout bounds each TaskRun CLI invocation, not the entire experiment; --oracle-timeout bounds each independent evaluation.

The attempt directory retains status, checks, content identity, the candidate patch (including new files), and result JSON. SQLite retains the authoritative final observation. Stopped attempts are final snapshots; manually continuing the underlying TaskRun does not rewrite their score. Use a fresh attempt for comparison. Interrupted attempts remain RUNNING in the ledger and block the comparison gate.

Outcome Meaning
PASS Runner claims completion and all independent checks pass
FALSE_COMPLETE Runner claims completion but an independent check fails
STOPPED Runner pauses or requests input; not proof of a correct stop
FAIL Runner does not complete and is not in a recognized input/pause state
ERROR Execution or evaluation could not establish a verdict

Exit codes: PASS 0, STOPPED 2, other attempt outcomes 1; invalid requests 2. Reports show both false completions per attempt and per completion, with explicit denominators. Raw public usage is retained; dollar cost stays unknown rather than equating subscription tokens with an API invoice. There is no inferred human time, maintainer acceptance, or independently measured test coverage.

--fixture uses the existing greeting-only scripted Codex provider, refuses other engines/providers, and needs no live-spend authorization. It tests plumbing; fixture results cannot qualify an AutoCode version. It does not solve arbitrary OSS issues.

Controlled improvement

autocode-arena propose --cohort baseline-v1
# Investigate the development failures, implement a candidate in another checkout.
# Run every declared case freshly under baseline-v1 and candidate-v2, then:
autocode-arena compare --baseline baseline-v1 --candidate candidate-v2

propose saves development failures, failed check names, and investigation areas. Uninvestigated root cause remains UNDETERMINED: an oracle failure alone cannot establish whether the bug was in requirements, planning, implementation or validation. It deliberately does not disclose holdout failures in an improvement proposal or automatically generate a self-modification. The operator can give this evidence to AutoCode's ordinary planning/build workflow and review the proposed change.

After investigation, autocode-arena annotate ATTEMPT --report attribution.json appends evidence references and free-text suspicion without modifying the saved attempt or its outcome. A confirmed diagnosis also needs --reviewer NAME and pinned independent fail-before/pass-after regression reports. propose includes eligible development attribution and still excludes holdout failures. Changed evidence makes the diagnosis unknown again; no annotation promotes a candidate. See failure attribution records for the shared Arena and scenario contract and its limits.

Declare distinct development, regression and holdout cases before comparing. The gate requires exactly one baseline and one candidate attempt for every catalog case, a single source version per cohort, identical case/configuration/approval policy, live evidence, no incomplete evaluations, no candidate false completion, no previously passing case lost, and at least one solve improvement on holdout. Repeated attempts cannot be cherry-picked inside a cohort. A successful gate emits CANDIDATE_FOR_HUMAN_REVIEW, never promotion. This small-sample rule is an engineering gate, not statistical proof of generalized improvement. Repeated use of a holdout contaminates it; rotate fresh holdouts and keep them out of improvement workspaces.

Trust and validation limits

These are trusted-code experiments, not a containment boundary for hostile OSS. Candidate code, build tools and evaluator code can execute on the same host, with its permissions. Process deadlines and the existing birth-identity supervisor help with ordinary failures; a separate directory does not prevent an agent from reading or altering evaluator files. Use a separate isolated machine/container with no credentials for unfamiliar code. Catalog, snapshots and SQLite are operator-owned: hashes detect accidental edits but are not signatures against a hostile writer.

The tests exercise CLI parsing, positive/negative oracle controls, fresh frozen checkouts, TaskRun approval boundaries using a scripted interface, persistent outcomes and comparison rejection cases. They substitute oracle transport and the TaskRun boundary; they do not establish live-provider performance. The shared supervisor and TaskRun have their own integration suites. A live Arena attempt that reaches the changed path is still required before marking the PR ready.

Reliability table

Dated sweep rows live in docs/reliability-sweeps.json. The published table is generated, never edited by hand (#694):

python tools/reliability_table.py docs/reliability-sweeps.json > docs/reliability-table.md
python tools/reliability_table.py docs/reliability-sweeps.json --readme   # short README copy

Fake-provider and live-model rows are separate tables and are never mixed. Each row names the master commit the sweep ran on. Add a row after a sweep with the date, profile, commit, mode, run/pass/false-completion counts and a note that points at the run's bug file or evidence directory. The generator refuses a row whose counts do not add up or whose mode is neither live nor fake. A test compares the committed table and the README copy with the generator's output, so regenerate both after changing a row.

A row may also record what each case cost (#721) as case_costs, one entry per case run, with the money fields of that run's status view (autocode --status, the JSON view's cost block) copied as they are:

"case_costs": [
  {"case": "bugfix-iso-weeks", "run": "<run directory name, or null>",
   "estimated_usd": 0.412, "reported_usd": null, "spent_usd": 0.412, "cap_usd": 5.0}
]

null means unknown, never zero: no rate in the price table, or nothing recorded. The table shows it as unknown, and a null cap_usd as no cap. spent_usd is the known spend the cap is compared with, so it is a floor when a stage had no rate. Pasting the whole cost block is fine; the other keys are dropped. The generator checks each amount with the status view's own rule (autocode_cost.check: null or a finite, nonnegative number) and refuses a boolean, a negative or non-finite number, a string, a missing money field, a sample without a case name, or more samples than the sweep's runs. Each mode's table then gains a Cost per case section, and the row shows the median of its known spent values.