Software documentation has a failure mode that code does not share, which is that it can be wrong without producing an error, stale without looking any different from what is current, and confidently misleading without anyone noticing for months or years, because the only people who can detect a documentation error are the people who already know the correct answer, and those people do not read the documentation. They wrote it, or they wrote the code it describes, and they have no reason to re-read what they already know. The people who do read the documentation are the people who do not yet know the answer, and when the documentation lies to them, they have no way to know it is lying.
This has always been true for new developers joining a team. It becomes structurally more dangerous when the reader is an AI coding agent, because an agent trusts what it reads with a completeness that no human developer ever would. A human reads a README and thinks this might be out of date. An agent reads the same README and treats it as ground truth, because the agent has no memory of what was correct last month, no intuition for when documentation has drifted, and no mechanism for detecting that the code contradicts the comment. It reads, it trusts, and it acts, and if what it read was wrong, what it builds will be wrong, and the error will only be caught by someone who already knew the right answer - which is to say, the one person on the team who never needed to read the documentation in the first place.
The Two-of-Three Problem
A codebase I work on currently has three copies of the same setup instruction: one in the main README, one in the contributor guide, and one in the onboarding document for new developers. Two of them say to run bun run reeweb:install, and one of them - the onboarding guide, which was written before the command was renamed - says to run bun run install, which fails because the script does not exist under that name. A new contributor follows the onboarding guide, gets an error, and spends twenty minutes checking their Bun version, their Node version, their PATH, and every other possible cause before they think to check whether the command in the document is even correct. The two correct copies are maintained by the people who run the install every few weeks. The stale copy is maintained by nobody, because it lives in a document that is only read by people who do not yet know enough to spot the mistake.
This pattern is so reliable that I have stopped treating it as a maintenance failure and started treating it as a structural guarantee. If the same piece of information exists in n places, and those places are not linked to a single source of truth, then as n grows, the probability that at least one copy is wrong approaches certainty. Two of three might be correct today, but a change tomorrow will update two of them, and next week's change will update one, and next month's change will update none of them because the person making the change did not know all three copies existed. The drift is not anyone's fault. It is the system working exactly as designed, and the design is the problem.
Denormalization is a database term, but documentation has the same problem: the more copies you make, the more certain it becomes that one of them is wrong.
Why Agent Workflows Make This Worse
A human developer who reads a stale document eventually discovers the error. The code does not compile. The deployment fails. The API returns an unexpected response. Something breaks, the developer investigates, and the contradiction surfaces. The process is frustrating but self-correcting: the stale document gets updated, or at least the developer learns not to trust it.
An AI coding agent has none of these self-correction mechanisms, and the reason is worth being precise about, because it is not that agents are careless but that their fundamental mode of operation makes them structurally unable to detect staleness. An agent reads a set of files, constructs a mental model of the codebase from those files, and then acts on that model. The model is only as good as the files it was built from. If one of those files was a stale copy of a document whose canonical version says something different, the agent's model is wrong from the start, and every action it takes based on that model will be wrong in a way that is consistent with the stale document. The consistency is what makes it dangerous, because a wrong answer that is internally consistent looks more convincing than a wrong answer that contradicts itself, and the agent will defend the wrong answer with the authority of the documentation it read, since from its perspective the documentation is the truth.
The second problem is that agents have no concept of recency. When a human opens a file and sees a comment dated 2022, or a README that references a version of a tool that has been deprecated, or an onboarding guide that uses a different project structure than the one they see in the actual repository, something in their brain flags the mismatch. The flag is not conscious, not always, but it is there, built from years of encountering stale documentation and learning that it stings. An agent has no such reflex. It reads bun run migrate --dry-run with the same confidence as bun run migrate --confirm, because both are just text, and there is nothing in the text itself that marks one as outdated and the other as current.
The third problem, and the one that I think will surprise people who are still getting used to agentic workflows, is that an agent will sometimes reproduce the stale information as if it were authoritative. Ask an agent to write a deployment guide for your project, and it will read the three copies of your checklist and synthesize them into a fourth document that may or may not contain the correct instruction, because the agent has no way to weight the copies by recency or authority. It sees three sources that disagree and it has to choose, and its choice is not informed by anything a human would use to decide - which person wrote which copy, which copy was updated most recently, which team owns which document - because none of that metadata exists in the files the agent can read.
The Generator Answer
The instinct, when you first notice this problem, is to write better documentation processes. Checklists for updating all copies. Reminders in the CONTRIBUTING guide. A policy that says every change must update every relevant document. I have tried all of these, and they work for about two weeks, which is how long it takes for the first urgent change to arrive at an inconvenient time and for the person making it to update the one document they know about and move on.
The answer that actually works is to stop making copies. Every piece of information should live in exactly one place, and every other place that needs it should reference that place rather than reproducing it. This is not a documentation technique. It is an architectural principle, and it is the same principle that drives every design decision in the tools we build at Reepolee.
In Reepolee, the database schema is the single source of truth for the shape of the application. The SQL files under sql/sqlite/ and sql/mysql/ define every table, every column, every constraint, and every index. The generator reads those files and produces the TypeScript types, the form templates, the validation rules, the route handlers, and the navigation structure. There is no separate TypeScript definition of the schema that can drift away from the SQL. There is no ORM model file that someone forgets to update when they add a column. The SQL is the model, and everything else is derived from it, and when the SQL changes, the generator re-derives everything, and there is nothing to drift because there is nothing to copy.
The same principle applies to translations. In Reepolee, translations live in co-located {locale}.json files - one file per locale, sitting next to the route it translates. There is no translations table in the database and no separate CMS for managing strings. The JSON files are the source of truth. The generator reads them, the admin UI edits them, the AI translation pass fills gaps in them, and the template engine resolves them at render time. A translation key that appears in a template but not in the JSON file renders as its key path, so the absence is visible rather than silent. There is no second copy to fall out of sync because there is no second copy at all.
In ReeWeb, our static site generator, the same principle shows up in the responsive images pipeline. The file config/responsive_images.ts defines the width breakpoints and encoder quality settings as the single source of truth. Two consumers read that file: the build-time image generator that produces the variants, and the <responsive-image> component that writes the <picture> markup. Change a breakpoint in the config, and both the generated images and the HTML update together. There is no second config file for the component to read, no documented convention that someone has to remember to keep aligned, no drift to detect because there is no second place for the information to live.
The generator pattern is not about avoiding work. It is about eliminating the possibility of drift by eliminating the copies that drift is made of.
Link, Do Not Copy
The simplest version of this principle, the one that applies to any project regardless of whether it uses generators, is to link rather than copy. When you find yourself about to paste the same information into a second document, stop and ask whether a link would serve the same purpose, and in almost every case it will, because a link says the truth lives there, go read it while a copy says the truth lives here too, and good luck keeping it current.
Our own documentation follows this rule obsessively. Or at least it tries to. The Reepolee approach post does not summarize the argument for on-premise infrastructure - it links to the post that makes that argument in full. The column naming post does not re-explain the generator pattern - it links to the post that already established it. The in-house operational apps post does not repeat the argument about internal teams - it links to the post that developed it. Each idea lives in its canonical location, and every reference to it is a link, not a summary, because a summary ages and a link does not.
The same rule is encoded in our AGENTS.md file, which is the document an AI coding agent reads when it first opens this repository. The file reads:
PRIMARY RULE: CODE IS THE SOURCE OF TRUTH
When any document (including this one) disagrees with the code, the code wins.
Docs drift. Treat every
.mdfile as a map, not gospel. Before you act on anything a doc claims (a path, a function name, a flag, a default), open the file and confirm it in the source.
That instruction is not a reminder to human developers, who already know that documentation can be wrong. It is a directive to the agent, which does not know and cannot know unless told. The agent arrives with no context, no memory of prior conversations, and no instinct for what is stale. It reads the AGENTS.md and it obeys (more or less, depends on the model), because obedience is what agents do, and the instruction tells it to treat every document as a hypothesis to be verified rather than as a fact to be trusted. This is single-source-of-truth architecture applied to the relationship between documentation and code: the code is the source, the documentation is the index, and when they disagree, the index defers to the source.
Prompt Like You Are Briefing a Junior Developer
There is a second insight here that took me longer to notice, and it is about how we talk to agents, not just how we structure information for them. When a team of developers who have been working together for two years describes a task to each other, the description is compressed. It uses shorthand that everyone in the room understands because they were in the room when the shorthand was invented. It references decisions that were made in meetings nobody wrote down, patterns that emerged from conversations nobody archived, and tradeoffs that everyone accepted but nobody documented because the acceptance was enough. This compression works because the people receiving the instruction share a context that fills in the gaps.
An AI agent does not share that context. Neither does a junior developer on their first week. Both of them arrive with a blank understanding of the specific project, the specific conventions, the specific decisions that shaped the code they are about to change. When you say add the usual validation to the user form to a colleague who has been on the team for a year, they know what usual validation means, because they have written it a dozen times and they have read the code reviews that established the pattern. When you say the same thing to an agent, or to a junior developer, they have to guess, and their guess will be based on whatever documentation they can find and whatever general knowledge they bring, and neither of those sources knows what your team means by usual.
The fix is not to write more documentation, although documentation helps. The fix is to describe tasks with the same precision you would use when briefing someone who has never seen the codebase before, because from the agent's perspective, it hasn't. Name the files. Name the functions. Name the conventions. Do not assume that the agent knows that validation in this project means a Zod schema in a file next to the route handler, because that convention exists in the heads of the people who established it and nowhere else unless someone wrote it down. And when you write it down, link to the canonical location rather than reproducing it, because the canonical location will change and the reproduction will not.
Describe the task to your agent the way you would describe it to a new hire who started this morning, because as far as the agent is concerned, that is exactly what happened.
We follow this rule in our own AGENTS.md as well, and not just in the primary rule about code being the source of truth. The file says:
Agent-created documents go in
.agents/. All plans, notes, logs, working docs - everything an agent writes goes there.
This is a naming convention that a human developer might infer from context, but an agent cannot infer anything, and this single sentence, stated explicitly and placed where the agent reads it on every session, eliminates an entire category of confusion about where files should live. It costs almost nothing to write and it prevents the agent from scattering plans and notes across random directories. The precision is not pedantry. It is the difference between the agent doing what you meant and the agent doing what you said, which in software are often not the same thing.
The Cost of Assuming Context
I want to be specific about what goes wrong when you assume an agent shares your context, because the failures are subtle enough that they are easy to dismiss as the agent being bad at its job when the real problem was that the job was described in a dialect the agent had not been taught.
A developer asks an agent: fix the login redirect bug. A human colleague who has been on the team for six months knows that the login redirect bug refers to the ticket filed three weeks ago where users who log in from the pricing page get redirected to the dashboard instead of back to the pricing page. They know this because they were in the standup where it was discussed, or because they read the Slack thread, or because they saw the ticket on the board and absorbed the context through proximity. The agent knows none of this. It searches the repository for login redirect and finds four different redirect paths in three different route handlers. It picks the one that seems most plausible, changes it, and the actual bug is still there because the agent changed the wrong redirect in the wrong file.
The same developer, after learning from this experience, writes: in src/routes/auth/login.ts, the redirect after successful login should return the user to the page they were on before logging in, which is stored in the redirect_to query parameter. Currently the handler always redirects to /dashboard regardless of the redirect_to value. This prompt names the file, names the behavior, names the expected outcome, and names the current failure. It leaves no room for interpretation because interpretation is what the agent does when the prompt is ambiguous, and an agent interpreting an ambiguous prompt in a codebase it has never seen is exactly as reliable as a junior developer interpreting an ambiguous ticket on their first day.
The connection between these two problems - denormalized documentation and imprecise prompting - is that both of them fail because information that exists in someone's head was never put into a location where the agent could find it. In the documentation case, the information was put in three locations and one of them went stale. In the prompting case, the information was never put anywhere at all, because it lived in the shared mental model of the team, and nobody realized that the agent did not have a copy of that model. The solution in both cases is the same: every piece of information the agent needs should exist in exactly one place, stated in exactly one way, and referenced from everywhere else.
This Is How We Do It
The principle that holds all of this together is simple enough to state but takes discipline to maintain: information about how software works should flow in one direction, from the source to the consumers, and never in the other direction, because bidirectional flow is how drift enters the system. The code is the source. The documentation references the code. The prompts reference the documentation. The agent reads the prompts, the documentation, and the code, in that order, and when it finds a contradiction, the code wins, because the code is the only artifact in the chain that cannot be wrong without producing an error.
This is the rule we encoded in our AGENTS.md, and we have been living by it for long enough to see the difference it makes. When a document drifts, the agent is told to verify against the code. When a prompt is ambiguous, the agent is told to ask questions rather than guess. When information needs to be shared, it is linked rather than copied. None of this is complicated, and none of it requires special tooling, but all of it requires the recognition that an agent is not a faster version of a senior developer but a different kind of collaborator entirely - one that brings infinite patience and zero context, and one that will trust your documentation more than your documentation deserves to be trusted.
If you take one thing from this post, let it be this: the next time you are about to copy a piece of information from one document to another, stop and ask whether the document you are copying from is the canonical location for that information. If it is not, find the canonical location and link to it. If a canonical location does not exist, create one, and then link to it from everywhere else. An agent that reads your documentation six months from now will not know that you made this decision, and it will not thank you for it, but it will also not spend an afternoon debugging a problem that was caused by a stale copy of a document you forgot you had written, and that, in agentic workflows, is the same thing as a thank-you.