An agent can produce a week of plausible code before I have rebuilt the mental model of the system it changed. That speed is useful. It also makes it easy to become the person who approves code without understanding the product and its failure modes.
Typing is only one part of engineering. Working through an implementation teaches me where state lives, which assumptions I have made, and what the system does when an operation fails halfway through. I want to keep that learning even when an agent writes most of the diff.
What Gets Delegated First
The obvious tasks are implementation work: moving a function, wiring an API, writing tests, formatting a migration, fixing a type error. The subtler delegation happens when I let an agent choose the invariants, infer the product promise, settle a trade-off, and judge its own result. At that point I may receive a coherent explanation and a passing test suite without ever having asked whether it solved the right problem.
“Add a cache” is an implementation request. “A revocation must take effect before the write returns” is the rule that makes the design possible. Cache policy, invalidation timing, failure handling, and tests follow from it. When I leave the rule unstated, the agent has to invent one.
Write the Decision Before the Prompt
FERS gave me a concrete version of this problem. The C++ simulation core owns the world. A C interface exposes opaque handles. Rust owns the handle on the desktop side. An agent can write bridge calls and tests, but the ownership decision has to be coherent before those calls exist. A callback that outlives the native context would expose a flaw in the design, however tidy the generated wrapper looks.
For a substantial change, I write down the user-visible promise, the state that owns it, the failure path, and the evidence that would prove the behavior. A page is usually enough. When two designs remain plausible, I ask the agent to show the trade-off in the actual codebase before it implements either one.
Keep the System Map in My Head
Generated code tends to look complete at the file level. Systems fail across files and boundaries. I need to know the path from user request to durable state and back: the caller, authorization, transaction, queue, worker, external effect, cache, and UI acknowledgment. When reviewing a change, I trace that path and ask where the promise becomes irreversible.
Small changes help me keep that map current. A change inside one boundary is easy to inspect. A change crossing schema, service, UI, and deployment needs an explicit contract at each crossing. I stop at those crossings and ask the agent to trace them with me.
Review Against an Independent Oracle
An agent can write implementation and tests that agree with each other while both embody the same misunderstanding. The strongest tests get their expected result from elsewhere: a product rule, a protocol, an independent calculation, a known fixture, or a failure case chosen before implementation.
For a numeric algorithm, I use hand-derived small cases or a separate reference method. For a web change, I run the page, inspect narrow and wide layouts, and try the keyboard path. For a background job, I interrupt it between durable steps and see what a retry does. For a deployment workflow, I ask what happens after the first stage succeeds and the second fails.
The test should be able to disagree with the implementation. If it merely repeats the same logic in an assertion, it is decoration.
Read the Parts That Carry Risk
I spend my reading time on the sections that hold authority: transactions, ownership and lifetimes, data transformations, authorisation, failure handling, external calls, migrations, and deployment gates. I should be able to explain why they are shaped as they are and what changing one would break.
I still hand-code small critical paths from time to time. Working through the mechanics gives me a better model of the system and a better review standard for the next generated path.
The Compiler Has a Different Job
Strong type systems and compiler checks force many ownership and interface choices into the open before a program runs. That is one reason I increasingly like Rust for new systems work. The compiler can reject an invalid borrow. The rule about when a cached permission expires still has to come from the product and its engineers.
Agents and compilers both speed up the loop. I still own the promise the system makes to its users.
Keep the Explanation
When an agent-assisted change lands, I want to explain it without reopening the agent’s transcript. I should know which invariant it preserves, where that invariant is enforced, how a failure recovers, and which test could have caught a wrong implementation. If I cannot give that explanation, I go back into the system until I can.
Agents give me time back from mechanical edits. I intend to spend that time understanding the system I am building. The code can arrive quickly. Ownership takes the time it takes.