TDD with an Agent
Intent
Walk the agent through the red–green cycle with phase-specific prompts: first failing tests, frozen by a commit, then a minimal implementation until green — with no right to edit the tests. The oracle is written before the code and therefore doesn't inherit its bugs.
Also known as
Test-driven development with agents, red–green–refactor, test-first.
Problem
Ask the agent to "build the feature and cover it with tests" — and it will do exactly that, in that order: the implementation first, then tests for it. Such tests look like coverage but verify little:
- The tests are copied off the code. The agent writes them while looking at the finished implementation — they repeat its structure and its bugs. If a condition is inverted in the code, the test enshrines the inverted condition as the norm.
- Tautology instead of verification. The expected value is computed the same way the code computes it — the test passes by construction and can never disagree with the code.
- Fitting to the oracle. If the tests already exist, an agent under "make it green" pressure knows how to cheat: a stub instead of logic, a special case hidden under a specific test.
A single "use TDD" instruction is not enough: without explicit phase gates the agent slides back into its habitual order — code, then tests.
Solution
Separate the red and green phases into different prompts and keep the gate between them in the developer's hands.
- Declare the rules. Say it outright: we work test-first — so the agent doesn't create implementations or stubs ahead of time.
- The red phase. The agent writes tests from expected input/output pairs, runs them, and confirms they fail. Implementation is explicitly forbidden in this phase. A test that never failed proves nothing.
- Freeze the oracle. The tests are committed. From this moment they are the reference, not a draft.
- The green phase. The agent writes minimal code until the tests pass, running them and iterating — this is the Feedback Loop with a ready-made oracle. Editing the tests is forbidden: changing them is the developer's decision.
- The overfit check. A fresh subagent looks at the implementation: is it fitted to the specific tests (see Writer and Reviewer).
- Refactoring — as a separate move after green, under the tests' protection, not inside the cycle.
Work in vertical slices: one test → one minimal implementation → the next test. All tests in bulk means verifying imagined behavior: the test structure gets locked in before the task is understood. And agree on the seams in advance: tests live on public boundaries, not on internals — otherwise they break from refactoring, not from behavior changes.
Structure
The phases run left to right, each with its own prompt: the red one produces a failing oracle, the commit freezes it, the green one spins the implementation loop with the tests frozen, then a fresh pair of eyes checks the implementation for overfitting, and only after that comes refactoring under the tests' protection. The loop at the bottom is the vertical slices: the cycle repeats one test at a time, each next slice building on what the last one taught.
Participants / Components
- Developer — supplies the cases and the seams, holds the phase gates, is the sole authority over editing tests.
- Agent — writes tests in the red phase and the implementation in the green one; doesn't blend the phases, because each arrives as a separate prompt.
- The test oracle — failing in the red phase, frozen in the green one; a specification of behavior independent of the implementation.
- Seams — the agreed public boundaries the tests live on.
- Reviewer — a fresh subagent checking the implementation for overfitting.
When to use
- Logic with verifiable input/output pairs: parsers, calculations, validation, data transformations.
- Bug fixes: a failing reproduction test before the fix is the cheapest insurance against the bug's return.
- Code where the cost of regression is high and the tests will live on as a specification.
For interface markup, prototypes, and exploration the pattern is overkill: there is nothing to pin down with an input/output pair, and the Feedback Loop with screenshots or a Throwaway Prototype works better.
Consequences and trade-offs
- ➕ The oracle is independent of the implementation: the tests don't inherit the code's bugs, because they were written before it.
- ➕ Cheating is visible: with frozen tests a stub won't pass, and an attempt to edit a test is an explicit violation, not a quiet tweak.
- ➕ The tests read like a specification and survive refactorings — they are bound to seams, not to internals.
- ➖ Slower than a direct request: two phases, commits, an overfit check — on a trivial edit this is bureaucracy.
- ➖ The discipline rests on the developer: skip a gate, and the agent has quietly slid into "code, then tests".
- ➖ Quality is bounded by the seams: tests on badly chosen boundaries will be brittle, no matter how many phases there are.
Implementation
- Start with the declaration: "we're doing TDD: tests first, implementation after."
- The red prompt: "write tests for cases X, Y, Z; run them and show they fail; don't write the implementation." Supply the cases yourself — that's your part of the specification; ask the agent to propose missed edge cases.
- Agree on the seams before the tests: "what's the public boundary here? which seams do we test at?" — reject tests on internals.
- Commit the red tests. From here the rule holds: only the developer changes tests, as a separate decision.
- The green prompt: "implement until the tests pass; don't edit the tests; run and iterate." Demand evidence — the test runner's output.
- After green — with a fresh context: "check that the implementation isn't fitted to the tests: stubs, special cases for specific test inputs."
- Ask for refactoring separately, under the protection of green tests.
- Repeat one slice at a time; anchor the "red before green" and "don't edit tests" rules in Project Memory.
In the toolkits the cycle comes pre-assembled: in
Superpowers the test-driven-development skill is
mandatory inside every plan task, in
Matt Pocock's skills /tdd adds seams and vertical
slices and moves refactoring out into review.
Example
The bug: a user with an expired session isn't logged out and stares at an infinite spinner. The developer starts with the red phase:
We're doing TDD. Write a test reproducing the bug: the session has expired — an API request returns 401 — the user ends up on /login. Run it and show that it fails. Don't write the fix yet.
The agent writes a test at the "HTTP client → response handler" seam and runs it: red — on a 401 the client goes into an infinite retry. The test is committed.
Now fix it. Don't edit the test; run it and iterate until green.
The agent finds that the interceptor retries all errors indiscriminately, adds a 401 exception with a redirect — green, the test runner's output in the reply. The final touch:
With a fresh subagent: check that the fix isn't fitted to the test — that the 401 handling works for all requests, not just the endpoint from the test.
The reviewer confirms: the change is in the shared interceptor. The bug is closed, and its return is now caught by a test that was born before the fix — and therefore verifies the behavior instead of transcribing it from the code.
Anti-patterns and common mistakes
- Tests after the fact. "Build the feature and cover it with tests" produces tests copied off the implementation — coverage exists, verification doesn't.
- Skipping red. A test that never failed may be passing for any reason — including that it verifies nothing.
- All tests in bulk. Horizontal slicing locks in the test structure before the task is understood; work in vertical slices.
- The agent edits the oracle. Editing a test in the green phase is rewriting the specification to match the answer. Only the developer, only as a separate decision.
- Tests on internals. Mocking internal collaborators and asserting on private methods breaks with refactoring, not with behavior changes — the seam was chosen wrong.
- A tautological oracle. An expected value computed the same way as in the code passes by construction. References come from an independent source: the spec, a hand-worked example, a known-good answer.
Known uses
- Claude Code best practices — the canonical phased workflow: tests from input/output pairs with an explicit "we're doing TDD", confirming the failure, committing the tests, implementing without the right to change them, and an independent overfit check.
- Superpowers — TDD as a mandatory mode: every plan item is implemented by a subagent through red–green–refactor; the cycle can't be skipped.
- Matt Pocock's skills —
/tdd: pre-agreed seams, tracer-bullet vertical slices, a ban on tautological tests, refactoring moved out into review. - Kent Beck, Test-Driven Development: By Example — the primary source of the practice itself; with agents it gets a second wind: a cycle that used to demand human discipline can now be imposed by prompts.
Related patterns
- Feedback Loop — the general pattern whose disciplined form TDD is: the oracle is written before the code, one per step.
- Writer and Reviewer — the overfit check at the end of the cycle: the implementation is judged by someone other than its author.
- Four Phases — test cases are naturally born in the plan phase: the approved plan names what counts as "works".