feat(batch): report Stage 1 and Stage 2 together, never one alone - #340
Open
vaibhavdabas16 wants to merge 1 commit into
Open
feat(batch): report Stage 1 and Stage 2 together, never one alone#340vaibhavdabas16 wants to merge 1 commit into
vaibhavdabas16 wants to merge 1 commit into
Conversation
The end-of-batch stats printed a single headline number:
78/156 intercepted
which is the number TIGER-AI-Lab#243 says must never stand on its own. Raw Stage-1
interception overcounts success by about 2x -- the interceptor sees the
right request, the judge is what decides whether it carried the right
intent. On V2, 156 intercepted runs came back with 78 judge-confirmed:
50% precision. Quoting interception alone makes an agent look twice as
good as it is; quoting the judged rate alone hides how much of the corpus
the interceptor reached.
batch-summary.json was worse off: its totals are job statuses, where
"passed" already folds both stages into one verdict, so the file carried
no Stage-1 number at all. Anything downstream had to re-walk every run
directory to recover it.
- Split the run-directory walk out of print_run_stats into
collect_run_rows(), which now also reads judge_match and the judge
model from run-meta.json.
- Add stage_totals() and format_stage_totals(). The formatter refuses to
render Stage 1 alone: with --no-judge it says stage 2 was not run
rather than letting interception stand as the score.
- Per-run table gains a Stage1/Stage2 column pair.
- batch-summary.json gains a "stages" block; job-status totals unchanged.
A judge call that failed after retries counts as stage2_unjudged, not as
a stage-2 failure -- it needs re-judging, it is not a verdict. For the
same reason stage2_rate is null rather than 0.0 when no judge ran.
Scope note: the leaderboard rows and README tables TIGER-AI-Lab#243 also asks about
live outside this repo (the HF dataset and the website). This covers the
artifacts this repo produces, and documents the rule in eval/scoring.md.
This was referenced Sep 2, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Closes #243 for the artifacts this repo produces.
The end-of-batch stats printed one number:
That is precisely the number #243 says must never stand alone. Raw Stage-1 interception overcounts success by about 2×: the interceptor sees the right request, the judge is what decides whether it carried the right intent.
batch-summary.jsonwas worse off than the console. Itstotalsare job statuses, wherepassedalready folds both stages into one verdict — so the file carried no Stage-1 number at all. Anything downstream had to re-walk every run directory to recover it.What changed
print_run_statsintocollect_run_rows(), which now also readsjudge_matchand the judge model fromrun-meta.json.stage_totals()andformat_stage_totals(). The formatter refuses to render Stage 1 alone: with--no-judgeit says stage 2 was not run rather than letting interception stand as the score.Interceptedcolumn becomesStage1/Stage2.batch-summary.jsongains astagesblock. Job-statustotalsare unchanged.Two judgment calls
A failed judge call is not a Stage-2 failure.
judge_match: nullmeans no verdict exists — an HTTP 402, a timeout, an unsupportedapi_type. Counting it as "the judge said no" would push the run onto the model's record for an outage. It lands instage2_unjudged, and the console saysn awaiting a verdict. This matches the reasoning already inis_judge_inconclusive(#299 / #338).stage2_rateisnull, not0.0, when no judge ran. A zero reads as "the judge rejected everything", which is the same misreading in the other direction. Same forstage1_precision.Rendered in the table as
-, distinct fromno.Scope
#243 also asks for this on "every leaderboard row and headline". Those live outside this repo — the HF dataset (
leaderboard/results.csv) and the website. This PR covers the artifacts this repo produces, and writes the rule down ineval/scoring.mdwith the empirical table above, so the leaderboard change has something canonical to point at. Happy to open a follow-up against the website/dataset if you want the same treatment there — say the word and I'll do it rather than guess at the right repo.rescore-summary.jsonandclawbench-analyzealready report both stages; the new docs section tabulates all four artifacts side by side so the invariant is visible in one place.Verification
219 passed, 4 skipped(213 onmain+ 6 new). The pre-existingtest_host_tasks.py::…[v1-lite]failure is a Windows symlink artifact present onmain, unrelated.Six tests in
tests/test_two_stage_reporting.pybuild a batch output directory with a known split — 3 intercepted, 1 judge-confirmed, 1 unjudged — and assert the counts, the precision, both null-vs-zero behaviours, that the summary line always names both stages and the judge model, thatbatch-summary.jsoncarriesstageswhile leavingtotalsintact, and that an empty batch does not divide by zero.Merge notes
Touches
runner/batch.py, as does #339 (imports anddetect_enginethere,print_run_stats/write_summary_jsonhere — no overlapping lines).CHANGELOG.mdconflicts textually with my other open PRs; trivial either way.