The file started at forty lines. Then someone added the database migration convention. Then the “always use the shared logger, never console.log” rule. Then the API error-envelope shape, the test-naming pattern, the three paragraphs about how to write a commit message. Eighteen months later your root rules file is 600 lines, and the agent still drops console.log into a React component.
That is not a discipline problem. It is an attention problem, and you built it on purpose.
One file means the agent reads frontend rules to edit a SQL migration
Section titled “One file means the agent reads frontend rules to edit a SQL migration”Here is the mainstream view, and it is reasonable: persistent context should live in one place so nothing gets lost, the agent always sees everything, and there is a single file to maintain. That is the whole appeal of rules — write it down once, the agent reads it every session, you stop repeating yourself.
The flaw is that “always sees everything” is the cost, not the feature. When your agent opens a database migration, it loads your migration conventions — and also your React hook rules, your CSS-modules policy, your GraphQL resolver patterns, and the commit-message essay. None of that applies to the file in front of it. Every irrelevant rule is a small tax on the relevant ones. The model has finite attention to spend on the tokens it’s given; you just spent a third of it on frontend conventions during a backend task.
So why does throwing a bigger context window at this not fix it?
Bigger models give you more room to drown the signal
Section titled “Bigger models give you more room to drown the signal”The seductive answer is capacity. A model with a million-token window can hold your 600-line file ten times over — surely bloat stops mattering. It doesn’t, and the reason is mechanical. A larger window changes how much the model can read, not how well it weights what it reads. Pack a prompt with rules that don’t apply to the current file and you’ve lowered the signal-to-noise ratio of the whole context, regardless of how much headroom is left. More capacity means more room to bury your one load-bearing rule under fifty that don’t fire today. Anthropic’s own guidance for Claude Code puts the practical ceiling around 200 lines per rules file and warns in plain terms that longer ones “reduce adherence” — not because the model runs out of room, but because every extra line is a thumb on the scale against the ones that matter. Curation beats capacity, every time.
The fix is not a smaller file. It’s the right file at the right moment.
Scope rules to directories so the agent loads only what it’s touching
Section titled “Scope rules to directories so the agent loads only what it’s touching”Most agents resolve rules hierarchically: a broad file at the repo root, plus narrower files deeper in the tree that load only when the agent works inside that subtree. In tools like Claude Code the leaf files aren’t even pulled into context at session start — a subdirectory’s rules enter the window the moment the agent first reads a file in that directory, and not before. The root holds what’s true everywhere. The leaves hold what’s true there.
Strip the root down to the genuinely universal:
# AGENTS.md (repo root)
- Conventional Commits for all messages.- Never commit secrets; read config from env, never hardcode.- Run `make check` before declaring a task done.- This is a monorepo: backend/ is Go, web/ is React+TS, infra/ is Terraform.Then push the specifics down to where they apply:
- Errors return the envelope `{ error: { code, message } }` — never bare strings.- Use the shared `logger` package. No `fmt.Println` in handlers.- Every migration needs a paired down-migration in the same PR.- No `console.log` in committed code; use the `debug` hook.- Styling is CSS Modules only. No inline styles, no Tailwind.- Data fetching goes through `useQuery`; never `fetch` in a component.Now when the agent edits backend/handlers/orders.go, the React rules are not in the context at all. The error-envelope rule isn’t competing with a CSS policy for the model’s attention — it’s one of three relevant lines. The agent’s working set matches its working directory. Signal stays high because noise never loads.
This also fixes the maintenance story you didn’t know you had. A monolithic rules file is a merge-conflict magnet and an authorship blur — nobody owns line 412. Directory-scoped files let the team that owns backend/ own backend/AGENTS.md, the way they already own the code under it. None of this is theoretical at scale: when guidance conflicts, the convention is that the file closest to the edited code wins, and OpenAI’s own monorepo already ships 88 separate AGENTS.md files — one near each package the agent might touch.
Directory isn’t the only axis — some rules follow a file type
Section titled “Directory isn’t the only axis — some rules follow a file type”Scoping by folder handles “everything under backend/ obeys these rules.” But some conventions cut across the tree. Every test file, anywhere, should follow the same arrange-act-assert shape. Every .tsx component gets the same accessibility checks. Force those into a directory file and you copy them into web/, admin/, and packages/ui/ — three copies that drift apart the moment one gets edited.
For that, the finer tool scopes by file pattern instead of folder. Claude Code’s .claude/rules/ directory lets a file declare the globs it applies to in frontmatter, and the rule loads only when the agent touches a matching file — wherever in the tree that file lives:
---paths: - "**/*.test.ts"---
- Arrange-act-assert, one behavior per test.- No network or filesystem in unit tests; mock at the boundary.Now your test conventions ride the file pattern, not the folder. The agent loads them when it opens web/cart.test.ts and when it opens packages/ui/Button.test.ts, and never when it’s editing production code. Directory files answer where am I working; path-scoped rules answer what kind of file is this. Most real repos want both axes, and the two compose: a web/AGENTS.md for the React app, a **/*.test.ts rule for how everyone tests.
The catch: a leaf rule only fires after the agent reads a file there
Section titled “The catch: a leaf rule only fires after the agent reads a file there”Lazy loading is what keeps the noise out, but it has a sharp edge worth knowing before you trust it. A directory-scoped file is invisible until the agent reads its first file in that subtree. So if you ask it to “add an endpoint” and it starts reasoning about backend/ from memory before opening anything there, the conventions you so carefully scoped aren’t in context yet — they load a beat too late. The same files also don’t survive a context compaction: when a long session compacts, the root file gets re-read from disk, but nested files stay gone until the agent next opens a file beneath them. Neither failure is dramatic, and the fix usually isn’t either — point the agent at a representative file first, or keep the genuinely load-bearing invariants at the root where they’re always present. The deeper lesson: scoping trades guaranteed presence for higher signal. For most conventions that’s a great trade. For the handful a single miss breaks, it isn’t — and those belong somewhere they can’t lazily fail to load.
Push task-specific instructions into commands, not rules
Section titled “Push task-specific instructions into commands, not rules”Even a well-scoped root still rots if you treat it as a junk drawer for procedures. “How to cut a release,” “how to add a feature flag,” “how to backfill a table” — these are tasks, not standing conventions, and they have no business loading on every edit.
That’s what slash commands are for. A command is context you invoke deliberately, only when you’re doing that thing:
Cut a release:1. Confirm `main` is green in CI.2. Bump the version in `package.json` per semver.3. Generate the changelog from Conventional Commit messages since the last tag.4. Tag `vX.Y.Z`, push the tag, open the release PR.The release procedure is forty lines the agent reads zero times a day until you type /release. Leaving it in the root file means paying for it on every unrelated edit. Rules are for what’s always true; commands are for what you’re doing right now. Keep them separate and each stays sharp.
Commands also catch the failure that scoping alone can’t: drift. Scoped or not, prose doesn’t change when code does — a rule describing an auth flow you ripped out in February reads as true on every session until someone notices, and a wrong rule is worse than a missing one. A missing rule makes the agent ask or explore; a wrong one makes it confidently build on a false premise you don’t catch until it’s three files deep. So make checking a first-class action — a command that audits your rules against the code on demand:
For every AGENTS.md in the repo:1. Extract each concrete claim (file paths, function names, env vars, flows).2. Grep the codebase to confirm each is still true.3. Report a table: claim | HOLDS / DRIFTED / GONE | evidence.Do not edit anything. Only report — I decide what to fix.Run it after a big merge or before you trust the rules on a hairy task. It deliberately reports and stops: drift detection and drift correction are different decisions, and letting the agent auto-rewrite context from its own reading of the code is just a fresh way to manufacture confident-but-wrong rules. You find the lie on a Tuesday by running a command, instead of inside a broken feature three weeks later.
When not to bother
Section titled “When not to bother”Scoping is a response to scale, not a virtue in itself. A 40-line rules file in a single-package repo doesn’t need splitting; shattering it into five eight-line files trades one cheap tax for the standing overhead of remembering where everything lives. The technique earns its keep when the file has grown long enough that most of it is irrelevant to any given edit, and when the tree is deep enough that “where am I working” is a real question with different answers. Below that threshold, one short, curated root file is the correct answer — and reaching for hierarchy anyway is just the planning version of the bloat you’re trying to escape. The enemy was never the single file. It was the unscoped one that grew past the point where any single edit needs most of it.
What to do Monday
Section titled “What to do Monday”Open your root rules file and read every line as the agent does — as a tax on the next edit, wherever it happens. Four buckets:
- Universal and declarative (“Conventional Commits,” “never hardcode secrets”) → stays at the root.
- True only in one part of the tree (“no
console.log,” “errors use the envelope”) → moves to a directory-scoped file. - A procedure you run on demand (“cut a release,” “add a migration”) → becomes a command.
- Non-negotiable (“schema changes ship a migration,” “no secrets in commits”) → stops being a rule and becomes a hook.
That last bucket is the line scoping can’t cross. A rule is a nudge — the agent usually honors it, sometimes doesn’t, and for most conventions that trade is fine. But where a single miss is a broken build or a leaked secret, no amount of perfect placement helps, because the mechanism itself is probabilistic. A hook is deterministic code that runs on an event and gates instead of asking nicely: a pre-commit hook that rejects any change to db/schema.ts without a matching migration catches the agent the hundredth time it forgets. Scoped rules carry the conventions that benefit from a nudge; hooks carry the ones that demand a guarantee.
A root file that survives this is short, and short is the point. The agent doesn’t ignore you because it’s careless. It ignores you because you handed it your frontend rules while it was editing a database migration, and asked it to know better.
Curate the context to the cursor. The signal was never the problem — the noise you bundled with it was.
For the per-tool mechanics of scoped persistent context, see Rules; for invokable, on-demand procedures, see Slash commands; and for turning a non-negotiable convention into a deterministic gate, see Hooks.