You asked for something small: the upload handler should retry five times instead of three. The agent finds the line, writes the replacement, reports success, and the tests stay green. Two days later the archive job starts retrying five times as well, because it happened to contain the same three lines of text, and the replacement matched there too. Nobody introduced a bug. The tool did exactly what it was asked — find this text, put that text in its place — and it had no way to tell that one of those matches was the function you meant and the other was a stranger that read the same.

No deed describes a field by paces

Land used to be sold by description. The plot past the old oak, down to where the stream turns. It worked until the oak came down, and then two neighbours owned the same grass. So countries built registers: every plot gets a number on a plan, and a transfer names that number and nothing else. Two things follow from the number. A filing can no longer drift onto the plot next door, because it does not describe a place, it points at one. And the registrar checks before recording: if that plot was split since your surveyor last looked, the filing comes back refused, rather than being applied to whatever stands there now.

An edit can be built the same way. Instead of asking for text to be swapped, the agent names a node — this function, this call — and carries an anchor derived from what it expects to find there: a hash of the block, not a line number. Before anything is written, the harness checks that the anchor still matches the file. If a commit changed that body while the agent was thinking, nothing is applied and it has to read the block again before trying once more. In systems that go all the way, the text on screen is a printout taken from the register, kept so a human can review it, and not the record itself.

Rejection is cheaper than a wrong edit

This is where the idea stops being tidy and starts being about risk. A refused patch is a nuisance that costs you one more turn. A patch that lands on the wrong twin is a defect that ships, survives review because the diff looks reasonable, and gets found by a customer. Precondition checks turn the first failure into the only kind you get, and that is what makes it reasonable to leave an agent working through a long task while someone else pushes commits to the same branch.

A compiler can carry the same check further in. The patch declares operations on the nodes of the program, and the compiler verifies the hashes, the types, the ownership, the shapes. If one precondition does not hold, the operation is rejected and the store is left untouched: the agent gets a stale-state error and has to look again before it decides what to do. Verification moves from the end of a write-format-retry shuffle to the moment of the change. It is worth being clear about what this does not buy. An anchor protects the target of an edit, and a type check proves the program is coherent. Neither one knows what you wanted. The tests still have to run.

Checking belongs where the change lands

Once the check sits at the mutation, the loop around it can get narrow. Teams doing this on compiler bugs give the agent a minimal reproduction and a tool that runs it and hands back the relevant diagnostic, then let it work in one tight circuit: locate, edit, recompile, validate. Because compiling is slow, branches that fail an intermediate check are dropped rather than explored, and the search stays inside the part of the repository that the errors point to. None of that is prompt wording. It is domain knowledge spent on tools and on what the agent gets to see after acting.

The pattern generalises past code, to anything an agent changes in place that something else can validate. Point at the thing rather than describe it, and let whatever understands the material say no. Text is how a program looks. Structure is what it is, and only one of the two can refuse a bad edit.