The short version

  • One file at the root does not scale. Everything in it is paid for on every request, whether or not it is relevant to the file being edited.
  • Placement is the decision, not prose quality. A rule in the wrong layer is either diluted or missing at the moment it matters.
  • The test is one word. If a rule needs "except in" to be accurate, it is not a root rule.
  • Prohibitions fail without an alternative. The generated-files rule is the case study, and the reason it fails is not carelessness.
  • Design rules so they compose. Put the scope inside the rule, so a directory file only ever has to add. Never rely on precedence to resolve a contradiction you wrote deliberately.
  • Be honest about the payoff. The evidence that context files make agents more capable is contested. The evidence that they reduce wasted context and make behaviour predictable is a matter of arithmetic.

If you are still deciding what belongs in a context file at all — the exact commands, the architecture map, which conventions are worth stating and which material to leave out — that is a different problem, and we covered it in June 2026 in writing AGENTS.md and CLAUDE.md that actually steer agents. Read that one first if your repository has no context file yet; it has the do-and-don't table, a full worked example file, and the method for testing whether a rule changes behaviour at all.

This guide starts where that one ends. It assumes you already know what a good rule looks like and asks the harder question: where should each rule live, and does the layering survive contact with a real repository — a generated client an agent will eventually try to edit, and a root rule that quietly contradicts a directory rule nobody has noticed yet. You can write a beautifully phrased instruction about migration safety and still have an agent break your database, because the instruction was sitting in paragraph nineteen of a root file at the moment the agent was three directories away editing a schema.

What a root-only file actually costs

The failure is structural rather than stylistic. A root context file is, in most tool implementations as of 2026, loaded into the model's context for the session regardless of what the agent is about to do. Ask it to fix a typo in a README and it still carries your API error-handling contract, your Terraform naming convention and your notes about the data pipeline's schema ownership. Rewriting that file more crisply does not change the arithmetic. Only moving material out of it does. Three costs follow, and they have different fixes.

Tokens. Every line in that file is paid for on every turn, and in a long agentic session the arithmetic compounds. This is the one effect the 2026 evidence actually supports: in the ablation study we covered in our report on the AGENTS.md ablation, the single statistically significant finding across the entire experiment was that moving material out of the always-injected system prompt and into files the agent retrieved on demand reduced cache-creation tokens on one of the two agents tested. Not a capability gain. A cost effect, attributed to injection mechanics.

Dilution. Harder to measure, more consequential, and the cost that placement addresses directly. When a model holds forty instructions and three of them apply to the current task, the three do not get more attention because they are relevant. Instructions compete. A rule obeyed reliably as one of five becomes a rule obeyed most of the time as one of forty, and "most of the time" is the failure mode hardest to catch in review, because the code looks fine and the violation is quiet. Notice what that implies about the economics: the marginal cost of a new root rule is not the tokens it adds, it is the reliability it removes from every rule already there.

Rot. A single root document accumulates, because deleting a rule feels like taking responsibility for whatever happens next. The June guide covers staleness as a general hazard and the habits that keep one document honest; what layering adds is structural. A stale rule in a directory file has a natural expiry, because it dies when the directory does. A stale rule at the root outlives every reason it was written for and keeps loading into every session for years.

Watch out

The instinct when an agent does something wrong is to add a line to the root context file. Resist it. Adding rule forty-one to a file that already has forty is not a fix; it dilutes the other forty and makes the next failure marginally more likely. Ask instead where that rule is actually true. If the answer is "in the workers directory", the rule belongs in the workers directory.

The placement test: where is this rule actually true?

The hierarchy itself is not the interesting part, and we are not going to re-derive it here. A root file, a nested file inside any directory whose rules differ, and an untracked personal file for your own preferences: our June 2026 guide to AGENTS.md and CLAUDE.md sets out the mechanics — what each file holds, how the nearest file merges with the ancestors above it, and why the personal one stays gitignored and out of your colleagues' sessions. Take all of that as read.

What describing the hierarchy does not give you is a decision procedure, and the decision is where teams get stuck. Every rule anybody proposes sounds like it belongs at the root, because the root is the file everybody knows exists and putting something there requires no argument.

Which layer, in three questions

Ask, in order. Is this true in every directory in the repository, without qualification? Then it is a root rule. Is it true only inside one subtree, and would it be noise for somebody working elsewhere? Then it belongs in that subtree's file. Is it true only for you? Then it belongs in the gitignored local file and nowhere else.

The middle question does the real work, and it has a one-word tell. If the rule needs the words "except in" to be accurate, it is not a root rule — it is a directory rule that somebody promoted, and promoting it has quietly made the root file false somewhere in the tree. Applying that single test to an overgrown root file usually removes a third of it without anyone having to argue about style, because the qualifiers are already sitting there in the text; nobody has read them as a signal before.

The directory layer is the one most teams skip, and it is where the leverage is. It carries what governs one part of the system and would be noise everywhere else: the API layer's error contract; the frontend's rules about which components may fetch their own data; the migrations folder's immutability rule, covered in depth in our guide to letting a coding agent touch your database schema safely; and the infrastructure directory's rules about what may be applied and what must only ever be planned, which our guide to coding agents and infrastructure as code treats properly. Each is unarguable inside its own directory and actively unhelpful outside it. An agent editing a React component does not need your Terraform state-locking policy, and carrying it into the session costs tokens and attention for nothing.

Pro tip

Add the local context filename to .gitignore on the same commit that creates the root file, before anyone has written a local file worth losing. The pattern only survives if the gitignore entry exists from day one; retrofitting it after somebody has committed their preferences means having a conversation about whose habits are now team policy, and that conversation rarely goes well.

The scope is hiding in the wording

The quality of a rule and the placement of a rule turn out to be the same question asked twice. A vague rule cannot be placed, because you cannot say where it is true, and making it precise almost always surfaces a path. The June guide has a do-and-don't table for the quality dimension; this one is about what the rewrite reveals about scope.

Rule as usually written Rewritten so its scope is visible Layer
Write clean code Every exported Python function has a docstring with an Args and a Returns section. Root
Follow the project's style Run make fmt before finishing. Never hand-edit formatting or reorder imports manually. Root
Use good commit messages Subject line is <type>(<scope>): <summary>, imperative mood, 72 characters maximum. Root
Keep secrets safe Never read, print or copy .env, *.pem or anything under infra/secrets/. Ask for the value. Root
Add tests Every bug fix in application code adds a regression test that fails on the parent commit and passes on this one. Root
Handle errors properly Every handler in api/ returns a Problem object. Never a bare string, never a stack trace. api/
Don't break the API Do not rename or retype a response field in api/v1/. Add a new field and mark the old one deprecated. api/
Be careful with the database Never edit a migration whose version appears in schema_migrations. Add a new migration instead. migrations/
Don't touch generated code Every file under api/generated/ is produced by make api-client from api/openapi.yaml. Fix the spec, not the output. api/generated/

Read down the middle column and count the paths. Seven of the nine rewrites name one, and the two that do not — the formatter and the commit format — are precisely the two that genuinely belong at the root. The path is not decoration added to make a rule sound rigorous. It is the answer to "where is this true?", which is the same answer as "which file does this go in". Vagueness and misplacement are one failure seen from two angles.

The same logic runs below the file level, which is why a section headed "Never touch" gets found and a sentence about credentials buried in a paragraph on deployment does not. Name your headers for what somebody would search for, put one rule per bullet, and never leave a rule to be inferred from prose. It is the same reasoning that governs writing good tool descriptions, which we cover in our guide to stopping wrong tool calls with better selection and descriptions: in both cases you are writing for a reader that pattern-matches rather than infers.

A monorepo, laid out properly

Abstract principles are easy to agree with and hard to apply, so here is the whole thing on a realistic repository. Assume a service with an API, a web frontend, background workers, infrastructure code and a migrations directory — a shape that will be familiar whether you are in a Bengaluru product team or a London startup.

The tree

acme-platform/
├── AGENTS.md                 # ROOT — true everywhere. Lean.
├── CLAUDE.md                 # Thin: points at AGENTS.md. No duplication.
├── AGENTS.local.md           # GITIGNORED — personal preferences only
├── .gitignore                # contains: AGENTS.local.md
│
├── api/
│   ├── AGENTS.md             # error contract, auth, versioning rules
│   ├── v1/
│   └── generated/
│       └── AGENTS.md         # "do not edit by hand" lives HERE
│
├── web/
│   ├── AGENTS.md             # component conventions, state, styling
│   └── src/
│
├── workers/
│   └── AGENTS.md             # idempotency, retry and queue rules
│
├── infra/
│   ├── AGENTS.md             # plan-only rules, state locking, naming
│   └── secrets/              # never read — prohibited at root
│
└── migrations/
    └── AGENTS.md             # never edit an applied migration

Seven context files sounds like a lot until you notice that the root one is now a page long instead of ten, and that an agent editing a worker loads roughly two pages of rules rather than ten pages of which one page applies.

The root file, in full

This is the whole thing. If your root file is substantially longer than this, the surplus almost certainly belongs one level down.

# AGENTS.md

Acme Platform is the billing and subscription service. It issues
invoices, reconciles payments and exposes a customer-facing API.
It does not handle authentication; that is a separate service.

## Runtime
- Python 3.12. Node 22 for `web/` only.
- Dependencies are managed with `uv`. Do not edit
  `requirements.txt`; edit `pyproject.toml` and re-lock.

## Commands
- Tests:      `make test`        (do not invoke pytest directly)
- One test:   `make test T=path::name`
- Lint:       `make lint`
- Format:     `make fmt`         (run before finishing any change)
- Type check: `make types`

## Commits
- Subject: `<type>(<scope>): <summary>`, imperative, max 72 chars.
- Types: feat, fix, chore, docs, test, refactor.
- Scope is the top-level directory: api, web, workers, infra.
- One logical change per commit. A schema change and the code
  that depends on it count as one logical change.

## Never touch
- Do not read, print or copy `.env`, `*.pem`, or anything under
  `infra/secrets/`. If you need a value, ask for it.
- Do not edit any file under `api/generated/` or
  `web/src/api/generated/`. See the AGENTS.md in those directories.
- Do not edit a migration that has already been applied. See
  `migrations/AGENTS.md`.

## Scope of this file
Rules that are true everywhere in this repository live here.
Anything narrower belongs in the AGENTS.md of the directory it
applies to. If you add a rule here that needs the words
"except in", move it.

A directory AGENTS.md ADDS to this file; it does not replace it.
If a directory rule appears to contradict a rule here, that is a
bug in these files, not a decision for you to make. Say so and
ask. Do not silently pick one.

Note the last section. Writing the layering rule into the root file is the mechanism that stops the file growing back, and it gives the next person a reason to push a rule downward that is not "because Rishi said so in a review". Its second paragraph does something else again, which we come back to below: it tells the agent what to do when the layers disagree, which is the one thing precedence cannot tell it.

The migrations directory, in full

# migrations/AGENTS.md

Rules for this directory only. The root AGENTS.md still applies.

## The one rule that matters
Never modify a migration file that has already been applied to
any environment. Applied migrations are immutable history. To
change something, add a new migration.

A migration is "applied" if its version appears in the
`schema_migrations` table of dev, staging or production. Check
before editing:  `make db-applied`

## Adding a migration
- `make db-new NAME=add_invoice_currency`
- Every migration needs a matching `down` that has been run
  locally at least once. An untested down is not a down.
- Expand and contract: add the column, backfill, switch reads,
  then drop the old column in a LATER migration. Never in one.

## Forbidden without an explicit human instruction in the task
- `DROP TABLE`, `DROP COLUMN`, `TRUNCATE`
- `ALTER COLUMN ... TYPE` on a table with more than 10k rows
- Anything that takes an ACCESS EXCLUSIVE lock without a
  stated `lock_timeout`

## Review
Schema changes are reviewed by the data owner listed in
`migrations/OWNERS`, not by the general reviewer rota.

Ask where each of those rules would have gone in a single-file world. The immutability rule would probably have reached the root. The lock-timeout rule would not, being too specific to justify a line in a document everyone reads. The expand-and-contract sequencing definitely would not, because it takes four lines to state properly and nobody wants four lines about migrations in a file a frontend engineer also has to read. Those are exactly the rules that prevent expensive mistakes, and the single-file model structurally excludes them.

The generated-files rule, worked in full

Of all the rules that belong one level down, the generated-files rule is the one worth working through end to end. It is nearly universal — almost every repository has an OpenAPI client, protobuf stubs, a schema dump or an index page built by a script. It is cheap to state and everybody already agrees with it. And it fails more instructively than any other rule in the file, because it is the one case where every other guard rail you own says yes.

Why this rule fails when nothing else does

When an agent tries to read a secret, the tooling notices, the reviewer notices, and the failure is loud. When an agent edits a generated file, the edit is locally correct. The tests pass, because the edit fixes the bug they were failing on. The type checker passes. The linter passes. The diff is one line in a file the reviewer has never opened, in a directory they have no reason to look at twice. Every signal in your pipeline agrees this is a good change, and every signal is wrong, because the change has a hidden expiry date attached.

Here is the failure as it plays out. The rule lives in the root file as one bullet among forty: "Do not edit generated files." An agent is asked to fix a bug where the API client is sending a malformed date. It traces the bug correctly, all the way into api/generated/client.py, finds the offending line, and fixes it. Tests pass. The pull request is approved in four minutes, because a one-line diff with a green build is what a fast review looks like. Three weeks later somebody adds an endpoint, runs make api-client as part of that unrelated change, and the generator overwrites the file. The fix vanishes inside a large, plausible, generated diff that nobody reads line by line. The bug comes back. The investigation that follows starts from a report insisting the bug was already fixed, and a git history in which the fix is present, correct, reviewed and no longer in the file — and it costs several times the original bug, in the currency teams have least of.

Why the agent did it anyway

Nothing in that story required the agent to be careless, and this is the part worth sitting with, because the usual response — write the rule more forcefully — addresses none of it. Four things went wrong at once, and they are four different problems.

  • The rule was a category; the file was a path. "Generated files" is an abstraction. api/generated/client.py is a path whose contents look like ordinary Python with ordinary imports. Connecting the two is an inference the agent has to make unprompted, three directories away from where the rule was stated, while nothing in the immediate context mentions generation.
  • It was competing with thirty-nine other instructions. Nothing marks it as the consequential one. To the model it carries the same weight as the commit-message convention it is also holding.
  • It was stated at session start and needed at turn thirty. By the time the agent reached that file, the recent context was stack traces, test output and source. The root file was a long way back and had not been referenced since.
  • The prohibition had no alternative, and it was blocking a goal. The agent had a clear success criterion — make the failing test pass — and the rule offered only an obstacle. Where a prohibition names no other route to the goal, the goal tends to win. This is the mechanism people most often miss, and it is also the cheapest one to fix.

The directory file that prevents it

Put a context file in the directory itself and all four mechanisms are addressed at once.

# api/generated/AGENTS.md

STOP. Every file in this directory is generated. Do not edit any
of them by hand. The next generator run will discard your change
without warning, the bug will reappear, and the git history will
still show the fix as present and correct.

Source of truth:  `api/openapi.yaml`
Template:         `tools/codegen/templates/`
Regenerate with:  `make api-client`
CI enforces this: `make api-client && git diff --exit-code api/generated/`

## If you found a bug in a file here, fix it in one of these places
1. The spec is wrong: edit `api/openapi.yaml`, then regenerate.
2. The generator is wrong: edit `tools/codegen/templates/`, then
   regenerate. Expect other files in this directory to change too.
3. Neither is wrong and the caller needs different behaviour: add
   or change a wrapper in `api/client_ext.py`. That file is NOT
   generated and IS safe to edit.

Option 3 exists so that this directory is never a dead end. If
none of the three fits, stop and ask. Do not edit a file here.

## Owner
`tools/codegen/` and this directory are owned by @platform, per
`.github/CODEOWNERS`. Any change here needs their review.

Four things changed, one per mechanism. The rule is adjacent to the file being edited, so it arrives at the moment of the decision rather than at session start. It is about this directory by construction, so no inference from category to path is required — the agent does not have to recognise the file as generated, because the directory it is standing in says so. It states the consequence, so the prohibition reads as a mechanism rather than a preference, and agents, like people, work around rules they experience as arbitrary. And it names three legitimate destinations, which converts a dead end into a route. That last one does more work than the capital letters at the top.

Recommended

Every prohibition should answer three questions in the file where it lives: what not to do, what happens if you do it anyway, and what to do instead. A rule that answers only the first is a rule the agent will follow until it has a reason not to, and a failing test is a very good reason.

The extension point is part of the rule

That third option — the non-generated wrapper — deserves separating out, because a great many teams write the prohibition and never create the destination. If your repository has no client_ext.py, no editable template, and a spec that needs a cross-team review to touch, then the honest reading of your rule is "there is no legal way to fix this bug today". Nobody obeys that rule for long. An engineer under deadline edits the generated file and leaves an apologetic comment; an agent under a failing test edits it and leaves nothing at all. Creating the legitimate destination is not an optional nicety attached to the rule. It is the part of the rule that makes the rest of it credible, and it is worth ten minutes at the point you write the prohibition rather than after the third incident.

The test applies to every other prohibition you have. Migrations are immutable, so where does the corrective change go? A new migration, named in the rule. Secrets must not be read, so how does the agent get a value it genuinely needs? It asks, and the rule says so. Infrastructure must not be applied, so what may it do? Produce a plan and attach it. A prohibition with a named destination is a routing instruction. A prohibition without one is a wall, and walls get climbed.

Detecting that it has already happened

Prevention is placement. Detection is a separate job, and you want it for two reasons: placement improves the odds rather than guaranteeing the outcome, and you almost certainly have historical instances you do not know about.

The cheapest detector is a CI job that regenerates and diffs. It is one step, and it converts a three-week silent loss into a red build on the pull request that introduced it.

# .github/workflows/codegen-check.yml (fragment)
- name: Generated files must match their source
  run: |
    make api-client
    git diff --exit-code -- api/generated/ || {
      echo "::error::api/generated/ does not match api/openapi.yaml."
      echo "Either someone edited generated output by hand, or the"
      echo "spec changed and nobody regenerated. Run: make api-client"
      exit 1
    }

This check is what makes the directory rule credible rather than aspirational. A rule that can be violated with no consequence erodes; a rule that produces a red build the same afternoon gets learned — by the humans, and by the agent, which reads the failure message and can act on it, which is exactly why the message names the command to run.

Make the change look generated to a human reviewer. A one-line edit in client.py is visually indistinguishable from ordinary work in a review interface, which is why it got approved in four minutes. Mark the paths so that it is not.

# .gitattributes
api/generated/**        linguist-generated=true
web/src/api/generated/** linguist-generated=true

Collapsed diffs mean the reviewer sees "generated file changed" as the headline rather than a plausible one-liner, which is a question they will ask. Pair it with a CODEOWNERS entry pointing those paths at whoever owns the generator, so any edit forces a review from the one person who will recognise the problem instantly rather than the one person who happened to be online.

Then audit what has already happened. A genuine regeneration usually stands alone in its own commit, so any commit touching a generated path alongside hand-written code is a candidate.

# every commit that has touched generated output, newest first
git log --format='%h %ad %an %s' --date=short -- api/generated/

# the suspicious subset: generated output changed in the same
# commit as hand-written code
git log --format='COMMIT %h %s' --name-only -- api/generated/ \
  | awk '/^COMMIT/{c=$0} !/^COMMIT/ && NF && $0 !~ /^api\/generated\// {print c}' \
  | sort -u

Run that on any repository that has had agents working in it for a few months. If the output contains commits whose messages describe a bug fix rather than a regeneration, you have had this failure and did not notice. That is not an argument for distrusting agents — the same query on most repositories turns up humans, usually more of them. It is an argument for putting the rule where the decision is made and backing it with a check that fails loudly rather than a sentence that fails silently.

Every article here is written by a Verified Builder. Want your name on the next one?

AI Tech Connect lists AI engineers, founders and researchers across India and the UK — and the people hiring browse it to find them. Adding your profile is free.

Become a Verified Builder →

Writing rules that compose instead of conflicting

Layering introduces one failure the single-file model does not have: two files that disagree. The June guide notes that the nearest file usually wins, and as a description of how tools load files that is accurate. It is not a design principle, though, and treating it as one is the commonest way a layered set of files goes wrong in its second year. What follows is about making contradiction impossible rather than resolving it after the fact.

What actually happens when two rules disagree

Suppose the root file says "Every exported function has a docstring with an Args and a Returns section", and web/AGENTS.md says "Use TSDoc; do not write Python-style docstring sections". Three outcomes are possible, and only one of them is the one you assumed.

  • The nearer rule wins. What you wanted, and it will happen much of the time. It is also the outcome you observe while testing, which is precisely why the other two go undetected for months.
  • The model attends to whichever rule is more salient in that session. Salience is not file order. A root rule phrased absolutely — "Every", "Never" — can outweigh a nearer rule phrased as a preference, whichever file the tool loaded last. The result is intermittent: the same request produces the right convention on Tuesday and the wrong one on Thursday, and the team files it under model flakiness rather than under a bug in files they wrote themselves.
  • The model satisfies both, badly. It writes TSDoc with Args and Returns headings inside it. This is the worst of the three, because the artefact looks deliberate. A reviewer assumes somebody chose it and it survives — then it gets copied, because the next agent in that directory reads the existing code as the convention.

Documented precedence rescues you from none of this. Load order is a tool behaviour, attention is a model behaviour, and the two are only loosely coupled. A rule that works only because of an undocumented precedence quirk in the tool version you happen to run is a rule you will debug after an upgrade, in a week when you have forgotten it exists. Treat precedence as a fallback you are grateful for, never as a mechanism you design around.

Narrow at the source rather than override at the destination

The discipline is a single sentence: put the scope inside the rule, so that the directory file only ever has to add. The root rule above is not badly placed. It is badly written. It claims something universal that was never universal, and the contradiction was created when somebody typed it, not when somebody wrote the TSDoc rule two months later. Rewrite it as "Every exported Python function has a docstring with an Args and a Returns section" and the conflict disappears at the source. web/AGENTS.md now adds a TSDoc rule rather than fighting a Python one, and there is nothing left to arbitrate.

This is why the "except in" test earns its keep twice over. The first time, it tells you a rule has been promoted too far up the tree. The second time, it tells you a root rule is silently false somewhere — a latent conflict, untriggered only because nobody has yet written the directory rule that will contradict it. Those are the ones that surface six months later as the intermittent failure above.

The conflict What the agent does with it Rewritten so it composes
Root: "Every exported function has a docstring with Args and Returns." web/: "Use TSDoc." Writes TSDoc containing Args and Returns headings, or alternates between the two conventions from session to session. Scope the root rule to Python. The directory rule then adds a TSDoc requirement rather than replacing anything.
Root: "Run make fmt before finishing." vendor/: "Do not reformat these files." Formats the vendored tree, then a later run reformats it back. Unexplained churn in every diff that touches the directory. Move the exception into the tool: "Run make fmt; it skips paths in .fmtignore." Put vendor/ in that file. The rule and the tooling now agree.
Root: "Never edit a file under a generated/ directory." tools/codegen/: "Edit the templates here freely." Hesitates on templates, or refuses outright, or asks the same clarifying question on every task. Name paths, not concepts: "Never edit files under api/generated/ or web/src/api/generated/." Templates were never in scope.
Root: "One logical change per commit." migrations/: "Ship the migration with the code that needs it." Splits the migration from its code, producing a commit that fails on its own and a bisect that lies. State the exception inside the general rule: "One logical change per commit; a schema change and the code that depends on it are one logical change."
Root: "Add a regression test for every bug fix." infra/: "There are no unit tests in this directory." Writes a meaningless assertion to satisfy the rule, or stalls and asks how to test Terraform. Root: "Every bug fix in application code adds a regression test." infra/AGENTS.md then adds what verification means there: attach a plan diff to the pull request.

Read the right-hand column as a set and a pattern shows up. In every case the fix moves information up into the general rule — a qualifier, a path list, a named exception — so that the specific rule can be purely additive. That is counter-intuitive, because the instinct is to push detail downward. The distinction is that you push the rule down and pull its scope up, so the root file says less about more, in language precise enough that nothing below it needs to argue.

When you genuinely need an override

Narrowing is not always available. A vendored subtree that must keep upstream's formatting, a legacy service that keeps its old commit scope until it is decommissioned — these are real, and pretending otherwise produces files that lie, which is worse. When you need an override, write it as one, with four parts: it names the root rule it overrides, states plainly that this directory is an exception, gives the reason, and says when the exception ends.

# legacy/reporting/AGENTS.md

## Override of the root commit convention
The root AGENTS.md requires the commit scope to be a top-level
directory. This directory is an explicit exception: use `report`
as the scope, not `legacy`.

Why: the release tooling in `tools/release/changelog.py` still
parses `report(...)` and silently drops anything else from the
changelog. See ENG-4417.

Ends when: ENG-4417 ships. If ENG-4417 is closed and this section
is still here, delete the section.

The expiry line is the part people leave out and the part that matters most. An exception without one is permanent by default, and permanent exceptions are how a well-designed set of layered files degrades back into a pile of mutually inconsistent documents over about two years. Directory files help here structurally: an exception in legacy/reporting/AGENTS.md is deleted the day that directory is, whereas the identical exception in the root file outlives the code it was about and keeps loading into every session long after anybody can explain it.

Auditing for conflicts you already have

Two greps find most of them. In the root file, search for the absolutes, and for each hit ask whether the claim holds in every directory with a context file of its own. In the directory files, search for the words that mark an implicit override. Every hit in the second list is either a deliberate override, which should have the four parts above, or a bug.

# absolutes at the root — each one is a claim about EVERY directory
grep -nEi '\b(always|never|every|all|must|only)\b|do not' AGENTS.md

# implicit overrides hiding in directory files
grep -rnEi '\b(except|unlike|instead|however|overrides?|but here)\b' \
  --include=AGENTS.md . | grep -v '^\./AGENTS.md:'

Run both after any change to the root file, and add the second whenever you create a directory file. It catches the class of problem otherwise discovered by watching an agent produce the wrong convention twice and concluding, wrongly, that the model has got worse.

Watch out

An implicit override that happens to work today is the most dangerous line in a layered set of files, because it produces correct behaviour without anybody understanding why. It survives a tool upgrade, a model change or a directory move on luck alone, and when the luck runs out the symptom shows up in a part of the system nobody connects to a Markdown file.

AGENTS.md and CLAUDE.md as of 2026

The naming question comes up in every team that adopts this pattern, and the honest answer as of 2026 is that it matters less than people think, provided you avoid one specific mistake.

Capability AGENTS.md CLAUDE.md
Tool recognition (as of 2026) The closest thing to a cross-tool standard — recognised by Codex CLI, Copilot CLI, Gemini CLI, Cursor and Claude Code Claude Code's native file
Hierarchical loading of nested files Not standardised by the convention; behaviour varies by tool A documented capability
Imports from other files Not part of the convention Supported
Portability if the team changes tools High — the file travels The content travels; the mechanics do not
Best used as The source of truth for repository conventions The Claude-specific layer that references the source of truth
If you maintain only one Make it this one Only if the whole team is on Claude Code and stays there

The guidance that has settled by 2026 is straightforward: if you maintain only one file, make it AGENTS.md, because it is the one that keeps working when somebody on your team switches tools. If your team is on Claude Code and wants hierarchical loading or imports, keep CLAUDE.md as well — but keep it as a layer that references AGENTS.md rather than one that restates it.

The duplication trap

Here is the mistake, and it is close to universal among teams that adopt both files. Somebody copies AGENTS.md to CLAUDE.md so that both tools have full context. For about six weeks this is fine. Then a convention changes — the test command, say, or the commit format — and it gets updated in one file. Nobody notices, because both files still exist and both still look plausible.

You now have two documents that disagree, and this is the same failure as the root-versus-directory conflict above, in a form where none of the composition techniques apply: there is no scope to narrow, because the two files are meant to say the same thing. An agent following the stale one produces work that is confidently wrong in exactly the way that is hardest to spot in review, because it is following a rule that used to be correct. Worse, the moment your team discovers the divergence, they stop trusting both files, and the entire practice quietly dies over the following quarter.

The fix costs three lines:

# CLAUDE.md

All repository conventions live in @AGENTS.md. Read that file.
Do not duplicate its content here.

This file exists only for Claude Code specifics: nested loading
behaviour and imports. If you are about to add a convention
here, add it to AGENTS.md instead.
Avoid

Never keep two context files with overlapping content, in any combination — root and directory, AGENTS.md and CLAUDE.md, committed and local. The moment two files can disagree, they eventually will, and a stale rule that an agent follows faithfully is more expensive than a rule that was never written down.

What the evidence supports, and what it does not

It would be easy to end this guide with a claim that layering makes agents dramatically more effective. That claim is not supported, and pretending otherwise would be exactly the kind of unexamined convention this article is arguing against.

As of 2026, the most rigorous public test of whether context files improve agent output is a controlled ablation across two frontier coding agents on three real Python repositories, and its headline finding is a null. Context strategy did not measurably move correctness on either agent. Adding the repository's real AGENTS.md converted no near-miss failure into a pass. We covered the study, its design and its confounds in full in our report on the AGENTS.md ablation, and it is worth reading properly before you cite it in either direction.

Two caveats keep that finding honest. The first is the author's own: with fifteen to seventeen tasks and three repeats, the minimum detectable effect was well above thirty percentage points, so an effect of around ten points — which would be an enormous practical win — would have been invisible to the design. An absence of a measured effect is not evidence of no effect. The second is scope: three repositories, all Python, and the paper says plainly that results may not generalise.

So what does justify the work in this guide? Three things, none of which depend on the correctness claim.

Reduced wasted context, which is arithmetic rather than inference. A four-thousand-word root file loaded on every turn of a long session costs what it costs. Moving three thousand of those words into directory files that load only when relevant reduces that cost, and the ablation's one significant result — reduced cache-creation tokens under selective retrieval — is consistent with the mechanism.

Predictable behaviour, which is what you actually want. The purpose of writing down the commit format is not to make the agent smarter; it is to stop it inventing a format, and to stop you typing the format into every prompt. The purpose of the generated-files rule is not to improve pass rates; it is to prevent one specific expensive mistake. These are governance outcomes, and governance is a reasonable thing to want from a system that writes code into your repository.

Human legibility. A well-layered set of context files is the best onboarding documentation most repositories have, because it is the only documentation with a reader who complains immediately when it is wrong. That benefit accrues whether or not any model reads it.

Frame it that way and the guidance survives the evidence changing. If a larger study eventually shows a real correctness effect, nothing here needs rewriting. If further studies confirm the null, nothing here needs rewriting either, because none of the three justifications above rests on the agent becoming more capable.

From a verified Builder

"We stopped arguing about whether the context file made the agent better and started measuring whether it made the agent predictable. Different question, much easier to answer, and it turned out to be the thing our reviewers actually cared about. Nobody on the team could tell you if pass rates moved. Everybody could tell you that we stopped getting pull requests with the wrong commit format."

— Meera, Verified Builder · Bristol, United Kingdom

Which layer owns this change?

Every argument in this guide collapses if the files go stale. The general habit — update the file in the same commit that changes the thing it describes — is covered in the June 2026 guide and we will not restate it. What layering adds is a second question a single-file repository never has to ask, and it decides whether the structure is still standing in its third year: which layer owns this change?

Make it a review trigger rather than a calendar reminder. Quarterly documentation reviews do not happen, and when they do they fall to whoever is least busy, who is by definition the person least qualified to know which rules have gone stale. Tie the update to the change: any pull request that changes a convention updates the context file at the layer that convention actually governs, in the same pull request. Three questions in review, ten seconds in total. Did this change a convention? Is it universal, or local to a subtree? Does the file at that layer now say something false?

The second question does the new work, because it catches the two ways a layered set of files decays. Neither is ordinary staleness, and neither exists in a single-file repository.

Upward drift

Rules migrate towards the root, because the root is the file everybody knows exists and editing it requires no decision about where anything goes. Nobody does this deliberately. It happens one plausible line at a time: an incident produces a rule, the rule goes in the root file because that is where rules go, and two quarters later the root is a page and a half again while the directory files have not been touched since the day they were created. You are back to a single-file repository with extra steps. The reviewer question that catches it has to be asked every time rather than occasionally: why does this belong at the root rather than in workers/? If the answer contains the word "except", the rule moves down.

Orphaned layers

The second decay mode is specific to layering and nobody anticipates it. A directory is deleted and its context file goes with it, which is the system working. But a directory gets split and the rules stay with only one half; or code moves from api/ to services/ and the error contract stays behind, governing a directory that no longer contains the code it was written for. You now have a rule loaded when it should not be and absent when it should be, which is the dilution problem inverted and harder to spot. No tooling catches this cheaply. One reviewer question does: did any code here move across a directory that owns a context file?

Two supporting habits, both sharpened by layering. Delete aggressively — a rule nobody can remember the reason for is a rule to remove, and if removing it causes a problem you find out quickly and can add it back with the reason attached this time. And date the exceptions, as in the override above: an exception in a directory file expires with the directory, which is one more reason they belong down there rather than at the root, where they outlive their reason and nobody feels entitled to delete them.

The wider point is that context files are one part of a larger 2026 tooling surface, and the same placement discipline runs across all of it. The recommended practice for tools is directly analogous: curate a small set of high-signal tools per agent and expose the long tail through tool search rather than loading everything into every request. Same principle, different surface — which is the argument our piece on how context engineering replaced prompt engineering makes at greater length.

Why this lands differently in India and the UK

The layered pattern pays off in both markets, but the layer that earns its keep is different, and it is worth being specific rather than gesturing at both regions.

For Indian services firms and global capability centres, the payoff concentrates in the directory layer, because of the shape of the work. An engineer in a Chennai or Pune delivery team may move between four client codebases in a quarter, each with its own error-handling contract and its own idea of what a commit message looks like. That context-switching cost falls on the engineer, and it falls hardest at the moment of least attention — the small change in an unfamiliar subtree at the end of a day. Directory-level files put the relevant convention adjacent to the code being touched, so the engineer and the agent both meet it at the moment it is needed. A second effect matters commercially: when conventions live per-directory rather than in one senior engineer's head, a new team member becomes productive on a client repository considerably faster, and that ramp time is a real line in a services P&L. The same layering also makes it far easier to keep one client's rules genuinely separate from another's, which matters when the codebases belong to organisations with different contractual constraints on what may be shared.

For UK startup teams the shape is inverted. One monorepo, a small team, everybody knows the codebase — but the conventions change every few weeks because the product is still being discovered. There the leverage is entirely in the maintenance discipline. A ten-person team in London or Manchester rarely suffers from not knowing the convention; they suffer from a context file describing the convention they had two quarters ago. The same-pull-request update rule is worth more to them than any amount of careful layering, because their failure mode is drift rather than distance — upward drift specifically, since with three directories and four engineers everything lands in the root file and nobody notices until it is unreadable. Small team size adds a second argument. When three engineers hold the conventions in their heads, the context files are the only thing standing between you and a total loss of institutional knowledge when one of them leaves, which given UK startup churn rates is not hypothetical. The layered version is sharper still: a departing engineer's knowledge is almost always directory-shaped — they were the person who understood the workers, or the billing code — so the directory file is the artefact that captures what is actually at risk.

Both markets share one thing, and it is why this is worth an afternoon. The teams that get value from coding agents are not the ones with the cleverest prompts. They are the ones that have made their repository legible — where the rules governing a piece of code sit next to that code, are stated in a form that can be checked, and are updated by the same person who changed the thing they describe. That work is unglamorous and it compounds, and it is the same work that makes a codebase pleasant for humans, which is a reasonable sanity check whenever you are unsure whether a rule is worth writing down at all. For the other half of the discipline, our guides to debugging with a coding agent and to handling thinking blocks across model upgrades cover what happens once the agent is working inside a repository you have organised properly.