Make the Agent Prove It
When an agent says "it works", that is a prediction. A test is a measurement. Here is how to get one.
give an agent a check it cannot argue with, write the failing test before the code, and review an AI diff for the five things that actually go wrong
Бұл бет әлі аударылмаған, сондықтан ағылшынша нұсқасы көрсетіліп тұр.
When an agent finishes and says the feature works, it is not reporting. It is guessing — a very well-informed guess about what would happen if somebody ran the code. A test is different. Somebody ran it, and here is the output.
Turning that guess into an output you can read is the cheapest skill in this course, and the one that separates a weekend prototype from something you would let a stranger use.
The agent stops when the work looks done
Anthropic’s own guide for Claude Code now opens with a section called “Give Claude a way to verify its work” — it is the first thing on the page, before planning, before context, before anything else (code.claude.com/docs). The reasoning is blunt. The agent needs a signal to decide it is finished. If you have not given it one, the only signal available is this looks finished. And then you become the test suite: you click around, you find the broken thing, you paste it back, and you do that again on every feature for the rest of the project.
You can see that cost in the survey data. The frustration developers named most often was not wrong answers. It was answers that are nearly right.
Almost-right code is expensive precisely because it passes a human glance. It does not pass a test.
A check is anything that returns pass or fail
You do not need a testing framework to start. A check is anything the agent can run that comes back with a yes or a no it cannot argue with. The Claude Code guide lists the usual candidates: a test suite, the build exit code, a linter, a small script that diffs your output against a fixture, or a browser screenshot compared against a design.
There are four levels of how hard you make the agent obey that check, in rising order of setup:
- Ask in the prompt. “Run
npm testand keep going until it passes. Show me the output.” Free, and it works more often than people expect. /goal <condition>. Claude Code runs a separate small model that re-checks your condition after every turn.- A Stop hook. A script that refuses to let the turn end until your command exits zero. Claude Code overrides it after eight consecutive blocks, so it is a strong nudge rather than a cage.
- A verification subagent — a fresh model whose only job is to try to refute the result.
Two rules from the same page, both worth writing on your wall. Ask for evidence — the command and its output — never for assertions. And: if you cannot verify it, do not ship it.
What to test first when you have no tests
The classic picture is a pyramid, and it is worth ten seconds of your attention because it explains why your instincts about testing are usually inverted.
The pyramid describes where you should end up after a year. It does not describe your first afternoon. With zero tests, write one test at the top: the single path your product exists for. For QAIRU Event Sign-up that is open the page, fill in a name and an email, press reserve, see a confirmation. One test, covering the only thing that makes the app worth deploying.
Then push downward, and let the bugs choose for you. Every time something breaks, that bug becomes a unit test before it becomes a fix. Anthropic’s bug-fix pattern is exactly this: describe the symptom, the likely location, and what “fixed” means, then ask for a failing test that reproduces it, and only then the fix. Your suite ends up made entirely of things that genuinely went wrong in your project — a far better suite than one made of things a model imagined might go wrong.
Write the test that fails, then let it code
Red/green TDD is the oldest trick here, and it turns out to fit agents better than it ever fit humans. Simon Willison’s guide gives the one-line version: append “Use red/green TDD” to a build prompt, and the agent writes the test, watches it fail, then implements until it passes (Agentic Engineering Patterns).
The red step is the part people skip and the part that carries the value. A test you never saw fail might be passing for the wrong reason. It might be passing because it asserts nothing. It might still pass if you deleted the feature entirely. Watching it go red first is the proof that the test is wired to the behaviour it claims to describe.
Four reasons this works unusually well with an agent specifically:
- The loop closes without you. The agent can run the test itself, read the failure, and iterate. You are no longer the thing standing between attempt one and attempt two.
- It bounds the work. Willison lists this directly: red/green guards against code that does not work and against unrequested extra code. The test defines the edge of the task.
- It survives the session. The next session starts with no memory of this one. The test does not.
- The agent will optimise for whatever you measure. When Anthropic built a C compiler with sixteen parallel Claude instances, the stated lesson was that the agent solves exactly the problem the tests define (building a C compiler). Strong tests, right product. Weak tests, confidently wrong product.
“Add duplicate-email checking to the sign-up form, and add tests.”
The agent writes the feature, then writes tests that describe the feature it just wrote. Everything is green and nothing has been proven. If the feature has a hole, the test was written from the code that has the hole.
“Write one failing test for duplicate emails. Run it. Show me the failure. Stop.”
You read a real failure message before a single line of production code exists. Then you say go. The test was written blind to the implementation, which is the only condition under which it means anything.
Feature: [one sentence — what must be true when this works].
Files I expect this to touch: [path, or "you decide, but tell me first"].
Use red/green TDD, in this order:
1. Read the existing tests in [test dir] first. Copy their style and their runner.
2. Write ONE failing test for the behaviour above. Nothing else. No production code yet.
3. Run it. Paste the failure output and tell me in one sentence why it fails.
Then STOP and wait for my OK.
4. After my OK: write the minimum code to make that test pass.
Run only that test while iterating.
5. Run the full suite once at the end. Paste the final output.
Rules:
- Never delete, skip, or loosen an existing test to get green.
- If an existing test starts failing, stop and tell me. It may be a real bug.
- No extra features, no refactors I did not ask for.One end-to-end test for the path that matters
Unit tests check functions. Only an end-to-end test checks the product. Playwright drives a real browser through your real app — it is the same tool Anthropic’s long-running harness uses to let an evaluator agent operate the app and grade it against criteria (harness design, 24 Mar 2026).
Set it up once:
npm init playwright@latest
# choose TypeScript, put the tests in ./e2e, let it install the browsers
npx playwright testThen this is the whole test for QAIRU Event Sign-up — the one path the app exists for:
// e2e/reserve-seat.spec.ts
import { test, expect } from '@playwright/test';
test('a visitor can reserve a seat and sees a confirmation', async ({ page }) => {
await page.goto('http://localhost:3000/');
await expect(page.getByRole('heading', { name: 'QAIRU Meetup' })).toBeVisible();
await page.getByLabel('Name').fill('Aiganym');
await page.getByLabel('Email').fill('aiganym@example.kz');
await page.getByRole('button', { name: 'Reserve a seat' }).click();
await expect(page.getByText('Seat reserved')).toBeVisible();
});
Read it out loud and it is just the thing a person does. That is the point: it fails when a human would fail, not when an internal function signature changes. Notice it asks for elements by their visible role and label rather than by CSS class — so a restyle does not break it, but removing the button does.
Your AGENTS.md from Module 6 already promises an E2E command. Now one exists, so make that line true — the agent cannot guess a command it has never seen:
## Commands
- E2E: `npx playwright test` (needs the dev server running on port 3000)
Reading the diff it just handed you
Tests catch behaviour. They do not catch the slow rot — and there is now a large dataset on what the rot looks like. GitClear analysed 623 million code changes between 2023 and 2026 and found the same direction in every metric:
Copy-pasted lines went from 9.4% of changes to 15.7% in the first half of 2026, while cross-file reuse fell 35%. This is not a claim that AI writes bad code. It is a claim about what unreviewed AI code does to a repository over time: an agent asked to add a feature will happily write a fifth copy of a function that already exists four times, because writing is cheap for it and searching costs context. Nobody decided to accumulate that debt. It accumulated because no human read the diff.
ТереңірекHow much to trust these numbers
GitClear sells code-quality tooling, and the study is correlational — there is no AI-versus-non-AI control group. One more caveat: the “moved lines” metric can undercount refactoring when an agent rewrites a function in place rather than relocating it. A separate vendor study from CodeRabbit (17 Dec 2025, 470 open-source PRs) found AI-co-authored pull requests carried about 1.7x more issues — 10.83 versus 6.45 per PR — with logic errors 1.75x higher. The same dataset found human PRs had 1.76x more spelling errors and 1.32x more testability issues, which is a useful reminder that nobody in this comparison is clean. Treat all of it as a direction, not a measurement.
So: five things to look for in an AI diff, in this order.
- Does it do what you asked? Not “does it look reasonable” — does it satisfy the specific requirement. Name the requirement it misses.
- Would the tests fail if the feature broke? Delete the feature mentally and ask which test goes red. If none, the tests are decoration.
- Was anything swallowed? An empty
catch, a default value that hides a missing case, atrythat turns a crash into silence. This is the failure that reaches production and never shows up in a log. - Does this code already exist? Duplication is the clearest signal in the GitClear numbers above, and the easiest of the five to catch — you just have to ask.
- What changed that you did not ask for? Scope creep in an agent diff is common and it is where unreviewed risk hides.
Security gets its own module next — for now, the review rule is that input validation, auth checks and secrets in code go on this list too.
You are reviewing a change you did not write. Start by reading it: `git diff main...HEAD`.
Your job is to find what is wrong with it, not to defend it. Assume it is flawed.
Check, in this order:
1. Correctness: does it do what [SPEC.md / the task] says? Name what is missing or wrong.
2. Edge cases: empty input, a duplicate submit, very long text, non-Latin characters,
the network call failing. For each: is it handled AND tested?
3. Tests: which of these tests would still pass if I deleted the feature entirely?
List them by name. Was any test skipped, weakened, or deleted in this diff?
4. Hidden failure: every catch block that swallows an error, every default value
that masks a missing case.
5. Duplication: is there code here that already exists elsewhere in this repo?
Show me both copies with file and line.
6. Scope: anything changed that the task did not require?
For each finding give: file and line, what breaks, a concrete failing scenario, a fix.
Put style opinions in a separate list at the end.
If you genuinely find nothing serious, say so plainly — do not invent findings.Let a second agent try to break it
The pattern above has a name. Anthropic’s “Building effective agents” calls it the evaluator-optimizer loop: one model generates, a second critiques against explicit criteria, and the output goes back around until the critic is satisfied (19 Dec 2024). The long-running harness post gives the reason it is worth the extra tokens in one line — agents grade their own work too generously, so the evaluator is kept structurally separate and drives the real running app rather than reading the code. The multi-agent guidance from January 2026 packages the same idea for daily use as a verification subagent: one agent whose only job is to test and validate, instructed that it must run the complete suite before it is allowed to pass anything.
You do not need any of that infrastructure today. The beginner version is two keystrokes: /clear, then paste the review prompt. A fresh context reviews better because it is not attached to code it just wrote.
Give QAIRU Event Sign-up something it cannot fake
Two pieces of work, in this order.
First, the end-to-end test above. Install Playwright, save e2e/reserve-seat.spec.ts, run it, and make sure it goes green against your running app. Then break something on purpose — rename the button — and confirm the test goes red. A test you have never seen fail is not yet a test.
Then, one feature by red/green. The same email should not be able to reserve two seats. Paste the red/green prompt, fill in the feature line, and hold the agent at step 3 until you have read the failure message yourself. Only then approve.
Finish by putting the real E2E command into your AGENTS.md under ## Commands, and commit. Next session starts knowing how to check its own work.
The two prompts from this module, ready to paste: write the failing test first, then make a fresh agent attack its own diff.