Skip to content

Add swebench_bench.py: gold-check, pre-registered selection, post-hoc scoring, paired comparison - #236

Open
t-timms wants to merge 2 commits into
mainfrom
feat/swebench-gold-check-scoring
Open

t-timms wants to merge 2 commits into
mainfrom
feat/swebench-gold-check-scoring

Conversation

@t-timms

@t-timms t-timms commented Sep 24, 2026 •

Copy link
Copy Markdown
Owner

Why

Four steps of a SWE-bench evaluation that I did by hand on a real run (Qwen3.8-27B vs KAT-REAP50 on a 16 GB GPU), each of which gave a wrong or unverifiable number when skipped or done loosely (details in docs/benchmark_scoring.md):

  1. 8 of 23 SWE-bench Lite dev tasks cannot be resolved even by the gold patch in the currently published images (all 5 pvlib: NumPy 2 removed np.Inf; pyvista: missing libGL.so.1; 2 pydicom: test-infrastructure AttributeError). A score on them measures nothing. scripts/validate_driver.py's default smoke set contained one of them (Make the agent-in-loop verify oracle work with swebench 5.x #231 noted this).
  2. The task set must be fixed before any agent runs, or it can be tuned to a result.
  3. The in-loop verify tool is an oracle; final patches should be scored afterwards by the official harness.
  4. I first computed a paired sign-test result by hand and got the count wrong (5 strictly-ahead tasks instead of 4, p 0.06 instead of 0.125). Pooling draws of the same tasks as independent samples also overstates the evidence.

Changes

  • scripts/swebench_bench.py with subcommands gold-check, score, compare (pure, tested logic for the Wilson interval, exact sign test, pre-registered selection, failure classification and report parsing; harness calls behind swebench 5.x flags, interpreter from GODSPEED_SWEBENCH_PYTHON, dataset SWE-bench/SWE-bench_Lite).
    • selection: first N gold-valid tasks of a seeded random order (prefix-stable, independent of input order).
    • score: last prediction per instance; empty patches never reach the harness; Wilson 95% (clamped to [0, 1]); agent minutes and solved/hour from the runner's metrics.
    • score fails closed: a task the harness could not score (error_ids, infra_failure_ids, incomplete_ids, or missing from a report that accounts for every task, e.g. a rate-limited image pull) is not counted as unresolved. score exits 3, saves the details to <out>.incomplete.json and does not write <out>, so a resumable driver re-scores. --allow-harness-errors writes it anyway with complete: false and the problems recorded. ambiguous_failure is recorded but still counts as unresolved.
    • compare: per-task solved-draw counts, strictly-ahead counts, ties, exact two-sided sign test, pooled Wilson intervals labelled descriptive.
  • scripts/validate_driver.py: default smoke set swaps pvlib__pvlib-python-1606 for pydicom__pydicom-1256 (gold-verified); docs/adding_a_driver.md updated.
  • docs/benchmark_scoring.md, CHANGELOG.

Verification

  • tests/test_swebench_bench.py: 36 tests (5 cover the fail-closed scoring: fatal vs ambiguous categories, an old-schema report, exit 3 with no score file, --allow-harness-errors, ambiguous-only). Fixtures are real: the 23 dev instances with the gold-check result, the per-task outcomes of six scored draws, and the tasks selected on 2026-09-24. They pin the reported intervals (53-98, 41-93, 22-78, 60-91, 31-69), the sign test (4 vs 0 -> 0.125), and reproduce the previously chosen 8-task set from seed 20260924.
  • Against the real harness and Docker (swebench 5.0.2, cached images) on the 2026-09-24 predictions: gold-check 8/8 valid; score on the six draws gave 7/8, 6/8, 6/8 and 4/8, 4/8, 4/8, identical to the earlier scoring; compare gave 4 strictly more / 4 ties / 0 behind, p = 0.125, pooled 19/24 (59.5-90.8%) vs 12/24 (31.4-68.6%).
  • ruff check . and ruff format --check . clean.

Not verified

  • Fail-closed scoring against the real harness (swebench 5.0.2): while Docker Hub's anonymous pull budget was exhausted (ratelimit-remaining: 0, 100 per 3600 s), scoring one task whose image was not local made the harness report error_ids: [sympy__sympy-17139] with an empty unresolved_ids. The old code would have counted that as an unresolved task; the new code exited 3 and wrote no score file (score.incomplete.json only). The unit tests fake the subprocess. Across 80 harness reports from the earlier run, none had error, infra_failure or incomplete entries (two had one ambiguous_failure each), so the earlier headline numbers are unaffected.
  • gold-check (plain, --sample 3 --seed 20260925 --split test) was run end to end on the Lite test split (3/3 gold-valid), including the datasets load; the larger 40-task selection was driven by a separate chunked wrapper, not by this subcommand.
  • The failure classifier only knows the signatures seen so far; anything else is reported as "no known infrastructure signature" (still listed as invalid).
  • The full test suite was not re-run on this branch (CI runs it); the touched areas pass.

…post-hoc scoring, paired comparison

Automates four steps of a SWE-bench evaluation, each learned on a real run where skipping it
gave a wrong or unverifiable number:

- gold-check: run the official harness with the GOLD patch on every candidate task. In the
  currently published images 8 of 23 SWE-bench Lite dev tasks do not resolve even with it
  (all 5 pvlib: NumPy 2 removed np.Inf; pyvista: no libGL.so.1; 2 pydicom: test-infra error),
  so a score on them says nothing about the agent. The failing test output is classified into
  a one-line reason.
- selection: the first N gold-valid tasks of a seeded random order (prefix-stable, independent
  of input order), fixed before any agent runs, so the task set cannot be tuned to a result.
- score: score the final patches (last prediction per instance) afterwards with the official
  harness, independent of any in-loop verify tool; empty patches never reach the harness;
  Wilson 95% intervals (clamped to [0, 1]), agent minutes and solved per hour from the
  runner's metrics.
- compare: paired per-task comparison of two arms over several draws with an exact two-sided
  sign test on the tasks where they differ, next to pooled Wilson intervals (descriptive only:
  draws of the same tasks are not independent samples).

Also: scripts/validate_driver.py's default smoke set replaced pvlib__pvlib-python-1606, whose
gold patch no longer resolves in the current image, with pydicom__pydicom-1256 (gold-verified);
docs/adding_a_driver.md updated; docs/benchmark_scoring.md; CHANGELOG.

Tests: tests/test_swebench_bench.py (31), with the real 23-instance dev list and gold-check
result and the per-task outcomes of six scored draws as fixtures; harness runs are faked.
@github-actions

Copy link
Copy Markdown

🤖 Godspeed Review

LLM review skipped (no API key configured).

Security

No secrets detected in changed files.

`swebench_bench.py score` treated every task not in `resolved_ids` as unresolved. A task the
harness never scored (an image pull refused by Docker Hub's anonymous rate limit, a container that
did not start) landed in `error_ids` / `infra_failure_ids` / `incomplete_ids` and was silently
counted as a failure, deflating that arm's score with no sign in the output.

`score` now reads those lists (and any task missing from a report that accounts for every task),
exits 3 without writing `--out`, and saves the details to `<out>.incomplete.json`. A resumable
driver therefore re-scores instead of treating the draw as finished. `--allow-harness-errors`
writes the score anyway, with `complete: false` and `harness_problems` recorded. An
`ambiguous_failure` is recorded but still counts as unresolved. `compare` warns on draws scored that
way.
@github-actions

Copy link
Copy Markdown

🤖 Godspeed Review

LLM review skipped (no API key configured).

Security

No secrets detected in changed files.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant