AI

Architect, not author: what a local Qwen builder wrote for me, and what the review caught

My local Qwen 3.8 Flash builder writes the code while frozen tests I wrote and mutation-testing reviewers decide whether it ships. Five merged pull requests, two review cards and three delegation transcripts say what each layer actually caught.

For agentsView .mdOpen in ClaudeOpen in ChatGPT
For agentsView .mdOpen in ClaudeOpen in ChatGPT
Contents
The loop of the methodThe lead writes frozen tests and the contract types (185 lines of frozen tests on the loader card). The builder writes the code and never touches the frozen tests. A reviewer that did not write the code runs mutations (11 of 12 and 17 of 19 mutants caught on the two review cards). The lead verifies by hand, by reading the spec and running the site end to end (found the silent-highlight gap). Then the card merges, and the same loop runs on the next change.LEADWrites the dealfrozen tests andthe contracttypes, beforeany code exists185 lines offrozen testsBUILDERWrites the codethe frozen testsstay out ofreach477-line loaderREVIEWERRuns mutationsbreaks the codeon purpose tosee which testsnotice11 of 12 and 17of 19 mutantscaughtLEADVerifies by handreads the spec,runs the siteend to endfound thesilent-highlightgapmergesame cards, next change
The loop of the methodThe lead writes frozen tests and the contract types (185 lines of frozen tests on the loader card). The builder writes the code and never touches the frozen tests. A reviewer that did not write the code runs mutations (11 of 12 and 17 of 19 mutants caught on the two review cards). The lead verifies by hand, by reading the spec and running the site end to end (found the silent-highlight gap). Then the card merges, and the same loop runs on the next change.LEADWrites the dealfrozen tests and the contract types, before any codeexists185 lines of frozen testsBUILDERWrites the codethe frozen tests stay out of reach477-line loaderREVIEWERRuns mutationsbreaks the code on purpose to see which tests notice11 of 12 and 17 of 19 mutants caughtLEADVerifies by handreads the spec, runs the site end to endfound the silent-highlight gapmergesame cards, next change
The method as a loop: the lead freezes the tests, the builder writes code behind the boundary, an independent reviewer mutates it, the lead verifies by hand — and the cards merge.Source: the essay’s account of the five merged cards; the numbers it cites from the delegation transcripts and the two review cards.

On 14 November 2026 a local Qwen 3.8 Flash builder on my DGX Spark wrote a 477-line YAML loader for this site. It passed all 475 tests in the suite, and I still did not trust it. The gap a lead review found: a bar chart whose highlighted label is misspelled was silently ignored instead of rejected, because the validation code sat after an early return. The frozen test for that gap took 1,125.42 seconds and 207 tool calls before the builder found the return itself.

The method behind that merge is simple to say. I write what a change must do as tests the builder cannot touch. The builder writes the code. A reviewer that did not write the code runs mutations. I verify by hand. The person who owns the method is closer to an architect than an author, and the numbers below are what each layer actually caught.

The deal: frozen tests

The site is Astro 7.4.1 on Node 22.23.0, tested with vitest. Every delegated card carries lead-written frozen tests, and the file header says so in the builder’s face: // FROZEN (lead-written; the builder never edits this file). The contract types are written by hand before any implementation exists. This is Pre-registration applied to software: the decision rules are committed before the code they judge.

Five pull requests merged into the site repository that Saturday, the first merge at 12:44 and the last at 13:14. They ran this way; the first of them changed no product code at all.

The morning of the five merged pull requestsA time axis from the first merge at 12:44 to the last at 13:14, with the five merged pull requests as markers and their titles: G17-0 stop pinning the real content in tests, G17-A adaptive phone title limit, G17-B1 figures as data (loader and registry), G17-B2 figures as data (renderers), G17-0b two more tests stop assuming today’s content. Under the axis, the three review catches: the highlight typo, the borrowed data-loader file, and the two frozen tests pinned to today’s content.MERGED PULL REQUESTS12:44 G17-0: stop pinningthe real content in tests12:44 G17-A: adaptive phonetitle limit12:52 G17-B1: figures as data(loader + registry)13:12 G17-B2: figures as data(renderers)13:14 G17-0b: two moretests stop assuming today’scontent12:4412:5213:0013:0813:14REVIEW CATCHESthe highlight typo: a label matching norow was silently ignored (frozen testadded, builder fixed)the borrowed file: data-loader.tschecked out from the sibling branchright after the 12:57:55 failure; thelead dropped it on rebasetests pinned to today’s content: noyaml today, and the outliers note notfitting at 6 titles; the end-to-end runturned both into invariants
The morning of the five merged pull requestsA time axis from the first merge at 12:44 to the last at 13:14, with the five merged pull requests as markers and their titles: G17-0 stop pinning the real content in tests, G17-A adaptive phone title limit, G17-B1 figures as data (loader and registry), G17-B2 figures as data (renderers), G17-0b two more tests stop assuming today’s content. Under the axis, the three review catches: the highlight typo, the borrowed data-loader file, and the two frozen tests pinned to today’s content.MERGED PULL REQUESTS12:44 G17-0: stop pinningthe real content in tests12:44 G17-A: adaptive phonetitle limit12:52 G17-B1: figures as data(loader + registry)13:12 G17-B2: figures as data(renderers)13:14 G17-0b: two moretests stop assuming today’scontent12:4413:0013:14REVIEW CATCHESthe highlight typo:a label matching norow was silentlyignored (frozentest added, builderfixed)the borrowed file:data-loader.tschecked out fromthe sibling branchright after the12:57:55 failure;the lead dropped iton rebasetests pinned totoday’s content: noyaml today, andthe outliers notenot fitting at 6titles; theend-to-end runturned both intoinvariants
The morning of the merges: five pull requests between 12:44 and 13:14, with the three catches of the review layers annotated.Source: the merge times and titles of the five pull requests, and the review records (the merged pull requests of 14 November 2026 in the site repository).
Data
The five merged pull requests, in merge order
PRMerged (local)Title
112:44G17-0: stop pinning the real content in tests
212:44G17-A: adaptive phone title limit
312:52G17-B1: figures as data (loader + registry)
413:12G17-B2: figures as data (renderers)
513:14G17-0b: two more tests stop assuming today’s content

Source: the merged pull requests of 14 November 2026 in the site repository

Lines added per commit, loader and renderer cardsOne bar per commit, each bar the sum of git show --numstat insertions on that commit. The lead’s two commits added 283 lines (the frozen tests, the hand-written contract types and a renderer stub at 8ec6ecd) and 1 (the missed frozen case at 6fb5a3c). The builder’s three commits added 708 (the 477-line loader with wiring, extra tests and docs at 6d20a16), 120 (docs plus extra render tests at a094bae) and 54 (the highlight fix at 9fb065b).0200400600800Lines added per commit →283708120541Lead: frozen tests, contract types, stubBuilder: the 477-line loader, wiring, extra tests, docsBuilder: docs plus extra render testsBuilder: the highlight fix: 41 loader lines, 13 new testsLead: the one missed frozen case
Lines added per commit, loader and renderer cardsOne bar per commit, each bar the sum of git show --numstat insertions on that commit. The lead’s two commits added 283 lines (the frozen tests, the hand-written contract types and a renderer stub at 8ec6ecd) and 1 (the missed frozen case at 6fb5a3c). The builder’s three commits added 708 (the 477-line loader with wiring, extra tests and docs at 6d20a16), 120 (docs plus extra render tests at a094bae) and 54 (the highlight fix at 9fb065b).0200400600800Lines added per commit →283708120541Lead: frozen tests, contract types, stubBuilder: the 477-line loader, wiring, extra tests, docsBuilder: docs plus extra render testsBuilder: the highlight fix: 41 loader lines, 13 new testsLead: the one missed frozen case
The loader card’s five commits, one bar per commit: the lead’s two in ink, the builder’s three in accent, every line a git numstat insertion.Source: git show --numstat on the five commits (the merged pull requests of 14 November 2026 in the site repository).
Data
Lines added per commit on the loader and renderer cards
CommitWhoLines added
8ec6ecdLead283
6d20a16Builder708
a094baeBuilder120
9fb065bBuilder54
6fb5a3cLead1

The loader card is a fair cross-section: the figure has one bar per commit, every line a sum of git show --numstat on that commit. Before the work I wrote 185 lines of frozen tests, 184 at 8ec6ecd alongside the hand-written contract types and a renderer stub, and 1 more at 6fb5a3c, the review finding. The builder’s three commits added 708 lines (the 477-line loader at 6d20a16, with index wiring, extra tests and the docs section), 120 (docs plus extra render tests at a094bae) and 54 (the highlight fix, 41 loader lines plus 13 new tests, at 9fb065b). The builder’s tests always sit in a separate file with its own header, and the frozen file never moved except when I added a case it had missed.

The phone-title card went the same way and showed what the frozen tests are for. The feature: the phone atlas labels the newest N pieces, and the build picks the largest N in 6..0 that passes every phone check. The lead review record for that PR says vitest 445/445, astro check 0 errors, frozen tests untouched, and one mutation run: the reviewer changed the fitter to always return limit 6, and the outliers test caught it. A builder that hard-codes the old constant dies to a test written before the feature existed.

What the builders did

The transcripts of the delegated runs sit in one folder, each with a start line, one tool call per line, and a final line with the printed duration. Three of them cover the two figure cards, the ones where the method earned its keep.

Tool calls and wall clock of the three delegated runsThree panels on one scale: the first renderer attempt used 11 tool calls and 67.3 seconds and wrote no code; the renderers took 49 calls and 715.49 seconds; the highlight fix took 207 calls and 1,125.42 seconds for 41 lines of validation, because the check sat after an early return.050100150200Tool calls →First renderer attempt: read files, wrote no codeRenderers: the three renderersHighlight fix: the invisible early-return bug114920703006009001,2001,500Wall clock, seconds →First renderer attempt: read files, wrote no codeRenderers: the three renderersHighlight fix: the invisible early-return bug67.3 s715.49 s1,125.42 s
Tool calls and wall clock of the three delegated runsThree panels on one scale: the first renderer attempt used 11 tool calls and 67.3 seconds and wrote no code; the renderers took 49 calls and 715.49 seconds; the highlight fix took 207 calls and 1,125.42 seconds for 41 lines of validation, because the check sat after an early return.050100150200Tool calls →First renderer attempt: read files, wrote no codeRenderers: the three renderersHighlight fix: the invisible early-return bug114920703006009001,2001,500Wall clock, seconds →First renderer attempt: read files, wrote no codeRenderers: the three renderersHighlight fix: the invisible early-return bug67.3 s715.49 s1,125.42 s
The three delegated runs, tool calls in one panel and seconds in the other: the longest run found the invisible early-return bug.Source: the tool-call lines and the final duration of each transcript (the three delegation transcripts in my delegation cache).
Data
Tool calls and printed wall clock of the three delegated runs
RunTool callsWall clock (s)
First renderer attempt1167.3
Renderers49715.49
Highlight fix2071,125.42

Source: the three delegation transcripts in my delegation cache

The first renderer delegation read the card, the frozen tests, the spec types and the drawing helpers: 11 tool calls, duration 67.3 seconds, and no code. The record does not say whether it stopped itself or was cut short; a fresh delegation started 16 seconds later. The second wrote the three renderers in 49 calls and 715.49 seconds. The third, the highlight fix, needed 207 calls and 1,125.42 seconds for 41 lines of validation, because the bug was invisible: the check ran after a return, so the frozen test never reached it.

The transcript of the fix makes the wrong turns readable. The builder first blamed the environment: it grepped vitest’s and vite’s caches for its own probe strings, cleared them, and suspected a concurrent process was rewriting the file. It instrumented the checker with probes at every early-return point; only then did it see that checkBar returns early on the no-whisker path and its tail never ran. Its own summary line is blunt: “The reason was not a cache (I burned a long debug session ruling out vitest/vite caches, stale modules, duplicate files)”. The point is that the builder could not see its own blind spot, and the frozen test would not let it ship one.

The other episode was smaller. At 12:57:55 of the second run the suite failed with Cannot find module '../src/figures/data-loader.ts': the frozen loader test imports a file that the sibling card was still writing. The builder found that file in a commit on the sibling card’s branch, 6d20a16, and pulled it in with git checkout 6d20a16 -- src/figures/data-loader.ts; the suite then read 1 failed | 492 passed (493), then 493 passed (493). Its final report, as my session record has it, lists the file as restored by checkout from the sibling branch’s commit and flags it as outside the card’s file list; the delegation transcript I kept is cut off before that report. The live transcript does not carry that report in full, so I am quoting what the delegation returned to me. I dropped the borrowed file on rebase, because it collided with the version already merged from the loader card. My review record for that PR is one clause: “rebased on main (dropped builder’s copy of data-loader)”.

What the reviewers caught

A reviewer that did not write the code does two things no test can: it reads the spec against the implementation, and it mutates the code to see which tests notice. Two independent review cards, on an unrelated Python repo, give the counts.

The first reviewed a retry-and-registry module. Full suite: 93 passed, baseline main was 66. The reviewer ran 12 mutations and 11 died: the exhausted path keeping the first attempt instead of the last, the attempt counter hard-coded, a dropped error prefix, the registry allowing duplicates. So did the subtle one, a blocked result after failures skipping the usage sum. One survived: nothing tested that the fake runner copies the caller’s script list, so a regression removing the defensive copy “would pass the whole suite” (the reviewer’s words). The reviewer lists that copy among the contract’s sentences it verified against the code, and calls it “defensive copy … works (but untested)”. A written rule, no test pinned it, and the builder had passed every test while skipping it. The review’s frozen additions took the suite from 93 to 100 tests.

The second reviewed a circuit breaker: 137 passed, 19 mutants, 17 caught. The two survivors were behavioural: a backdated trip that shortened an active cooldown (probe: cooldown_until moving from 111 to 60, a banned backend back in service early), and float truncation at the trip boundary. Both became MEDIUM findings, and the review’s closing line is the honest kind: approve, with a follow-up card.

What each layer caughtThree layers as rows of squares. The frozen tests: one builder bug caught before merge, the silently ignored highlight label (the row’s other squares mark layers with no recorded catch and are illustrative). The reviewer’s mutations on the two review cards: 11 of 12 mutants died on the retry-and-registry card, the survivor the untested defensive copy of the caller’s script list; 17 of 19 died on the circuit-breaker card, the survivors a backdated trip that shortens an active cooldown and float truncation at the trip boundary. The lead’s hand read and end-to-end run: three catches. Each mutation row sums to the mutants that card ran.Frozen tests, the site cardscaught before merge: 1the highlight label matching no row was silently ignored; the frozen test for the gap took the 207-call runReviewer mutations: the retry-and-registry card11 of 12survivor: nothing tested that the fake runner copies the caller’s script list, so a regression removing the defensive copy wouldpass the whole suiteReviewer mutations: the circuit-breaker card17 of 19survivors: a backdated trip shortens an active cooldown (cooldown_until 111 to 60); float truncation at the trip boundaryThe lead: hand read and end-to-end run3 catchesthe highlight typo, the borrowed file, tests pinned to today’s content
What each layer caughtThree layers as rows of squares. The frozen tests: one builder bug caught before merge, the silently ignored highlight label (the row’s other squares mark layers with no recorded catch and are illustrative). The reviewer’s mutations on the two review cards: 11 of 12 mutants died on the retry-and-registry card, the survivor the untested defensive copy of the caller’s script list; 17 of 19 died on the circuit-breaker card, the survivors a backdated trip that shortens an active cooldown and float truncation at the trip boundary. The lead’s hand read and end-to-end run: three catches. Each mutation row sums to the mutants that card ran.Frozen tests, the site cardscaught before merge: 1the highlight label matching no row was silently ignored; the frozentest for the gap took the 207-call runReviewer mutations: the retry-and-registry card11 of 12survivor: nothing tested that the fake runner copies the caller’s scriptlist, so a regression removing the defensive copy would pass thewhole suiteReviewer mutations: the circuit-breaker card17 of 19survivors: a backdated trip shortens an active cooldown(cooldown_until 111 to 60); float truncation at the trip boundaryThe lead: hand read and end-to-end run3 catchesthe highlight typo, the borrowed file, tests pinned to today’s content
Each layer’s catch, as rows that each sum to a stated whole: the two mutation rows are the reviewer cards’ own runs, survivors named where they sit.Source: the transcripts, the lead review records and the two independent review cards.
Data
Mutants caught and survived, per reviewer card
Reviewed moduleCaughtSurvived
Retry-and-registry module111
Circuit breaker172

Source: the two independent review cards

The site cards had their own reviewer pass, which is the human kind. On the loader card, the lead review record reads: “found and fixed one gap (highlight labels matching no row were silently ignored: frozen test added, builder fixed)”. The mutation run on the phone card was mine. Between them, every finding about the builders’ code came from a mutation or a hand-read, not from a failing test. On those cards, every builder problem the records show was caught before its merge.

What the end-to-end run caught

The last PR of the morning was not about the builders at all, and it is the one that justifies the whole procedure. After the loader and renderer cards merged, I ran the full build with a published note plus a YAML figure file, exactly what the feature promises an author can write without code. Everything passed except two tests that quietly assumed today’s content: the loader test expected src/figures/data/ to be empty forever (“no yaml today”), and the phone-fit test expected a note named outliers not to fit at 6 titles. Both were frozen tests written before any content existed, both green at their merge times, and both went red when the content came. Both now assert invariants over whatever content exists, with the phone-fit check pinned against an independent largest-passing-limit reference.

A test that pins the present fails in the future. The method’s own tests were the last thing that needed fixing, and only an end-to-end run, the lead’s job, found it.

What this does not show

One codebase. One operator, who wrote the specs, did the lead reviews and ran the end-to-end check himself. Five merged cards, two reviewer cards, three delegation transcripts. The two reviewer cards are from a different repository and a different kind of module (a runner contract and a circuit breaker in Python) than the site’s figure cards, so their mutation counts show what the reviewer role catches, not what it caught here. No control group: I have not run the same cards with the builder writing its own tests, so I cannot say what the frozen tests alone contribute versus the reviewer or the hand check. The durations are wall-clock of delegated runs on one GB10 box, not effort, and the 207-call fix happened once. Small n, no generalisation.

What I take from it is about where thinking goes, not who types. The spec is the work: writing a frozen test for the misspelled highlight label is worth more than the 41-line fix, and the mutation that kills the hard-coded limit is worth more than the 121-line feature. The builder is fast and literal: it will look for the shortest way to a green suite, including a file from another branch, and say so afterwards. The tests and the mutations are what make literal a good property. The open question, untested so far: how much of the review value is me, and how much would survive a reviewer agent that reads the spec the way the two cards’ reviewers did.