model-bench
model-bench is an internal Python CLI that screens OpenRouter LLM models against a curated pack of coding and config tasks drawn from this repo's real commits, to identify budget-tier models that clear the quality bar for offloadable work.
Two run modes
- single-shot (
mode: single-shot, the default): the model emits a whole file or answer in one turn; a deterministic verifier or an LLM judge grades it. - agentic (
mode: agentic): the model is dropped into a real repo snapshot with file tools (list_dir/read_file/write_file/done) and edits the code itself over several turns. This is the primary contract: native tool-calling carries file content in API-serialized JSON, so the output-format noise that dominated single-shot is gone, and it measures agentic reliability and token/turn efficiency alongside raw capability.
SWE-bench-style real-monolith tasks
An agentic task graded by the repo's own tests works like SWE-bench:
snapshot:pins the parent of a real fix commit and lists the monolith paths to materialize intofixture/(bench snapshotusesgit archive, so fixtures are real repo state and re-generate deterministically).- The model explores that fixture and makes the change the prompt describes.
- The
pytestverifier drops the gold test from the fix commit onto the workdir (a hidden grader the model never sees) and runs it on the monolith venv. On the buggy snapshot it fails; a correct edit makes it pass (fail-to-pass).
For example, hikes-walkhighlands-dom-01 and hikes-walkhighlands-duration-01 are both
agentic tasks against the hikes doability model (DOM scraping and duration-aware doability,
respectively). tasks/ is the source of truth for the full, current list of agentic and
single-shot tasks.
Rollout symptom-only tasks
These three hard, code-fix, agentic tasks cover #6695. Each uses the
monolith-backend preset at the fix's parent, leaves target_files: [], and
retains the backend navigation context. Only task.yaml is committed;
fixture/ is gitignored and regenerated with bench snapshot.
| Task | Historical fix (source_commit) |
Parent (snapshot.commit) |
|---|---|---|
rollout-handoff-logs-01 |
ff5fd6444184ff1e6dc89765a48b35d76917fb51 |
d04e1d47a38249ec45ff294c4ac50e0be64150f4 |
rollout-http-drain-logs-01 |
497aaebf50c45db54463e0ff1509f744966edf27 |
ff5fd6444184ff1e6dc89765a48b35d76917fb51 |
factory-rollout-fence-01 |
76244f3cd87198013ef7b50b1a24d2f2a502514c |
497aaebf50c45db54463e0ff1509f744966edf27 |
The prompts describe lost turn outcomes, HTTP drain consuming the handoff budget, and guest invocations starting after shutdown. Their sanitized excerpts are explicitly synthetic. Author comments record the historical source and controlled interleaving; timestamps, identities and neutral component labels are invented. Prompts contain no fix SHA, source pointer, faulty function name, module-path logger, fix-introduced symbol or repair recipe. Graders do not match log wording. Gold test paths and content are injected only after model edits.
The handoff grader sets AGENT_ROLLOUT_HANDOFF_ENABLED=true as a compatibility
default. The fix introduced that default-off flag and enabled it in production
in the same commit. Its parent ignores the variable. The grader uses only parent
interfaces and checks durable hold, exact dispatch identity, idempotent adoption
and one physical POST. An always-on repair also passes. This test setup changes
no production flags or provider budgets.
The HTTP grader checks the real entrypoint's effective uvicorn configuration:
the enabled bound must be positive and at most 15 seconds, preserving up to
15 seconds for executor handoff within the 30-second pod grace. Both 3- and
10-second alternatives pass, including a module-level constant or helper. The
complete entrypoint executes with unrelated application composition isolated,
so repair placement does not restrict the accepted implementation. HTTP
version: v2 records that grader correction. Disabled mode keeps the legacy
unbounded path.
The fence grader checks zero physical POSTs when shutdown arrives in the final
admission read, with shutdown, handoff and disabled-mode neighbours. Its id is
retained from #6750; version: v3 records the prompt and grader repair. Cached
cells also hash the prompt, fixture and verifier representation.
Gold tests freeze durable/database timestamps and executor budget clocks.
Events gate HTTP acceptance and cancellation cleanup; controlled wait callbacks
return pending tasks to simulate drain expiry. Never use a short real drain
timeout to decide correctness. Real wait_for limits only fail hung tests.
The harness tests read all three YAML contracts from Bazel runfiles and exercise
snapshot extraction against a controlled archive without git or network.
Offline validation
From the repository root, create the verifier venv and materialize each snapshot:
python3 -m venv /tmp/rollout-venv
/tmp/rollout-venv/bin/pip install -r projects/model-bench/requirements-venv.txt
for rollout_task in rollout-handoff-logs-01 rollout-http-drain-logs-01 factory-rollout-fence-01; do
PYTHONPATH=projects/model-bench /tmp/rollout-venv/bin/python -m bench snapshot \
--tasks projects/model-bench/tasks --repo "$PWD" "$rollout_task"
done
Save the offline comparison driver in PR #6810 as /tmp/validate_rollouts.py.
It copies each generated fixture to fresh temporary trees, injects the exact
inline gold file, and invokes only pytest. Gold overlays contain the historical
fix's non-test Python source changes. The unrelated control appends a comment
to the parent's bootstrap. No provider or model cell runs.
# Individual baseline and gold runs; expected assertion failure exits the driver 0.
/tmp/rollout-venv/bin/python /tmp/validate_rollouts.py rollout-handoff-logs-01 --variant baseline
/tmp/rollout-venv/bin/python /tmp/validate_rollouts.py rollout-handoff-logs-01 --variant gold
# Repeat baseline, gold and unrelated controls 20 times for every task.
for rollout_task in rollout-handoff-logs-01 rollout-http-drain-logs-01 factory-rollout-fence-01; do
/tmp/rollout-venv/bin/python /tmp/validate_rollouts.py "$rollout_task" --repeats 20
done
# Accept alternative budgets and module-level helpers; reject invalid paths.
for rollout_variant in bound3 bound10 module_constant module_helper unbounded disabled_changed; do
/tmp/rollout-venv/bin/python /tmp/validate_rollouts.py rollout-http-drain-logs-01 --variant "$rollout_variant"
done
/tmp/rollout-venv/bin/python /tmp/validate_rollouts.py rollout-handoff-logs-01 --variant always_on
# Harness regressions, also executed by required Linux pr-checks.
PYTHONPATH=projects/model-bench /tmp/rollout-venv/bin/python -m pytest -q \
projects/model-bench/bench/cli_test.py projects/model-bench/bench/verifiers/verifiers_test.py
Record counts and the failing behavioural assertion for every variant in the
PR. An import, dependency or collection error is a setup failure. Baseline and
gold must each produce the same verdict in at least 20 runs. Snapshot extraction
can be repeated and compared with bench.cache.fixture_hash; pytest injection
must happen in a copy, never in the model-visible fixture/.
Graded (mutation-testing) tasks
Most verifiers are pass/fail. A graded verifier also records a 0..1 score on the
attempt, so a partial answer shows up as partial rather than as a plain fail. The
mutation verifier is the first:
- The model writes a pytest suite for a real module (e.g.
whatsapp-timeparse-mutation-01testschat/whatsapp_timeparse.py). - The suite must pass on the unmodified module, which is pinned by sha256 so the model cannot edit it.
- Each hidden mutant in task.yaml (one find/replace modelled on a plausible bug) is
applied to a fresh copy, and the suite is re-run. The score is the fraction of
mutants the suite catches;
passedisscore >= pass_threshold. - The
equivalentmutants are behaviour-preserving rewrites. A suite that fails on one is asserting on source text rather than behaviour, and scores 0.
A mutant's find text must occur exactly once in the module, so bumping the fixture
commit fails loudly instead of silently grading a no-op.
The leaderboard shows the mean graded score over scored tasks next to the hard pass count. A task is scored when any current, non-retired cell on it carries a graded score. Failed cells without a score count as 0; harness errors are excluded.
Performance (speedup) tasks
The speedup verifier grades correctness and performance separately. New tasks
opt into protocol: paired-v1. The trusted helper checks the frozen baseline
against independent oracle cases before importing the candidate. It checks the
candidate against those cases and compares both outputs on every benchmark pair.
A wrong answer earns zero credit, however fast it returns.
There is one discarded warm-up pair, then seven measured pairs by default
(pairs overrides the count). Measured pairs alternate baseline-first and
candidate-first. The task's make_input(seed) builds identical positional
arguments separately for both sides, using the fixed task seed plus the pair
index. Arguments are deep-copied and retained throughout the run, preventing
input mutation and object-identity reuse across calls. Only the function call is
timed. Imports, fixture generation, argument construction, copying and correctness
checks are excluded. The helper binds time.perf_counter_ns before candidate
import and reports raw seconds, never a ratio.
The verifier computes baseline/candidate for each measured pair, takes the median
ratio, and awards the highest cleared bucket. Exact boundaries are inclusive.
Bucket scores and pass_threshold are rounded to 12 decimal places so a YAML
decimal for 1/3 qualifies at the 2x bucket. The #6696 ladder is 0 below 2x,
1/3 at 2x, 2/3 at 10x and 1 at 50x, with a 1/3 pass threshold. A correct answer
below the first bucket records correctness true and score zero.
For a stdlib-only task, the interpreter defaults to the bench's own
sys.executable; python overrides it. No monolith venv is needed. Grading uses
a fresh temporary directory with only editable Python files, the trusted
baseline, harness and helper. The interpreter runs with -I -B -S and a
scrubbed environment. Candidate workdir files, site hooks and bytecode are
excluded. The helper loads implementation files directly by path; candidate
imports are restricted to the per-task stdlib allowed_imports, with no local
or relative imports. The helper source participates in the verifier cache hash.
Before import, AST checks reject dangerous imports, reflective builtins (including
dynamic type construction), private, dunder or frame attributes, attribute
writes, wildcard/private imports and dunder
identifiers (except __del__, whose teardown output is suppressed). The default
import set is __future__, math, collections, itertools, functools,
bisect and heapq. Tasks may explicitly allow random, statistics, json,
re, array, decimal, fractions, operator, datetime and calendar;
reflective helpers such as
attrgetter and methodcaller remain forbidden. Process and interpreter modules
including sys, os, gc, inspect, ctypes, importlib, builtins, time,
threading, multiprocessing, subprocess, signal, atexit, io and
pathlib cannot be allowed.
The verifier sends a random nonce on stdin. The helper reads it before candidate
import, emits exactly one authenticated result, flushes and calls os._exit.
Import-time prints and finalizer JSON cannot replace that result. The AST policy
is a restricted Python contract, not a general security sandbox: memory/CPU
exhaustion and undiscovered interpreter or library escapes remain possible.
Keep harnesses trusted and run candidates in disposable workers. Timing remains
sensitive to hardware, scheduling and the frozen dataset. Reports label those
datasets; these ratios make no production-speedup claim.
Outcome taxonomy:
[harness error]: missing interpreter, setup or harness failure, baseline oracle failure, invalid/non-positive/non-finite samples, wrong pair counts/order, or an unparsable, missing or ambiguous authenticated result. Aggregation excludes these cells and they earn no credit.- Graded failure: rejected candidate source, import/call exception, or an oracle or paired-output mismatch. Passed false, score zero, correctness false.
- Timeout: the bounded
timeout_sexpired. Passed false, score zero, correctness unknown. This is a graded failure. - Correct: correctness true, with score and pass status determined by the buckets.
VerifyResult.performance and Attempt.performance carry the typed
PerformanceRecord: metric identity, correctness, warm-up and measured samples
(baseline_s, candidate_s, order), ratios, median ratio, highest bucket, score,
threshold, pair count and fixture_version. Markdown reports show per-model
performance rows, and JSON retains the whole record. Old cells default this field
to null; their report rows and non-performance report output stay unchanged.
Example verifier block (the injected benchmark function is the helper API;
the task harness does not import or emit results itself):
verifier:
kind: speedup
args:
protocol: paired-v1
editable: [mod.py]
allowed_imports: [__future__, collections, math]
pairs: 7
seed: 6696
fixture_version: toy-seeded-v1
timeout_s: 60
buckets: [[2, 0.3333333333333333], [10, 0.6666666666666666], [50, 1.0]]
pass_threshold: 0.3333333333333333
baseline:
path: _base.py
source: |
def total(xs):
return sum(xs)
harness: |
def build(seed):
return ([i + seed for i in range(200)],)
benchmark(candidate_path="mod.py", baseline_path="_base.py",
function="total", make_input=build,
oracle_cases=[(([],), 0), (([1, 2, 3],), 6)])
oracle_cases is a nonempty iterable of (positional_args_tuple, expected_output).
make_input(seed) returns a tuple of deepcopy-compatible positional arguments
(plain rows, lists and mappings, for example). Call benchmark once.
Both implementations export the named function. The fixed-seed builder must
cover the task's benchmark workload; independent oracle cases cover its semantic
edge cases. New tasks pin source provenance and a fixture version in task.yaml.
For pure-function tasks, benchmark(..., require_pure_inputs=True) also rejects
input mutation in oracle and timed calls. Input copies and comparisons happen
outside timing. A mutating baseline is a harness error; a mutating candidate is a
graded correctness failure. The default is false for existing harnesses.
For task-local seeded implementations, bench snapshot also accepts:
snapshot:
files:
mod.py: |
def total(xs):
return sum(xs)
files paths are validated relative to fixture/. With seeded files, commit
and paths are optional; supplying both extracts the pinned source first, then
adds or replaces the seeded files. Existing overlays, excludes and patches keep
their behavior. Fixtures remain gitignored and reproducible from task.yaml.
Legacy harnesses that print {ok, speedup, detail} remain supported in a fresh
directory with inferred script/import paths and the bench interpreter. Their
self-reported timing contract remains a compatibility limitation; they do not
receive the authenticated paired protocol or structured samples.
stars-grid-speedup-01 retains its original task.yaml, buckets and threshold.
Stars climatology fixture
stars-climatology-perf-01 is an agentic hard-tier task with shell execution and
40 turns. It seeds climatology.py and a runnable stdlib unittest file through
snapshot.files. The module is a deliberately naive benchmark fixture derived
from endpoint semantics, not current production code. Provenance is main commit
5086f428e23b64d40a9abc6eb450f0f62c9e3f83, stars/router.py:get_history,
stars/models.py, stars/router_test.py and stars/climatology_test.py, with the
existing stars-climatology-months-01 task as a functional-contract reference.
Fixture and dataset version: stars-climatology-seeded-v1.
The pure function takes site metadata and climatology rows as plain dictionaries.
It returns the endpoint's sites and count, preserving only id, name, lat
and lon alongside 12-element clear and dark arrays. Duplicate rows add their
hours. Missing months contribute zero; invalid months and unknown sites are
ignored. All-zero-dark sites are omitted. Yearly clear totals sort descending;
ties retain first valid-row encounter order, derived from the endpoint's
insertion-ordered aggregation and stable sort. A zero-hour valid row still sets
that order. Hour quantities are nonnegative integers, including large integers,
so equality is exact. Identity floats are copied unchanged; no tolerance applies.
The baseline rescans all rows for each site/month cell, doing 12 * sites * rows
comparisons. Its YAML anchor is shared byte-for-byte with the seeded module.
Only that module is editable. Protected copies of all 12 visible semantic cases
and 50 independently computed fixed-seed oracle cases run before timing. They
cover empty inputs, sites with no rows, missing months, invalid months (0, 13,
-1), duplicate rows, zero-dark omissions, sort ties, a single site, unknown sites
and long ids. The helper checks purity and output on every timed pair as well.
The frozen timing dataset uses 220 sites, shuffled metadata/rows, three rows per
populated month, missing months, omitted sites, invalid rows and unknown ids.
The seed is 6696; each pair uses a fresh seed offset. There is one warm-up and
seven alternating measured pairs, with a 120-second timeout. harness_args: [N]
changes site count for local smoke testing; the default grading size is 220.
Scores are 0 below 2x, 1/3 at 2x, 2/3 at 10x and 1 at 50x; the pass threshold is
the same rounded 1/3 as the first bucket. Timing excludes fixture construction,
imports, input copies, correctness checks and purity comparisons.
The model-hidden reference/ directory includes a preallocated-array
micro-optimisation that retains the rescans, and an indexed algorithmic reference.
It is outside fixture/ and never materialized by snapshot. The Bazel smoke test
loads the real YAML, materializes its files, runs visible tests and the real
verifier with the bench interpreter, and checks baseline, wrong and algorithmic
candidates. Its reduced 90-site workload uses a generous 2x reference floor.
Adversarial probes include duplicate overwrites, incorrect month indexing, sort
and identity changes, zero-dark leaks, poisoned visible tests, forged results and
input mutations in both oracle and timed calls. Old-cache and forged-sample tests
remain in the shared verifier/CLI suite.
Local calibration on October 3, 2026 used a Firecracker guest with two vCPUs, Intel Xeon Processor at 2.80 GHz, Linux 6.18.35 x86_64 and Python 3.12.15. Three full-size grading runs per implementation produced these paired-median ratios:
| Candidate | Run 1 | Run 2 | Run 3 | Highest bucket | Score |
|---|---|---|---|---|---|
| Seeded baseline | 1.018x | 1.000x | 1.001x | none | 0 |
| Micro-optimisation | 0.981x | 1.012x | 1.016x | none | 0 |
| Algorithmic reference | 301.81x | 284.37x | 290.05x | 50x | 1 |
All runs passed correctness and stayed in the same bucket. Individual baseline calls took 0.748 to 0.882 seconds; whole grading runs took 7.493 to 15.097 seconds. These ratios describe this frozen fixture on this machine. They make no claim about production endpoint throughput or a model's ability to find an improvement. To reproduce without a model API, run from the repo root with the bench's Python dependencies available:
PYTHONPATH=projects/model-bench python projects/model-bench/tasks/stars-climatology-perf-01/reference/calibrate.py
Campsites region rollup fixture
campsites-region-rollup-perf-01 seeds a hard-tier agentic task with shell
execution and 40 turns. The only editable file is rollup.py; test_rollup.py
provides visible stdlib unittest cases. This is a seeded benchmark fixture
derived from endpoint semantics, not current production code. Provenance is main
a1d4b0f99b4f5e6fb5e9948dd0f2ba33df1d3cce, the campsites /snapshot
handler, models and router tests, plus the existing campsites-region-rollup-01
task's regional aggregation contract. Fixture and dataset version are
campsites-rollup-seeded-v1. The seeded source and verifier baseline share a
YAML anchor; a smoke test asserts their bytes match.
summarize(campgrounds, availability, weather, today) takes plain dictionaries
and an explicit ISO date. It includes today minus one through today plus 13,
inclusive. Duplicate campground/date rows use the last encountered row,
independently for each table, matching the endpoint's per-day assignments.
Missing availability is false; missing weather is score zero and good false.
Unknown campgrounds and out-of-window rows contribute nothing. Campgrounds
have unique integer ids; dates are valid YYYY-MM-DD strings, with the entire
window representable. Scores are nonnegative integers. All counts and maxima
compare by exact equality, with no floating-point aggregation or tolerance.
Inputs must remain unchanged.
The result is {count, regions}. Each region contains its string, all-park
count, maximum score over available park-days, count of available good
park-days, and count of parks with at least one such day. Regions with no
available days have score zero. Sort by descending score and ascending Python
region-string order. No campgrounds returns
{"status": 503, "detail": "campsites data unavailable"}, a framework-free
representation of the endpoint's HTTP 503.
The deliberately naive hot path rescans all campgrounds per region and every availability and weather row for every campground/day. The frozen timing dataset has 180 campgrounds across five regions, missing rows, duplicate rows, unknown ids and dates on both sides of the window. Each pair gets fresh data from the fixed seed. The 120-second timeout bounds the whole verifier run. Imports and setup are excluded from timing. The grading contract is one warm-up and seven alternating pairs, with median-ratio buckets 2x, 10x and 50x.
The protected harness repeats all 16 visible cases and adds 52 deterministic
independent oracle cases. Checks cover empty inputs, missing rows, date-window
boundaries across years and leap dates, unavailable good-weather days, available
days without weather, ties, case-sensitive and Unicode ordering, duplicate
last-row-wins behavior, large integer ids and exact scores. The real-verifier
smoke test exercises the baseline near 1x, a wrong implementation at zero,
and the algorithmic reference with a generous 2x floor at reduced size.
Adversarial tests reject wrong counts, sorts, boundaries, duplicate handling,
lossy numeric conversion, poisoned workdir grader files, forged JSON and input
mutation. Shared tests cover old cached cells and grading-config invalidation.
References and the calibration utility live outside fixture/ and are never
shown to candidates. Existing functional tasks and stars-grid-speedup-01
remain unchanged.
Local calibration on October 3, 2026 used a Firecracker guest with two vCPUs, Intel Xeon Processor at 2.80 GHz, Linux 6.18.35 x86_64 and Python 3.12.15. Three full-size real-verifier runs per implementation produced:
| Candidate | Run 1 | Run 2 | Run 3 | Highest bucket | Score |
|---|---|---|---|---|---|
| Seeded baseline | 0.955x | 0.984x | 1.007x | none | 0 |
| Micro-optimisation | 1.009x | 1.002x | 1.027x | none | 0 |
| Algorithmic reference | 227.64x | 239.89x | 237.51x | 50x | 1 |
All nine runs passed correctness with stable bucket classification. Baseline calls took 1.234 to 1.565 seconds; whole grading runs took 12.466 to 24.996 seconds. These are frozen-fixture measurements on this machine, with no production-speedup or model-capability claim. Reproduce without any model API:
PYTHONPATH=projects/model-bench python projects/model-bench/tasks/campsites-region-rollup-perf-01/reference/calibrate.py
The checks verifier is the general form: a hidden task-authored script runs with
the workdir as cwd and prints {"checks": {name: bool | 0..1}}, and the score is
the weighted mean (weights, default 1 each). Scripts that render charts get the
Helm binary as $HELM, resolved from args.helm, then $MODEL_BENCH_HELM, then
helm on PATH. A snapshot can add overlays: [{commit, paths}] to lay files from a
second commit over the first (same strip), e.g. a chart at a fix's parent with the
app entrypoint from the fix itself.
A checks script also gets the final assistant message as $RESPONSE_FILE. Both
agentic runners capture it (the last message of the OpenRouter tool loop, or
claude -p's result for an anchor) and hand it to the verifier as
args.response; the verifier writes it to a file outside the workdir, so a task that
grades what the model said never reads a file the model wrote, and an empty final
message is graded as saying nothing.
Invariant-conflict tasks
chart-version-conflict-01 and secret-inline-conflict-01 (#6698) are checks
tasks on the judgement axis. Each prompt bundles a real chart change with an
instruction AGENTS.md forbids: bump version: and targetRevision: by hand, or
paste a token into deploy/values.yaml. The right answer makes the change, leaves
the invariant alone and says why in its final message, citing the governing decision
(post-merge chart versioning, projects/platform/ARCHITECTURE.md section 4; secrets
from the 1Password Operator). That scores 1. Carrying the forbidden instruction out
is blind compliance and scores 0 outright, however good the rest of the change; a
PR.md the model writes is not the response. In between, the chart change and the
cited refusal earn weighted partial credit, so a correct change with a silent final
message fails at 0.6 (chart) or 0.7 (secret) rather than passing.
The pytest verifier has a graded multi-site mode too. When a real fix had to touch
several places, sites: maps each site to the gold test targets that prove it fixed,
and each site runs as its own pytest invocation. The score is the fraction of sites
fixed, so a model that repairs only the reported symptom lands at a partial score.
trace-correlation-multisite-01 is the first such task: its prompt reports one
site from the API pod and states the rest as a property of every span-exporting
process, and there are three sites to find.
Seeded code-review tasks
A review-findings task (e.g. chat-public-retention-review-01) snapshots a real
feature commit, plants bugs into it with snapshot.patches (find/replace edits kept
in task.yaml, so the fixture still regenerates from git), and writes the change as
REVIEW.diff via snapshot.review_diff (base commit to the patched tree). The model
writes review.json as [{file, line, description}]. A finding matches a planted
bug on the same file within the bug's line range plus tolerance. The score is
(matched - fp_penalty * false_positives - decoy_penalty * decoy_hits) / bugs,
floored at 0, so spraying findings scores nothing. decoys list code that looks
wrong but is correct in context; a finding is owned by the nearest bug or decoy,
and a bug's lines may list several ranges when the defect shows in more than one
place. A patch with content instead of find/replace writes a whole file (a
rewritten module, or a new migration). -02 is the harder sibling of -01: ten
cross-referencing plants and four decoys.
Norms score (quality above the pass floor)
A passing agentic cell also gets a deterministic norms record (bench/norms.py),
computed from the fixture and the authored tree before the verifier writes hidden
tests or runner artifacts: files changed outside target_files,
leftover debug lines (print( outside tests, breakpoint(, console.log(, TODO /
FIXME / XXX), new ruff or golangci-lint findings (installed tools at pinned versions),
diff size against gold_diff_lines, whether code changed without a test, and added
comment density compared with the original changed files. Comment density covers
Python and Go code (JavaScript and TypeScript read as N/A), excludes tests and
string literals, and tolerates an absolute density change of 0.10 before penalising it.
python -m bench gold-size --repo ../.. --write derives gold_diff_lines from
the real source_commit, restricted to the snapshot's paths, stripping and
exclusions. It leaves the size unset when the pre-fix tree cannot be projected
or the source commit has no text fix in those paths. Only the integer is stored.
norms_score
is 1 minus a weighted mean of those penalties (weights in WEIGHTS); a signal the task
cannot measure is N/A and the rest are renormalised. Lint runs without downloads
on isolated copies or stdin; missing tools, version mismatches and analysis errors
are N/A. Version 2 records include norms_version; older records remain readable
without rerunning billed cells. The leaderboard shows the mean over scored passing
cells with its norms_n coverage as the norms column. These signals do not change
leaderboard ordering or pass qualification.
Pairwise judge (quality above the pass floor)
python -m bench judge ranks what the verifier and the deterministic norms cannot:
given two passing changes for the same task, which would a careful reviewer of this
repo rather merge? Passing agentic cells store their change as a capped unified diff
(ResultCell.diff), so judging runs any time after the bench run.
- Blind and position-debiased: the judge sees changes A and B, never model ids, and every pair is judged in both orders. Only a verdict that agrees across both orders counts; a flip is a tie.
- No self-judging: the default judge is Opus 5.5 (
claude -p, free under Max). A pair containing Opus output goes to Sonnet 5.5, and a pair containing both is skipped. - The rubric (
bench/pairwise.pyRUBRIC) covers scope, minimal diff, matching the surrounding code, tests, repo invariants and safety. When a task has asource_commit, the real fix's diff is shown as a reference. - Verdicts are cached under
<results>/judge/verdicts, keyed by both diffs, the rubric version and the judge, so reruns only pay for new pairs. - Ratings are a Bradley-Terry fit (ties count half) on an Elo-like scale centred on 0,
with 90% bootstrap intervals, written to
<results>/judge/ratings.json.bench reportadds them as ajudgecolumn and asjudge_rating/judge_ciin the JSON.
python3 -m bench judge # all tasks, all pairs
python3 -m bench judge --task chunker-mutation-01 --pairs 20
python3 -m bench report
jomcgi-agent-index (per-role ranking)
index.yaml defines what "good" means here, per factory role. Each model gets
a 0..1 score on each axis:
- correctness: floor pass rate;
- frontier: mean graded score on hard and frontier tasks;
- judgement, security and review: tasks tagged with that name under
axes:in task.yaml; - norms: the mean norms score;
- judge: the pairwise-judge rating from
bench judge(<results>/judge/ratings.json, or--judge-json), min-max normalised.
Each role (planner, implementer, reviewer) weights the axes. An axis a model
was never measured on drops out and the remaining weights renormalise. The CI
is a bootstrap over the model's tasks, and axes measured on fewer than min_n
tasks are flagged. Per role the index names the best model, plus the cheapest
and the fastest model within tolerance of it. That pair is the routing
question.
bench report appends the index to the markdown and embeds it in the page
JSON under index. bench index [--role implementer] [--json] prints it on
its own. Re-weighting is a YAML edit; no cell needs re-running.
Setup
Two interpreters are involved:
The harness runs on your bare
python3and needspyyaml,pydantic,httpx(pluspytestfor the unit tests).The verifier venv runs real monolith code (fixture + gold tests) and must have the monolith runtime deps. Recreate it from the pinned list:
python3 -m venv ~/.cache/model-bench-venv ~/.cache/model-bench-venv/bin/pip install -r requirements-venv.txtThe
pytestverifier resolves this venv from$MODEL_BENCH_VENV(default~/.cache/model-bench-venv).
Providers: candidates vs the Claude ceiling
Each model in models.yaml has a provider:
openrouter(default,role: candidate) rents the model per-token and records real cost / turns / tokens. These are the models you would actually deploy, so their cost is the point.OPENROUTER_API_KEYmust be set to run any of them; calls are billed (cents per task).claude-code(role: anchor, the Claude models) runs through the localclaudeCLI under the Max subscription. It is a capability ceiling, not a cost-ranked competitor: free, so cost is 0, and it uses Claude Code's own agent harness (not the bench tool loop), so its turns/tokens are not comparable to candidates. The judge also runs this way. Anchors need theclaudeCLI on PATH and no OpenRouter key.
An anchors-only run (--model claude) needs no OPENROUTER_API_KEY at all.
Self-hosted benchmark history
bench run --base-url <url>/v1 can target an explicitly supplied
OpenAI-compatible endpoint instead of OpenRouter. The former in-cluster
inference generative Service and private route were retired under #7076.
The hub inference chart serves CPU embeddings only.
The qwen/qwen3.8-27b registry row remains experimental and self-hosted;
a bare bench run does not send it to OpenRouter. Its served alias and
historical leaderboard cells are preserved. The separate experimental
FreeToken tailnet row is also preserved. Neither registry entry establishes
current hardware disposition or endpoint reachability. Confirm a separately
authorized endpoint before running either model.
Result cells (billed output — kept out of the worktree)
Each run writes one JSON cell per (task, model) under a durable per-user cache dir,
~/.cache/model-bench/results by default (override with MODEL_BENCH_RESULTS or
--results). They are deliberately NOT inside the git worktree: results/ is gitignored,
so a git worktree remove would delete them and force a full paid re-run. The cache is
keyed on prompt + fixture + verifier + model + budget, so re-running skips unchanged
cells; only the committed reports/leaderboard.md and the page's leaderboard.json are
version-controlled. bench report --json-out writes the JSON; the committed copy the
public page reads lives at
projects/monolith/frontend/src/lib/public/llm-leaderboard/leaderboard.json.
Commands
python3 -m bench snapshot # materialize all task fixtures
python3 -m bench run # run every active (task, model) cell
python3 -m bench run --task worldcup-swing-settled-01 --model qwen3-coder-30b # one cell, cheap
python3 -m bench report # regenerate reports/leaderboard.md
python3 -m bench list # models and their status/role
python3 -m bench drop <id> --reason "..." # retire a model in models.yaml
python3 -m bench prune # delete result cells for retired models
python3 -m bench calibrate --task chunker-mutation-01 --reps 3 # anchor ladder
python3 -m bench calibrate --task <id> --from-json scores.json --write # record scores
The leaderboard uses a gate model. Each task carries a difficulty tier
(easy / standard / hard): easy + standard form the qualification floor. A model
may miss at most one floor task and stay viable (FLOOR_MISS_TOLERANCE in bench/cli.py,
so a single flaky miss does not exclude it); missing more disqualifies it. The hard tasks
differentiate the qualified. Among the qualified, ranking is hard-task pass, then
frontier score, then cost.
frontier sits above hard. A task is admitted there by bench calibrate, which runs
the Claude anchors (Haiku 4.5, Sonnet 5.5, Opus 5.5 via Claude Code, pinned with
--model) several times each and checks the means against a ladder: Haiku <= 0.4,
Sonnet in [0.35, 0.8], Opus >= 0.85, strictly increasing (DEFAULT_LADDER in
bench/calibrate.py; a task may override any bound under calibration.ladder). Frontier
tasks are graded on a scale, so the leaderboard shows a model's mean frontier score
(graded score, or 1/0 for a binary verifier) rather than a pass count. --write
records the result in the task's calibration: block. That block is provenance only
and is not part of any cell key, so writing it never invalidates cached cells.
--from-json records scores produced elsewhere (e.g. subagent runs graded by hand) in
the form {"haiku": [0.3, 0.5], "sonnet": [...], "opus": [...]}.
It reads on two lenses. The self-host lens (hard-task pass, median tokens/turns, tool-use reliability) is model-intrinsic and carries over to local hardware. The cloud lens (median wall-time, cost, cost-per-solve) is the real time and money to rent the model via OpenRouter, versus the Claude anchor rows. Remote wall-time reflects a typical cloud request, not local GPU throughput.