Run a controlled matrix comparison of approaches over a set of tasks — e.g. the kitsoki pipeline vs a naive single prompt, across a harness/model candidate matrix — scoring outcome, compliance, cost, and time, adjudicating implementation-coupled oracles, then regenerating a report + slidey deck offline with zero re-spend. Use when the user says "bake-off", "compare kitsoki vs single-prompt", "run a matrix comparison", "which model/harness is best at X", "kitsoki-vs-X study", "benchmark these approaches on these tasks", or wants an evidence-backed, cost-accounted comparison of contenders across a model grid. Generalises tools/bugfix-bakeoff (the reference implementation).
Compare approaches on the same tasks under identical conditions, score
each cell on outcome / compliance / cost / time, roll up, and deck it. The
reference implementation — the worked first instance — is
tools/bugfix-bakeoff/ (kitsoki's bugfix
pipeline vs a naive single prompt, across an Opus/Sonnet/GLM/GPT model grid, on
real fixed bugs). This skill is the reusable method; cite those files, do not
re-derive them, and do not hardcode the bug9/12/14 specifics into a new study.
Harness consolidated (2026-06). The reference impl is now the ONE manifest-driven harness under
tools/bugfix-bakeoff/external/— kitsoki's own bugs are justprojects/kitsoki. The legacy four-piece flow (prepare.sh·run_cell.sh·score.py·bakeoff.yaml) was retired; map the old names to the new pieces:
legacy now bakeoff.yamlexternal/projects/<name>/manifest.yaml+external/candidates.yamlprepare.sh+run_cell.shexternal/drive_cell.sh(one cell, worktree+drive+score)score.pyexternal/bench.py score/verify/cost/summarize(new) external/escalate.sh— cheap→expensive model/effort ladderaggregate.pyaggregate.py(kept; reads the external manifest + candidates.yaml)
Read first (the reference impl):
tools/bugfix-bakeoff/README.md — the runbook.tools/bugfix-bakeoff/external/projects/kitsoki/manifest.yaml — the manifest shape.tools/bugfix-bakeoff/external/candidates.yaml — the model/effort axis + escalation ladders.tools/bugfix-bakeoff/results/SCHEMA.md — the cell/summary contract every tool honors.bench.py · drive_cell.sh · escalate.sh · aggregate.py — the four pieces.A cell = (task × candidate × contender). Three axes:
manifest.yaml: treatments → [kitsoki, single].){key, profile, model, effort, provider, invoker} — e.g. GLM-5.2 / Opus-4.8 / Sonnet-4.6 / GPT-5.5, each a
configured kitsoki profile. (candidates.yaml.) invoker decides
how a control cell runs: claude_p → claude -p; session → studio-MCP.baseline_sha, a hidden oracle_test, and affected_test_pkgs.One hermetic worktree per cell — never shared.
drive_cell.sh cuts the per-cell worktree (a detached git worktree add on its
own branch, keyed by (project, task, candidate)) at the task's baseline_sha
before driving. A shared checkout is concurrent-checkout bug #9 — hard-isolate
by (task, candidate, contender). Re-running drive_cell.sh reuses an existing
worktree at the right SHA.
The #1 money-waster. The baseline must actually exhibit the condition the task
tests (the bug present, the test RED, or noncompile) before you spend a dollar.
Several "fixes" in the marathon were a test/lint added on top of an
already-merged behavioural fix, so <fix>^ was already GREEN → a degenerate cell
that proves nothing. (#1/#2/#8 were dropped for exactly this; only #9/#12/#14
reproduced.)
For each task, run the oracle at the baseline and confirm RED before scheduling:
cd .worktrees/bakeoff-<task>-<any>-<any> # any prepared worktree at baseline
# go oracle: copy the oracle in from the fix, run it, expect FAIL/noncompile
git show <fix_sha>:<oracle_test> > <oracle_test>
go test -run '^TestXxx$' ./path/to/oracle/pkg ; echo "rc=$?" # MUST be non-zero
git checkout -- <oracle_test> 2>/dev/null; rm -f <oracle_test> # leave tree cleanA baseline that is GREEN is a study finding (note it), not a cell to run.
Hidden oracle. Each task's oracle = the real fix's own regression test,
kept out of the candidate's tree. bench.py score overlays it (the isolated
oracle test file injected/written into a throwaway scratch copy of the candidate
tree), runs it there, and never touches the candidate worktree, so the tree is
never polluted. The candidate must never see it (that would leak the answer).
Oracles are often wording/impl-coupled → they false-fail a behaviourally correct fix done a different way (Opus refused with different wording; the kitsoki pipeline used a per-session-path approach where the oracle asserted a sentinel — both correct). Prefer behavioural oracles when authoring.
Adjudication step. When the oracle fails (or noncompiles) but the behaviour
is plausibly correct, an LLM/human judge decides solved|partial|failed on
behaviour. Record the override; keep the raw oracle_status so the JSON
never lies:
# Deterministic grade (oracle GREEN/RED). bench.py writes the cell JSON; edit its
# outcome.adjudicated/adjudication_note when a judge overrides on behaviour.
python3 tools/bugfix-bakeoff/external/bench.py score \
--project <name> --bug <task> --tree <worktree> \
--candidate <cand> --treatment <contender> \
--out tools/bugfix-bakeoff/results/cells/<task>-<cand>-<contender>.jsonThis sets outcome.quality, adjudicated=true, and the note; oracle_status
stays the raw automated result. Rollups key on the (possibly adjudicated)
quality.
Per results/SCHEMA.md, every cell scores three families:
quality ∈ {solved, partial, failed}:
solved = oracle pass ∧ build ok ∧ affected suite green; partial = oracle
pass with a regression/build issue, or oracle noncompiles against a
differing-but-plausible impl; failed = oracle fail/absent. May be
adjudicator-overridden.rate):
reproduced_red, added_regression_test, suite_green, in_scope,
stage_order. Diff baseline..HEAD ∪ working tree, not just git status —
candidates often commit their fix+test, leaving status clean (changed_files
in bench.py).payload.meta.cost_usd —
sum the native figure (extract_kitsoki prefers it; exact by construction).claude -p subscription transcripts carry NO cost → price from
message.usage via a correct rate table. The current rates: Opus
$5/$25 (cache 0.5 / 6.25 / 10), Sonnet $3/$15. pricing.py's Opus row
was historically stale at 15/75 — verify it before trusting USD. The corrected
table reproduces kitsoki's native cost to ~0.4%.cost_extract
only reads Claude Code message.usage (returns zero on kitsoki traces); so
bench.py sniffs format (a kind: agent.call.complete line ⇒ kitsoki) and
dispatches.
Also: wall_time_s and guidance_turns.drive_cell.sh and escalate.sh are the only cost-bearing pieces.
bench.py and aggregate.py make no LLM calls. Per contender/invoker:
claude_p (Opus, Sonnet) — fully scripted:
claude -p "$(cat prompts/<task>.md)" --output-format json --model <model> \
--permission-mode acceptEdits \
--allowedTools Bash Edit Write Read Glob Grep MultiEdit--dangerously-skip-permissions; worktrees are disposable. Resume guidance
turns with claude -p --resume <sid> "<msg>".session for GLM, GPT) — studio-MCP
session_new under the candidate's profile (the profile/agent-def controls
the maker model — see Pitfalls). Pass:
profile: <candidate.profile>, harness: "live",trace: (otherwise the trace goes to a random temp file —
the filed P1: session_new uses os.CreateTemp),initial_world.base_branch / base = the baseline SHA, so the pipeline
cuts from the buggy parent, not main (where the bug is already fixed),test_cmd = the changed-area packages (a repo with pre-existing
unrelated reds bounces every fix forever; the authoritative grade is your own
oracle, not the pipeline's internal CI).The committed summary.json (+ per-task agenteval reports) makes the study
fully reproducible — the report/deck regenerate with no LLM calls:
cd tools/bugfix-bakeoff
python3 aggregate.py --generated-at 2026-06-24T00:00:00Z # cells/*.json -> summary.json
python3 aggregate.py --generated-at 2026-06-24T00:00:00Z --emit-agenteval # + agenteval/<task>/latest.json
python3 ../session-mining/eval_pilot_report.py \
--summary results/report.json --markdown results/report.md --deck results/deck.html(--generated-at is required — the build bans implicit wall-clock timestamps;
BAKEOFF_GENERATED_AT works too. --markdown/--deck/--summary each take a
path.) summary.json and the agenteval.Report files are the durable artifacts.
.kitsoki.local.yaml) and the model alias prices in
pricing.py.external/projects/<name>/manifest.yaml: bugs/tasks
(baseline_sha = <fix>^, isolated oracle_test, oracle.run, fix_source),
treatments; the candidate/effort axis + ladders live in
external/candidates.yaml. Copy a reference project's shape.bench.py verify --project <name>
(RED@baseline, GREEN@real-fix). Drop degenerate (GREEN-at-baseline) tasks;
record the drop as a finding.drive_cell.sh --project <name> --bug <task> --candidate <cand> --score per cell — or escalate.sh --project <name> --ladder default to climb the cheap→expensive model/effort ladder per bug.bench.py score (run by drive_cell.sh --score) → results/cells/<task>-<cand>-<contender>.json.--adjudication/--adjudication-note where a
coupled oracle false-failed a behaviourally-correct fix.aggregate.py [--emit-agenteval] → summary.json.eval_pilot_report.py --markdown --deck (offline).task-bakeoff storyA kitsoki story stories/task-bakeoff/ (being built in parallel) wraps this
method into a drivable workflow that produces the slidey report directly:
the rooms encode Setup → manifest → pre-flight → run cells → score → adjudicate →
aggregate, calling the same tools/bugfix-bakeoff/* scripts as host steps, and
the final room renders the deck (the --deck HTML / a slidey spec). When that
story lands, drive it with kitsoki-mcp-driver; until then run this skill's
manual runbook. Keep the scripts the single source of truth — the story orchestrates
them, it does not reimplement scoring/pricing.
baseline..HEAD ∪ working tree.meta.cost_usd.session_new profile: supersedes a story model:; pin a sonnet profile for
the sonnet cell. Relative imports read disk.trace: → trace lost to a temp file (filed P1).test_cmd → pre-existing reds bounce every fix forever; your
oracle is the authoritative grade.--permission-mode acceptEdits --allowedTools "Bash Edit Write Read Glob Grep MultiEdit"); the classifier
blocks --dangerously-skip-permissions.oracle_status.drive_cell.sh writes the trace path directly ($CACHE/traces/<cellkey>.jsonl,
echoed as trace=…) and passes it as the session's explicit trace:; if the
session still leaves that file empty/absent, that absence is a finding, not a
script bug.drive_cell.sh/escalate.sh are the only cost-bearing pieces and are run
manually, never in CI or automatically. bench.py/aggregate.py are
deterministic and free; the reference impl ships offline tests against fixture transcripts/
worktrees (oracle runner + cost extractor are dependency-injected). The committed
summary.json lets the whole study re-derive its report/deck with zero spend.
Codex discovers this skill directly. After adding/moving it, re-link into Claude
Code's .claude/skills/:
make setup1f4abf0
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.