5.4 KiB
5.4 KiB
T5.5 — PairwiseSequential: reference and comparison
| Field | Value |
|---|---|
| Phase | P5 — Grading |
| Size | M — 1 to 3 days |
| Status | Not started |
| Flags | — |
| Spec | inlined below |
| Blocks | T5.6, T5.7 |
Goal
The default grading strategy: one current version, at most one challenger, one
Judge::compare per new episode.
Facts (inlined — no spec read needed)
current version ──► episode ──┐
├──► Judge::compare ──► verdict
challenger ──► episode ──┘ │
(same TaskId) ▼
accumulate into WinRecord
- Grading answers one question: has the challenger accumulated enough evidence to replace the current version? Not "rank these eight", not "what is each episode worth".
- The reference is the current version's recorded episode on the same
TaskId. It is already on disk — no re-run, no group to fill, no group timeout. ATaskIdnever seen before has no reference and degrades per T5.11. - One comparison per episode. Against the tournament's
(G/2)·log₂(G)pairs doubled for both orderings, that is 24 judge calls dropping to 8 at G = 8, and the saving grows with G rather than shrinking. ResourceProfiledeclares exactly one model — the agent's, already resident. That is precisely why the default grading path adds no resident model and forces no swap.- A fixed reference is a cacheable prefix. The same reference episode leads every comparison in a decision, so it can be cached across calls. The tournament cannot do this — shuffling into brackets makes every pair a novel combination by design.
- Episodes are bracketed within a verifier outcome class, never across. A verified pass beats a verified fail by definition and that pairing is never shown to a judge.
- What this gives up, stated rather than discovered later: parallel exploration
and composable strengths. One challenger at a time is hill-climbing; a
Relativescore answers "better than the current version on this task" and does not compose across tasks.
Steps
- Implement
EvaluationStrategywithresources()naming exactly one model — the agent's pinned model — andcalls_per_episode: 1.0. - Reference lookup: given
(TenantId, TaskId), find the current version's most recent recorded episode. ReturnNonefor a novel task and hand off to T5.11. - Check the verifier outcome class before pairing. Reference and candidate must share a class or no comparison is issued.
- Build the comparison prompt with the reference half first and byte-stable across calls. Assert that stability in a test — prompt caching depends on it.
- Issue exactly one
Judge::compareper new episode and fold the verdict into aWinRecord(consumed by T5.6). - Instrument two counters:
comparecalls and model loads. Both are asserted.
Acceptance
- Grading one episode issues exactly one
comparecall and zero model loads — both asserted by counting. - A second episode on the same
TaskIdreuses the identical reference, so the reference half of the prompt is byte-identical across calls.
Verify
Harness: a mock Judge recording every (prompt_bytes, verdict); a model
load counter distinct from the call counter; two recorded episodes on one
TaskId.
Integration test — tests/it_pairwise_reference.rs:
- Grade one episode against an existing reference. Assert exactly 1
comparecall and exactly 0 model loads. - Grade a second episode on the same
TaskId. Assert the reference half of the prompt is byte-identical between the two calls — capture both prompts from the mock judge and compare the reference prefix directly. This is the property prompt caching depends on and it is easy to break invisibly. - Assert the reference selected is the current version's recorded episode,
not the highest-scoring one — plant a better-scoring non-current episode on
the same
TaskIdand assert it is not chosen. - Outcome-class guard: reference is a verified pass, candidate is a verified
fail. Assert zero
comparecalls. - Novel
TaskId: assertcompareis not called and the path hands off to T5.11'sNoReference. - Assert
resources().models.len() == 1and that it equals the pinned agent model.
Command: cargo test -p grading pairwise
False pass:
- Step 2 comparing whole prompts. They legitimately differ in the candidate half; comparing the whole thing either always fails or, if the test is loosened to "both non-empty", always passes. Compare the prefix.
- Step 1 counting calls but not loads. A judge running on a non-resident model returns the right verdict and costs tens of seconds per call.
- Step 3 omitted: "pick the best episode as reference" produces sensible-looking verdicts against a moving target, and no other assertion here catches it.
Traps
- Re-rendering the reference half per call with a timestamp or a fresh id in it. It still works and it silently destroys the cacheable prefix.
- Selecting the reference by "best episode" rather than the current version's episode. That makes the comparison a moving target.
Background (not required to do this task): rust-agentic-sys.md §11.1, §11.3, §11.8, §14.2 · rust-agentic-task.md