fab-kit findings · 2026-08-10 · experiment report, part 2

Kimi k3 vs Codex sol vs Claude opus: two changes, six pipeline runs

Two draft intakes — rvza (config-show-compose-defaults, a surgical Go fix) and ttff (agy-interactive-pane-capability, a prose-heavy repo-wide sweep) — were each run to a draft PR three times through the full fab-fff pipeline: on kimi (kimi --auto, k3), on codex (gpt-5.6-sol @ xhigh), and on claude (claude-opus-5 @ high). The kimi and codex arms ran as one concurrent batch (~11:25 IST); the claude arm ran ~2.5 hours later as its own two-run batch — with less machine contention, which makes its wall-clock numbers conservative. This page extends the earlier two-provider report; the rubric, scoring protocol, and kimi/codex data are unchanged.

Kimi is ~2.6× faster than either; claude and codex are effectively tied on quality (9.15 vs 9.05 mean), each winning one change; codex ships the leanest diffs, claude the largest.

Claude took the small surgical change outright (9.20 — the highest rvza score, catching a vacuous-test regression the others shipped). Codex kept the sweep-heavy crown (9.45 vs claude's 9.10, which lost points only to prose polish). Kimi stayed the speed pick: third on quality both times, but at 2.5–2.6× the pace. Diff size, the new metric, splits cleanly: codex is the most economical on both changes; claude writes 47–55% more lines than codex — extra tests, guards, and docs that scorers judged substantive rather than churn.

kimi · mean wall-clock
20.8 min
13m 35s / 28m 04s · worktree → draft PR
codex · mean wall-clock
52.6 min
33m 27s / 71m 42s
claude · mean wall-clock
58.4 min
34m 45s / 81m 57s
kimi · mean quality
8.53 /10
8.40 / 8.65 weighted rubric
codex · mean quality
9.05 /10
8.65 / 9.45
claude · mean quality
9.15 /10
9.20 / 9.10

The six runs

Same intake, same pipeline per triple — only the worker provider differs. All six PRs are draft, CI-green, produced without human intervention, and all six have now completed review-pr.

PRArmChangeWorker roster Wall-clockReworkCopilotDiff (lines · files)Score
#570 kimirvza kimi --auto (k3), all stages, pane 13m 35s00362 · 98.40
#572 codexrvza sol@xhigh; hydrate sol@high; ship luna@low 33m 27s01283 · 98.65
#575 claudervza opus-5@high pane; ship sonnet@med + review-pr opus@high native 34m 45s00439 · 129.20
#571 kimittff kimi --auto (k3), all stages, pane 28m 04s15706 · 258.65
#573 codexttff sol@xhigh; hydrate sol@high; ship luna@low 71m 42s20686 · 269.45
#576 claudettff opus-5@high pane; ship sonnet@med + review-pr opus@high native 81m 57s241036 · 259.10

Where the time went

Stage spans reconstructed from .fab-dispatch/ record and result-file timestamps; x-axis is minutes since each run's worktree creation. Gaps between segments are orchestrator triage time; only the final rework cycle's files survive, so a reworked run's apply segment includes its interleaved failed reviews. The vertical PR tick is the measurement endpoint; dashed-gray segments after it are the post-PR Copilot review-pr stage. Dash-outlined segments are estimated from commit timestamps (stages that ran on the native seam and left no dispatch record).

apply review hydrate ship review-pr (post-PR) rework delivery
kimi · rvza
#570
3m 53s
13m 35s
codex · rvza
#572
15m 11s
33m 27s
claude · rvza
#575
11m 23s
34m 45s
kimi · ttff
#571
17m 31s · ×1
28m 04s
codex · ttff
#573
51m 27s · ×2
71m 42s
claude · ttff
#576
55m 42s · ×2
81m 57s
01530 45607590 min
Data table — stage spans (IST)
RunWorktree bornApply spanReviewHydratePRReview-pr
#570 kimi·rvza11:24:5711:27:50–11:31:4311:32:44–11:34:1411:35:05–11:36:0611:38:3211:39:44–11:44:59
#572 codex·rvza11:25:0011:28:46–11:43:5711:45:06–11:50:3711:52:16–11:55:4411:58:2712:04:23–12:13:30
#575 claude·rvza13:46:1813:48:49–14:00:1214:01:02–14:08:2414:10:57–14:18:4714:21:03~14:21–14:29:55 (native)
#571 kimi·ttff11:25:0311:28:15–11:45:46 (×1)11:46:16–11:48:0111:48:59–11:50:2911:53:0711:54:13–12:03:36
#573 codex·ttff11:25:0511:28:02–12:19:29 (×2)12:20:10–12:27:3812:28:22–12:32:5312:36:47~12:37–12:41:25 (est)
#576 claude·ttff13:46:2113:48:26–14:44:08 (×2)14:44:28–14:58:2715:00:29–15:05:0715:08:1815:08:18–15:17:27 (native)

Diff economy

Lines touched (insertions + deletions) and files changed per PR, from git diff main...branch --stat. Size alone is not a quality signal — the scorers judged where the extra lines went (see verdicts) — but it is a real review-cost and merge-risk signal for the human who reads the PR.

kimi · rvza #570
+326 / −36 · 9 files
codex · rvza #572
+237 / −46 · 9 files
claude · rvza #575
+372 / −67 · 12 files
kimi · ttff #571
+443 / −263 · 25 files
codex · ttff #573
+414 / −272 · 26 files
claude · ttff #576
+675 / −361 · 25 files

Codex is the most economical on both changes — its rvza diff is 22% smaller than kimi's and 36% smaller than claude's for a near-identical score, and its scorer singled out "deletes a special case rather than adding a branch" as the diff's defining trait. Claude is consistently the largest (+55% vs codex on rvza, +51% on ttff): the extra lines are three additional tests, a sweep-regression guard, beyond-intake doc sweeps, and a hazard write-up — substance the scorers credited (both claude runs scored 9–10 on test quality), but also the most review surface for a human to read. Kimi sits between, skewing additive (+326/−36 on rvza) where codex balances additions against deletions.

Quality scores

Each PR scored blind-to-its-siblings by an independent sub-agent against the same anchored 0–10 rubric, grading the final branch state against the intake, with mandatory file:line evidence and scorer-run tests. Weights: intake fidelity 20% · correctness 20% · diff hygiene 20% · sweep completeness 15% · test quality 15% · prose & docs 10%. Bars per dimension: kimi, codex, claude.

kimi (k3) codex (sol) claude (opus-5)

rvza — config-show-compose-defaults #570 · #572 · #575

Intake fidelity 20%
9
10
10
Correctness 20%
9
8
9
Diff hygiene 20%
8
8
9
Sweep completeness 15%
9
9
9
Test quality 15%
7
8
9
Prose & docs 10%
8
9
9
Weighted total
8.40
8.65
9.20

ttff — agy-interactive-pane-capability #571 · #573 · #576

Intake fidelity 20%
9
10
10
Correctness 20%
10
10
9
Diff hygiene 20%
9
9
8
Sweep completeness 15%
6
10
10
Test quality 15%
9
9
10
Prose & docs 10%
8
8
7
Weighted total
8.65
9.45
9.10

Scorer verdicts

Top strengths and weaknesses per PR, as reported by each scoring sub-agent. Kimi/codex cards repeat the earlier report for side-by-side reading.

kimi

#570 · rvza

8.40
  • Reuses the existing readModelDefaults/DefaultsMapFor projection, so bare show cannot drift from the keyed and --origin views.
  • Complete five-surface mirror sweep; repo-wide grep finds no live stale claim.
  • The sharpest behavior flip — a config-less repo now prints the whole defaults tier — is documented in memory prose but pinned by no test.
  • Now-unreachable empty-state guard kept with a message that would be wrong if it fired; misleading commit subject.

"The implementation is the right one … the one real gap is test coverage of the empty-repo case … minor hygiene noise keeps this short of the top band."

codex

#572 · rvza

8.65
  • Smallest correct implementation — deletes a special case rather than adding a branch; net +20/−23 impl diff.
  • Mirror sweep genuinely complete; tests parse real YAML output and assert typed values.
  • Shipped dead code its own plan had flagged as a deletion candidate — the Copilot round had to remove it.
  • Stale doc-comment residual in internal/config; PR body never mentions the output-size blast radius.

"A tight, well-targeted change that does exactly what the intake specified and nothing more … what keeps it out of the top band is self-review rather than judgment."

claude

#575 · rvza

9.20
  • Reuses renderShowKey's exact four-layer merge; the plan explicitly rejected the cheaper fold that diverges on non-map middle-tier leaves.
  • Caught that composing defaults made an existing assertion vacuous and replaced the fixture — "a regression most implementations would have shipped."
  • Documented a real hazard beyond intake scope: the composed output is a view, not a file to paste back (derived agent.profiles rows become live overrides).
  • Kept a dead guard branch its own plan flagged; paste-back caveat missing from one spec surface; thin PR-body Changes section.

"Nine lines of real substance … correct in the way that matters most … the sweep is the strongest part. The only things I would change are cosmetic."

kimi

#571 · ttff

8.65
  • Go change exactly minimal; every test assertion flipped rather than deleted, coverage strengthened.
  • Two stale "agy is headless-only" claims survive in normative spec text (config.md:224, stage-models.md:323) — in files the change edited elsewhere.
  • Root cause: the plan's verification grep matched tokens, not the phrase-class restatement ("headless grammar only").

"Falls short on exactly the axis the intake flagged as the repo's #1 rework cause … widening the sweep grep to phrase classes would take this to a 9-plus."

codex

#573 · ttff

9.45
  • Sweep complete including the habitually-missed frontmatter-description + index mirror-row class; every residual verified legitimate.
  • Every removed negative assertion replaced by a positive one at the same seam; error-path coverage relocated, not deleted.
  • One 92-char line in rendered config explain output breaks the block's wrap; one "instead of" transition clause in permanent memory.

"A near-exemplary execution of a change that is 80% sweep discipline and 20% code … what keeps it off a perfect score is cosmetic."

claude

#576 · ttff

9.10
  • Sweep complete in both directions — every stale claim gone from all 14 target files, and the 9 untouched agy-mentioning files verified to carry no capability claim.
  • Sweep made durable: a new test fails if any retired literal reappears in the rendered reference.
  • Net coverage gain — the generalized modeCommand table picks up codex's previously-unasserted pane rung; command value verified against the installed CLI.
  • Transition narration survives in three permanent docs ("it is no longer a shipped state") — the FKF present-truth class this repo treats as a standing rule.
  • One test comment narrates a deletion to the reviewer; one inherited inaccuracy sat in a rewritten sentence and wasn't corrected.

"A strong, complete execution … the sweep is the standout, plus a new regression guard that keeps it that way. What holds it short of top marks is prose polish … nothing here would block a merge."

Reading the result

Quality now has two winners — by task shape

Claude took the surgical Go change (9.20, the only rvza run to catch the vacuous-test regression); codex kept the sweep-heavy change (9.45 vs claude's 9.10). Their means are statistically indistinguishable (9.15 vs 9.05). Kimi placed third on both, held back by test coverage on rvza and the sweep miss on ttff.

Kimi's speed advantage holds against both: ~2.6×

Claude is marginally slower than codex on both changes (34m45s vs 33m27s; 81m57s vs 71m42s) — and the claude batch ran with only two concurrent pipelines instead of four, so its numbers are if anything flattered. The ordering kimi ≪ codex ≈ claude is robust.

Diff size is a personality trait, not noise

The pattern repeats across both changes: codex writes the leanest diff (its rvza change is net-negative on implementation lines), claude the largest (+51–55% vs codex), kimi in between and skewing additive. Claude's extra lines bought real things — a sweep-regression guard, three extra tests, a documented footgun — but the review surface a human must read grows with it. If diffs-a-human-reviews is the constraint, codex; if defense-in-depth is, claude.

The sweep lesson generalizes: grep phrase classes, and their contrastive forms

Kimi shipped two stale claims phrased outside the plan's grep tokens ("headless grammar only"). Claude's run surfaced a second invisible class: cycle-1 rework was a contrastive phrasing miss — "unlike agy" carries the retired claim by contrast and no literal grep finds it. Claude's internal reviewer also pre-fixed the exact two stale lines kimi's PR still carries, and its PR ships a regression test pinning the retired literals out of the rendered reference. Sweep-verification greps should target phrase classes AND contrast markers (unlike X, except X, X alone).

Reviewer strictness tracks the worker model — and predicts Copilot's reaction

Kimi's reviewer accepted ttff after 1 cycle; Copilot then raised 5 comments. Codex's reviewer forced 2 cycles to a 0-finding pass; Copilot found nothing on either codex PR. Claude's reviewer also forced 2 cycles and was the only one to produce substantive findings on the small change too (the vacuous-test catch) — Copilot's 4 comments on #576 were one cosmetic wording class. Internal review depth, not apply-stage brilliance, is where the quality gap is manufactured.

6/6 pipeline success, zero human touches

Three different provider CLIs — kimi and codex via pane workers, claude via pane + native seams — each drove intake-to-review-pr unattended to CI-green draft PRs, including rework choreography and Copilot-comment triage. The provider choice is a pure cost/quality/speed/diff-size knob.

Method & caveats

Scoring protocol

One sub-agent per PR with an identical prompt: read the project constitution and quality/review policies, the intake and plan, the full main...HEAD diff; run go test + gofmt in the PR's own worktree; score six anchored dimensions 0–10 with mandatory file:line evidence; grade against the intake, never against sibling PRs. Anchors: 10 flawless · 8–9 minor nits · 6–7 one real gap · 4–5 significant gap · ≤3 major failure. One prompt asymmetry: the #576 scorer's instructions included the phrase-class grep lesson learned in round 1; the round-1 ttff scorers were not given it — the #571 scorer discovered the phrase-class misses on its own initiative and the #573 scorer independently verified variant phrasings, so the sweep dimension was in practice examined at comparable depth across all three arms.

DimensionWeight
Intake fidelity — every "What Changes" item, nothing beyond scope20%
Correctness — logic read directly; tests run by the scorer20%
Diff hygiene — minimal diff, pattern conformance, commit/PR quality20%
Sweep completeness — mirror classes, aggregate specs, memory15%
Test quality — pin-by-value, coverage relocated not deleted15%
Prose & docs — present-truth framing, no transition narration10%

Caveats

  • n = 2 changes per provider — directional evidence, not statistics.
  • The judge is Claude (Fable 5), and one contestant is Claude (Opus 5) — a same-family bias toward the claude arm cannot be excluded and matters more in this round. Mitigations: anchored rubric, mandatory cited evidence, scorer-run tests, no sibling comparison; the claude arm's two verdicts cite the same class of concrete evidence as the others. Treat the claude-vs-codex gap (±0.10–0.35) as within judge noise.
  • Provider identity was visible in branch names; scoring could not be fully blind.
  • The claude batch ran ~2.5h after the kimi/codex batch with less machine contention; its wall-clock is a lower bound relative to the others'. #573 and #575/#576 ship (and some review-pr) stages ran on the native seam with no dispatch record — those spans are estimated from commit timestamps (dash-outlined).
  • #573 was scored before its review-pr ran; Copilot subsequently reviewed it with 0 comments and no commits landed, so its scored state is its final state. #576 was scored at final HEAD including its Copilot-fix commit.
  • Scorers hit pre-existing tmux-environment test flakes (TestPaneReady_ReadyReport, TestDispatchDeliver_Integration) and independently verified each reproduces on the main baseline — not counted against any PR.
  • Intermediate rework-cycle timestamps are overwritten by the final cycle; apply spans for reworked runs include interleaved failed reviews.
  • kimi ran its default model via --auto (k3); codex and claude rosters varied model/effort by stage (see table). Per-stage cost was not measured.