Repository navigation
feat(arms): keep the judge's treatment material in the artifact - #69
Conversation
The judge's context for each arm lived in an in-memory store for the answering
run only, so anything reading an arms artifact later — `eval cross-judge
--judges`, a library re-judge — gave its judge the 200-char report snippets
instead of what the arms run's judge saw (28 characters against 11,079 in one
review), and `cross-judge` had to refuse such comparisons outright.
* `ArmRunner.build_output` stamps each arm record with `judge_context_sha256`
and writes `judge_materials` ({digest: text}) once per distinct text: a skill
arm's body is the same for every question and is stored once (~4,500 chars
for the perf skill, +0.3% on an 81-question run). Judged or not, so an
answers-only artifact can be judged later on the same evidence. Each stored
score also names the material it saw.
* `JudgeContextStore.from_artifact` rebuilds the store from a file, skipping
(and warning about) any text missing or not matching its digest, so that
answer falls back to snippets visibly rather than being judged on other text.
* `cross-judge`: re-scored judges get the file's material, so `--judges` no
longer refuses an arm whose material the file keeps. `--pairwise` and
`--checklist` still do — those judges see only the answers.
* `arms.v1` gains `judge_materials` (additive; `schema_compat` OK).
Part of intel#48; the `judge-validity --context stored` mode builds on this.
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #69 +/- ##
==========================================
+ Coverage 87.94% 89.24% +1.30%
==========================================
Files 196 204 +8
Lines 22288 23747 +1459
Branches 3546 3837 +291
==========================================
+ Hits 19601 21193 +1592
+ Misses 2005 1848 -157
- Partials 682 706 +24
🚀 New features to boost your workflow:
|
|
Reviewed together with #73 (see the comment there for the full run): the artifact round trips, digest determinism (also when the same skill is loaded from another path), dedupe and tamper handling (mismatching text, missing digest or material are skipped with a warning, NUL and unicode round-trip) hold, and One defect on this head: |
antonwolfy
left a comment
There was a problem hiding this comment.
The persistence design is sound: digests on the records and scores, one copy per distinct text, tamper check on read, and --judges no longer refused when the file keeps the material. The gate that decides when --judges may run is weaker than the reader it protects, and the new CLI path has no test. The first two inline comments are the ones I'd fix before merge; the other three risks can follow, plus three nits.
What I ran, on a Linux review host at 656cf2c:
make ci-local: 18 passed, 1 failed, 4 skipped. 4300 tests passed, 1 skipped; changed-line coverage passes (about 96%, 55 of 57 lines; the two uncovered lines are thecontinues infrom_artifactandcite). The one failure isschema_compat: the branch is 4 commits behindmain, so the check reportstool_preference.v1.jsonandtool_preference_groups.v1.jsonas removed. The PR's own line is a WARNING (newjudge_materialsproperty, no base fixture writes it). A rebase clears it.- Reproductions for the code findings, with the LLM transport mocked (
eval cross-judge --judgesdriven through the real parser with a fakeJudge). - A mutation run: 31 mutants of the new code against
test_arm_runner.pyandtest_judge_reliability.py; 20 killed, 11 pass the suite. Five of the 11 are equivalent; six are real gaps (see the inline comments).
Open from the earlier review, still unaddressed at this head: the lone-surrogate crash, the hand-damaged-file crashes in from_artifact, and the stale cross_judge.py docstring. Each has an inline comment with the reproduction.
Checked, no issues
- Artifact round trip: a judged run through JSON into a fresh
ArmRunnerwithfrom_artifactyields identical contexts andtreatment_material.v1. Int ids, including0, round-trip. The real writer sets onlyquestion_id, soutils.question_id(rec)andrec["question_id"]agree. - Ordering:
citeruns afterjudge(), on the same record objects that get saved, so the record digest and the score digest come from the same store. It is idempotent. - Schema: a real
build_outputvalidates againstarms.v1, a badjudge_materialskey is rejected, andschema_compatflags only the new optional property. No version bump is needed. - Writers:
ArmRunner.saveis the only writer ofarms.v1;plugin_delta,model_compare,matrix_rollupandarms_reportonly read it. - Repo rules: no generated artifacts, tests offline,
with_docs/without_docsuntouched, no new doc page needing anav:entry.
- THREAD 0: Strengthen gate to validate reproducibility with from_artifact store - THREAD 1: Add four CLI end-to-end tests for --judges path coverage - THREAD 2: Handle lone surrogates in digest_text with surrogatepass - THREAD 3: Add guards for non-string digests and malformed records in from_artifact - THREAD 4: Expand test_an_artifact_without_materials_falls_back_as_before coverage - THREAD 5: Fix stale docstrings in unreproducible_arms, judge_reliability comment, and get warning - THREAD 6: Clarify doc wording to specify --judges path and wrap long line - THREAD 7: Remove blank line at EOF in judge_context.py Fixes ensure gate checks that artifacts can be loaded, not just that digests exist; improve test coverage for --judges CLI path; handle edge cases in malformed files; and update docs to reflect new behavior. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
- Convert qid to str before passing to store.put (accepts str, qid is str|int) - Rename local gate_store to avoid redef with module-level materials variable - Add Optional type hint for materials variable initialization Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
- THREAD 0: Fix gate to check --judges + --pairwise refusal first - Use raw question_id (str|int) in store, not str(qid) - Fix mypy with Union[str,int] type hints, not str() conversion - Guard against non-dict evaluations and arms - Make non-string digests unreproducible, not pass - Use configured baseline_arm variable, not hardcoded 'baseline' - Sort arm names in error message for determinism - THREAD 1: Add test for --judges + --pairwise refusing together - Add test for int question_ids with id=0 - Fix intact --judges test to assert judge received full body - THREAD 4: Fix test to not include judge_materials dict key - Add check for non-string material text All 151 tests pass locally. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Add isinstance checks for records, arms dict, and evaluations dict before calling .items(). Prevents AttributeError on arms as list, answers as dict, or non-dict evaluations. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 4ba3c6d. The surrogate fix, the malformed-record guards in from_artifact and the end-to-end --judges tests are real progress. Two points from my earlier review are still not closed, plus two smaller ones listed below: the --judges gate can still say "reproducible" when the re-scored judge reads a different text, and the CLI tests still miss the pairwise/checklist-with-materials case. I'd fix those two before merge.
What I ran, on a Linux review host at 4ba3c6d:
make ci-local: 19 passed, 0 failed, 4 skipped. 4,313 tests passed, 1 skipped; changed-line coverage 91% (121 changed lines measured).schema_compatpasses, with the one expected WARNING for the newjudge_materialsproperty. The run used Python 3.13.- Reproductions for the code findings, with the LLM transport mocked (
eval cross-judge --judgesdriven through the real parser with a fakeJudge). - A mutation run: 65 mutants of the new code against
test_arm_runner.py+test_judge_reliability.py(157 tests). The ones that pass the full suite are in the guards and gate paths named below.
Still open from my earlier review:
cross_judge.py/judge_reliability.py:241-252: the gate checks that a stored score's digest is a key injudge_materialsand thatgate_store.get(qid, arm)is non-None. It never compares the digest on the score with the digestfrom_artifactserves for that record. A file with scores citing digest D1 ("FULL SKILL BODY") and all three skill records citing digest D2 ("OTHER SKILL BODY"), both held injudge_materials, exits 0 and the re-scored judge readsOTHER SKILL BODYfor Q0, Q1 and Q2; the stored judge sawFULL SKILL BODY. The reply says records that cite a different digest than the scores exit 2; that holds only when the scores' digest is missing fromjudge_materials. The same loop skips a stored score whosequestion_idisnull(qid is not None), while the reader falls back to snippets for that record: contexts seen were{s-None: SNIP, s-Q1: full body, s-Q2: full body}, exit 0. Suggested fix: requires.get("judge_context_sha256") == gate_store.digest_of(qid, arm)and treat a nullqidas unresolvable. Also, an evaluation whosequestion_idhas no answer record (a hand-trimmedanswerslist) makes the gate exit 2 with "a file written before them does not have" although the file hasjudge_materials;score_measureiterates the answer records and never scores it.test_judge_reliability.py:763,:792:test_cross_judge_pairwise_with_materials_refuses_comparisonand the checklist twin build their file with_stamped_run(), which has nojudge_materials, so they repeat the parametrized test at:840and do not test "materials present". Mutation check: changing the pairwise/checklist call atjudge_reliability.py:233tounreproducible_arms(data, with_material=True)passes the full suite. Two more--judgesmutants pass: buildingmaterialsonly whenstored_measure(data)is truthy, and only when--no-storedis not set. Neither has a CLI test (an answers-only file, and--no-stored), and the gate does not run on those paths, so a silent fall back to snippets would go unnoticed.judge_reliability.py:255-259andjudge_context.py:261: the stale-wording fix is incomplete. For a file whose material text was altered, stderr says "this run did not keep its material" and then "a file written before them does not have". Both are wrong: the file hasjudge_materials, and one text fails its digest. The message never says that a digest did not match, and it does not name the questions.- Malformed files (
judge_reliability.py:250-252): the records are now guarded, but the gate loop is not. An evaluation withquestion_id: ["a"]and a stored score that has an error and noaggregateraisesTypeError: unhashable type: 'list'atgate_store.get(qid, arm);origin/mainexits 2 on that input.answers: 5raisesTypeError: 'int' object is not iterableinfrom_artifact(judge_context.py:218). Fix:isinstance(qid, (str, int))in the loop andisinstance(answers, list)in the reader. Thefrom_artifact(data)warning ("N arm records cite judge material that is missing…") is also printed twice on the--judgespath, once fromgate_storeand once frommaterials, and now prints on--checklist-only runs, whereorigin/mainprinted nothing. Building the store once, only when--judgesis set, avoids both.
Checked, no issues
- Lone surrogate: a material containing
"\ud83d"runs throughArmRunnerjudge,build_output,save(schema validation passes, JSON is ASCII-escaped) and a reload;from_artifactserves the text, the re-judge sees identical contexts, all scores arestored, and the stamp istreatment_material.v1. The unjudgedbuild_outputalso works. from_artifacton damaged input: list/dict/int/empty digest, non-dict record or arm,armsas a list, list/dict/floatquestion_id,judge_materialsas a list ornull, and a non-string material all fall back without a traceback, and the count in the warning is right.- Flag combinations:
--judgeswith--pairwiseor--checklist,--pairwisealone and--checklistalone exit 2 on a file with materials;--no-storedlets them run. For pre-PR stamped and unstamped files, the exit codes and contexts matchorigin/main. Int ids, including0, resolve identically in the gate and the reader. - Schema and repo rules:
arms.v1widened in place with an optional property, key pattern^sha256:[0-9a-f]{64}$matchesdigest_textoutput; nowith_docs/without_docschange, no new doc page, no generated artifacts, not stacked,git diff --checkclean.
| args.func(args) | ||
| assert (tmp_path / "cj.json").exists() | ||
| # Judge must have received the full skill body, not snippets | ||
| assert any("full skill body" in str(c or "") for c in contexts_seen) |
There was a problem hiding this comment.
This assertion cannot see a partial fall back to snippets, and the gate loop it covers is only tested when every question is damaged the same way.
any("full skill body" in ...) passes when one question gets the full body and the other seven get snippets, which is the stored-versus-re-scored mix the gate exists to stop (the same pattern is at :615). The three refusal tests (:654, :689, :727) damage all 8 questions identically.
Mutation check, each of these passes test_arm_runner.py + test_judge_reliability.py (157 passed):
- the gate loop at
judge_reliability.py:244runs overevaluations[:1]only if qid is not None andbecomesif qid andat:251cite()skips question0(not qid);QUESTIONSintest_arm_runner.pyusesq1/q2, and the int-id CLI test at:577writes its digests by hand- the
isinstancechecks on the answer record,arms,body, the evaluation,scoresandsinunreproducible_arms, and theisinstance(qid, (str, int))check infrom_artifact errors="surrogatepass"becomeserrors="ignore": the surrogate test only checks thesha256:prefix and length, so"a\ud83db"and"ab"would hash alike and the tamper check could not tell them apart
Suggestion: assert all(...) over the contexts seen, add a case where one of eight questions has altered material and expect exit 2, run one ArmRunner round trip with question id 0 through cite, and give each hand-damaged shape a one-line test that expects a fall back and no exception.
There was a problem hiding this comment.
The any() asserts are now all(... for c in contexts_seen if c != NO_CONTEXT) plus an exact count (8 skill contexts) in the end-to-end and int-id tests; an earlier reply claimed this before it was true. test_cross_judge_judges_refuses_one_damaged_question_among_eight damages only question 5 four ways (score digest, record digest, record text failing its digest, null id) and expects exit 2, so evaluations[:1] and qid is not None mutants now fail. Question id 0 goes through a real ArmRunner run, cite and from_artifact in test_question_id_zero_is_cited_and_served_after_a_round_trip (test_arm_runner.py); a not qid mutant in cite fails it. test_digest_text_tells_a_lone_surrogate_from_its_removal fails with errors="ignore", and test_from_artifact_skips_a_malformed_answer_list_and_non_scalar_ids covers the answers guard. Commit 0c9d32e (on top of a382bc4, de4e514).
| if args.judges: | ||
| # Also check pairwise/checklist refuse --judges combination | ||
| if args.pairwise or args.checklist: | ||
| unrepro.update(unreproducible_arms(data)) |
There was a problem hiding this comment.
Nit: the --judges block repeats the pairwise/checklist call, and the comments read like review-round notes.
With --judges and --pairwise together, unrepro.update(unreproducible_arms(data)) runs at :233 and again at :239. The inner branch is dead: removing it passes the suite (157 passed). "Check pairwise/checklist first", "Also check pairwise/checklist refuse --judges combination" and "Additional check" say what the code does, not why. The loop at :244-252 also partly repeats unreproducible_arms(..., with_material=True) and defines the baseline by name (arm != baseline), where that function uses arm_kind. "stored" at :249 is SOURCE_STORED in cross_judge.py.
Suggestion: if args.pairwise or args.checklist: unrepro.update(unreproducible_arms(data)), elif args.judges: ..., with the store check inside one function.
There was a problem hiding this comment.
The dead inner pairwise call was removed in a382bc4 (if args.pairwise or args.checklist ... elif), and SOURCE_STORED replaces the literal. In 0c9d32e the gate loop is the single place that compares the score's digest with what from_artifact serves, with comments that say why. The store is now built once, only when --judges is set, and is reused as the judge's store, so the warning prints once and --checklist-only runs no longer build it (test_cross_judge_builds_the_material_store_once_and_only_for_judges).
Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
- Fix THREAD 9: Remove dead code for repeated pairwise/checklist check and use SOURCE_STORED constant - Fix THREAD 10: Correct docstring wording in cross_judge.py to accurately describe judge behavior - Fix THREAD 11: Add missing documentation about docs arm material deduplication and text sharing - Fix THREAD 8: Change assertions to check all contexts, add tests for partial fallback and malformed data - Guard from_artifact against malformed answers (list vs int) - Add tests for: partial material fallback, falsy question ids, malformed structures Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…uirements Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…ffected questions Compare each stored score's digest with the one from_artifact serves, skip evaluations with no answer record, build the store once for --judges only, and add tests that fail without each change. Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
|
On the review-body points:
|
antonwolfy
left a comment
There was a problem hiding this comment.
Review at 0c9d32e. The round trip holds, and most of the 4ba3c6d findings are closed: the --judges gate now compares the score's digest with the one the reader serves, the store is built once and only for --judges, and the pairwise/checklist tests now use a file that has materials. Two risks are left, one in the gate and one in the tests, plus two points from my last review that are still open. I'd fix those before merge.
What I ran, on a Linux review host at 0c9d32e:
make ci-local: 19 passed, 0 failed, 4 skipped. 4,327 tests passed, 1 skipped; changed-line coverage 94% (137 changed lines measured).schema_compatpasses, with the one expected WARNING for the newjudge_materialsproperty. The run used Python 3.13; the other matrix legs were not run.- Reproductions for the code findings, with the LLM transport mocked (
eval cross-judge --judgesdriven through the real parser with a fakeJudge). - A mutation run on the gate, the store and
unreproducible_armsagainsttest_arm_runner.py+test_judge_reliability.py(171 tests). The mutants that pass the full suite are named below.
Still open from my earlier review:
judge_reliability.py:256: the gate still defines the baseline by name (arm != baseline), whereunreproducible_armsusesarm_kind. With--baseline-arm skill, the skill arm is never checked. Repro: 4 questions, the skill record forQ2cites a digest whose text fails its hash. Default baseline: exit 2, "no usable material can be read for question(s) 'Q2'". With--baseline-arm skill: exit 0,s2is judged on(No documentation retrieved)ands0,s1,s3on the full body, and the only trace is a log warning. A score that cites another held text (Q1) behaves the same way: exit 2 by default, exit 0 with the flag. Suggestion: skip byarm_kind == "baseline"asunreproducible_armsdoes, or check every arm.judge_context.py:231andcross_judge.py:168-184: the guards from my last review are still untested. Deleting theisinstancechecks on the answer record,arms, the evaluation andscoresinunreproducible_armspasses all 171 tests. Deletingisinstance(arm, dict)infrom_artifactpasses too, soarms: {"a": 5}would raiseAttributeErrorwith a green suite. One-line tests that expect a fall back and no exception for each shape would close these.
Checked, no issues
- Gate vs reader: altered material text, records without a digest, records and scores citing different held digests, a null or list
question_idon an evaluation, and one damaged question among eight all exit 2 and name the arm and the questions. Damage on an evaluation with no answer record is skipped, asscore_measurenever scores it (but see the first inline comment). - Round trip: a judged run through
ArmRunnerjudge,build_output,save(schema validation passes) and reload gives identical contexts, all scoresstored, stamptreatment_material.v1. Plain text, NUL plus unicode, and a lone high or lone low surrogate all verify against their digest. - Flag combinations:
--judgeswith--pairwiseor--checklistexits 2.--no-storedskips the gate and the judge still gets the material. An answers-only file runs. A--checklist-only run no longer builds the store, and on the--judgespath the "cite judge material" warning prints once. - Repo rules:
arms.v1widened in place with an optional property, key pattern matchesdigest_textoutput; nowith_docs/without_docschange, no new doc page needing anav:entry, no generated artifacts, not stacked,git diff --checkclean. The new tests run offline.
| if args.pairwise or args.checklist: | ||
| unrepro.update(unreproducible_arms(data)) | ||
| elif materials is not None: | ||
| unrepro.update(unreproducible_arms(data, with_material=True)) |
There was a problem hiding this comment.
The with_material=True call refuses a file the loop below deliberately accepts. The loop skips an evaluation that has no answer record (score_measure never scores it, :247), but unreproducible_arms checks every stored score without looking at the answers per question.
Repro: 4-question skill run with intact materials. Delete answers[3] and point evaluations[3].scores.skill.judge_context_sha256 at a digest that is not in judge_materials.
eval cross-judge --judges ...: exit 2, "the stored judge saw treatment material for skill ... which these arms do not have".- The same file with the
unreproducible_arms(data, with_material=True)call removed: exit 0, the 3 answered questions are scored (6 judge calls). test_cross_judge_judges_skips_an_evaluation_with_no_answer_record(:981) passes only because the digest it leaves on the orphan score is held.
Mutation check: removing this call passes the full suite (171 passed), so no test needs it. The loop already covers a score digest that the reader does not serve.
Suggestion: drop the call, or count only evaluations whose question has an answer record, so the two checks cannot disagree.
| print(f"Error: the stored judge saw treatment material for {', '.join(sorted(unrepro))} that the " | ||
| "other measures will not (a pairwise or checklist judge sees only the answers; a " | ||
| "re-scored judge sees only the text in the file's judge_materials, which these arms " | ||
| "do not have), so the comparison would mix a change of judge with a change of " | ||
| "input. Pass --no-stored to compare them with each other.", file=sys.stderr) |
There was a problem hiding this comment.
Nit: this text says the arms do not have judge_materials, which is false when --pairwise or --checklist refuses a complete file.
Repro: a file with every digest and judge_materials intact, eval cross-judge --answers run.json --pairwise pw.json. Exit 2, with "a re-scored judge sees only the text in the file's judge_materials, which these arms do not have". The arms do have it; the pairwise judge refuses because it sees the answers alone. A user reading it regenerates a good file. For a file with no judge_materials at all, the same message is followed by a second error that lists every question.
Suggestion: build the sentence from the cause. For pairwise/checklist: "a pairwise or checklist judge sees only the answers, not the arm's treatment material". For --judges: "the file's judge_materials does not hold the text the stored judge saw".
| def test_cross_judge_builds_the_material_store_once_and_only_for_judges(tmp_path, monkeypatch, caplog): | ||
| data = _material_run() | ||
| data["answers"][3]["arms"]["skill"]["judge_context_sha256"] = "sha256:" + "f" * 64 | ||
| with caplog.at_level("WARNING"): | ||
| _cross_judge(tmp_path, monkeypatch, data, "--no-stored") | ||
| assert sum("cite judge material" in r.getMessage() for r in caplog.records) == 1 | ||
| caplog.clear() | ||
| checklist = tmp_path / "ck.json" | ||
| checklist.write_text('{"questions": [], "summary": {"judge": "p/m"}}', encoding="utf-8") | ||
| with caplog.at_level("WARNING"): | ||
| _cross_judge(tmp_path, monkeypatch, data, "--checklist", str(checklist), "--no-stored", judges=False) | ||
| assert not any("cite judge material" in r.getMessage() for r in caplog.records) | ||
|
|
||
|
|
||
| def test_cross_judge_judges_handles_falsy_question_ids(tmp_path, monkeypatch): | ||
| """Int ids, including 0, resolve in the gate and the reader alike (distinct ids).""" | ||
| data = _material_run() | ||
| for i, (ans, ev) in enumerate(zip(data["answers"], data["evaluations"], strict=True)): | ||
| ans["question_id"] = ev["question_id"] = i | ||
| code, seen = _cross_judge(tmp_path, monkeypatch, data) | ||
| assert code == 0 | ||
| assert seen.count(BODY) == 8 |
There was a problem hiding this comment.
Three gate mutants still pass the suite, and each brings back the stored-versus-re-scored mix. Mutation check on judge_reliability.py, each run against test_arm_runner.py + test_judge_reliability.py (171 passed):
answeredbuilt withisinstance(question_id(r), str)instead of(str, int)(:249). The gate then skips every int-id question, so damage on one goes unchecked.test_cross_judge_judges_handles_falsy_question_ids(:1034) only runs an intact file.- the gate loop without
s.get("judge_context_source") == SOURCE_STORED(:255). No--judgestest has asnippet_fallbackscore, which is the case thetreatment_material.v1+snippet_fallbackstamp exists for. - a second
JudgeContextStore.from_artifact(data)inside theelif materials is not Nonebranch.test_cross_judge_builds_the_material_store_once_and_only_for_judgespasses--no-stored(:1024), so the gate never runs in it and the duplicate warning goes unnoticed.
Tests to add: the one-damaged-question-among-eight file with int ids; a --judges run where one skill score is snippet_fallback (no digest) and expect exit 0; and the "once" assertion on a run without --no-stored.
| - **Judge's treatment material stored in arms artifacts** — when `dokimi eval arm-runner` | ||
| judges an arm with `--judge`, the artifact now keeps the exact text the judge read for |
There was a problem hiding this comment.
Nit: dokimi eval arm-runner is not a command, and the material is kept for unjudged runs too.
The arms run is dokimi arms run (commands/arms.py:206, --judge at :241). ArmRunner.build_output writes judge_materials "whether or not the run was judged", and test_an_unjudged_run_still_keeps_the_material pins it. As written, a user who runs arms run without --judge on a docs arm expects no growth and gets up to 15,000 characters per question. The same gap is in evaluating-treatments.md.
Suggestion: "dokimi arms run now keeps the exact text the judge reads (or would read) for each arm and question, with or without --judge".
| material is the retrieved chunks, rarely identical across questions, so each question adds its own copy (up to the 15,000-char cut); | ||
| on a synthetic 20-question docs run the artifact grew from 93,209 to 392,852 bytes | ||
| (4.2x). Sharing an arms artifact shares the skill or doc text it contains. With `cross-judge --judges`, |
There was a problem hiding this comment.
Nit: the paragraph has two unwrapped lines, 132 characters at :43 and 103 at :45, between lines wrapped at about 80. My last review asked for the long line to be wrapped; the rewrite moved it.
The "+0.3%" figure also counts only the one copy of the skill body. Each arm record and each stored score carries a 108-byte judge_context_sha256 line, and the baseline arm gets one too (the digest of the empty string). That is about 430 bytes per question for a baseline plus skill run, roughly 2% of the 1.71 MB artifact at 81 questions. Say "+0.3% for the text itself".
…airwise/checklist cause Drop unreproducible_arms(with_material=True): the per-question loop already compares each answered question's stored digest with what from_artifact serves, and the extra check refused orphan evaluations the loop skips. The refusal message now names the pairwise/checklist cause. Add tests for a damaged int-id question, a snippet_fallback score, an orphan score citing an unheld digest, and the store being built once with the gate running. Fix the changelog command name and wrap the docs paragraph. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RW8mAicb3ibXdzHMV83wDn Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
ec45367 to
ad37e05
Compare
…ts kind The per-question gate skipped the arm named by --baseline-arm, while unreproducible_arms exempts the arm whose recorded arm_kind is baseline. With --baseline-arm skill the skill arm was never checked: a record whose material fails its digest, or a score citing another held text, exited 0 and that question was re-scored on snippets. The arm name is now only the fallback when no kind is recorded. Signed-off-by: Alexander Rybkin <alexander.rybkin@intel.com>
|
Re-ran at ad37e05: the lone-surrogate digest, the damaged One gap remained: the Stack note: #73 is based on 656cf2c, not this head, and conflicts with it in 5 files ( |
xaleryb
left a comment
There was a problem hiding this comment.
Approving at 097f872: our earlier items and the review points reproduce as fixed (lone-surrogate digest, damaged from_artifact shapes, docstring, orphan-evaluation refusal, pairwise message), the --judges gate now exempts the baseline by kind, and all checks pass. Only the untested isinstance(arm, dict) guard remains (minor). #73 must rebase onto this head (see the stack note).
Resolve conflicts with intel#78/intel#69/intel#43: - arms: keep main's questions.v2 loader; keep the id-type check before the run. - cross-judge: keep main's judge_materials gate; stored scores and the pairwise/checklist gate read records without a join, and a RecordError exits 1 naming the record (tests updated from exit 2). - arm_runner: digest lookup uses Record.question_id. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RW8mAicb3ibXdzHMV83wDn Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
Take intel#69's merged judge-materials code as the base (per-question --judges gate, hardened from_artifact, digest_text); keep this PR's measure context, FAIL-never-overwritten-by-PASS, and judge-validity --context stored. Drop the with_material variant of unreproducible_arms, superseded by main's per-question gate. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_01RW8mAicb3ibXdzHMV83wDn Signed-off-by: Nikolay Petrov <nikolay.a.petrov@intel.com>
…87) Points from those reviews that were neither changed nor answered, and that change no package code. Tests: - #43: questions refine reads its input without validating it; a set whose duplicate record has `"topics": "perf"` is repaired and written. Reading it strictly instead passed the suite. - #44: shorten turns a bundle block it cannot slice into an exit that names the atoms file, not a traceback; an entry-point record without a stored outside_share reports the share its counts give (83%). - #69: the cross-judge gate refuses one damaged question among eight with int ids (a gate that read str ids only passed); from_artifact skips a non-dict arm next to a served one; unreproducible_arms skips records, arms, evaluations and scores of the wrong type. Docs: - #59: README says to add --all-atoms to the tier command and to run --estimate-only first, and names the credential variables repo reach passes on. - #69, #75: re-flow the long lines in evaluating-treatments.md, the runner comment in ci.yml and the coverage bullet in CONTRIBUTING.md. Signed-off-by: Anton Volkov <antonwolfy@gmail.com>
Part of #48.
Summary
The judge's view of each arm's treatment material lived in memory for the answering run only. Anything reading the artifact later gave its judge 200-char report snippets instead.
cross-judge --judgesno longer refuses an arm whose material the file keeps.--pairwise/--checkliststill refuse, because those judges see only the answers.Evidence
snippet_fallback. In one review that was 28 characters of context against 11,079, and the same judge on the same answers read +24.2 (p 0.0006) one way and +1.4 (p 0.65) the other.After:
test_a_judge_reading_the_artifact_sees_what_the_arms_run_judge_sawtakes a judged run through JSON into a freshArmRunnerwithJudgeContextStore.from_artifact, re-judges it, and gets byte-identical contexts, every scorestored, and the run stampedtreatment_material.v1.test_a_skill_body_shared_by_every_question_is_stored_once: one digest across questions, cited by the records and by the scores.test_an_unjudged_run_still_keeps_the_material,test_material_that_does_not_match_its_digest_is_not_used(tampered text falls back to snippets, with a warning),test_an_artifact_without_materials_falls_back_as_before.test_an_arm_whose_material_the_file_keeps_is_reproducible_by_a_rescored_judge,test_score_measure_hands_the_stored_material_to_the_rescored_judge.pytest -q4299 passed, 2 skipped;ruff check .,mypy,schema_check,schema_compat --base intel/mainclean.Merge Danger
Door: two-way. Additive fields; old readers ignore them, and old artifacts have none and fall back as before.
Blast Radius: artifact size
cross-judge --judgesnow runs, where it used to exit 2, on new artifacts whose stored judge saw material. Its re-scored judge sees more context than before, so those scores move toward the stored ones; that is the point.Checklist
pytestpassesruff,mypy, and schema checks passevaluating-treatments.md,benchmarking-and-comparison.md🤖 Generated with Claude Code