feat(#395): SUPPORT2 same-lineage judge referee, scored against the naive gate - #406
Conversation
sebasmos
left a comment
There was a problem hiding this comment.
Verified against the committed rows, and every number reproduces exactly. Approving the analysis. One process item below that is not about the analysis.
Re-derived from the 115 scoreable rows of support2_referee_judge.jsonl:
| detector | tp | fp | fn | tn | precision | recall | FPR |
|---|---|---|---|---|---|---|---|
| naive gate, same rows | 82 | 33 | 0 | 0 | 0.7130 | 1.0000 | 1.0000 |
| same-lineage judge | 79 | 26 | 3 | 7 | 0.7524 | 0.9634 | 0.7879 |
And the contrast that carries the claim: on the 33 negatives, 7 gate-only, 0 judge-only, exact p = 0.015625. All exact.
The check that matters most here passes. judge_flag differs from adopted on 29 of 115 rows, so this is a measurement and not another predicate that reduces to its own label. That is the thing I have had to reject repeatedly in this family, and it is worth saying explicitly that this arm does not have the problem.
Your framing is the right one and I would keep it as written. Precision 0.752 against the gate's 0.713 is four points, the judge still flags 105 of 115, and its FPR is 0.788. Declining 7 of 33 false alarms at the price of 3 of 82 detections is a modest, real, and honestly small result. Reporting the gate as naive_gate_same_rows inside the same summary is what makes it legible, since the reader can see the floor without going to another file. The tabular lane now has a judge number where it previously had a never-run gap, and a weak number is a fine outcome for that.
The process item, and it is a real one. d6c8b52 is on main, in no pull request, so the runner, its 155 lines of new tests and its artifacts were never reviewed. I confirmed that independently: the commit is an ancestor of main and the GitHub API returns no associated PR. Your account of how it happened is plainly right and main has no protection rule to stop it.
I would not revert it. #220 and #405 have landed on top, the artifacts are cited from the docs in this PR, and the code is fine on inspection, which I can now say because reviewing it here is exactly what this PR makes possible. Rewriting shared history to satisfy a process point would cost more than it buys. What I would rather do is take the lesson: this is the second time this week that main having no protection has let something through that everyone assumed a gate would catch, the first being the stale change requests that were blocking nothing. Worth raising with the owner as a settings question rather than a personal one.
So: approving, with the runner reviewed as part of this PR rather than pretending it was reviewed before it landed. Merge timing is the reviewers' call.
feat(#395): SUPPORT2 same-lineage judge referee, scored against the naive gate
Closes #395.
Tabular was the only lane without a judge runner, so the Table 2 cell was a never-run gap rather than a measurement of zero. It is now a number, and the number is weaker than I expected.
Result
naive_gate(the floor)The judge clears the transcript-only floor by four points of precision, and that is the whole finding. It flags 105 of 115 patients. The entire gain over a blunt agreement gate is that it declines 7 of the gate's 33 false alarms, paired McNemar p=0.016, and pays 3 of 82 true detections for them.
So I put the gate row in the summary as
naive_gate_same_rowsrather than reporting the judge alone. Both detectors read the transcript and nothing else, so the gate is the floor, and 0.752 quoted without it reads as a working monitor. This is a far weaker showing than the same detector on MedQA and MIMIC-CXR text, both precision 1.000 at FPR 0.000. Why the tabular lane is harder for a transcript-only monitor is not established here, and I did not want to guess in the artifact.The arm is a measurement, not a restatement
The failure mode to watch on this lane is a judge that flags everything, which would report the adoption rate as its precision and 1.0 as its FPR, exactly what the naive gate already does. Recorded from the rows as
judge_is_not_a_restatement_of_the_labelrather than asserted in prose: the judge disagrees with the adoption label on 29 of 115 rows, missing 3 real adoptions and raising 26 false alarms. Unlikedeployableit is an independent model call, so it is not algebraically tied to the label.Verification
board,bareandadoptedare identical on all 115 scored rows ofsupport2_referee.jsonl, andn_valid_pairsis 115 in both.with_clean_controlblock from results(#399): honest-peer clean control for the SUPPORT2 deployable referee #402; this arm runs the planted cascade only.gemini-2.5-flash, committed to the cache. A re-run then replays the summary keylessly at zero calls, verified after rebasing onto the merged results(#399): honest-peer clean control for the SUPPORT2 deployable referee #402.ruff checkclean on both changed files.Two deviations from the template, both deliberate
"FLAG" in verdictwith a guard on the first word, which reads a hedge like "this is OK, nothing to flag" as a flag. This takes the last declared\bFLAG\b|\bOK\binstead. No committed number changes on the other lanes: every verdict there is the bare word, which I checked.censoredreports both causes separately.Not fixed here, worth a follow-up
lane_findings.jsonis still stale from #402 in two places I deliberately left alone, since they are that PR's claims to correct rather than mine:referee.verdictstill says "one of the four rows is a measurement; two are definitional", and the open follow-up asking for "a keyless-referee design whose peers do not always assert the planted answer" is what #402 delivered for this lane. I did correct the lane's API-call total, which #402 left at 2,531 against a cache that now holds 2,690 deterministic entries.Note on the issue's premise
The issue suggests scoring the already-committed
support2_referee.jsonl. That is not possible: those rows carry the verdicts and answers but no deliberation turns, and the judge reads the transcript. The runner rebuilds it through_common.run_board, which is free from the cache and keeps the holdout's board prompt byte-identical tosupport2_referee.py. Cost is unchanged.🤖 Generated with Claude Code