Stop asking your agent nicely
7m read time

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 exactly the behaviour you should expect, not a failure. 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 effect. 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. When the convention is one your codebase already breaks in three hundred places, reach for a baseline that only tightens: what exists is grandfathered, what gets added fails the build.

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.

(5 of 36)
1My Claude Code setup: status line, plugins and terminal2Superpowers: teaching Claude Code to think before it types3Claude Code hooks: deterministic control over AI workflows4The CLAUDE.md file: give your AI permanent memory5Stop asking your agent nicely6What's new in Claude Code: notes from the London talk7The best number in Opus 4.8 isn't a benchmark8Stale memory is worse than no memory9The agent is just a loop10Build an MCP server, then ask whether it should exist11Skill, subagent, hook, or slash command? Pick the right one12Log in to MCP servers from your shell13How to give Claude safe access to your SQL database14The day 'default' became 'Manual'15Claude Code skills: how to write one that works16How to write a proper Claude Code subagent17Claude Code permissions: the guide I wish the docs were18Sandboxing Claude Code: put your agent in a box that holds19Prompt injection defense for developers who ship agents20Best Claude model for coding: which one for which task21Refactoring legacy code with a coding agent: start with characterization tests22MCP server authentication: OAuth, scopes and rate limits23Opus 5 is here and your effort settings just expired24AI agent incident response: what to do when your coding agent goes wrong25Claude Code /doctor: what it checks and what changed26Claude Code context management: when to /clear and when to /compact27Git worktrees for parallel coding agents: what they isolate and what they share28Claude Code plan mode: decide before the agent writes29Debugging with a coding agent: give it the search, keep the hypothesis30Claude Code checkpoints and /rewind: how to undo an agent's changes31Claude Code cross-session messaging: how to make your sessions talk to each other32Audit logging for AI agents: what Claude Code records and what deserves a human33Sharing Claude Code config across a team: what a repo can and cannot enforce34Your Claude Code session has no clock35Claude Code in a large codebase: scoping an agent to the part that matters36Recovering a Claude Code session your picker will not show you