·6m read time·1,188 words·

Stop Asking Your Agent Nicely

Your CLAUDE.md is a suggestion. Hooks are a wall. ADRs and custom lint rules are the missing layer between them, the difference between hoping your agent obeys and making it impossible to disobey.

You wrote a beautiful CLAUDE.md. You spent an afternoon on it. You documented your folder structure, your DTO collocation rule, your "never use default exports" preference, your testing conventions, the lot.

The agent read it. For about ten minutes, it behaved.

Then somewhere around tool call thirty, it quietly created a default export, dropped a Zod schema inline next to a route handler, and added a console.log for good measure. You didn't notice until code review, and by then the pattern had spread across four files.

This is not an agent failure. This is exactly the behaviour you should expect. And the fix is not a better-written CLAUDE.md.

The probability problem

Everything you write in a prompt, including CLAUDE.md and AGENTS.md, is a probabilistic instruction. The model attends to it, weights it, and most of the time follows it. Most of the time is not all of the time. And on a long-running task with dozens of tool calls, "most of the time" compounds the wrong way.

I've written about this before. The hooks post made the case that for things that must happen every single time, you don't put it in a prompt. You put it in a hook.

But hooks are the wall at the end of the corridor. They block dangerous commands, run formatters, kill bad merges. They don't teach. They don't carry intent. They tell the agent "no" without ever telling it "why."

Between the prompt (probabilistic) and the hook (final wall) there's a layer most teams haven't built. Two tools live in that layer. Architecture Decision Records and custom lint rules.

ADRs: the why layer

A CLAUDE.md tries to be everything. Stack, conventions, glossary, do's and don'ts, project structure. It grows to 400 lines and the agent reads it once at session start and then doesn't fully recall the middle of it when it matters.

An Architecture Decision Record is the opposite. One decision. Two pages. Context, the alternatives you considered, the decision, the consequences. Stored as a markdown file in docs/adr/, numbered, immutable.

The format was popularised by Michael Nygard around 2011, well before any of this AI nonsense. It survived because it has one feature nothing else does: it captures the reasoning behind a decision at the moment the decision was made. Six months later, when someone (or something) wonders why all DTOs live in src/dtos/users/ and not next to the route, the answer is two clicks away.

Here's what changes when you give an agent ADRs. Instead of guessing at your conventions from the code (where it sees both the new pattern and the leftover legacy code and weights them both equally), it can read the decision. It can grep docs/adr/ for the relevant record, find the one that says "we collocate DTOs because of X, Y, Z," and respect it. Not as a rule it might forget, but as documented context it can re-fetch on demand.

The other underrated thing: agents are excellent at writing ADRs. Point Claude at your existing codebase and tell it "scan this for decisions that aren't documented and draft ADRs from the patterns you find." It will produce a first draft that's 80% there. You correct it. Now your tribal knowledge is a file, not a vibe.

This is also a defence against the lava layer. Code petrifies when nobody remembers why it's there. An ADR is a thermometer pressed into the rock.

Lint rules: the how layer

ADRs explain the why. Lint rules enforce the how.

Generic ESLint covers syntax and common foot-guns. It does not know that in your project, Zod schemas live in src/dtos/ and never inline in routes. It does not know that the presentation layer is never allowed to import from infrastructure. It does not know that you forbid console.log because you use a structured logger.

You can document all of that. The agent will still get it wrong, because documentation is a suggestion.

Custom lint rules are different. They fail the build.

The interesting reframe, from a recent Just Understanding Data post, is that for AI agents, error messages stop being errors and start being teaching prompts. When the agent puts a Zod schema inline and the linter fires back:

text
DTO must live in src/dtos/<feature>/. See ADR-0014.
Example: see src/dtos/users/create-user.dto.ts

The agent reads that message, fixes the violation, and the next time it generates similar code, the pattern is reinforced. After two or three iterations, it stops making the mistake. Not because it has learned in any deep sense, but because the structured feedback shapes the next generation.

There's a critical detail: disable inline-disable. If the agent can write // eslint-disable-next-line to escape the rule, it will. Configure your linter so custom architectural rules cannot be silenced from inside the file. The whole point is that the rule is non-negotiable.

Alvin Sng at Factory.ai puts it bluntly: agents write the code; linters write the law. Lint runs on save, in pre-commit, in CI, and inside the agent's own tool loop. "Lint green" becomes the definition of done. The agent self-heals against the rules, no human in the loop required for the boring corrections.

The enforcement stack

Here is the hierarchy I'd argue every team working with agents should build, in order of strength:

LayerMechanismGuaranteeCost
PromptCLAUDE.md, AGENTS.mdProbabilisticCheap
ADRdocs/adr/*.mdSemantic context, searchableMedium
LinterCustom ESLint rulesDeterministic, blocks buildMedium
HookPreToolUse, PostToolUseAbsolute, blocks tool callHigh

Each layer catches what the one above missed. Prompts handle the common case. ADRs give the agent something to consult when it needs reasoning. Linters block patterns that survived the prompt. Hooks stop the rare disaster.

Most teams have layer one. A few have layer four. Almost nobody has the two in the middle. That gap is where your codebase quietly rots.

What to actually do

Don't try to build all of this at once.

  • Start with one ADR. Pick the convention you've corrected the agent on most often this week. Write it as an ADR. Two pages, no more. Reference it in CLAUDE.md so the agent knows the folder exists.
  • Write one custom lint rule. Pick the next most-violated convention. Encode it as an ESLint rule with a structured error message that names the ADR. Set severity to error. Disable inline-disable.
  • Watch what breaks. The first time the agent hits the rule, it will try to bypass it. Watch how. That tells you whether your error message is teaching or just blocking.
  • Ratchet. Every time you correct the agent on the same thing twice, that's a candidate for an ADR or a lint rule. Don't write a third correction. Encode it.

The prompt is not the spec. Neither is CLAUDE.md. The spec is the set of constraints the system actually enforces. Everything outside that set is a hope.

The agent is not lazy and it is not malicious. It is guessing, very fast, against a model of what your code "probably" looks like. If you don't want it to guess, stop hoping it remembers your conventions. Encode them somewhere it has to look.

Make it impossible to get wrong.

// series: Claude Pro(5 of 5)