Repository navigation
Conversation
…ompare repo report gives each row the mean of every process count over the cells that have one; repo compare adds them as descriptive measures (process:<key>, counter:<name>), outside Holm's family. An absent or null process is skipped, so older batches compare as before. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
|
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #177 +/- ##
==========================================
+ Coverage 91.64% 91.67% +0.03%
==========================================
Files 222 223 +1
Lines 27409 27459 +50
Branches 4508 4517 +9
==========================================
+ Hits 25118 25173 +55
+ Misses 1620 1617 -3
+ Partials 671 669 -2
🚀 New features to boost your workflow:
|
antonwolfy
left a comment
There was a problem hiding this comment.
Review at c301aa6. The measure plumbing is clean: Holm is untouched, null and absent process are skipped, not zeroed, and the report JSON only gains a key. One bug at the edge, where only one arm carries process, and one test gap. One nit on the docs.
What I ran, on a Linux review host at c301aa6:
make ci-local: 20 passed, 8 failed, 7 skipped. The 8 failures (ruff, zizmor, pre-commit, bandit, mypy, deptry, mkdocs, pip-audit) exited 127, tools missing from my venv:make devstops at the tools install on this base, which #179 fixed onmain. Static checks, mypy and the strict docs build pass on GitHub's CI at this head. The test step passed: 7073 passed, 21 skipped. Changed-line coverage 100% (48 lines).- A reproduction of the
unpairedfinding, with no model or network access. - A mutation run on the new tests: 34 mutants, 10 of which pass the 4 new tests (see the inline comment on the test).
Checked, no issues
- Holm:
PRIMARYis still gate and score, and the process rows keepp_holmNone. - Scoping: only arms named in the compared selectors add process measures; there is no name clash with
metric:,scoreorcost_usd. - Values: bool, NaN, strings and a null
processare skipped;guidance_readcounts the list length. - Report:
summarizeonly adds aprocesskey per row; a row without it prints no extra line.
| base_vals = _task_values([c for c in cells if selector_matches(base_sel, c)], metrics) | ||
| rows: list[dict[str, Any]] = [] | ||
| unpaired: list[dict[str, Any]] = [] | ||
| measures += _process_measures(cells, [baseline, *treatments]) |
There was a problem hiding this comment.
When only one arm carries process, every shared task becomes an unpaired entry for every process measure, and the real unpaired tasks scroll out of view.
Setup: 12 tasks, with process only on the treatment arm (the baseline comes from a batch written before process existed, the case the PR description says compares "as they do today"), plus one task zz that has no baseline cells.
compare([batch], "guidance:none")["unpaired"]has 73 entries, 1 of them real (zz,no cells in baseline). The other 72 areno process:tool_calls in baseline,no process:commands in baseline, and so on, 6 per task.format_compareprints the first 10 and thenand 63 more:unpaired: zz (m-1, no cells in baseline), a (m-1, no process:tool_calls in baseline), b (m-1, no process:tool_calls in baseline), ... and 63 more. With a few more tasks,zzis cut off.--jsonunpairedgrows by about 6 entries per task.
The PR introduces this: on the base the list holds only tasks that are really missing. test_process_absent_or_null_is_skipped (test_repo_process_counts.py:65) pins the per-task entry for the partial case, but not this all-tasks case.
Suggestion: in _process_measures, keep a measure only when both the baseline and at least one treatment report it for some task. Or keep a process measure's missing-side entries out of unpaired, since a descriptive measure absent from an arm is not a left-out task.
There was a problem hiding this comment.
Fixed in 75bd017: _compare_model skips a process:/counter: measure when the baseline or the treatment reports it for no shared task, so an older baseline batch adds no unpaired entries. Test: test_process_on_one_arm_only_does_not_flood_unpaired (unpaired is just zz; fails on the old code). The partial case (one task null) still lists that task.
| summary = compare([tmp_path], "guidance:none") | ||
| assert [r["measure"] for r in summary["comparisons"]][3:] == ["process:tool_calls"] | ||
| assert _row(summary, "process:tool_calls")["n_tasks"] == 1 | ||
| assert {(u["task"], u["measure"]) for u in summary["unpaired"]} == {("a", "process:tool_calls")} |
There was a problem hiding this comment.
The tests do not pin "a missing value is skipped, not counted as zero" within a task, or the selector scoping of _process_measures.
Mutation check: each of these passes all 4 tests in this file.
_process_lists:v.get(name)changed tov.get(name, 0.0), so a rep without the counter counts as 0 and drags the per-task mean down._process_measures: the selectors[baseline, *treatments]changed to[baseline], or totreatments, or theselector_matchesfilter changed toif True.
The fixtures give every rep of a task the same value, and the null case nulls a whole task, so the mean over reps is never checked. Only two arms are used, so scoping is never checked.
Test to add: one task with three reps where rep 3 has no process (expect the mean of the other two in compare's process:tool_calls row), and a batch with a third arm that carries a counter the compared arms lack (expect no counter:<name> row).
There was a problem hiding this comment.
Added in 75bd017: test_a_missing_process_value_in_a_task_is_not_zero (rep 3 without process, mean of the other two) and test_process_measures_come_only_from_the_compared_arms (a third arm with its own counter yields no row). I did not rerun the full mutant set.
| - The report gives, per (task, arm, model), passes over finished cells with a | ||
| Wilson 95% interval (score-only tasks: the mean of `score.metric`), grader | ||
| failures (counted as failures too), missing cells, and mean cost and turns. | ||
| When cells carry `result.json` `process` (§4.1), a line under the row gives |
There was a problem hiding this comment.
Nit: this sentence points to §4.1 for result.json process, but §4.1 at this PR's base describes events.jsonl and does not define process, its keys (tool_calls, failed_commands, guidance_read, counters) or the manifest process_metrics. Nothing at the base writes the field either, so until #157 lands the report and compare never show it.
Suggestion: merge after #157, so the §4.1 reference resolves and the field is written.
There was a problem hiding this comment.
…are's unpaired list When the baseline batch predates result.json process, every shared task became an unpaired entry for every process measure and hid the tasks that were really missing. A process measure an arm reports for no task is now left out. Pins the missing-value-is-not-zero mean and the selector scoping. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Part of #41 (follow-up to D4, #157).
Summary
repo reportandrepo comparenow show theresult.jsonprocesscounts. #157 adds those counts; this PR is useful once #157 lands. It does not stack on #157: the code readsprocessdefensively, so an absent ornullprocessis skipped and older batches report and compare as they do today.dokimi/repos/ +├── process_counts.py # process_values / process_means / process_order / process_line ├── report.py # row["process"] = per-arm means; one "process ..." line under the row └── compare.py # measures += process:<key>, counter:<name> (descriptive, not in Holm)process:{tool_calls,commands,failed_commands,denied_commands,guidance_read}(guidance_readis the number of paths read), pluscounter:<name>for each manifestprocess_metricscounter.compare, a counter becomes a measure as soon as any compared cell reports it. Values are per-task means over the cells that have the counter, with a t interval and a raw p, the same way as cost and turns.Evidence
repo compareshowed only gate/score/cost_usd/turns/metric rows.After: output on a synthetic batch:
tests/test_repo_process_counts.py(syntheticresult.jsonwith and withoutprocess): measure order and deltas,p_holmNone, null/absent skipped (not zero), non-counts ignored, report means and the single line. Fullpytest -q: 7072 passed;ruff check .andmypyare clean. No function's radon CC went up.Merge Danger
Door: two-way
The change only adds new keys and rows, and nothing is written to disk.
Blast Radius: narrow
A consumer that expects a fixed measure list in
repo compare --jsonwill see extra rows when cells carryprocess. There is a one-line import conflict with #153 incompare.py; it does not conflict with #155 or #157.🤖 Generated with Claude Code