Precedence for Nested AGENTS.md: A Merge Design for Many Projects in One Workspace
Put several projects in one workspace, each with its own AGENTS.md, and which instruction the agent follows turns ambiguous. Root and per-project rules quietly collide; one wins, or both blend. Taking 'closer is stronger' as the base rule, this designs a merge that distinguishes overriding from appending, with working Python and field notes.
Keep several projects in one workspace and a day comes when the agent's behavior turns suddenly unreadable. The root AGENTS.md says "commit messages in English," yet one project keeps writing them in Japanese. Look closer and that project has its own AGENTS.md, carrying a different instruction.
Nested instruction files are a natural idea in themselves: shared promises at the root of the workspace, project-specific promises inside each. The trouble is that when the two collide, "which one wins" is defined nowhere. The agent interprets the unwritten precedence loosely, anew each time.
What this designs is the merge rule that removes that "loosely." Several AGENTS.md files, bound into one in a fixed order, by a fixed method.
What happens with several AGENTS.md in one workspace
First, look concretely at where the ambiguity comes from.
I keep Dolice Labs' several sites bound into one workspace myself. The root holds promises shared by all sites — "Japanese in polite form," "no bare URLs" — while each site's folder holds that site's specifics: category names, build steps.
Three patterns emerge here. Only the shared rule takes effect and the specific one is ignored. Only the specific rule takes effect and the shared one falls away. Or the two blend halfway into a result matching neither. Which one happens shifts with how the agent reads it at that moment. Nothing is worse than a bug that won't reproduce.
Make "closer is stronger" the base rule
To cut this ambiguity, place one clear principle. The AGENTS.md closer to the file being edited has the stronger instruction. That's all.
workspace/├── AGENTS.md ← weak (shared rules for everything)├── claudelab/│ ├── AGENTS.md ← medium (shared within this site)│ └── articles/│ └── AGENTS.md ← strong (specific to this area)└── rorklab/ └── AGENTS.md
Level
Role
Priority
Workspace root
Promises shared by all projects
Low (foundation)
Project root
Specifics shared within the project
Medium
Subfolder
Rules specific to that area
High (final say)
Just sharing the single sentence "closer is stronger" makes "which instruction finally takes effect when editing this file" uniquely determined. Ambiguity is born from the absence of a rule. Even one line, once written down, dissolves it.
✦
Thank you for reading this far.
Continue Reading
What follows includes implementation code, benchmarks, and practical content we hope you'll find useful. This site runs without ads — server and development costs are supported entirely by members like you. If it's been helpful, we'd be truly grateful for your support.
WHAT YOU'LL LEARN
✦A breakdown of how root and per-project AGENTS.md files collide into unreadable behavior, and how to set precedence with 'closer is stronger' as the base rule, paired with a scope table
✦Instead of deciding vaguely in prose, a merge that combines multiple AGENTS.md section by section, distinguishing override from append, given as drop-in Python
✦Guidance on splitting shared rules into the root and specific rules into each project, plus keeping AGENTS.md thin, drawn from binding several sites into one workspace as an indie developer
Secure payment via Stripe · Cancel anytime
✦
Unlock This Article
Get full access to the rest of this article. Buy once, read anytime. This site is ad-free — your support goes directly toward keeping it running.
Once the principle is set, lower it into the process that actually binds the files. Collect AGENTS.md from the edit target toward the root, and stack them weakest first.
from pathlib import Pathdef collect_agents_files(target: Path, root: Path) -> list[Path]: """Collect AGENTS.md from target toward root, return weakest first (root first)""" files = [] cur = target if target.is_dir() else target.parent while True: candidate = cur / "AGENTS.md" if candidate.exists(): files.append(candidate) if cur == root: break cur = cur.parent return list(reversed(files)) # root -> near = weak -> strong
The reversed line is the point. You collect in the "near -> root" direction, but stack in "root -> near." Lay the weak as the foundation and pile the strong on top afterward, so the closer instruction wins last.
Distinguish overriding from appending
Stacking alone is not enough, because instructions come in two kinds. Ones that should settle to a single value ("commit in English" — override) and ones that should add up ("a forbidden-word list" — append). Read AGENTS.md section by section and merge each kind differently.
import re# Separate sections to append (merge) from sections to override (replace)APPEND_SECTIONS = {"Forbidden words", "Prohibitions", "Glossary", "Caveats"}def parse_sections(text: str) -> dict[str, str]: sections, cur, buf = {}, None, [] for line in text.splitlines(): m = re.match(r"^#{1,3}\s+(.*)", line) if m: if cur is not None: sections[cur] = "\n".join(buf).strip() cur, buf = m.group(1).strip(), [] else: buf.append(line) if cur is not None: sections[cur] = "\n".join(buf).strip() return sectionsdef merge(files: list[Path]) -> dict[str, str]: merged: dict[str, str] = {} for f in files: # process weakest first for name, body in parse_sections(f.read_text(encoding="utf-8")).items(): if name in APPEND_SECTIONS and name in merged: merged[name] += "\n" + body # add up else: merged[name] = body # closer overrides return merged
Mistake override for append and you stumble in a quiet way. Treat a forbidden-word list as override and, meaning to add a project-specific word, you wipe the shared list whole. Treat the commit language as append and both English and Japanese survive as instructions, leaving the agent to waver. Sorting by the nature of the section is the design that holds up in production.
Shared rules at the root, specific rules per project
With the merge mechanism settled, the judgment of "what goes where" arrives next. What I recommend, when unsure, is keeping the shared side thin and the specific side thick.
Put at the root only what every project should keep identically.
Push any rule with even one exception down from the root into each project.
Don't park "probably shared" in the shared file; promote it only once a second project genuinely needs the same instruction.
There's a reason for this lean. A root rule reaches every project, so a mistake there has wide blast radius. Write an App Store asset-naming convention at the root as "probably shared" and an unrelated constraint rains down even on a project that only handles AdMob reports. Promoting to shared can wait until the second need is confirmed.
Field notes: keep AGENTS.md thin
One last impression reached by running this. AGENTS.md grows fat if left alone.
Each time something goes wrong you want to add a line, and before long every file is enormous. Long instruction files raise the number of collisions when merged and make precedence hard to trace. I make a point of regularly rereading each AGENTS.md and trimming on "is this truly needed" and "does it duplicate the level above."
Closer is stronger, separate override from append, keep the shared side thin — hold just these three and the agent's behavior stays readable even with many projects bound into one workspace. Readable behavior is, when running unattended, the reassurance I value most.
Share
Thank You for Reading
Antigravity Lab is ad-free, supported entirely by members like you. We publish practical guides daily with implementation code, benchmarks, and production-ready patterns. If you've found it useful, we'd love to have you on board.