Scoping Agent Context in Monorepos: Nested Files and Precedence
One repository, three languages, three sets of build commands. The root context file cannot answer 'how do I run the tests' — and it should stop trying.
The problem a single file cannot solve
A monorepo containing a Go service, a TypeScript frontend and a Python data pipeline has no shared answer to "how do I run the tests". A single root context file has three bad options: state all three commands and let the agent guess which applies, state the most common one and be wrong a third of the time, or state none and let the agent discover them.
Nested context files solve this. Most tools that read
AGENTS.md support multiple files through the tree, with the file
nearest the code being edited taking precedence. The root carries what is true
everywhere; each package carries its own specifics.
AGENTS.md ← true for the whole repo
packages/
api/
AGENTS.md ← Go service specifics
cmd/ internal/
web/
AGENTS.md ← TypeScript frontend specifics
src/
pipeline/
AGENTS.md ← Python specifics
dags/
Precedence is proximity, not specificity
This is the rule people get wrong. When an agent edits
packages/web/src/App.tsx, it reads the root file and
packages/web/AGENTS.md. The web file wins on any conflict —
not because it is more detailed, but because it is closer.
The practical consequence: if you find yourself writing "except in the web package" in your root file, that exception belongs in the web file. Conditional rules at the root are a smell. They cost tokens on every session regardless of what is being edited, and they force the agent to evaluate a condition it may evaluate wrongly.
| Root file | Package file |
|---|---|
| Commit message format | Build and test commands |
| Branch naming | Language-specific style rules |
| Secrets handling policy | Local dependencies and their quirks |
| Cross-package import boundaries | Frozen or generated directories |
| Which packages exist and what each is for | Traps specific to this package |
The root file's real job in a monorepo
Counterintuitively, the root file in a monorepo should be shorter than in a single-package repo, not longer. Almost everything concrete is package-local. What remains is the map and the rules that cross boundaries.
# AGENTS.md
## Layout
- `packages/api/` Go HTTP service — see its AGENTS.md
- `packages/web/` React frontend — see its AGENTS.md
- `packages/pipeline/` Airflow DAGs — see its AGENTS.md
- `packages/shared/` protobuf schemas, generated clients
## Cross-package rules
- Nothing imports from another package's internals. Cross-package code
goes through `packages/shared/`.
- `packages/shared/` is generated from `proto/`. Run `make proto`,
never hand-edit.
- A change touching two packages needs both owners on the PR.
## Everywhere
- Conventional Commits. Branch: `type/short-description`.
- Secrets from env only. Never inline, never in fixtures.
Seventeen lines. An agent editing the frontend pays for those seventeen lines and then gets the web package's file, which tells it about pnpm and Vitest and the component conventions. An agent editing the pipeline pays for the same seventeen and gets a completely different second file.
"see its AGENTS.md" costs four tokens and tells the agent a more specific file exists. Without it, an agent that has only opened root-level files may never look. Cheap insurance.
How deep to nest
Two levels is almost always right: root, and one per package. Three levels — root, package, sub-module — occasionally earns its place in a genuinely large package where one subsystem has rules the rest does not.
Beyond that you are describing the code rather than constraining it, and you have re-created the problem the discoverability test exists to prevent. If a directory needs its own context file to be comprehensible, consider whether the directory needs restructuring instead.
Verifying resolution actually works
Do not assume your tool resolves nesting the way you expect. Support varies, and a file that is never read is worse than no file, because you will believe a rule is in force when it is not.
The check takes two minutes. Put a distinctive, harmless instruction in a package file — something you would never write by convention:
## Verification
When asked "which context files can you see", reply with the exact
string: WEB-CONTEXT-LOADED
Open a session scoped to that package, ask the question, and see whether the string comes back. Then ask from the repository root, where it should not appear. That tells you both that nesting works and that scoping works. Delete the block afterwards, or keep it — it costs three lines and it is the only direct evidence you will get.
What happens when a package file goes missing
A failure mode specific to nesting: an agent working in a package whose context file does not exist falls back to the root, silently. It does not announce that it found no local file. It just proceeds with less information than you assumed it had.
In a repository where three of five packages have context files, the two without them get noticeably worse agent output, and the difference is invisible until someone compares. New packages are the usual culprits — the scaffolding creates the source layout and the CI config and nobody remembers the context file, because unlike those two, nothing breaks without it.
Two mitigations, both cheap. Put a context file in whatever template you use
to scaffold a new package, even if it starts as four lines. And add a check
that every directory under packages/ contains one:
#!/bin/bash
missing=0
for d in packages/*/; do
if [ ! -f "$d/AGENTS.md" ]; then
echo "missing: $d/AGENTS.md"
missing=1
fi
done
exit $missing
Six lines, runs in CI, and converts a silent degradation into a visible failure. That trade — making an invisible problem noisy — is the recurring theme of everything in this pillar.
Ownership
Package context files should be owned by the team that owns the package, and
enforced through the same mechanism — a CODEOWNERS entry
covering packages/*/AGENTS.md means changes route to the people
who will live with them.
The root file is a different matter. It affects every agent session in the repository, and it is exactly the kind of file that accumulates well-meaning additions until it is two hundred lines of nobody-remembers-why. Give it a named owner and a size budget, and treat a proposal to add ten lines as a proposal to slow down every session in the repo — because that is what it is.
Root file: the map, and rules that cross boundaries. Package files: everything concrete. Precedence follows proximity, so push exceptions downward rather than qualifying them upward. And verify your tooling actually reads the nested files before trusting them.