Expand description
Paired A/B comparator: two arms — each an (ArmSpec) engine on a
backbone — over the same task and the same OutcomeContract.
The original claim was “CAR + gpt-5.5 codes as well as Codex + gpt-5.5, and here is where its harness wins.” That is only meaningful when both arms run the same model — ALE’s finding is that backbone choice is ~3× the spread of harness choice, so native-on-Haiku vs Codex-on-gpt-5.5 measures the model, not the runtime.
Arms therefore carry their own backbone, which lets one comparator express
the whole family (see docs/proposals/coder-ab-value-surface.md):
the same-backbone harness delta, the cross-tier diagonal
(native@gpt-5.4 vs codex@gpt-5.5 — what the runtime is worth in model
tiers), and ablation (native vs native with a mechanism off, the only
shape that isolates a single mechanism).
The fairness rule is now checked rather than asserted
(AbReport::same_backbone). It previously could not be: the report held one
operator-supplied backbone string while the pin reached only the native
loop, so the external arm ran on whatever its CLI was configured for.
Design mirrors bench/car_bench/ale’s orchestrator, ported to Rust and to
the in-process coder path:
- Honest denominator. An arm that never actually ran (worktree/setup/
inference-transport failure) is
infra_failedand excluded — never scored as a task-0. The paired stats are computed over the tasks both arms scored, exactly likecompare_pair’streatment ∩ controlintersection. - Paired significance. McNemar’s test over the discordant pairs (treatment-pass/control-fail vs treatment-fail/control-pass) — the right test for two harnesses judged on the same items, and the regression gate Slice 4 re-runs to decide whether an applied change was a real improvement or noise.
- Cost axis. Per-arm mean cost and cost-per-pass, so “as good” can be qualified by “and cheaper/dearer.”
The execution seam is injected (AbArmRunner) — the same
injected-closure philosophy as car-builder’s generate seam and
car-verify::cwm’s EffectModel — so the statistics core is unit-testable
with scripted arms and no live inference. The live runner (real
run_native_loop / run_external_loop in fresh worktrees) is wired by the
car coder-ab CLI.
Structs§
- AbCell
- Both arms’ outcomes on one task — the paired unit.
- AbReport
- The full report: every paired cell plus the aggregate stats. Serializes to
the timestamped JSON the
car coder-abCLI writes tobench/results. - AbTask
- One coding task both arms attempt, verified against the identical contract. The corpus is a JSONL of these — curated from CAR’s own merged PRs / closed issues (revert the fix, keep the PR’s tests as the contract, have both arms re-derive it).
- ArmOutcome
- A single arm’s result on a single task, normalized so both the native
LoopOutcomeand the external CLI’s result map onto one shape. - ArmSpec
- A fully-specified arm: which engine, on which backbone.
- Paired
Stats - Aggregate paired statistics over the scorable intersection.
- Round
Attribution - Attribution over one A/B round’s treatment-arm losses.
Enums§
- ArmEngine
- Which engine an arm drives.
Traits§
- AbArm
Runner - The injected execution seam: run one arm on one task, in its own fresh
worktree, verifying against
task.contract. Never panics — an arm that can’t be attempted returnsArmOutcome::infra. Scripted in tests; the live impl (realrun_native_loop/run_external_loop) is wired by the CLI.
Functions§
- attribute_
round - Attribute the treatment-arm losses in
report.read_events(path)returns the event-log JSONL for a transcript path (live: read the file; tests: an in-memory map). Only paired-scorable native losses are attributed — an infra failure is not a harness lesson. Interventions from a layer that defers elsewhere (is_evolution_actionablefalse) still mark a loss as addressed but are dropped from the fixer feed. - is_
evolution_ actionable - Whether a diagnosed layer is one the Evolution Agent can act on autonomously.
ProceduralSkilldefers to CAR’s separate skill-distillation path, so it is not fed to the harness-evolution fixer here. - run_
ab_ suite - Run the full paired suite: each task through both arms, against the same
contract, folded into an
AbReport. - run_
ab_ suite_ resumable - Resumable, incrementally-observable
run_ab_suite. A long real-corpus run is expensive, so this makes it interruptible and iterative: