Your AGENTS.md says "Never import @acme/db from src/domain/." Read that line again. It's a lint rule, written in English, handed to the one reader in your pipeline that is allowed to ignore it.

You don't have to take my word for it. Anthropic's Claude Code memory docs put it in one sentence: Claude treats CLAUDE.md files "as context, not enforced configuration." The same goes for an AGENTS.md, which Claude Code reads in place of CLAUDE.md when a repo has none, and which the AGENTS.md site says is "used by over 60k open-source projects."

So here's the take. Every rule in your instruction file that a linter, a git hook or a permission rule could check is a bug waiting in prose. Move it to the tool. Keep the file for the calls no tool can make.

Your rule file is a request, and the docs say so

Claude Code's best practices page splits every instruction into two kinds. Your prompt, CLAUDE.md and skills are non-deterministic: the model reads them and decides, and "there's no guarantee that it'll follow your instructions." Hooks, deny rules and managed settings are deterministic: Claude Code "applies this every time, no matter what the model decided to do."

The page goes further than you'd expect from the company that sells the model: "If something has to happen every time, make it a hook, a deny rule, or a managed setting. CLAUDE.md is for guidelines, and Claude is allowed to decide a guideline doesn't apply."

And the tell it gives you: "If you keep adding emphasis to a CLAUDE.md line and Claude still misses it, that's a sign the line belongs in a hook." An IMPORTANT: you've added three times is a rule asking to be moved.

A thick rulebook on a desk with its pages being pulled out and folded into small gates and turnstiles along a conveyor belt
Most of an instruction file is rules that want to be gates.

Where prose rules go missing

The model choosing not to follow a line is one way a rule fails. The line not being in front of the model is another, and that one is mechanical.

Compaction is the first. When a long Claude Code session compacts, the context window docs say the project-root CLAUDE.md is re-injected from disk. Path-scoped rules and nested CLAUDE.md files are different: they load when Claude touches a matching file, "so compaction summarizes them away with everything else" until a matching file is touched again. A rule you only typed into the chat can be lost with the rest of the history. The best practices page says the same of "don't push until I've reviewed": "the sentence can get lost when the conversation compacts, so anything that matters belongs in a deny rule."

Size is the second. Codex stops adding AGENTS.md files once their combined size reaches project_doc_max_bytes, 32 KiB by default. Claude Code's docs ask you to keep each CLAUDE.md under 200 lines, because "longer files consume more context and reduce adherence."

Subagents are the third. The memory docs note that subagents which start without project instructions start without your AGENTS.md too.

None of these touch ESLint. Nobody summarizes eslint.config.js, and nobody stops reading it at 32 KiB.

The 40-rule AGENTS.md

Here's an AGENTS.md for a TypeScript service in a pnpm monorepo. I made it up for this post, but each line is the kind of rule these files collect. Count how many a tool could check.

# AGENTS.md ## Setup 1. Use pnpm. Never npm or yarn. 2. Never run `npm install`, not even to try something. 3. Ask before adding a dependency. 4. Never run `pnpm update`. 5. Never edit pnpm-lock.yaml by hand. 6. Never read or print .env files. ## Architecture 7. Never import @acme/db from src/domain/. 8. Never import express in src/domain/. 9. src/domain/ must not import @acme/http. 10. No deep imports like @acme/db/src/client. 11. Business rules live in src/domain/, free of I/O. 12. Ask before changing exports of packages/api-client. ## TypeScript 13. Never use `any`. 14. Keep `strict: true` in tsconfig.json. 15. Use `===`, never `==`. 16. Await or handle every promise. 17. No `@ts-ignore`. 18. No console.log. Use the logger. ## Formatting 19. Two-space indent. 20. Single quotes. 21. Trailing commas everywhere. 22. Max line length 100. 23. Always use semicolons. 24. LF line endings. ## Files 25. Never edit src/gen/. Run `pnpm codegen`. 26. Never edit a migration that is already committed. 27. Every migration gets a down step, or a reason why not. ## Git 28. Never push to main. 29. Never force-push. 30. Never use --no-verify. 31. Conventional Commits for every message. 32. Run lint before every commit. ## Done means 33. Run `pnpm typecheck` before you say you're done. 34. Run `pnpm lint` before you say you're done. 35. Run the tests before you say you're done. ## Style 36. Prefer named exports. 37. If a test fails, fix the code, not the test. 38. Match the error handling of the module you're in. 39. One concern per PR. Say so when a change grows. 40. Money is integer cents. Never floats. 

My count: 32 of those 40 lines are checks. Eight are judgment.

Where the 32 rules went

Rules Moved to
1, 2 preinstall: npx only-allow pnpm, a Claude Code deny rule, a Codex rule
3 Claude Code ask rule Bash(pnpm add *)
4 Claude Code deny rules for pnpm update and pnpm up
5 CI: pnpm install --frozen-lockfile
6 Claude Code deny rules Read(./.env), Read(./.env.*)
7–10 ESLint no-restricted-imports
13, 16, 17 typescript-eslint no-explicit-any, no-floating-promises, ban-ts-comment
14 Claude Code ask rule on tsconfig*.json
15, 18 ESLint eqeqeq, no-console
19–24 Prettier config, prettier --check
25 Claude Code deny rule Edit(/src/gen/**)
26 CI: fail if git diff shows a modified migration
28, 29 GitHub branch protection, ask rule on git push
30 Claude Code deny rule on --no-verify
31 commit-msg hook: commitlint --edit $1
32 pre-commit hook running pnpm lint
33–35 Claude Code Stop hook, required CI check

The architecture rules become one object in your flat config. ESLint's no-restricted-imports takes gitignore-style group patterns and a message, and the message is what the agent reads when the rule fires:

// eslint.config.js: one entry in your config array { files: ["src/domain/**/*.ts"], rules: { "no-restricted-imports": ["error", { patterns: [ { group: ["@acme/db", "@acme/db/*", "express", "@acme/http", "@acme/http/*"], message: "src/domain/ stays free of I/O. " + "Add a port here, an adapter in src/infra/.", }, { group: ["@acme/*/src/**"], message: "Import the package entry point.", }, ], }], }, }, 

no-explicit-any is in typescript-eslint's recommended config. no-floating-promises needs typed linting and comes with recommended-type-checked. In Go, depguard in golangci-lint does the import job.

The package-manager rule gets three locks. The pnpm docs give the preinstall script, which makes npm install and yarn fail for humans. For agents, a deny rule in .claude/settings.json, plus the rest of the agent-side rules:

{ "permissions": { "ask": [ "Bash(pnpm add *)", "Bash(git push *)", "Edit(/tsconfig*.json)" ], "deny": [ "Bash(npm *)", "Bash(yarn *)", "Bash(pnpm update *)", "Bash(pnpm up *)", "Bash(git commit *--no-verify*)", "Read(./.env)", "Read(./.env.*)", "Edit(/src/gen/**)" ] }, "hooks": { "Stop": [ { "hooks": [ { "type": "command", "command": "pnpm check 1>&2 || exit 2" } ] } ] } } 

pnpm check is a script that runs pnpm lint && pnpm typecheck, and pnpm lint runs ESLint and Prettier with --check. The Stop hook follows the docs' own typecheck example: exit code 2 blocks the stop, and stderr goes back to Claude as the reason it can't finish yet, up to a block limit. The hook leaves the test suite out to stay fast. CI runs the tests.

In the permissions docs, a /path pattern in project settings is relative to the working directory, and a trailing * also matches the bare command, so Bash(npm *) catches npm alone too.

git commit -n needs its own rules; my lefthook post has them.

Codex gets its own copy, in a .rules file under <repo>/.codex/rules/. The Codex rules docs mark rules as experimental and load project rules only when the project's .codex/ layer is trusted:

# .codex/rules/repo.rules prefix_rule( pattern = ["npm"], decision = "forbidden", justification = "This repo uses pnpm. Run pnpm install.", ) 

Test it with codex execpolicy check --pretty --rules .codex/rules/repo.rules -- npm install before you rely on it.

The rest belongs to CI, where it binds everyone. pnpm's --frozen-lockfile fails the install when the lockfile is out of sync, and it's on by default in CI. GitHub blocks force pushes on protected branches by default. The migration rule is a few lines, with origin/main fetched:

pnpm install --frozen-lockfile pnpm check pnpm test changed=$(git diff --name-only --diff-filter=M \ origin/main...HEAD -- db/migrations/) if [ -n "$changed" ]; then echo "Committed migrations were edited: $changed" >&2 exit 1 fi 

Commit messages get commitlint in a commit-msg hook, with commitlint --edit $1 as the command.

Read the edges before you trust the agent-side half. The best practices page says rules "match tool calls, not intent": a Read deny catches cat .env, but "it won't catch a script that opens the file." That's why the rules that matter most also have a copy in a linter, a git hook or CI.

The 9 lines that stay

# AGENTS.md  1. Check your work with `pnpm check` and `pnpm test`. If a check or hook blocks you, fix the cause. Never disable the rule; if it looks wrong, say so. 2. Business rules live in src/domain/, free of I/O. 3. Ask before changing exports of packages/api-client. 4. Every migration gets a down step, or a reason why not. 5. Prefer named exports, unless a framework needs a default. 6. If a test fails, fix the code, not the test. If the test is wrong, change it and say why. 7. Match the error handling of the module you're in. 8. One concern per PR. Say so when a change grows. 9. Money is integer cents. Never floats. 

Line 1 replaces rules 33 to 35, and it's the line the docs value most: when your repo has a check, "the single most useful line in your CLAUDE.md is the command that runs it." It also covers the gap a linter leaves open, an agent adding an eslint-disable comment to get past a red line.

Line 5 is where this take bends. A linter can ban default exports. The best practices page still keeps Prefer named exports in the file, because it "has plenty" of exceptions and a hook is for a rule with zero of them. So the test has two parts: can a tool check it, and is a failure always a bug. Lines 2 to 9 are context and judgment, where a tool would be wrong some of the time.

The docs also warn about the opposite mistake: "If you ever find yourself at forty of them, most are probably preferences that Claude could have weighed on its own." That's about hooks and deny rules. Look at the table again. Most of the 32 went to ESLint, Prettier and CI, tools your repo runs for humans anyway, so the agent-only set stays at one hook and eleven permission rules.

Several agents, one set of gates

I build Hermes IDE, so weigh this section with that in mind.

The split matters more once you run more than one agent. Hermes runs Claude Code, Codex, Antigravity CLI, GitHub Copilot CLI, OpenCode and others side by side, each in its own real terminal and its own hermes/ worktree (per-repo setup). Codex reads AGENTS.md natively and Claude Code reads it when there's no CLAUDE.md, so it's the instruction file you can share. The agent-side gates are not shared: .claude/settings.json binds Claude Code, and .codex/rules/ binds Codex.

Two agent sessions side by side in Hermes: Codex on orders-types and Claude Code on orders-api, each in its own hermes/ worktree, with the sessions sidebar
Hermes 2.3.4: Codex and Claude Code in split panes, each in its own hermes/ worktree, with status, context use, cost and branch in the sidebar.

The gates that hold for every agent are the ones in the repo: ESLint, Prettier, the git hooks and CI. Each agent runs in a real terminal, so its git commit meets the same hooks yours does. Hermes adds two things around that. Your own checks can decide when a task is done before you land it. And since 2.3.4 a context meter shows how full each conversation is and offers Compact when it gets close, so you can see compaction coming.

The Hermes context view for a Claude Code session, showing Claude Code's context breakdown, per-session context gauges in the sidebar and the token total with estimated cost
Hermes 2.3.4: a Claude Code session after pressing the Context chip, with Claude Code's /context breakdown (44.4k of 1M tokens, 4%), the per-session context gauges in the sidebar, and the token total and estimated cost.

Hermes is free to download, source-available under BSL 1.1, and runs on macOS, Windows and Linux. It isn't affiliated with Nous Research, Anthropic, OpenAI or any agent vendor.

Open your own AGENTS.md and count. How many of your lines would a tool check better than the model? Tell me your ratio in the comments.

If this was useful

The 9 lines that stay are a prompt, so write them like one: specific, short, with the reason attached. The Prompt Engineering Pocket Guide is my short book on that, from instruction placement to the structure that keeps a model on task. It's one of 8 short books in Pocket Guides for Developers, covering agents, RAG, prompts, observability, events, databases and system design: the whole series is here.

If you run several agents on one repo, Hermes IDE puts them side by side, each on its own branch. It's built in the open on GitHub.

Prompt Engineering Pocket Guide: Techniques for Getting the Most from LLMs