A compiler translates. An agent also fills the gaps.

This autumn I worked on a private project, a small embedded device. Like the GStreamer patch, it was C. This time the agent wrote the code, with patterns I had never used.

I started the way I would on a production system: small pull requests, each one read line by line. Then I noticed how good the code was, and I started reading only the tests. Then I was mostly asking questions. Why this design, why this order? What happens if the connection drops halfway, or the same message arrives twice? Then I checked whether the answers were true.

They were. Every time.

I cannot remember the last time I looked at what a compiler produced. On that project, without deciding to, I had started treating the agent the same way. This article is my attempt to work out whether that was progress or negligence, and what has to be true before I do it everywhere.

The diff is losing

There are three reasons, and the last one matters most.

The first is speed. Across the summer, the number of changes in my repositories more than doubled while the median change stayed at around 35 added lines. Small steps kept each change readable. They did not keep the total readable. An agent produces a clean change in a few minutes. Reading it properly takes me ten. That arithmetic only goes one way.

The second is fluency. In The Skill I Am Not Sure I Still Have I wrote that my hands are going: I barely write code from an empty file any more. The feeling that something in a diff is off, before I can say what, comes from having written thousands of them. Diff review runs on that feeling, and that feeling is what is fading. I once wrote that a two-thousand-line change cannot be reviewed, only approved. Without the fluency, the same will be true of a hundred and fifty lines. I will still be approving. I will just be doing it slowly.

The third is that the diff was never where it broke. The most expensive flaw in this series was in no diff at all. In The Test Data I Did Not Write, twenty clean changes to a deduplication step carried one wrong assumption for fourteen months. I reread the change that should have caught it and found nothing to object to. Diff review was the level I was best at, and it was the level where the problem was not.

The compiler, and why it does not quite fit

The obvious move is the one every generation of developers has made: stop looking at the layer below. Nobody reads the assembly. The older member of my computer club who could read Z80 machine code as hex had a skill nobody needs today, and nobody needs it because the layer above became trustworthy enough that checking it stopped paying off.

So it is tempting to call the agent the next compiler. I did, for a while. It does not quite hold, but not for the reason usually given.

The usual objection is determinism. Same source, same compiler, same output, every time. Run the same prompt twice and you get two different programs. That is true, and it matters less than it sounds.

The real difference is that a compiler does not decide anything. Source code is a complete specification of the program: every behaviour is in there, and the compiler only translates it. A prompt, a plan, a two-hour session with an agent: none of it is complete. Most of what ends up in the code was never said. The agent fills the gaps, and every gap it fills is a decision.

The fixtures in the deduplication story were exactly that. Nobody asked for the register record and the roaming record to carry the same operator name. The agent needed a pair of records, so it wrote one, and writing one meant deciding what a real pair looks like. It decided wrong, and reasonably. A perfectly deterministic agent would have made the same decision on every run. Reproducibly wrong is not an improvement.

A compiler translates. An agent translates and decides, and it does not tell you which is which.

How the compiler earned it

The analogy hides something else: our trust in compilers was never blind. It was built, and it took a long time.

When IBM delivered the first Fortran compiler in 1957, its own team expected programmers to reject it unless the generated code ran nearly as fast as hand-written assembly, and they spent much of their effort making sure it did. Compilers are still not correct today. Researchers at the University of Utah spent three years feeding random C programs to mainstream compilers, GCC and LLVM among them, and reported more than 325 bugs nobody had known about. Every compiler they tested could be made to crash, and every one silently produced wrong code from valid input. In his Turing Award lecture, published in 1984, Ken Thompson showed that a compiler can plant a backdoor that appears in no source code anywhere. His conclusion was blunt: “You can’t trust code that you did not totally create yourself.”

We trust compilers anyway. Not because they are flawless, but because somebody else is checking: language specifications, large test suites, fuzzers like the one in Utah, and millions of programs that would break loudly if the compiler got something wrong. I do not read the assembly because that work is being done, somewhere, by something that is not me.

For a long time, the somebody else was what I was missing with agents.

What I would actually be trusting

So the question is what exactly I would be trusting, and what checks it instead of me.

It splits in two.

The first part is translation. Does the code do what we agreed in the session? Does the PR description match the change, and the commit message the commit? Is there anything in there nobody asked for: an error that is caught and dropped, an extra code path, a test that tests nothing? No hidden bombs. This is the part I want to trust the way I trust the compiler, and it is the part the agents are getting better at fastest.

I have to be honest about the history here. Everyone who has used agents seriously has had the moment: clean code, good names, green tests, looks good to me, and something fundamentally wrong underneath. That moment is why the people most open to these tools tend to be the most careful with them. In the deduplication article I wrote that the agent’s summary is a claim, not evidence, because one change described a functional test suite it did not contain. That was a translation failure, and I have not forgotten it. It is why I do not trust the translation yet. It is not a reason never to.

The second part is decisions: every gap the agent filled with an assumption about the world. That part I cannot hand over, and not because the agent is not good enough. It is because the agent does not have the information. It did not know that the federal register and the roaming networks never name an operator the same way. I knew that, or should have, from years of working with both. No model release puts that knowledge in the session unless somebody puts it there.

Trust the translation. Own the decisions.

What replaces the diff

If I stop reading the diff, two things have to happen. Something other than me has to check the translation, the way test suites and fuzzers check the compiler. And the decisions have to become visible, because they are now the only thing I review.

Here is what I am trying. None of it is settled.

Ask for the decisions. At the end of a session I ask the agent to list every assumption it made that I did not state: about the data, about the callers, about what can safely be ignored. The list is usually short. It usually contains one thing I would not have agreed to. That list is where my review starts now.

Sample instead of reading everything. One change in ten, picked at random rather than by how risky it looks, read the old way: in the IDE, against the session, line by line. If the summary and the code disagree, trust goes back down and I read everything for a while. That is how an auditor works. It also keeps my hands from going completely.

The reviewer I no longer beat

On production code I still open the diff. But I skim it more than I read it. I no longer try to work out whether the mechanics are right, because I know the agent gets them right. So the honest reason I still open it is not what I find there. It is that I do not trust the agent as much as the evidence says I could.

The evidence is the second reviewer, the somebody else I was missing. Every pull request now also gets reviewed by another model, in my case GitHub Copilot, and it finds more than I do. This autumn, with newer models writing the code, it has found less and less, and what it still finds are odd edge cases: things that need several unlikely events in the wrong order. They are not the mistakes I used to catch in review, a wrong loop condition or a count that is off by one. Those I no longer see. I think we have already passed the point where another model is a better reviewer than a human. At least, than this one.

Where I still win is the question after the finding: is it worth fixing? Often it is not. The real fix for a rare race can add a lock, a state and a retry path, complexity that costs more in real life than the race ever will. The cheap fix is to detect the case and fail with a clear error. That is a decision, and it comes from years of living with complexity that never paid for itself.

And I have come to think it is good that the agent leaves those edge cases unhandled. Had it guarded against every rare case up front, the change would have been much larger, and most of that code would never run. Adding code later, once someone has shown that a case is real and worth it, is always the better deal than trying to take code out in review. It is also how a good developer works: write the simple version, let review find the edges. The agent made a decision by leaving those cases out. The second reviewer makes it visible. And I, with an agent at hand, decide which of them are worth fixing, and how.

The machine is now the better reviewer. I am still the better judge of what is worth fixing.

The models are getting better at that question too.

So what I am waiting for is no longer the agent’s quality. It is my own trust, and I think it will move sooner than most of us expect, because the gap is closing from both ends. The models get better at translation with every release. And I get better at the other side: writing decisions down before the agent has to guess, asking about outcomes instead of behaviour, knowing which questions bring the hidden ones out.

The compiler needed decades to earn its trust. I expect the agent to need years. Possibly fewer.

What I am keeping

Two articles ago I asked how long judgment stays sharp after the hands stop, and admitted I could not answer. This is part of an answer. The hands were the instrument for one level of review: reading code and feeling where it was wrong. That level is going the way of the hex dump, and maybe it should.

The level above needs different knowledge. That two data sources never agree on a name. That a comment in capitals is a warning. That a fixture somebody invented proves nothing about the world. Some of that came from writing code. More of it came from the world refusing to match the code I wrote. Whether building with an agent still teaches the second part is what I am watching now.

So I will stop reading the diff. Not blindly, and probably sooner than I am comfortable with. Something other than me is already checking the translation. What is left is to trust it, and to move my attention to where the expensive mistakes always were: in the things we decided without noticing we were deciding.

Originally published on Medium.