Constraints, Not Implementations
Why the context file that says what an agent must and must not do stays current for months, while the one that describes how the code works is stale by Friday - and how to decide what loads always versus what gets fetched on demand.
Part 8 laid out the three levels of a harness — tool configuration, codebase onboarding, workflow automation. All three share one failure mode, and it is the most common way I see harnesses die: the context file that was accurate the day it was written and quietly wrong three weeks later. Nobody deleted it. Nobody flagged it. The agent just kept reading a description of a codebase that no longer existed, and its output drifted with the description. This part is about the single editorial decision that prevents that rot: write down what the agent must and must not do, and make it go and look up how things currently work.
Two sentences with two lifespans
Consider two sentences that could both plausibly live in the root context file of a subscription-billing service — call it Meterly, an invented project that will carry the examples in this part.
The first: “Invoice matching lives in InvoiceMatcher, which does a three-pass reconciliation — exact reference match, then amount-and-date, then a fuzzy fallback.”
The second: “Never write to the ledger tables directly; every mutation goes through the posting service, which is the only component allowed to hold a ledger transaction.”
The first sentence is an implementation description. It was true when someone wrote it, and it stops being true the moment a colleague — or an agent — collapses the three passes into two, renames the class, or moves the fallback behind a feature flag. Nothing in the workflow forces anyone to update the sentence when the code changes, so it decays silently, and a decayed description is worse than none: the agent trusts it over what it would have discovered by reading the code.
The second sentence is a constraint. It describes a rule, not a mechanism. The posting service can be rewritten top to bottom and the sentence stays true, because the sentence was never about the code — it was about the boundary the code must respect. Constraints change when the architecture changes, which is rarely. Implementations change with every commit.
That asymmetry is the whole principle. A context file loads into every single session, which means every line in it is multiplied across everything the agent ever does for you. A stale constraint is a rare event you will notice, because architecture changes are loud. A stale implementation note is a weekly event you will not.
What earns a permanent seat
The always-loaded file — CLAUDE.md, AGENTS.md, whichever surface your tooling reads — is the most expensive real estate in the harness, and I allocate it the way I would allocate a new starter's first-morning briefing. Not “here is how everything works”, which they would forget and which would be out of date anyway, but “here is what will get you into trouble”.
For Meterly, the static tier looks like this:
# Meterly — agent context
## Commands
- Build: `dotnet build meterly.sln`
- Test: `dotnet test --filter Category!=Slow`
- Full suite (pre-PR only): `dotnet test`
## Constraints
- Never write to ledger tables directly; all mutations go through PostingService.
- Money is `decimal` end to end. Introducing `double` anywhere in a money path is a defect.
- Public API contracts live in `contracts/`; changing one requires a new version, never an edit.
- Prefer extending an existing projection over adding a new read model.
- Anything touching refunds or tax rates: stop and ask before implementing.
## Conventions
- One handler per message type; handlers stay under 150 lines.
- Migrations are additive; destructive migrations need a written plan first.
Read the categories rather than the lines: commands the agent will need every session, rules that must never be broken, preferences that resolve genuine ties, and an escalation line for the territory where I want a human decision. What the file conspicuously does not contain is a tour of the modules. There is no paragraph explaining how PostingService works internally, what the projection rebuild does, or which classes participate in invoice matching — because the code answers those questions and the code is always current.
Notice also what the four kinds of line are doing: must, must not, prefer, escalate. That taxonomy is one of the primitives this series closes on, and part 13 gives it a full treatment; for now it is enough that every line in the static tier is one of those four verbs rather than a description.
What the agent should fetch instead
Everything descriptive belongs in the second tier: context that is derived on demand, at the moment it is needed, from sources that cannot be stale.
When an agent working on Meterly needs to understand how invoice matching currently behaves, the right move is not to read a summary I wrote in March. It is to read InvoiceMatcher itself — or, for anything non-trivial, to run a research pass first: explore the relevant code paths, produce a short task-specific summary, and plan against that. The summary is disposable by design. It was generated from the actual code this morning, it serves one task, and it is thrown away rather than promoted into the permanent file where it would start decaying.
The same on-demand rule covers the current state of half-built features, the documentation of external APIs, and the history of why something is shaped the way it is — the commit log and pull-request record answer historical questions with timestamps attached. My rule of thumb for the boundary: if the answer can be regenerated from the repository, do not write it down; if it cannot, it is probably a decision, and decisions belong in the static tier. The three-pass matcher can be rediscovered by reading code. “Money is decimal end to end” cannot — no amount of reading the current code tells you whether that is a rule or a coincidence. Writing it down is what makes it a rule.
Stale context is a poisoning you did to yourself
Part 6 covered context poisoning — wrong information in the window steering the model confidently off course. A stale context file is the self-inflicted version, and it is nastier than most poisoning because it arrives with authority: the agent has every reason to believe the file the harness hands it on session start.
Two habits keep the static tier honest. The first is scope discipline — everything in the previous two sections. A file that contains only constraints, conventions and commands has very little surface area that can go stale. The second is treating the harness as part of the definition of done: when a change alters a convention, a boundary or a build command, the context file updates in the same pull request, reviewed by the same eyes. If I find myself correcting the agent for the same mistake twice, that correction gets engineered into the harness — a line in the file, or better, a hook that enforces it mechanically. Mitchell Hashimoto has described this reflex as the core of the discipline: when the agent errs, you build the thing that makes that error impossible next time, rather than patching the output and moving on. A convention that matters is worth a hook; a hook cannot be skim-read and ignored, and it never goes stale without failing loudly.
The checklist
Before a line goes into the always-loaded file, I ask four questions of it:
- Will this still be true after the next ten merges? If not, it is an implementation detail wearing a constraint's clothing.
- Could the agent discover this by reading the repository? If yes, let it — on demand, from the source of truth.
- Is this a must, a must-not, a prefer, or an escalate? If it is none of the four, it is probably a description.
- Would I rather enforce this with a hook? Documentation asks; automation insists.
A harness built this way stays small, and small is not a compromise — it is the point. The always-loaded file spends context-window budget in every session, and part 3 already established what that budget buys. But smallness creates its own question: when the repository is large and the constraints are many, how does a short file steer an agent through a million lines it has never seen? Next, the answer OpenAI's engineers landed on — and the benchmark run that proved a harness alone can move an agent from Top 30 to Top 5.