Your Context Files Are a Code Smell
Cory Rylan
- 5 minutes
You know the file. AGENTS.md, CLAUDE.md, .cursorrules, whatever your harness calls it. It started as a few lines. Now it's four hundred. "Always use design tokens." "Never hardcode hex values." "Don't add a dependency without checking the bundle size." "Prefer composition over inheritance." A wall of please comply.
Every one of those normative lines is a guardrail you didn't write. The file isn't the solution to your agent problems. It's a symptom of your codebase problems. The more of it you have, the more likely it is that something underneath should have been enforced and isn't.
This isn't really a post about agents. It's a post about all the stuff we should have been doing as engineers the whole time. Agents just turned the lights on.
Context vs Instruction
Not every line in your context file is problematic. There are two very different things living in there.
Descriptive context. Where auth lives. The domain glossary. Which package owns what. Where the test guards are. A new human hire needs the exact same document on day one. None of it is enforceable, none of it implies bad code, and you should keep it. This is onboarding, not a smell.
Normative instructions. "Always use tokens." "Never do X." "Don't add a dep." Every single one of these is a rule a parser could check — and you wrote it as prose and shipped it on a hope.
The rest of this post is about the second pile. This is the signal that the enforcement by the system and codebase was never built.
"But judgment can't be encoded"
Sure, determinism has a ceiling. Type checks, tests, and lint catch a lot, but some calls really do come down to taste. "Don't abstract this prematurely." "This feels over-engineered." You can't write a fitness function for that.
However, here's the thing the objection misses: that ceiling is shared. A rule that's too contextual to encode as a check is also too contextual to encode as an instruction. "Use good taste about premature abstraction" is exactly as useless sitting in a markdown file as it would be as an ESLint rule. The agent can't act on it either. Anything genuinely above the ceiling lives in code review and in the moment — it never lived in your context file to begin with.
So the judgment ceiling doesn't rescue context files. A normative line in AGENTS.md is just a lower-fidelity, unenforced fitness function. Everything below the ceiling should be a real check. Everything above it can't be written down usefully anywhere. The middle, where the file pretends to live, is empty.
Closing the box
Take any normative line in your context file. It is exactly one of two things:
- Encodable. Then it's a smell. You just didn't write the rule. (And you no longer have a cost excuse.)
- Un-encodable and one-shot. Then it isn't durable policy. It belongs in the prompt or the PR description — not in a file that reloads on every single run.
There's no third bucket. The only thing that could justify a permanent block of prose is "recurring and un-encodable" — and that category is about to disappear.
The Elements team puts it: prompts are runtime hints, not durable data. If a fact lives only in a prompt or a README, the harness doesn't own it. If your enforcement only exists as a sentence you're hoping the model reads, you don't have enforcement.
We always should have done this
Step back from agents for a second. Types. Tests. Lint. Static analysis. Structural fitness functions. None of this is new. We've been preaching deterministic guarantees for years. Agents didn't invent the need, they just made the cost of not having it impossible to ignore.
An agent is a fast, tireless junior dev who does exactly what your enforcement allows and quietly ignores everything your prose merely requests. That's not a knock on the model. That's a mirror. Every "please don't" in your context file is a rule your CI should have been failing on, aimed at a contributor who finally takes you at your literal word.
"Agentic-ready codebase" is a fancy way of saying the hygiene we already knew we wanted, now with a forcing function holding us to it.
The cost excuse is dead
"Writing a custom lint rules for every little thing isn't worth it the effort."
That was a real argument in 2022. Authoring a custom lint rule used to cost the better part of a day. It isn't anymore. Writing a custom rule now costs minutes, and you write it with the agent itself. The economics that justified the prose collapsed. Deferral stopped being a tradeoff and went back to being laziness.
Migration state is the cleanest example, because it looks the most like legitimate prose and isn't. "We're moving from X to Y, prefer Y" is just a deprecation rule: error on new usage, baseline the existing call sites, let CI hold the line. The file that tracks the migration was never documentation. It was an unwritten rule wearing a trench coat.
What it looks like done right
I help maintain NVIDIA Elements, a design system. We have roughly sixty-five custom lint rules across two packages. Every one of them encodes an intention that a lazier repo would have dumped into AGENTS.md as a bullet point.
"No hardcoded hex, use the token" isn't a sentence in a markdown file. It's no-unexpected-css-variable.ts. The migration I just described? Six additional rules: no-deprecated-attributes, no-deprecated-css-variable, no-deprecated-packages, no-deprecated-icon-names, no-deprecated-popover-attributes, no-deprecated-global-attributes.
The detail that makes the whole approach hold up: nearly every one of those rules has a co-located test. The guardrails are themselves verified. That's the answer to "aren't you just moving trust around from the model to the linter?" No. The enforcement is deterministic and tested. The model's compliance is a property of the system, not a property of how nicely I asked.
Harness engineering is treating an agent failure as a system-design problem you solve permanently, not a prompt you retry. When the agent misuses a component, the answer isn't a better instruction, it's a rule that makes the misuse impossible. The supporting model is a "policy compiler" that runs from durable to disposable: metadata, then static tools, then CLI and skills, and only at the far disposable end, contextual markdown. Context files sit at the throwaway end of that gradient by design.
It Takes Time
Sixty-five tested rules is an end state, not an entry fee You don't sit down and write them upfront. You add a rule the moment the agent actually fails at something, not preemptively. The mature repo is where you end up after enough of those moments, not the cost of getting started. Start with almost nothing.
Descriptive context stays. Domain intent, architecture orientation, where the guards live — keep all of it, lean. The target here is narrow: normative code and architecture rules masquerading as documentation. That's the smell. Project-layer enforcement written as prose instead of code.
A large context file is liability laundering at the config layer. It's the same move as shipping code you didn't review and hoping it's fine, just pushed one step earlier in the pipeline. You're hoping the model complies instead of making compliance structural.
The metric isn't how many context files you have. It's the ratio of pleading to enforcement. Every line that pleads is a rule you owe your future self. Go count the imperatives in your AGENTS.md. That number is your backlog.