Skip to main content
Greenfield Production Systems

Writing

A model can't tell you what it didn't read.

Josh ·

Everyone who has worked with these models knows the feeling. The same system that produces beautiful code to solve a problem you defined will, in the next breath, make a hilariously stupid mistake and deliver it with exactly the same confidence. The obvious mistakes are not the problem; you catch those. The problem is the other thing, the one that gets worse, not better, the smarter the models get. A piece of good work and a bad output that looks like good work read the same from the outside, and if you’re not an expert in whatever you’re doing, it will fool you. That gap is what Addy Osmani named comprehension debt in March 2026: the growing distance between how much code exists and how much of it anyone, person or model, genuinely understands. New Relic’s June 2026 State of AI Coding report put a number on the consequence: 94% of engineering leaders rate AI-written code higher than human code at review, and 78% report more incidents once it ships. The code looks better and behaves worse, which is another way of saying the looking is the part that broke.

The first instinct, when a reading is wrong, is to prompt more carefully or reach for a stronger model. The answer to a problem like this has usually been more compute, and that holds here too — just not the kind that fits on a single prompt. We tried the stronger model, and it helped less than you would hope, in a particular way: it wrote more convincing shallow readings, so the wrong ones got harder to catch rather than easier. Telling the model to think harder, or to fan it out into more copies of itself, hits the same wall, because the thing you cannot trust is the model’s account of its own understanding, and a more fluent account is still an account. The compute that pays is the engineering kind: you orchestrate the models into a system that decomposes the work, breaking the codebase into pieces small enough to fit in a context window, and you define the gates that decide when each piece is done — gates that measure the work against the source the model was supposed to read rather than against what it says it read.

That is hard enough to arrange for one task. It gets harder when you build it into a factory meant to rebuild a legacy codebase on a modern framework, autonomously, with nobody in the loop to catch the moment a reading goes shallow.

Step back from the model for a second and the reason that moment is so dangerous comes into focus. For most of computing history, the knowledge of what a system actually did lived in two places: in the code, and in the heads of the people who wrote it. We never said that out loud, but we leaned on it every day, on the engineer who could explain why that branch existed and the one who remembered the edge case the comment doesn’t mention. Inherit a codebase nobody on your team wrote and the heads are already gone; the code is all that’s left, and now a model is your only reader, and you cannot tell its real readings from its plausible ones.

Once you accept that, the job changes. You stop trying to get a trustworthy reading out of the model and start building a separate, checkable account of what the system does, pulled from the source rather than borrowed from any single reading of it.

The first thing we learned was that this account cannot be a human-first document. Write it in prose, the way we always have, then point a model at it, and you are right back where you started: the model paraphrases and quietly re-interprets what it read, and you are trusting a reading you cannot check. A document written for humans and then read by a model is lossy at every step. So the account is built for the machine first, structured and typed, each entry cited to the source it came from, and the human-readable versions, the docs and reports people actually want to read, are generated from it by code, so the translation is lossless. Derive the human artifact from the machine one, never the other way around.

Everything else the factory ships afterward, from the rebuilt services to the tests and the docs, gets projected from that account. To build it at all, the factory decomposes the codebase into domains and points a team of agents at each, which is what makes the work tractable: a single agent cannot hold a whole system, but a team of agents each holding one bounded slice can. The catch is that the same decomposition is what lets every one of them stay shallow. A plausible pass over a single domain reads as done, and nothing about handing an agent a comfortable slice forces it to go deep. So decomposition is only half the design. The other half is an enforcement loop that keeps pushing, capability by capability, until the account is complete, and that loop, not the decomposition, is where the real work happens. Three checks carry most of the weight of running it.

The first check runs at the scale of a single line. Our factory reads a target codebase for guards, the conditions that let an operation through or send it back, and records the rule sitting behind each one. The early reader was wrong in a way that was hard to see. Take this synthetic guard (the names are invented; no system we have read appears below):

if (!isValid) return reject();

The reader logged a rule and recorded its subject as isValid: a local boolean, true or false, governing nothing. The real rule sat on the line just above, where isValid was assigned from a check on the incoming request. The reader had found the guard and stopped one assignment short of what the guard was for. The fix was to chase the value backward, one hop inside the same method, to that assignment line, and re-read the rule off whatever sat on the right-hand side. A guard written as items.Count > 0 gets walked back to where the local items came from, so the rule resolves to something like cart.Lines instead of a throwaway name. When the assignment is ambiguous, defined in two places or nowhere the analysis can see, the reader stops and marks the subject unknown instead of guessing. After we added the chase, the guard count did not move. No rules were invented, and the existing ones finally pointed at the thing they were about.

The same blindness runs at the scale of whole features, where it does more damage because nothing on screen is visibly wrong. Rebuilding Bugzilla, the factory reported full coverage while entire subsystems never entered the pipeline at all: reporting, charts, user settings, API tokens. Nothing had decided to skip them. They simply never came up, and a coverage number computed from the work the model elected to do will always certify the work the model elected to do, because a region the model never looked at leaves no trace to flag. So we moved the measurement off the model’s account of itself and onto the source. Enumerate every capability straight from the original code, then force each one to be either assigned to a service or set aside with a written reason. Run across Bugzilla’s Perl, that census turned up 3,284 capabilities and named the exact subsystems that had been going missing, each one now either built or carried as debt with a sentence explaining why. That inventory is also what makes the rest of the factory deterministic: the test skeletons are projected straight from it, one scenario per capability with the id carried across verbatim, and the coverage ledger is measured against it, so a capability the census missed is a capability no test will ever cover.

A smaller failure belongs to the same family. When the factory asks the model to cite where a behavior came from, file and line, the citation is sometimes invented: a plausible path to a file that does not hold the behavior, or does not exist at all. So we treat a citation as a claim, not a fact. A gate opens the named file, confirms the exact code the model quoted is actually there, recomputes the line number when the quote sits somewhere other than where the model said, and throws out any citation whose quote anchors to nothing, before it can reach a generated test or a bug report. What that gate is checking, underneath, is whether the model read what it says it read.

None of this was solved by a smarter model. Each piece was solved by deciding, in one more place, that the model’s confidence was the input to a check rather than the end of one, and then writing the dull code that does the checking. That checking code is most of the factory now, considerably more of it than the part that does the reading, and that ratio is the whole point: better models do not lower the need for engineering discipline, they raise it, because the distance between good work and work that looks like good work is exactly where that discipline has to live.

It is worth being plain about what this does not solve. The model still writes the shallow readings, and we have not built a model that never half-reads a codebase. What we have built is a separate record of what the system does, enumerated from the source and checked against it, carrying its own uncertainties out in the open instead of hiding them. When the code is disposable and no single reader can be trusted, that record is the thing worth keeping. The catalogs, transcripts, and coverage ledgers are public to read, down to the entries marked unknown, which are the ones I trust most: they are the places the machinery refused to guess.