Section 5Build — TDD with agents
Section 4 produced tickets whose "done" a stranger could check: open the app, book, look at the screen. This section turns that sentence into something even better — a check the computer can run. Each ticket's "done" criterion becomes an automated test, written before any code exists, and the build isn't finished until that test passes. The plan said what "done" means; the test makes the machine enforce it.
Left alone, an agent writes ninety messy tests and a huge block of code at once, then declares victory. The cure is TDD as automated discipline: red-green-refactor, run by the /tdd skill, one test at a time.
Here's the twist worth savouring: TDD is a decades-old human practice that most human teams never stuck with, because the discipline is tedious — write a tiny test, run it, write a tiny bit of code, run it again, tidy, repeat. Agents don't get bored. The loop that humans abandoned after two sprints is exactly the loop an agent will run forty times an hour without complaint, and each red-to-green cycle hands it unambiguous feedback: the test either passes or it doesn't, no opinion involved. An old idea finally has a workforce with the patience for it.
Write a failing test
Define "done" as an executable test — and watch it fail, so you know it tests something real.
Make it pass
Write the minimum code to turn the test green. No more.
Tidy up
Clean the code with the test as a safety net. Repeat for the next test.
What an automated test is. A small program whose only job is to check another program. "Call bookAppointment() with Tuesday 10:00; the calendar must now show Tuesday 10:00 as taken" — written as code, so a machine can run it in milliseconds, thousands of times, and answer with a plain pass or fail. A collection of these is a test suite: a growing safety net where every thread is one promise the software has made and keeps being held to.
Why the order matters — red before green
The sequence isn't ceremony; each step guards against a specific failure. Writing the test first and watching it fail proves the test can fail — a test born green might be testing nothing at all, and you'd never know. Writing the minimum code to pass keeps the agent from sprinting ahead into speculative features nobody asked for; anything the tests don't demand is scope creep by another name. And refactoring after green means every cleanup happens inside a safety net — if a tidy-up breaks a promise, a test turns red within seconds, naming exactly which promise. Skip a step and the loop quietly stops protecting you: code-then-test produces tests that flatter the code instead of challenging it.
Where TDD comes from. Kent Beck formalised test-driven development as part of Extreme Programming and laid it out in Test-Driven Development: By Example (2002). He's careful to call it a rediscovery, not an invention: programmers have specified inputs and expected outputs before coding since the dawn of computing. Beck's contribution was turning that instinct into a tight, repeatable loop — and framing its purpose as eliminating fear: with the net in place, you can change code without dreading what you might silently break.
Don't let the AI mark its own homework
Pocock's key move: the agent that writes the implementation is not the agent that wrote the test. One agent writes a single failing test and is forbidden from writing production code. A second agent writes just enough code to pass it. The tester can't quietly weaken the test to make its own code look good.
This rule exists because the failure it prevents is real, not hypothetical. Anthropic's own guidance for Claude Code documents that the agent will sometimes change the tests to make them pass rather than fix the implementation — the machine equivalent of a student erasing exam questions they can't answer. The same guidance recommends two guards you can adopt directly: commit the tests before any implementation exists, so if the agent later touches them the diff shows exactly what changed and you can revert it (committing, like everything else here, is an instruction rather than a chore — you never type git commands yourself; you tell the agent "commit the tests now" and it does); and say explicitly that you're doing TDD, because otherwise the agent may "pass" the test with a mock implementation — a hollow stand-in that returns the expected answer without doing the work. The two-agent split is the structural version of the same idea: instead of hoping one agent resists temptation, arrange things so the temptation has no path.
Why an agent would "cheat". There's no malice in it. The agent's goal, as it understands it, is "make the test pass" — and editing an assertion is a perfectly valid way to reach that goal unless something forbids it. It optimises for the target you gave it, not the intention behind the target. That's why this guide keeps reaching for structural fixes — separate agents, committed tests, explicit rules — rather than hoping the agent infers what you really meant.
Test behaviour, not internals
Because agents refactor constantly, tests bound to internal file structure break every time code moves — "death by a thousand false alarms." Instead force blackbox behaviour tests: launch the real app, press the button, see the status change. They survive refactors and double as a readable spec of the feature. This choice is what makes review-by-watching possible in Section 6.
The distinction sounds technical but it's really about what a test promises. A test of internals promises "the code is shaped like this" — a promise nobody outside the codebase cares about, and one that agents break hourly by design, because reshaping code is what refactoring is. A behaviour test promises "when the salon owner blocks a holiday, no customer can book that week" — a promise straight out of the ticket, in language the ticket's author can read. When a behaviour test breaks, something a user would notice has actually broken. When an internals test breaks, usually nothing is wrong at all — and a suite that cries wolf trains everyone, humans and agents alike, to stop listening.
"Blackbox" — testing from outside the box. Imagine the app as a sealed box: you can press its buttons and read its screens, but you can't see the wiring inside. A blackbox test interacts only through that outside surface — start the real app, click "Cancel booking", check the confirmation appears — so it keeps passing no matter how the wiring inside is rearranged. The opposite approach peeks into the box and asserts on the wiring itself, which is precisely what an agent rewires every day.
For interfaces there's one decision no test can settle: what the screen should look like in the first place. Settle it before the build starts, at the cheapest possible fidelity.
Before the agent writes any interface code, have it sketch the screen as a throwaway ASCII wireframe in chat — boxes, labels, arrows. Iterating on a text sketch takes seconds and no cleanup; iterating on rendered components takes minutes each round and leaves dead code behind. Agree on the sketch, then build it once.
Stay in scope — no "while I'm here" fixes
An agent implementing one ticket will often notice something else along the way — a bug in a neighbouring function, code that could be tidied, a naming inconsistency — and, being eager and capable, just fix it too. Resist this. A diff that does two unrelated things is harder to review, harder to roll back cleanly (Section 8), and harder to reason about later when git blame (git's who-changed-this-line view) shows one commit for two decisions. It also costs more tokens for no benefit: a focused change fits in less context and needs fewer corrective turns than one that drifted.
Ticket: "add a cancellation button to a booking." Agent also renames a variable it found confusing, tightens an unrelated validation rule, and reformats a file it happened to open. One diff, three decisions, one review.
Same ticket. The agent adds the button — nothing else — and leaves a short note: "noticed calculateSlot() doesn't handle a null salon ID; out of scope for this ticket." That note becomes a new ticket, reviewed and prioritised on its own.
Make the rule explicit in AGENTS.md — a community convention worth copying almost verbatim calls this "surgical changes": asked to fix a bug in one flow, the agent doesn't also refactor the logging it noticed nearby; it stays on task. Give it somewhere real to put what it noticed: a FOLLOWUP: comment convention, or a standing instruction to file a new ticket on your board (Section 4) rather than a code comment that quietly rots. Catch this even earlier by using plan mode — an out-of-scope addition is far easier to spot in a proposed plan than buried in a finished diff.
When the work is genuinely bigger — stack it
This subsection is for work too big for one ticket — skip it on your first pass; nothing later depends on it.
When you genuinely do want to land several small, related changes without one bloated diff, stack them: a chain of small branches — parallel working copies of the code, each merged (folded back) into the main line when ready — queued on top of each other, each independently reviewable and mergeable, instead of one giant merge. Tools built for exactly this — Graphite, the lighter git-spice, or GitHub's own native stacked pull requests (in private preview as of mid-2026) — exist because "just make smaller commits" doesn't survive contact with real, dependent work. As a rule of thumb, keep each individual change under roughly 200 lines and doing one logical thing a reviewer can hold in their head.
When it gets stuck — method beats guessing
Sooner or later a test refuses to go green and the agent starts thrashing: try a fix, fail, try a different fix, fail again — each attempt piling more noise into its own context until the original problem is buried under the debris of failed guesses. The instinct to "let it keep trying" is exactly wrong here; more attempts of the same kind produce more debris, not more insight. What's missing isn't effort, it's method.
An agent chasing a bug will thrash. Run /diagnose to force a disciplined loop: reproduce → minimize → hypothesise → instrument → fix → regression-test. It replaces guessing with method. For a web app specifically, Chrome DevTools for agents (an MCP server exposing the same debugger you'd use manually) turns "fix the sign-up bug" from a file-search guessing game into pointing the agent at the actual running page — it drives the browser, reads the failed network request, and finds the file itself via source maps (the map from a page's running code back to your source files), instead of you naming it.
The pattern behind /diagnose is the scientific method in miniature: first make the bug happen on demand, then shrink the situation until only the essential ingredients remain, then form one hypothesis and add instrumentation that can prove it wrong. Every fix that lands also gains a regression test — the bug's own failing test, kept forever — so the same mistake can never sneak back in unnoticed. A bug you fixed without a regression test is a bug you've merely postponed.
All of it ends in the same place: a pile of green checks and a claim of "done" — and Section 6 is about verifying that claim by evidence, without reading every line.
Quick check: the agent writes the feature, then adds tests afterwards, and everything is green on the first run. Why is that a warning sign, not a victory?
Because none of those tests has ever been seen to fail. A test born green might be testing nothing at all — and tests written after the code tend to flatter it rather than challenge it. Worse, the agent that wrote the code also wrote its own exam, so any assertion that was inconvenient could have been quietly softened. The fix is the loop this chapter runs: one failing test first, written by a separate agent, then the minimum code to turn it green. Green only means something when red came first.