Nested AGENTS.md files for a monorepo
One root AGENTS.md in a monorepo goes stale and burns context on directories the agent never opens. Here is the nested layout that fixes it.
What nested AGENTS.md means in a monorepo
Nested AGENTS.md in a monorepo means one small file at the repository root and one more file inside each service directory. The root file holds the few rules that are true everywhere, plus a map of where the other files live. Each service file holds the commands and the conventions for that directory alone. An agent editing services/worker/queue.py then reads the root file and the worker file, and spends no context at all on the front end it will never touch.
There is nothing to install. AGENTS.md is a convention, and the upstream project says so plainly:
AGENTS.md is just standard Markdown. Use any headings you like; the agent simply parses the text you provide.
That is why the technique is worth learning properly. The format will not change under you. What breaks is placement and maintenance, and both of those are your job.
Why does one big root AGENTS.md stop working?
A single 600-line AGENTS.md at the root of a repository holding a web app, a background worker and a Terraform directory fails in four separate ways.
It goes stale, because nobody owns it. The engineer who renames a test script in apps/web is editing files under apps/web. The root AGENTS.md is not in that diff, so no reviewer sees the mismatch. Six weeks later the file describes a build step that no longer exists, and the person who broke it has forgotten the change.
It costs context on every task. These files load at the start of the session, before the agent knows what you are going to ask. Claude Code's documentation puts a number on it: "target under 200 lines per CLAUDE.md file. Longer files consume more context and reduce adherence." Codex stops merging instruction files once their combined size reaches 32 KiB, the default project_doc_max_bytes. A root file that documents four services spends that budget on three of them for every single task.
Instructions start to contradict each other. The web directory wants pnpm test. The worker wants pytest -q. Written into one file, each rule is correct only some of the time, so the agent has to guess which one applies. Claude Code's docs describe the result: "if two rules contradict each other, Claude may pick one arbitrarily." A per-directory file removes the guess, because only one of the two rules is ever in context.
It fills up with facts the agent could read from the code. A directory tree, a dependency list, a summary of what each package does. Claude Code's /doctor check exists to strip exactly this. It "cuts content Claude can derive from the codebase, such as directory layouts, dependency lists, and architecture overviews" and keeps "pitfalls, rationale, and conventions that differ from tool defaults." That sentence is the best test I know for whether a line belongs in the file at all.
Does the agent read the root file, or only the nearest one?
This is where most people get the model wrong, so it is worth quoting the upstream convention rather than paraphrasing it:
Place another AGENTS.md inside each package. Agents automatically read the nearest file in the directory tree, so the closest one takes precedence and every subproject can ship tailored instructions.
And on conflicts:
The closest AGENTS.md to the edited file wins; explicit user chat prompts override everything.
"Takes precedence" reads to many people as "the root file is ignored". It is not. In the tools that implement the convention, every file on the path from the repository root down to the working directory is read and joined together. The nearest file wins only where two files say different things about the same subject.
Codex is explicit about the mechanism: "Codex concatenates files from the root down, joining them with blank lines. Files closer to your current directory override earlier guidance." Claude Code walks the same path for its own file name. Files in the directory hierarchy above the working directory "are loaded in full at launch", and "All discovered files are concatenated into context rather than overriding each other." Directories below the working directory behave differently: Claude Code loads those files on demand, "when Claude reads files in those directories."
Two practical consequences follow. The root file is a prefix on every session in the repository, so treat each line there as a line you pay for a hundred times a week. A per-directory file costs nothing whenever the agent is working somewhere else, which means detail is cheap there and belongs there.
This behaviour was checked against the Codex and Claude Code documentation in August 2026. Tools implement the convention slightly differently and they do change, so confirm the loading rules for whichever agent your team runs.
A worked layout for a repository with three services
repo/
AGENTS.md rules true everywhere, plus the map
apps/web/AGENTS.md TypeScript client, Vite, Vitest
services/worker/AGENTS.md Python queue consumer, pytest
infra/AGENTS.md Terraform and the deploy scriptsThe root file is short on purpose. It says where to look, and it carries only the rules that hold in every directory.
# AGENTS.md
This is a monorepo. Each top-level directory ships its own AGENTS.md.
Read this file and the AGENTS.md nearest the code you are editing
before you change anything.
- `apps/web` browser client
- `services/worker` queue consumer
- `infra` Terraform and deploy scripts
## Rules for the whole repository
- The package manager is `pnpm`. `npm install` writes a second lockfile
that CI ignores, so the install you tested is not the install that ships.
- Any `generated/` directory is build output. Edit the schema in
`schemas/` and run `pnpm codegen` instead.
- `.env.local` holds real credentials. Do not read it and do not print it.
- If you change code in a directory, update that directory's AGENTS.md
in the same commit.The per-directory file is where the detail goes, and it can be as long as the directory deserves.
# apps/web
Browser client. Vite and React, TypeScript with `strict` on.
## Commands
- `pnpm dev` serves on port 5173.
- `pnpm test` runs Vitest once and exits.
- `pnpm typecheck` runs `tsc --noEmit`.
## Conventions
- One component per file under `src/components/`.
- All HTTP goes through `src/api/client.ts`. Do not call `fetch` directly,
because the client attaches the auth header and retries on 429.
## Traps
- `pnpm build` does not type check. Vite strips the types instead of
checking them, so a broken type still produces a green build.
Run `pnpm typecheck` as a separate step.The worker file is the same shape with different content: the install command, pytest -q, the reason the consumer must stay idempotent, and the migration that has to run before the tests pass. The infra file is where you write the rules that stop an agent doing damage. Never run terraform apply. Run terraform plan and stop there, and name the state backend that is already configured so the agent does not try to initialise a new one.
Notice what is in none of these files: a description of what each service is for. That belongs to the humans. Upstream draws the same line, saying "README.md files are for humans: quick starts, project descriptions, and contribution guidelines", while AGENTS.md carries "the extra, sometimes detailed context coding agents need: build steps, tests, and conventions." The split between AGENTS.md and a human-facing README goes sentence by sentence through that boundary, and a DESIGN.md that records why the code is shaped the way it is covers the third file, the one that explains decisions rather than commands.
Who updates the file when the code changes?
One rule, and it goes in the root file: whoever changes code in a directory updates that directory's AGENTS.md in the same commit.
This works for a mechanical reason, not a cultural one. The per-directory file sits in the same diff as the code, so the reviewer of the pull request sees both at once. A root file belongs to everybody, which means it belongs to nobody, and it is never in the diff anyone is already reading.
Back the rule with a check on the pull request. It finds the nearest AGENTS.md above each changed file, then reports when that file was not touched.
#!/usr/bin/env bash
# Warn when code changed but the nearest AGENTS.md above it did not.
changed=$(git diff --name-only origin/main...HEAD)
nearest_doc() {
d=$(dirname "$1")
while [ "$d" != "." ]; do
if [ -f "$d/AGENTS.md" ]; then echo "$d/AGENTS.md"; return; fi
d=$(dirname "$d")
done
echo "AGENTS.md"
}
printf '%s\n' "$changed" | while read -r f; do
[ -n "$f" ] || continue
case "$f" in AGENTS.md|*/AGENTS.md) continue ;; esac
doc=$(nearest_doc "$f")
printf '%s\n' "$changed" | grep -Fqx "$doc" && continue
echo "note: $f changed but $doc was not updated"
doneOn a branch that reworked the API client without touching the docs, the output looks like this:
note: apps/web/src/api/client.ts changed but apps/web/AGENTS.md was not updatedKeep it a warning rather than a failure. A hard gate teaches people to add a blank line to the file so that CI turns green, and a file edited to satisfy a robot is worth less than no file at all. The warning gives the reviewer a question to ask, which is the part that actually works.
How do I spot an AGENTS.md that has gone stale?
There are two checks you can run today, and one symptom you will see inside a session.
Compare the age of each file with the age of the code it describes. %cs prints the commit date as YYYY-MM-DD.
for f in $(git ls-files '*AGENTS.md'); do
d=$(dirname "$f")
printf '%s doc:%s code:%s\n' "$f" \
"$(git log -1 --format=%cs -- "$f")" \
"$(git log -1 --format=%cs -- "$d")"
doneapps/web/AGENTS.md doc:2026-02-11 code:2026-08-07
services/worker/AGENTS.md doc:2026-07-29 code:2026-08-09
infra/AGENTS.md doc:2026-08-01 code:2026-08-01A doc date six months behind the code date does not prove the file is wrong. It tells you which file to read first, and that is all you need from a check that takes one second.
Look for paths that no longer exist. Documentation rots in one very specific way: it keeps describing code that was deleted. Every path in these files is written in backticks, so they are easy to pull out and test.
grep -o '`[^`]*`' apps/web/AGENTS.md | tr -d '`' | grep '/' | while read -r p; do
[ -e "$p" ] || [ -e "apps/web/$p" ] || echo "missing: $p"
doneRead the output rather than wiring this one into CI. It also flags globs such as src/**/*.ts and any URL you quoted, because both contain a slash and neither is a file on disk.
The symptom in a session. The agent reads the file, tries to open src/api/client.ts because the file told it to, and the tool returns:
No such file or directorySo it does the reasonable thing and writes its own fetch wrapper. That is the real cost of a stale file. The agent does not ignore your documentation. It follows the documentation, lands on a path that was deleted three months ago, and rebuilds code you already have.
Does Claude Code read AGENTS.md files?
No, and it is worth saying out loud because the nested layout depends on it. As of August 2026 the documentation states: "Claude Code reads CLAUDE.md, not AGENTS.md." The pattern still works, you just need a CLAUDE.md beside each AGENTS.md.
The import form is right when you want tool-specific lines on top of the shared ones. Put this in services/worker/CLAUDE.md:
@AGENTS.md
## Claude Code
Use plan mode for changes under `services/worker/migrations/`.The symlink form is right when there is nothing tool-specific to add.
git ls-files '*AGENTS.md' | while read -r f; do
ln -s AGENTS.md "$(dirname "$f")/CLAUDE.md"
done
ls -l apps/web/CLAUDE.mdln prints nothing when it succeeds, so check the listing: apps/web/CLAUDE.md -> AGENTS.md. Then start a session and run /context, where the loaded files appear under Memory files. On Windows a symlink needs Administrator rights or Developer Mode, so use the @AGENTS.md import there instead.
One trap belongs with this. After /compact, the root file is re-read from disk, but nested files in subdirectories are not re-injected. They come back the next time the agent reads a file in that directory. If a per-directory rule seems to stop applying halfway through a long session, that is usually why, and touching any file in the directory brings it back.
Settings that point other agents at AGENTS.md
Codex reads AGENTS.md natively. At each level it checks for AGENTS.override.md first, which gives one directory a local override without editing the shared file. It stops merging once the combined size reaches 32 KiB, the default project_doc_max_bytes, which is one more reason to keep the root file small.
Aider takes it through .aider.conf.yml with the line read: AGENTS.md.
Gemini CLI takes it through .gemini/settings.json with { "context": { "fileName": "AGENTS.md" } }.
Upstream documents a backward-compatible rename for repositories still using the older singular name: mv AGENT.md AGENTS.md && ln -s AGENTS.md AGENT.md.
In a very large monorepo, Claude Code's claudeMdExcludes setting skips ancestor files by path or glob, which is useful when another team's directory sits above yours.
How is this different from agent memory, or from a skill?
These mechanisms look similar and fail in completely different ways, so it is worth being precise about which one you are reaching for.
AGENTS.md is written by you, committed to git, reviewed in a pull request, and identical for everyone who clones the repository. Agent memory is written by the agent, stored outside the repository, and local to one machine. Claude Code's documentation draws the same line: CLAUDE.md holds "Instructions and rules" that you write, auto memory holds "Learnings and patterns" that Claude writes, and the memory directory is not shared across machines. The test is simple. If a fact has to be true for a colleague on a fresh clone, it cannot live in memory. How agent memory persists between sessions covers that half of the picture.
A skill is the third thing. AGENTS.md is context that loads every session; a skill is a procedure that loads when it is needed. The Claude Code docs give a usable rule: "If an entry is a multi-step procedure or only matters for one part of the codebase, move it to a skill or a path-scoped rule instead." The second half of that sentence is precisely what a nested AGENTS.md solves. The first half is what agent skills are for, and when the same procedure is needed in more than one repository, share the skill across repos rather than pasting the same paragraphs into ten different AGENTS.md files.
Upstream notes that "at time of writing the main OpenAI repo has 88 AGENTS.md files". That number is the whole argument. A big repository does not need a bigger file. It needs more small files, each one sitting next to the code it describes, each one owned by whoever last changed that code.
FAQ
Does a nested AGENTS.md replace the root file or add to it?
It adds to it. Upstream says "the closest one takes precedence", which describes what happens on a conflict, not what gets loaded. Codex "concatenates files from the root down, joining them with blank lines", and Claude Code concatenates every file it finds walking up from the working directory rather than overriding them. The nearest file wins only where two files give different instructions about the same subject. Write shared rules at the root once, and do not repeat them in every directory.
How big should the root AGENTS.md be?
Small enough that you would not mind it being pasted on top of every request you make in that repository, because that is what happens. Claude Code's documentation suggests targeting under 200 lines per file and warns that longer files "reduce adherence". Codex stops merging instruction files at 32 KiB combined by default. If your root file documents four services, most of it is dead weight for any single task. Move the detail down into per-directory files and leave a map behind.
How do I stop these files from going stale?
Put one rule in the root file: whoever changes code in a directory updates that directory's AGENTS.md in the same commit. Placing the file next to the code is what makes the rule stick, because the change then lands in the same pull request diff a human is already reading. Add a CI warning that maps each changed path to the nearest AGENTS.md above it, and every so often compare git log -1 --format=%cs on each file against the same command run on the directory it documents.
Does Claude Code read AGENTS.md files?
No. As of August 2026 the documentation states "Claude Code reads CLAUDE.md, not AGENTS.md." Create a CLAUDE.md in the same directory with @AGENTS.md on the first line, which loads the shared file and lets you add Claude-specific instructions below it. A symlink created with ln -s AGENTS.md CLAUDE.md works when there is nothing extra to add, though on Windows it needs Administrator rights or Developer Mode. Run /context in a session and confirm the file appears under Memory files.
Where do I put a rule that only matters sometimes?
Not in AGENTS.md. That file loads in every session, so every line in it competes for attention with the request you actually typed. A procedure with several steps that is needed occasionally belongs in a skill, which loads on demand. A rule that applies to one directory belongs in that directory's AGENTS.md. A fact the agent can read straight from the code, such as the directory tree or the dependency list, belongs in neither.