Repository navigation
feat(repo): compare flags comparisons with no headroom - #114
Conversation
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #114 +/- ##
=======================================
Coverage 91.67% 91.68%
=======================================
Files 223 223
Lines 27505 27562 +57
Branches 4525 4540 +15
=======================================
+ Hits 25216 25269 +53
- Misses 1620 1623 +3
- Partials 669 670 +1
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
🟡 Changes recommended
Pooling legacy and current score cells can incorrectly report a ceiling despite unknown legacy maxima.
1 open issue
What changed in this PR
Adds headroom notes to repository comparisons to distinguish saturated task results from evidence of no effect.
Changes:
- Classifies gate and score comparisons as ceiling, floor, or near-ceiling.
- Records task score maxima for score comparisons.
- Adds tests, contract documentation, and a changelog entry.
| File | Description |
|---|---|
| dokimi/tests/test_repo_run.py | Checks persisted score maxima. |
| dokimi/tests/test_repo_compare.py | Tests headroom classifications and text output. |
| dokimi/dokimi/repos/runner.py | Records score_max in results. |
| dokimi/dokimi/repos/compare.py | Computes and displays headroom flags. |
| dokimi/docs/repo-evaluation-contract.md | Documents headroom behavior. |
| dokimi/changelog.d/20261018_120000_napetrov_feat-repo-compare-ceiling.md | Announces comparison notes and metadata. |
Review effort: Balanced
antonwolfy
left a comment
There was a problem hiding this comment.
The rule is sound and the legacy-batch handling after c0c29af holds up, but the note misreads a regression, and the tests leave much of the new logic unpinned. I'd fix the inline risks before merge.
What I ran, on a Linux review host at c0c29af:
make ci-local: 19 passed, 0 failed, 4 skipped; changed-line coverage gate passes.- Reproductions for the code findings, with the LLM transport mocked (cells built with the
_cellhelper oftest_repo_compare.py). - A mutation run over
headroom(),_task_values,_headroom_note, the_compare_modelwiring and therunner.pyscore_max: 36 mutants, 16 of which passtest_repo_compare.pyandtest_repo_run.py(99 tests).
Test gaps (the mutants that survive):
- The tolerance and the floor condition are unpinned.
value >= topwithoutEPS,value >= top - 0.5, and a floor that checks only the baseline, only the treatment, or usesorall pass. Every fixture uses exact 0, 1 orscore_maxvalues. Add a case with a mean of reps just under the top (for example 0.6 of 1) that must stayNone, and a floor case where only one arm is at 0. len(pairs) >= 2is unpinned. Changing it to>= 1or>= 3passes. Add a one-task comparison with the baseline at the top and the treatment below: it must beNone.headroomon non-primary rows is unpinned.if True else Noneatcompare.py:257passes, because the only check is cost 0.1, whereheadroom()returnsNoneanyway. Use a cost of 1.0 on every task.- Pooling cells with different
score_max, and the treatment's own top, are unpinned.max(maxes)instead ofNone, and using the baseline's top for both arms, both pass. The new regression test covers only the unknown-max case.
Checked, no issues
- Holm and p:
headroomis added afterpaired_summary;pandp_holmare unchanged on a ceiling row. - JSON:
headroomisnullon cost, turns and metric rows; gate and score are flagged separately in a mixed task set. - Legacy batches: cells with and without
score_maxpooled for one task giveNone, as do cells with different maxima. - Repo rules: the changelog fragment follows
changelog.d/README.md; no versioned schema orwith_docs/without_docskey is touched.
There was a problem hiding this comment.
Warning
Copilot couldn't run its full agentic review because it didn't start before the timeout. Make sure your repository has a runner available, or add a copilot-code-review.yml file specifying one with the runs-on attribute. See the docs for more details.
Copilot review overview
3 open issues
Review effort: Lite
| "harness": opts.profile.name, "harness_version": meta["harness_version"], | ||
| "base_date": meta["base_date"], "score_task": bool(task.score), "agent": agent, "grade": grade} | ||
| "base_date": meta["base_date"], "score_task": bool(task.score), | ||
| "score_max": task.score["max"] if task.score else None, "agent": agent, "grade": grade} |
There was a problem hiding this comment.
No change. Tasks are validated before they run: manifest.py _check_task (repo validate) requires score to be {metric, max} with a numeric max, and grading.py already indexes task.score["metric"] on the same path. A malformed task is rejected at validate time rather than silently treated as an unknown top here.
antonwolfy
left a comment
There was a problem hiding this comment.
Review at fce566b. The five points from my earlier review are fixed: a collapse no longer reads as near_ceiling, grader-failed pairs stay out of floor, differing tops give null, and the runner and contract are pinned. Two things are left: the near_ceiling note still misreads a grader failure, and the new grader-failure and tops logic is only partly pinned by the tests.
What I ran, on a Linux review host at fce566b:
make ci-local: 19 passed, 0 failed, 4 skipped.- Reproductions for the code findings, with the LLM transport mocked (cells built with the
_cellhelper oftest_repo_compare.py). - A mutation run over the new code in
headroom(),_compare_modeland_task_values: 20 mutants, 9 of which passtest_repo_compare.pyandtest_repo_run.py(102 tests).
Checked, no issues
- Holm and p: unchanged;
headroomis still only a note. - Mixed sets: a gate
ceilingand a scorefloorin one batch are flagged separately, and the note wording matches the data. - Legacy cells: no
score_maxis never counted as the top; two legacy arms at 0 still givefloor. - Runner:
task.score["max"]cannot raise, because the manifest loader rejects a score withoutmax.
|
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 9b0c90e. The three fixes since fce566b are right: any grader failure gives null, a pair counts as at the top only when both arms tie, and the new tests pin EPS, the treatment side of floor, and the two-task minimum. Two points inline: a test gap in the tops wiring, which I'd fix before merge, and a nit on the contract text.
What I ran, on a Linux review host at 9b0c90e:
make ci-local: 19 passed, 0 failed, 4 skipped; changed-line coverage 100% (35 lines).- A mutation run over
headroom()and the_compare_modelwiring: 30 mutants, 7 of which passtest_repo_compare.pyandtest_repo_run.py(121 tests). Most are equivalent or sub-EPS; thetopswiring below is the one that matters.
Checked, no issues
- Earlier points:
any(failed)vsall(failed), failure on either side,>= 2,base_top.count(False) <= 1, the treatment-not-below check and theat_topEPS are all killed by the suite. - Past
score.max: 90 vs 80 on max 80 givesNone; 90 vs 90 givesceiling. NaN scores and a NaN or infscore_maxgive no flag. - Runner:
score_maxis written fromtask.score["max"], which the manifest check requires to be numeric. - Repo rules: the changelog fragment matches the code; no versioned schema covers
result.jsonor the compare output.
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 6ffb878. Both threads from my last review are fixed: the tops wiring is pinned by the new floor test, and the contract sentence reads correctly. One test gap is left, and one nit on the changelog.
What I ran, on a Linux review host at 6ffb878:
make ci-local: 19 passed, 0 failed, 4 skipped; 5,210 tests; changed-line coverage gate passes.- A mutation run over
headroom(), the_compare_modelwiring,_task_valuesand therunner.pyscore_max: 53 mutants, 10 of which passtest_repo_compare.pyandtest_repo_run.py(122 tests). Six are equivalent or differ only at float noise; the four that matter are inline. - Offline reproductions of
headroom()on unknown tops, with the LLM transport mocked.
Checked, no issues
- Earlier points: the treatment's top in
floor,any(failed), the two-task minimum,count(False) <= 1, the tops-mismatch guard and therunner.pyscore_maxlink are all killed by the suite. - Holm and p: unchanged;
headroomis only appended to the row, and isnullon cost, turns and metric rows. - Repo rules: the changelog fragment is in the right place and format; no versioned schema covers
result.jsonor the compare output.
|
antonwolfy
left a comment
There was a problem hiding this comment.
LGTM. Both threads from my last review are fixed: the tolerances are pinned at their boundaries and the changelog states the tops rule correctly. One nit inline.
What I ran, on a Linux review host at 40802bd:
make ci-local: 24 passed, 0 failed by the PR; changed-line coverage gate passes. The twogitleaksjobs only fail to download the tool; a directgitleaksrun over the PR's commits finds no leaks.- A mutation run over
headroom(), the_compare_modelwiring,_task_values,_headroom_noteand therunner.pyscore_max: 35 mutants, 3 of which passtest_repo_compare.pyandtest_repo_run.py(156 tests). The three are equivalent or differ only at the EPS float boundary. The four mutants from my thread (top - 0.1,baseline - 0.1,< 0.5, gate top0.5) all fail now. - Offline reproductions of
headroom()andrepo compareon pooled, legacy, mixed-top and multi-model cells, with the LLM transport mocked.
@xaleryb's question on agent-outage cells read as floor waits for the author.
Checked, no issues
- Holm and p: unchanged;
headroomisnullon cost, turns and metric rows and is the only new key on a compare row. - Legacy cells: no
score_max, or a mix of recorded and missing, or differing maxima, never giveceiling; no reader ofresult.jsonrejects the new key. - Docs and rules: the docstring, contract bullet and changelog fragment match
headroom()for ceiling, floor, near_ceiling and thenullcases; the fragment followschangelog.d/README.md; no versioned schema is touched.
|
On the agent-outage point: fixed in b8cbee4. |
|
|
On the exit-1 harness with no stdout (xaleryb): fixed in 0eb5eae, as decided by the owner. |
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 0eb5eae. The agent-outage rule from b8cbee4 and 0eb5eae does what the contract says, and the earlier threads' fixes still hold. One test gap in the new rule is worth closing before merge, and one nit on wrapping.
What I ran, on a Linux review host at 0eb5eae:
make ci-local: 25 passed, 1 failed, 5 skipped; 6,687 tests; changed-line coverage gate passes. The one failure ispip-audit, reporting a new gitpython 3.2.0 advisory (CVE-2026-114145, fix in 3.2.1) in the existing locks; it is not caused by the PR.- A mutation run over
_agent_outage()and its wiring in_task_values: 16 mutants, 3 of which passtest_repo_compare.py(107 tests). The three are inline. - Reproductions of rc, turns and
timed_outcombinations throughcompare, with the LLM transport mocked.
Still open from my earlier review:
dokimi/docs/repo-evaluation-contract.md:858-861: the paragraph was re-wrapped, but the outage clause added in b8cbee4 / 0eb5eae brought back lines of 86, 90 and 104 characters, against 70 to 79 around them. Re-wrap from "elsenull; a grader failure" to "A pair".
Checked, no issues
- Outage rule: an error result with no turn count or at most one turn, and a non-zero exit with no turn count that is not a timeout, both give
nullon gate and score rows. Killing theis_errorcheck, thenot turnschecks,not timed_out, theorjoin,anyover reps, and the_task_valueswiring each fail the suite. - Holm and p: unchanged;
grader_failedfeeds onlyheadroom(). - Docs: the module docstring, contract bullet, changelog fragment and
headroom()docstring all name the same outage rule.
A zero delta on a task set where both arms pass (or fail) every paired task reads as "no effect" but means the tasks had no room to move. Gate and score comparisons now carry headroom: ceiling, floor, near_ceiling (baseline at the top on all but at most one of 2+ tasks) or null, with a note in the text report. p values and Holm are unchanged. result.json records the task's score_max so score rows know their top. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…ore_max
When legacy cells (score_max=None) and current cells (score_max=1.0) appear
for the same task, do not report ceiling if legacy maxima are unknown.
Previously, _finite() would drop None values, leaving only {1.0}, which
incorrectly triggered the ceiling headroom flag. Now check if any cell has
an unknown max before setting top.
Fixes the case where both arms average 1.0 but some cells have unknown
score_max values.
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…lure or mixed score.max as no signal near_ceiling needs the treatment nowhere below the baseline, floor ignores pairs with a grader failure, and pairs whose tops differ have no headroom. Pin score_max in result.json to the task's score.max with a max-4 task, and rewrap and complete the contract bullet. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
… near-ceiling signal Headroom now returns None when the one non-top baseline pair is a grader failure, and tops and failures are computed only for gate and score rows. Adds tests for failures on either arm, on one rep, and for mixed or unknown tops; fixes the changelog and docstring to match the rules. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
A grader failure scores 0, which reads as the top when score.max <= 0: every cell failing on a max-0 score task was reported as ceiling. Floor and near_ceiling already skipped such rows; ceiling now does too, and the three per-branch checks collapse into one. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
score.max is what the oracle must reach, not a bound. A treatment scoring 90 against a baseline of 80 on max 80 (p 0.008, 8 up) was flagged ceiling, and one baseline at 70 made it near_ceiling. A pair counts as at the top only when both arms are there and tied. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
EPS on float means, the treatment side of floor, the two-task minimum of near_ceiling, a small loss, one known top per task, and no flag on cost or turns rows: each of these could be dropped with the suite still green. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
…ntract headroom text The mixed-tops test only covered the ceiling and near_ceiling paths, so reading the baseline's top for both arms passed the suite. Add a floor case where both arms score 0 and only the treatment's top differs. Also fix the garbled headroom sentence in the repo evaluation contract and say that non-gate rows carry null. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…g top wording Tolerance cases for headroom() and a gate top of 1.0 that a 0.5 mutant would miss; the changelog no longer says unknown tops give null, since only tops that differ do (a batch without a recorded top is never ceiling). Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
2025887 made a pair at the top only when both arms reach it and tie, but the docstring, the contract and the changelog still said "at the top": 1.0 vs 1.3 on max 1 read as ceiling or near_ceiling there, and gives null. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
An error cell whose num_turns is not a number ("1" from a hand-edited or
foreign result.json) raised TypeError in the outage check and aborted the
whole repo compare. It now counts as no turn count, as the turns row does.
Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
The module docstring, which the PR names as the rule's reference, listed only a grader failure as unmeasured; an all-outage task set read as floor there and gives null in the code, the contract and the changelog. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
…utage in compare headroom Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
…the agent outage rule Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
bb1e4f6 to
e077e13
Compare
|
Re-checked at e077e13: every earlier input passes (error cells with no turns, harness exit 1/137/-9 with no result end to end through |
Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
antonwolfy
left a comment
There was a problem hiding this comment.
LGTM. The outage rule, the tops rule and the unchanged p and Holm all hold at this head, and the three open threads are closed. One nit inline.
What I ran, on a Linux review host at d68fee9:
make ci-local: 29 passed, 0 failed, 7 skipped; 7,295 tests; changed-line coverage gate passes.- A mutation run over
headroom(),_agent_outage(),_task_values,_headroom_note, the_compare_modelandformat_comparewiring and therunner.pyscore_max: 46 mutants, 5 of which passtest_repo_compare.pyandtest_repo_run.py(176 tests). The five are equivalent, differ only at the EPS float boundary, or sit behind a guard that is dead code (rcis always an int). - Reproductions through
compare()andformat_compare()with the LLM transport mocked: oddrc,num_turnsandscore_maxvalues (str, float, bool, NaN, negative) raise nothing and serialise to JSON.
Checked, no issues
- Holm and p: 30 random multi-model batches with grader failures, errors and mixed turns give byte-identical
compare()output at the base and at the head, apart from the newheadroomkey. - Docs: the module docstring, the contract bullet, the changelog fragment and the
headroom()and_agent_outage()docstrings all state the same ceiling, floor, near_ceiling, tops, grader-failure and outage rules as the code. - Repo rules: no versioned schema covers
result.jsonor the compare output; no new doc page; no generated files; the tests are offline. - Readers: no other reader of
result.jsonor of the compare output is affected by the new keys.
Copilot's point on task.score["max"] raising KeyError does not apply: the manifest check requires a numeric score.max before repo run starts.
Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
antonwolfy
left a comment
There was a problem hiding this comment.
LGTM. I pushed a commit per nit, up to 050c642, which fix the nit from my review:
050c642:dokimi/dokimi/repos/compare.py:518: replaced the three-line comment with the one-line wording from the suggestion, which names the grader failure and the agent outage (see_agent_outage).
make ci-local at 050c642: 29 passed, 0 failed, 7 skipped.


Summary
A zero delta with no p reads as "no effect". On a task set where both arms pass every paired task, it means the tasks had no room to move. On oneDAL a compile-check tool cut new build breaks from 5.9 to 0.4 per run with Haiku 4.5. With Haiku 5.5 and Sonnet 5.5, both arms passed 9/9, and
repo comparewould have shown that as no effect (#41, #65).Real output for 3 tasks × 3 reps, where both arms pass every cell:
guidance:raw vs guidance:none, model m-1 measure tasks baseline treatment delta [95% CI] p p_holm up/down/tied gate 3 1.000 1.000 +0.000 [+0.000, +0.000] - - 0/0/3 cost_usd 3 0.100 0.100 +0.000 [+0.000, +0.000] - - 0/0/3 turns 3 10.000 10.000 +0.000 [+0.000, +0.000] - - 0/0/3 + gate ceiling: both arms pass all 3 paired tasks; no headroom to show an effectresult.jsonnow recordsscore_max, so score rows know their top. Batches recorded before this change never getceilingon score rows; they can still getfloor.compare.pydocstring and the contract'srepo comparebullet. A changelog fragment is added.Evidence
Before: two arms that both pass every task show a zero delta, no p, and nothing else.
After:
tests/test_repo_compare.pycovers the cases:score.max, an unknown max (cells disagree), and floor that includes a grader failure.64 passed.
tests/test_repo_run.pyasserts thatresult.jsoncarriesscore_max.make ci-local: 0 failed, 19 passed, 4 skipped. The skipped jobs need docker or network, run only on PRs, or are semgrep.Merge Danger
Door: two-way
The change adds one JSON field to the
repo compareoutput and one toresult.json; nothing is removed or renamed.Blast Radius: small
Readers of
repo compare --jsonsee a newheadroomkey. Text output gains a line only under flagged comparisons.🤖 Generated with Claude Code