TL;DR

  • Agents fail quietly. Your job is no longer writing code, it is building the check that catches the quiet failures.
  • Keep every diff small enough to review in one sitting, and commit before you prompt again.
  • Read the diff, not the summary. The summary is the least reliable part of the output.

The first month I worked this way, I shipped faster and understood less. That trade is fine for a throwaway script. It is not fine for anything that takes payments, stores an address, or sends an email on someone’s behalf. So I built a loop, and the loop is deliberately boring.

The premise is simple: an agent’s output is a proposal, not a completion. It is confident in proportion to how plausible the code looks, which is not correlated with correctness. Every part of the loop exists to reduce the distance between “looks right” and “is right”.

Start with a contract, not a prompt

The single highest-leverage change is a file in the repo that every session reads first. Different tools look for different names — AGENTS.md is the emerging convention, CLAUDE.md and .cursor/rules are common — and most agents will pick up whichever you use, or you can paste it into the first message.

# Project rules

## Commands
- dev: npm run dev
- typecheck: npx tsc --noEmit
- test: npm test
- build: npm run build

## Constraints
- TypeScript strict. No `any`, no non-null assertions to silence errors.
- No new runtime dependencies without asking.
- Every network call must handle failure and expose an error state.
- Do not reformat files you were not asked to change.

## Definition of done
- The build and the typecheck pass.
- The behaviour is verified by running the app, not by reasoning.
- The diff touches only files required by the task.

This costs five minutes and removes a whole genre of bad output: the agent inventing a test command, adding any to clear an error, or reformatting a file and hiding the real change in whitespace. It also gives you a checklist to hold it to. “You skipped the typecheck” is a factual correction, not a negotiation.

Keep the file short. A 400-line style guide gets skimmed by humans and truncated by agents.

One behaviour per prompt

The diff is your review surface. If the diff is 900 lines across 14 files, you will not review it; you will skim it and press merge. That is the failure mode, and it does not feel like one.

Rules that keep diffs small:

  • One behaviour per prompt. “Add optimistic likes” is a prompt. “Add optimistic likes, refactor the store, and update the README” is three prompts pretending to be one.
  • Commit before every prompt. Then git diff shows exactly what the agent did, and git checkout -- . is a complete undo.
  • If the agent starts “while I was in there” refactoring, stop it and revert. Unrequested refactors are where regressions hide, because they move the code you were about to test.
  • If a task genuinely needs 500 lines, ask for the plan first, approve it, then ask for the change in ordered steps.

Run it. Then run it again with the error path.

Reading generated code is unreliable in a specific way: it is syntactically clean. The bugs are semantic — the state updates but the derived value does not, the error branch renders nothing, the optimistic update never rolls back.

So the minimum verification is not “does it compile”, it is:

  1. Start the app and perform the action as a user would.
  2. Do it again with the failure injected: kill the network in devtools, submit an empty form, pass a value that should be rejected.
  3. Reload the page in the middle of the flow. Half of AI-generated state bugs only appear after a refresh.
  4. Run the build, not just the dev server. Production builds surface hydration mismatches, environment variable mistakes, and type errors that the dev server tolerates.

If the agent can run commands, give it the same loop: run the dev server, open the page, click the button, report what happens. It is much better at that than at predicting behaviour from source.

Read the diff, not the summary

An agent’s closing summary is written to be reassuring. It says “implemented error handling” whether or not the catch block swallows everything. Treat the summary as a hypothesis and the diff as the evidence.

The failures I look for, in order of how often they appear:

What to look for Why it matters
Deleted or skipped tests The fastest way to make a failing suite green
catch {} that swallows errors Makes the happy path look healthy in production
any, @ts-ignore, as unknown as Types were failing and the error was silenced
Changed unrelated files Hidden behaviour change, or a reformat hiding the real diff
New dependency Supply-chain and bundle-size cost you did not ask for
useEffect used as a data fetch Double-fetch, race conditions, no cleanup
Hardcoded values from the fixture Works in the demo, breaks on real data

None of these are visible in a summary, all of them are visible in a diff that fits on two screens.

Give errors verbatim

When something fails, paste the entire error, including the stack trace and the command that produced it. Paraphrasing (“it says something about hydration”) sends the agent searching a space of plausible errors instead of the actual one.

Include the reproduction: which URL, what you clicked, what the console printed. Then let the agent propose one fix at a time. Three speculative fixes in one message produce a diff that mixes a real fix with two random edits, and you cannot tell which was which.

Context hygiene

Most of the bad output I see comes from a long session, not a weak model. Symptoms: the agent starts re-solving a problem you already solved, or references a file you deleted twenty messages ago.

What works:

  • New session per task. The repo is the shared memory; the conversation is not.
  • Restate the goal in one sentence at the start of each task, and point at the files that matter.
  • Two strikes on the same error, then reset. If the agent has failed twice on the same failure, a third attempt in the same context rarely helps. Start clean with the error text and the current diff.
  • Keep the plan in the repo, not in chat. A short PLAN.md that gets updated survives a context reset; a plan in a conversation does not.

What not to delegate

Some code is worth writing yourself even when the agent could do it: authentication, payment flows, anything that deletes data, and database migrations. Not because the model cannot produce them, but because a subtle mistake there is expensive and hard to observe, and because you need to understand the code well enough to debug it at 2am.

The test is simple: if you cannot explain every line of the diff, you cannot ship it. Either spend the twenty minutes to understand it, or rewrite the part you do not. “It works” is a statement about the cases you tried, and you are the one who chose them.

The loop, compressed

  1. Write or update the contract file: commands, constraints, definition of done.
  2. Commit. State one behaviour.
  3. Let the agent implement, then run the build and the typecheck.
  4. Exercise the happy path and one failure path in the browser.
  5. Read the diff. Look for the seven failures above.
  6. Commit with a message that says what changed and why. If the next prompt breaks it, you know where to return.

That is the whole method. It is not fast in the way a demo is fast, but it is fast in the way that leaves you with a codebase you can still change next month, which is the only speed that compounds.

Most of what I write here comes out of running this loop on real projects — the spring values, the glass recipes, the debugging steps. If you want the tool that saves me the most repeated work, it is the spring playground, because it is the one place where “looks right” and “is right” are the same thing.