Files
poimen/tasks/T5.10-attempt-tournaments.md

5.2 KiB

T5.10 — Attempt tournaments

Field Value
Phase P5 — Grading
Size M — 1 to 3 days
Status Not started
Flags
Spec inlined below
Blocks

Goal

Grade the attempts of one step as a comparison group. On by default, even though T5.8/T5.9 are not, because it costs no extra agent runs.

Facts (inlined — no spec read needed)

Where comparison groups come from:

Source Group Cost Needs
Attempts attempts 1..N of one step, identical context free, already recorded nothing
Recurring tasks runs sharing a TaskId over time free, slow to fill TaskId
Replay one task re-executed under N variants N full runs blobs, sandbox
  • Attempt tournaments first. Retries are already on disk and attempt N+1 never mutates attempt N, so the attempts of one step are a group on identical context — the cheapest per-step credit signal available, and the only source of per-step credit the system has.
  • Two paths, not one:
    • Same-outcome attempts go to the judge. The question there is "which failure got further" — which no verifier can answer.
    • A fail→pass pair is never judged. The verifier has already ordered it; asking a judge which is better asks it to re-decide what verification decided. The pair is consumed structurally: what differed between attempt N and N+1 — context partition, tool selection, prompt — is attributed to the StepId as the change that turned a fail into a pass.
  • The bracketing rule has no exemptions. If a pairing crosses an outcome class, it is evidence for attribution, never input to a judge. This is the place the rule is easiest to break, because the two attempts sit side by side on disk and look like a free comparison.
  • Per-step credit attributes to StepId, which is why the StepId stability contract (T3.4) is load-bearing rather than cosmetic.

Steps

  1. Build the group: all attempts of one (BranchKey, StepId), partitioned by verifier outcome class.
  2. Route by class before any judge call. Same-class subsets go to the comparison path; cross-class pairs go to the attribution path. Make this a single branch point so it cannot be bypassed.
  3. Comparison path: hand the same-class subset to the configured strategy.
  4. Attribution path: diff attempt N against N+1 across context partition, tool selection and prompt ref; emit the delta attributed to StepId. No judge call.
  5. Persist per-step credit keyed by (TenantId, WorkflowVersion, StepId).
  6. Instrument the Judge::compare counter and assert zero cross-class calls — count calls rather than inspecting pairs, since a pair-inspection test passes against an implementation that builds the pair and then filters it late.

Acceptance

  • A run with three attempts produces per-step credit attributed to StepId.
  • A group containing both a failed and a passed attempt issues zero Judge::compare calls across the outcome boundary — asserted by counting calls, not by inspecting pairs.

Verify

Harness: a run with three attempts, plus a counting mock judge. The counter is the instrument — pair inspection is not.

Integration testtests/it_attempt_tournament.rs:

  1. Three attempts on one step, all failing. Assert compare is called on the same-outcome subset and per-step credit is attributed to the StepId.
  2. Mixed group — the guard: attempts 1 and 2 fail, attempt 3 passes. Assert zero Judge::compare calls cross the outcome boundary, counted at the judge. Assert the fail→pass pair produced a structural attribution record instead.
  3. Assert the attribution names the actual delta — plant a context-partition difference between attempts 2 and 3 and assert it appears in the record.
  4. Assert credit is keyed (TenantId, WorkflowVersion, StepId) and survives a workflow version bump that retains the StepId (T3.4).
  5. Assert this path runs with the tournament flags off — it is on by default while T5.8/T5.9 are not.
  6. Zero-cost assertion: no agent runs are spawned by grading; assert the run counter is unchanged.
  7. Single-attempt step: assert no comparison and no spurious credit record.

Command: cargo test -p grading attempt_tournament

False pass:

  • Inspecting the pair list rather than counting judge calls. An implementation that builds the cross-class pair and filters it just before dispatch passes pair inspection and still sends it under a later refactor. Count at the judge boundary.
  • Step 2 written with all-failing attempts, where no boundary exists to cross. The mixed group is the whole test.
  • Step 3 omitted: emitting an empty attribution record satisfies "credit produced" while carrying no signal.

Traps

  • Pairing the failure with its successful retry. It is free, it looks like signal, and it makes the judge re-decide what the verifier already decided.
  • Attributing credit to step index rather than StepId, which breaks at the next workflow version.

Background (not required to do this task): rust-agentic-sys.md §11.4, §11.5, §11.7, §11.8 · rust-agentic-task.md