Skip to content

feat(batch): report Stage 1 and Stage 2 together, never one alone - #340

Open
vaibhavdabas16 wants to merge 1 commit into
TIGER-AI-Lab:mainfrom
vaibhavdabas16:feat/report-both-scoring-stages
Open

feat(batch): report Stage 1 and Stage 2 together, never one alone#340
vaibhavdabas16 wants to merge 1 commit into
TIGER-AI-Lab:mainfrom
vaibhavdabas16:feat/report-both-scoring-stages

Conversation

@vaibhavdabas16

Copy link
Copy Markdown
Contributor

Closes #243 for the artifacts this repo produces.

The end-of-batch stats printed one number:

78/156 intercepted

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.

Model (v2) intercepted judge-confirmed precision
gemini-3.1-pro-preview 69 37 54%
gemini-3.5-flash 66 33 50%
aggregate 156 78 50%

batch-summary.json was worse off than the console. 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.

What changed

  • 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.
  • Added 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: the single Intercepted column becomes Stage1 / Stage2.
  • batch-summary.json gains a stages block. Job-status totals are unchanged.
Case                Model                   Actions  Screenshots  Recording   Duration  Stage1  Stage2
------------------------------------------------------------------------------------------------------
v2-1-shopping-etsy  gemini-3.1-pro-preview       12            1     8.4 MB     7m00s  yes     yes
v2-2-shopping-etsy  gemini-3.1-pro-preview       12            1     7.1 MB     7m00s  yes     no
v2-3-shopping-etsy  gemini-3.1-pro-preview       12            1     9.0 MB     7m00s  yes     -
v2-4-shopping-etsy  gemini-3.1-pro-preview       12            1     6.2 MB     7m00s  no      -

stage 1 (intercepted): 3/4 (75%)  |  stage 2 (judged, deepseek-v4-pro): 1/4 (25%)  |  stage-1 precision: 33%  |  1 awaiting a verdict
"stages": {
  "runs": 4,
  "stage1_intercepted": 3,
  "stage1_rate": 0.75,
  "stage2_judged_match": 1,
  "stage2_rate": 0.25,
  "stage2_unjudged": 1,
  "stage1_precision": 0.3333,
  "judge_models": ["deepseek-v4-pro"],
  "judge_ran": true
}

Two judgment calls

A failed judge call is not a Stage-2 failure. judge_match: null means no verdict exists — an HTTP 402, a timeout, an unsupported api_type. Counting it as "the judge said no" would push the run onto the model's record for an outage. It lands in stage2_unjudged, and the console says n awaiting a verdict. This matches the reasoning already in is_judge_inconclusive (#299 / #338).

stage2_rate is null, not 0.0, when no judge ran. A zero reads as "the judge rejected everything", which is the same misreading in the other direction. Same for stage1_precision.

Rendered in the table as -, distinct from no.

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 in eval/scoring.md with 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.json and clawbench-analyze already 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 on main + 6 new). The pre-existing test_host_tasks.py::…[v1-lite] failure is a Windows symlink artifact present on main, unrelated.

Six tests in tests/test_two_stage_reporting.py build 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, that batch-summary.json carries stages while leaving totals intact, and that an empty batch does not divide by zero.

Merge notes

Touches runner/batch.py, as does #339 (imports and detect_engine there, print_run_stats/write_summary_json here — no overlapping lines). CHANGELOG.md conflicts textually with my other open PRs; trivial either way.

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.
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.

Always report both Stage-1 (interception) and Stage-2 (judged); raw interception overcounts ~2×

2 participants