EPS
← All tasks·#2109Completed

Judge parser drops prose-then-trailing-integer responses (recovers ~8-28% of dropped draws)

kind: infraparent: #2091
track:

Highlight text on any card (body, plan, or an event) to anchor a comment, or leave a whole-task comment here. Mention @claude to summon a reply.

No comments yet.

Overview / Motivation

eval/utils.py::parse_judge_json + eval/graded_judge.py::_score_from_parsed silently DROP judged draws whose response is reasoning prose followed by a trailing bare integer — a shape Sonnet emits routinely. The score is present in the response and mechanically recoverable; the parser discards it, and drop-never-coerce (llm-judging rule 9) then records it as a legitimate content-drop. Measured drop rates across banked #1739 DVs suggest this class accounts for a large share of existing dropped draws.

Goal

Recover the prose-then-trailing-integer judge response shape so real judgments are not discarded, WITHOUT changing the score of any response that currently parses.

Evidence (measured on #2091, 2026-08-06)

Five failing draws re-issued through the exact production instrument (claude-sonnet-4-5-20250929, max_tokens=1024); all returned stop_reason=end_turn at 329-403 output tokens — no truncation, no refusal:

shapeparse_judge_jsonoutcome
bare 0int 0scored
prose + {"score": 5}dictscored
prose + {"score": 75}dictscored
prose + trailing bare 70Nonedropped
prose + trailing bare 70Nonedropped

parse_judge_json returns json.loads(text) verbatim and recovers a trailing JSON OBJECT after prose but not a trailing BARE INTEGER. _score_from_parsed already accepts a bare in-range numeric (its docstring documents Sonnet's envelope-less habit — the #778 r3 fix) but only when the response IS just the integer.

Dropped-draw rates in banked #1739 DVs (per_rollout_scores None), which this class plausibly dominates:

celldrawsdroppedrate
evil trait rungs53,33015,07628.27%
wildchat / hallucination10,0008308.30%
wildchat / sycophancy10,0002282.28%
wildchat / evil10,0001781.78%
sycophancy trait rungs86,5203710.43%

A #2091 pilot arm measured 28.0% (14/50) on hallucination_trait × wildchat.

Constraints (load-bearing)

  • Conservative anchor only. Recover a trailing line that is EXACTLY an integer in [0,100]. NEVER "last integer anywhere in the text" — these responses contain prose numerals ("14TB disk 5", "8TB disk 4") that a greedy scan would capture as scores.
  • Bit-identical on currently-parsing responses. Pin with a regression test over the existing parsed shapes (bare int, prose+JSON object, plain JSON object); any score change on those is a FAIL.
  • Drop-never-coerce is UNCHANGED. REFUSAL, non-numeric, and out-of-[0,100] returns still drop (rule 9). This recovers a parseable score that exists, never invents one.
  • Report the recovery rate. Emit a counter distinguishing recovered-trailing-integer draws from genuine content-drops so the change's effect is measurable rather than invisible.

Cross-arm comparability warning (READ BEFORE APPLYING TO ANY IN-FLIGHT TASK)

Banked judge caches store ONLY the parsed result (13-byte {"score": N} files) — the raw judge text is NOT retained, so banked arms CANNOT be re-parsed under an improved parser without re-judging. Any experiment comparing a freshly-judged arm against a banked arm therefore faces an instrument split if the new parser is applied to one side only. #2091 deliberately did NOT adopt this fix mid-run for exactly that reason (see its epm:progress v35). Landing this fix must not silently change the instrument for in-flight comparisons — coordinate with any live task reusing banked judgments.

Provenance

Diagnosed during #2091's G2 judge pilot gate (2026-08-06). Full analysis, including two refuted hypotheses and the banked-vs-greedy rate comparison, is in epm:progress v35 on task #2091.

Activity