Make Your Codebase Inevitable
In the previous post, I narrowed the harness down to the parts that matter most: Skills and Loop. Skills are what the agent knows. Loop is the cycle it runs and when it checks itself. The codebase supplies much of the raw material for both.
An agent enters each session without the tribal knowledge you carry between tasks. You are dropping a brand-new hire into your codebase and expecting good code, but it can only work with the context it finds there. If that context lives in your head, in Slack threads, or in code review comments from eight months ago, it might as well not exist.
An empty repo gives the agent nothing to imitate, so it falls back to the statistical median of its training data. You get slop by default, and it compounds. The first patterns that land become the examples it follows next time. Before long, the agent’s median code is your house style because you never gave it another one.
A mature repo creates the opposite problem. It contains three ways to write a repository, two generations of configuration, and no reliable indication of which one you still endorse. The agent finds a plausible example, copies it faithfully, and has a decent chance of choosing the one you wish had been deleted two years ago.
Both repos leave the agent to infer your intent from bad evidence.
flowchart TB
subgraph A["Ambiguous codebase"]
direction LR
A1["Competing patterns<br/>Ambiguous names<br/>Unclear domain boundaries<br/>Unreliable checks"]
A1 --> A2["Agent has to guess"]
A2 --> A3["Plausible but inconsistent code"]
end
An agent-friendly codebase helps the agent find its way around. An inevitable codebase goes further: for any given task, the names, types, examples, and checks all point toward the same implementation.
flowchart TB
subgraph B["Inevitable codebase"]
direction LR
B1["Established patterns<br/>Shared language<br/>Explicit domain boundaries<br/>Reliable checks"]
B1 --> B2["One obvious path"]
B2 --> B3["Consistent code"]
end
The ambiguity lives in your head
You can hold an absurd amount of ambiguity in your head without noticing.
You know that PaymentService is the old one and ChargeService is the one everyone actually uses. You know the utils folder is a graveyard. You know the third way of loading config, the one added last quarter, is now the blessed way. None of that is obvious from the tree. It lives in conversations and memory, and you silently route around the contradictions whenever you work.
An agent cannot silently route around those contradictions. It reads what is on disk and treats every surviving pattern as evidence. When the evidence is thin it invents; when the evidence conflicts it guesses.
There is a lazy take going around that code quality matters less once agents write most of the code. I think it is exactly backwards. Clean code was always for the next reader. The next reader is now a process that can ingest half your repo in seconds but holds none of your tribal knowledge. Every inconsistency you know to ignore looks like a viable option to the agent. Every dead pattern left around “for reference” is a reference it may use.
It turns out that the codebases that work for new hires also work for agents. Neither can read your mind.
The old arguments for consistency have become more urgent. You used to pay the ambiguity tax once per hire. Now you pay it every session.
Make the right path the obvious path
The fix is rarely a better prompt. A prompt can steer one session around one ambiguity. The repository will still be ambiguous tomorrow.
The repository needs to provide three kinds of evidence: a shared language, a record of decisions, and checks the agent can run.
flowchart LR
C["Shared language<br/>What things mean"] --> O["The obvious implementation"]
S["Recorded decisions<br/>What the team chose"] --> O
V["Executable checks<br/>What the code accepts"] --> O
Context: give everything one name
Context starts with the language of the project. This is the Domain-Driven Design idea of ubiquitous language, but agents make the benefit easier to see. When the entities, folders, service names, types, and docs use the same vocabulary, you can communicate precisely in very few words.
“Add a partial refund flow to the charge service” can be a complete instruction if Charge and Refund are real types inside a charges module that looks like every other module. If the same concept has three names, the prompt has to spend its words explaining which one you mean. Good names give the prompt more room to describe the actual change.
The tree has to agree with the vocabulary. A charges module that owns Charge and Refund gives those words a boundary. Scattering the same concepts across payments, billing, and utils makes the glossary much less useful.
Matt Pocock’s grill-with-docs skill is the best tool I have found for making this language explicit. It interviews you about a plan one question at a time. As the fuzzy terms become clear, it writes the canonical vocabulary into a CONTEXT.md glossary in the repo. The conversation ends, but the agreement stays in the project.
Agents are starting to carry memory across sessions. I turn it off. If project context is hidden in a tool-specific system file, the rest of the team cannot see or correct it. Memory also has a habit of preserving the wrong detail and carrying it into the wrong context. I would rather keep project knowledge somewhere the whole team can inspect.
Greenfield repos need this discipline most. Write the first example yourself, by hand, with care. That first module becomes the example every generated module will learn from.
And delete dead code. Seriously. If an old path stays in the repo “for reference,” the agent has no way to know that the reference is a warning rather than an endorsement.
Specification: keep decisions after the session ends
A shared language describes the system as it is. A specification records what you decided to change and why.
The implementation will show a later session what was built. It will not reliably preserve the alternatives you rejected or the constraints that shaped the choice. The spec keeps that reasoning next to the code, where the team can review it and change it.
Birgitta Böckeler’s taxonomy of specification-driven development gives this a useful ladder:
- In
spec-firstdevelopment, you write the spec, use it to guide implementation, and may discard it afterwards. - In
spec-anchoreddevelopment, the spec lives beside the code and changes with it. - In
spec-as-sourcedevelopment, the spec is the primary artifact and the code is generated output.
spec-anchored is the useful rung for most teams. It preserves the decisions and rationale without pretending that application code has become a disposable build artifact.
I have built this approach into Atelier. spec-brainstorm writes the decisions into design.md, spec-plan turns them into tasks in plan.json, and the later stages implement and validate against that record. The workflow can also move backwards when planning or implementation exposes something the spec missed.
In my own use, I often put an oracle-grill-me session between brainstorming and planning. It is a separate skill, heavily inspired by Matt Pocock’s work, but it fits here because it catches the hand-waving before it becomes a task. Whatever workflow you use, the important part is that the decisions remain in the repo after the conversation ends.
Validation: make correctness executable
An agent needs to validate a change quickly, but each check has a different cost and purpose. Keep them separate and predictable: make fmt, make lint, make build, and make test. The agent can then choose the cheapest useful feedback for the change it just made. An aggregate check is still useful before completion, but it should not be the only interface.
A fast test suite is useless if its failures mean nothing. Test behaviour rather than implementation, and test it at the layer where it matters. A domain rule belongs in a domain test. A boundary should be tested across the real boundary. Thousands of isolated unit tests that mock away the interesting parts can give you a lot of green without much confidence.
Linting and formatting protect a different property: the readability of the codebase the next session will inherit. The formatter removes style drift. Strict lint rules reject dead code and unsafe shortcuts before they reach review. If the agent introduces a pattern you do not want repeated, validation should fail immediately.
The checks are the codebase’s executable definition of acceptable. They should run quickly, and a failure should tell the agent what is wrong. Anything you can detect automatically should fail before review.
Fix the repo, not the prompt
When an agent produces bad code in one of my projects, the root cause is usually mine. It filled a gap my types allowed, or followed an ill-defined term in my domain model down the wrong path. The repo left that path open.
That reframing is useful because it gives you somewhere to act. “The agent is dumb” changes nothing. “The codebase allowed the wrong implementation” gives you a permanent fix.
Hashimoto’s rule for harness engineering is that every agent mistake should become a permanent improvement. The fix might be an explicit instruction in AGENTS.md or CONTEXT.md. It might be structural: remove the misleading pattern, tighten a type, or add a check. Each one changes the evidence or feedback available to the next session.
Do that consistently and the fixes compound. One failure improves every session that follows, including the sessions run by other people. It also improves the codebase for the humans who still have to read, review, and operate it.
Your harness answers two questions: what can the agent know, and how can it check its work? The codebase carries much of both. Its names, types, and examples show what belongs; its tests and tools catch changes that do not fit.
That does not make the agent smarter. It gives it fewer plausible ways to be wrong.