fix(evals): stop the judge grading mixed runners or skipping a grown condition set - #162
Open
Matthew-Selvam wants to merge 1 commit into
Open
Conversation
…condition set `group_responses()` keyed groups on (case_id, trial) alone and assigned `groups[key][condition] = response`. run_evals.py keys its own resume on (case_id, trial, condition, runner), so one responses file legitimately holds several runners; the later runner's rows overwrote the earlier runner's, and every condition was then graded from whichever runner happened to be written last, with no warning and exit 0. Raise on the collision instead. The judge's own resume key had the same shape: `judged` was a set of (case_id, trial), so a group judged in an earlier pass under a narrower --conditions was skipped forever. Judging baseline+candidate and appending comparator responses afterwards is the documented flow, because run_evals.py writes one condition per invocation; the rerun printed "skip judged" for every group, wrote no comparator rows, and exited 0, after which `run_evals.py score` gated on the two conditions that happened to be present. Record the conditions covered by each written group instead. Re-judging a partially covered group is refused with the missing conditions named, because the scorer rejects duplicate rows: re-judging would fail there anyway, and skipping is what lost the condition silently.
Matthew-Selvam
force-pushed
the
fix/judge-duplicate-and-partial-resume
branch
from
September 10, 2026 09:14
8c9b1bc to
ab89b39
Compare
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.
Summary
scripts/judge.pysilently graded the wrong thing in two ways. Neither produced an error, a warning, or a non-zero exit.1. A responses file with two runners was graded as one.
group_responses()keyed groups on(case_id, trial)alone and assigned into a dict:run_evals.pykeys its own resume on(case_id, trial, condition, runner), so a file holding rows from two runner invocations is a shape the harness produces by design. The later runner's row overwrote the earlier one, and every condition was then scored from whichever runner happened to be written last — reported under the conditions of the file, with no indication that half the input was discarded.Before (4 rows in, 2 retained; the
clauderows are gone):2. A group judged under a narrower
--conditionswas skipped forever.judgedwas a set of(case_id, trial), so--conditionswidening between passes changed nothing:Judging
baseline+candidateand appendingcomparatorresponses afterwards is the documented flow, becauserun_evals.pywrites one condition per invocation.evals/README.mdadvertises the comparator as a supported third condition, andrun_evals.py scorethen gates on the two conditions that happen to be present while the operator believes three were measured.Observable behavior
(case, trial, condition)ValueErrornaming the case, trial, and condition--conditionsafter a narrower passValueErrornaming the group and the conditions it lacks--conditionsWhy the second case raises instead of re-judging
The scorer rejects duplicate rows for one
(case, trial, condition)(_check_pairinginrun_evals.py), so re-writing the earlier conditions would fail later anyway. Skipping is what lost the condition without a word, so the remaining honest option is to refuse and name the two ways forward. This is a behavior change to a documented flow: the error text says to point--outputat a fresh file, or remove the rows first.Authorship and provenance — select exactly one
Agent/tool and model/version: Hermes Agent (deepseek-v4.1-flash) ran the review and wrote the fix and tests. Four parallel review subagents (same tool) each audited one subsystem; the two defects above came from the evals-harness reviewer, were then reproduced independently by the submitting agent before any code was changed. Model/version of the subagents was not recorded by the harness.
Agent contribution: Identified both defects, reproduced each against
mainbefore touching code, wrote the fix, wrote four regression tests, and verified by mutation that each test fails against the pre-fix source.Human verification: The submitting human (Matthew-Selvam) reviewed the complete diff, read
scripts/judge.pyandtests/test_judge.pyin full, and ran the commands in the Verification section below. The reproduction transcripts above were produced by the agent and re-run by the human.Known limitations or uncertain results: The duplicate-runner defect was reproduced with synthetic rows; no real two-runner
responses.jsonlexists in the repo to test against. Raised rather than fixed by adding the runner to the group key, because grouping by runner would silently halve the compared set instead of reporting the mixed file — a maintainer may prefer the other repair.scripts/judge.py --retriesstill bills retries that a failed group discards from the reported total (the harness's own comment says the ledger feeds budget decisions); that is a separate defect and is not fixed here.Labels
Target label: Target:Evals
Author label: Author:Hybrid
Workflow labels: bug
Safety and side effects
Side effects, permissions, network access, and cost: None. No new files, no network, no paid model calls: the tests use
sh -cstub runners on temporary files, matching the existing pattern intests/test_judge.py. The change only turns two silent-wrong-result paths into errors.Compatibility
Migration or rollback notes: Rolling back restores the silent behavior; no data is modified by the change. The only operator-visible difference is that a rerun with widened
--conditionsover an existing--outputnow stops with an actionable message instead of writing nothing and reporting success. The unchanged--conditionsresume path keeps its existing "skip judged" output and exit 0, verified by test.skills/i-have-adhd/SKILL.mdand its.cursormirror are untouched, so the sync check is unaffected.Verification
python3 -m unittest discover -s tests— 46 tests, OK (baselineorigin/mainis 43; 3 new: duplicate-condition rejection, the single-runner control case, and the end-to-end widened---conditionsguard)python3 scripts/run_evals.py validate—Evaluation cases are valid.git diff --check main..HEAD— cleantest_two_responses_for_one_condition_are_rejected_not_overwritten→ FAILED, confirming the test detects the defect rather than passing vacuouslytest_a_group_judged_under_narrower_conditions_is_not_silently_skipped→ FAILEDmain, before the fix, with synthetic two-runner input: 4 rows in → 2 retained,clauderesponses dropped (transcript above)main, before the fix: second pass with--conditions baseline candidate comparatorprintedskip judged direct-answer/trial 1and exited0Behavior evals: Not run. This changes the eval tooling itself, not the ruleset, so a baseline/candidate comparison of the skill would not measure it; the unit tests above exercise the changed code paths directly. No model calls were made and no cost was incurred.
Final accountability