单体仓库如何设计嵌套 AGENTS.md 文件
根目录一个600行的 AGENTS.md 容易过时并浪费上下文。本文介绍按服务目录拆分的嵌套布局,并说明每个文件该放什么规则。
单体仓库中的嵌套 AGENTS.md 是什么
单体仓库中的嵌套 AGENTS.md,指的是在仓库根目录放置一个简短文件,并在每个服务目录中再放置一个文件。根目录文件包含适用于整个仓库的少量规则,以及其他文件所在位置的目录。每个服务文件只包含该目录使用的命令和约定。编辑 services/worker/queue.py 时,代理会读取根目录文件和 worker 文件,不会在它永远不会修改的前端上消耗上下文。
无需安装任何内容。AGENTS.md 是一种约定,上游项目对此有明确说明:
AGENTS.md 只是标准 Markdown 文件。您可以使用任意标题;代理只会解析您提供的文本。
因此,值得认真掌握这项技术。格式不会在使用过程中发生变化。真正容易出问题的是文件的位置和维护方式,而这两项都需要由您负责。
为什么一个大型根目录 AGENTS.md 会失效?
在同时包含 Web 应用、后台 worker 和 Terraform 目录的代码仓库中,根目录下单个 600 行的 AGENTS.md 会在四个方面失效。
它会过时,因为没有人负责维护。 在 apps/web 中重命名测试脚本的工程师,实际修改的是 apps/web 下的文件。根目录 AGENTS.md 不在该变更中,因此审阅者看不到不一致之处。六周后,该文件仍在描述一个已经不存在的构建步骤,而造成问题的人也早已忘记了这次修改。
每项任务都会消耗上下文。 这些文件会在会话开始时加载,此时 agent 还不知道您要提出什么请求。Claude Code 的文档给出了具体限制:“每个 CLAUDE.md 文件应控制在 200 行以内。文件越长,消耗的上下文越多,遵循程度也越低。”当指令文件的总大小达到 32 KiB(默认值为 project_doc_max_bytes)时,Codex 会停止继续合并这些文件。记录四个服务的根目录文件,会在每项任务中都为其中三个服务消耗上下文预算。
指令会开始相互矛盾。 Web 目录要求使用 pnpm test。worker 要求使用 pytest -q。将这些规则写入同一个文件后,每条规则只在部分情况下正确,因此 agent 必须猜测当前适用哪条规则。Claude Code 文档描述了这种结果:“如果两条规则相互矛盾,Claude 可能会任意选择其中一条。”按目录拆分文件可以消除这种猜测,因为上下文中始终只会出现两条规则中的一条。
文件会被代码中已经包含的信息填满。 例如目录树、依赖列表,以及各个 package 功能的摘要。Claude Code 的 /doctor 检查正是为了删除这类内容。它会“删除 Claude 可从代码库推导出的内容,例如目录布局、依赖列表和架构概览”,并保留“陷阱、设计依据,以及与工具默认行为不同的约定”。这是我所知道的判断某一行内容是否应写入该文件的最佳标准。
代理会读取 root 文件,还是只读取最近的文件?
这里最容易被误解,因此值得引用上游约定,而不是对其进行转述:
在每个软件包中放置另一个 AGENTS.md。代理会自动读取目录树中最近的文件,因此最近的文件优先,每个子项目都可以提供针对性的说明。
关于冲突:
距离被编辑文件最近的 AGENTS.md 生效;明确的用户聊天提示优先级最高。
“优先”常被理解为“忽略 root 文件”。事实并非如此。在实现这一约定的工具中,从仓库 root 到工作目录路径上的每个文件都会被读取,并拼接到一起。只有当多个文件对同一事项给出不同说明时,最近的文件才优先。
Codex 明确说明了这一机制:“Codex 会从 root 开始拼接文件,并使用空行连接它们。距离当前目录越近的文件,会覆盖较早的指导。”Claude Code 也会为自己的文件名遍历相同的路径。工作目录上方目录层级中的文件“会在启动时完整加载”,并且“所有发现的文件都会拼接到上下文中,而不是互相覆盖”。工作目录下方的目录则不同:Claude Code 会按需加载其中的文件,即“当 Claude 读取这些目录中的文件时”加载。
由此可得出两个实际结论。root 文件是仓库中每次会话的前缀,因此其中每一行都应视为每周要重复支付一百次成本的内容。代理在其他位置工作时,不会产生每目录文件的成本,因此详细说明适合放在那里。
此行为已于 2026 年 8 月根据 Codex 和 Claude Code 文档进行核对。不同工具的实现略有差异,而且规则也会变化,因此请确认团队所使用代理的加载规则。
包含 3 个服务的仓库布局示例
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 scripts根目录文件有意保持简短。它说明应查找的位置,并且只包含适用于所有目录的规则。
# 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.每个目录的文件用于记录具体细节,其长度可以根据目录的实际需要确定。
# 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.worker 文件的结构相同,但内容不同:安装命令、pytest -q、消费者必须保持幂等的原因,以及测试通过前必须运行的迁移。infra 文件用于记录防止代理执行破坏性操作的规则。绝不要运行 terraform apply。只运行 terraform plan,然后停止;同时注明已经配置好的状态后端,避免代理尝试初始化新的后端。
请注意,这些文件都没有描述各个服务的用途。这部分内容属于面向人的文档。上游项目也明确区分了两者:“README.md 文件面向人:快速入门、项目说明和贡献指南”,而 AGENTS.md 则包含“编码代理所需的额外信息,有时还包括详细上下文:构建步骤、测试和约定”。AGENTS.md 与面向人的 README 之间的划分逐句说明了这条边界;记录代码结构原因的 DESIGN.md介绍了第三类文件,即用于解释设计决策而不是记录命令的文件。
代码发生变化时,谁负责更新文件?
根目录文件中只需写明一条规则:谁修改了某个目录中的代码,谁就必须在同一个提交中更新该目录的 AGENTS.md。
这条规则之所以有效,是因为它依赖机械机制,而不是团队文化。目录级文件会与代码出现在同一个 diff 中,因此 pull request 审阅者可以同时看到两者。根目录文件属于所有人,也就意味着实际上无人负责,而且它永远不会出现在任何人正在阅读的 diff 中。
在 pull request 上增加检查来落实这条规则。检查会为每个发生变化的文件查找其上级目录中最近的 AGENTS.md,然后报告该文件未被修改的情况。
#!/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"
done如果某个分支重构了 API 客户端,但没有修改文档,输出如下:
note: apps/web/src/api/client.ts changed but apps/web/AGENTS.md was not updated将其设为警告,而不是失败。硬性门禁会让人们只为让 CI 变绿而在文件中添加一个空行;为了满足机器人而修改的文件,价值甚至不如没有文件。警告会给审阅者提供一个需要追问的问题,而这才是实际有效的部分。
如何识别已过时的 AGENTS.md?
今天可以运行两项检查,还可以在会话中观察到一种症状。
比较每个文件的时间与其所描述代码的时间。 %cs 会将提交日期输出为 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-01文档日期比代码日期早六个月,并不能证明文件内容有误。它只能告诉您应先阅读哪个文件;对于只需一秒即可完成的检查来说,这已经足够。
查找已不存在的路径。 文档过时有一种非常具体的表现:它继续描述已经删除的代码。这些文件中的每个路径都写在反引号中,因此很容易提取并测试。
grep -o '`[^`]*`' apps/web/AGENTS.md | tr -d '`' | grep '/' | while read -r p; do
[ -e "$p" ] || [ -e "apps/web/$p" ] || echo "missing: $p"
done请直接阅读输出,不要将此检查接入 CI。它还会标记 src/**/*.ts 这类 glob,以及您引用的任何 URL,因为两者都包含斜杠,而且都不是磁盘上的文件。
会话中的症状。 代理读取该文件,按照文件指示尝试打开 src/api/client.ts,然后工具返回:
No such file or directory因此,代理会采取合理的做法,编写自己的 fetch 包装器。这就是文件过时的实际代价。代理并没有忽略您的文档。它遵循文档,访问了三个月前已删除的路径,然后重新编写了您已经拥有的代码。使用 Ponytail,它会让代理坚持采用能够解决问题的最小改动 之类的 skill,可以减少这种重复编写的倾向,但如果文件指向了错误位置,它仍然无法找到那个辅助程序。
Claude Code 会读取 AGENTS.md 文件吗?
不会。需要明确这一点,因为嵌套布局依赖于此。截至 2026 年 8 月,文档说明:“Claude Code 读取 CLAUDE.md,而不是 AGENTS.md。”这种模式仍然可用,只需在每个 AGENTS.md 旁边放置一个 CLAUDE.md。
如果要在共享规则之上添加工具专用配置,应使用导入方式。在 services/worker/CLAUDE.md 中写入:
@AGENTS.md
## Claude Code
Use plan mode for changes under `services/worker/migrations/`.如果没有工具专用配置需要添加,应使用符号链接方式。
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 成功时不输出任何内容,因此请检查目录列表:apps/web/CLAUDE.md -> AGENTS.md。然后启动会话并运行 /context,加载的文件会显示在 Memory files 下。在 Windows 上,创建符号链接需要 Administrator 权限或启用 Developer Mode,因此请改用 @AGENTS.md 导入方式。
还有一个容易踩坑的地方。执行 /compact 后,根目录文件会从磁盘重新读取,但子目录中的嵌套文件不会重新注入。下次代理读取该目录中的文件时,这些文件才会重新加载。如果某个目录规则在长会话中途似乎不再生效,通常就是这个原因;访问该目录中的任意文件即可使规则恢复。
将其他代理指向 AGENTS.md 的设置
Codex 原生读取 AGENTS.md。在每一层目录中,它会先检查 AGENTS.override.md,这样无需修改共享文件即可为某个目录设置本地覆盖规则。合并内容达到 32 KiB(默认的 project_doc_max_bytes)后,它会停止继续合并,因此也应尽量缩小根目录文件。
Aider 通过 .aider.conf.yml 使用该文件,配置行是 read: AGENTS.md。
Gemini CLI 通过 .gemini/settings.json 使用该文件,配置项是 { "context": { "fileName": "AGENTS.md" } }。
上游文档说明,对于仍使用旧单数名称的仓库,可以进行向后兼容的重命名:mv AGENT.md AGENTS.md && ln -s AGENTS.md AGENT.md。
在非常大的 monorepo 中,Claude Code 的 claudeMdExcludes 设置可以按路径或 glob 跳过祖先目录中的文件。当其他团队的目录位于您的目录上方时,这一设置很有用。
这与 agent memory 或 skill 有什么不同?
这些机制看起来相似,但失效方式完全不同。因此,需要明确你要使用的是哪一种。
AGENTS.md 由您编写,提交到 git,在 pull request 中审查,并且对所有克隆该仓库的人都相同。agent memory 由 agent 编写,存储在仓库之外,并且只对一台机器有效。Claude Code 文档也作出了相同区分:CLAUDE.md 保存由您编写的“指令和规则”,自动 memory 保存由 Claude 编写的“学习内容和模式”,而 memory 目录不会在不同机器之间共享。判断方法很简单:如果某个事实必须对刚刚完成克隆的同事也成立,就不能将它放在 memory 中。agent memory 如何在会话之间持久保存介绍了这一部分。
skill 是第三种机制。AGENTS.md 是每次会话都会加载的上下文;skill 是需要时才加载的操作流程。Claude Code 文档给出了一个实用规则:“如果某个条目是多步骤操作流程,或者只与代码库的某一部分有关,请将其移到 skill 或路径范围规则中。”这句话的后半部分正是嵌套 AGENTS.md 要解决的问题。前半部分则是 agent skills 的用途;如果同一操作流程需要用于多个仓库,应当 在多个仓库之间共享 skill,而不是将相同段落分别粘贴到十个不同的 AGENTS.md 文件中。
上游指出:“截至撰写本文时,OpenAI 主仓库中有 88 个 AGENTS.md 文件。”这个数字本身就说明了问题。大型仓库不需要更大的文件,而需要更多小文件。每个文件都应放在其所描述的代码旁边,并由最后修改这些代码的人负责维护。
FAQ
嵌套的 AGENTS.md 会替换根目录文件,还是在其基础上追加?
会在其基础上追加。上游文档所说的“最近的文件优先”,描述的是发生冲突时的处理方式,而不是加载方式。Codex 会“从根目录向下连接文件,并用空行分隔”;Claude Code 则会从工作目录开始向上遍历,将找到的每个文件连接起来,而不是用嵌套文件覆盖它们。只有当两个文件针对同一主题给出不同指令时,最近的文件才会生效。将共享规则只写在根目录文件中,不要在每个目录重复这些规则。
根目录的 AGENTS.md 应该多大?
应小到这样的程度:即使它会被粘贴到你在该仓库中发出的每个请求前面,你也不会介意,因为实际情况就是如此。Claude Code 文档建议将每个文件控制在 200 行以内,并警告较长的文件会“降低遵循度”。Codex 默认会在合并后的指令文件达到 32 KiB 时停止继续合并。如果根目录文件记录了四个服务的说明,那么其中大部分内容对任何单项任务都是无关信息。将详细内容下移到各目录的文件中,并在根目录文件中保留索引。
如何防止这些文件逐渐过时?
在根目录文件中加入一条规则:任何人在某个目录中修改代码时,都必须在同一个提交中更新该目录的 AGENTS.md。将文件放在代码旁边可以让这条规则真正执行,因为修改会进入人工已经在查看的同一个 pull request diff。添加 CI 警告,将每个变更路径映射到其上方最近的 AGENTS.md;并定期比较每个文件中的 git log -1 --format=%cs 与在该文件所说明的目录上运行同一命令得到的结果。
Claude Code 会读取 AGENTS.md 文件吗?
不会。截至 August 2026,文档明确写道:“Claude Code 读取 CLAUDE.md,而不是 AGENTS.md。”在同一目录中创建一个 CLAUDE.md,并在第一行写入 @AGENTS.md;这样即可加载共享文件,并在其下方添加 Claude 专用指令。如果没有额外内容需要添加,使用 ln -s AGENTS.md CLAUDE.md 创建符号链接也可以;但在 Windows 上需要 Administrator 权限或启用 Developer Mode。在会话中运行 /context,并确认该文件出现在 Memory files 下。
只在某些情况下适用的规则应该放在哪里?
不要放在 AGENTS.md 中。该文件会在每个会话中加载,因此其中每一行都会与实际请求争夺注意力。偶尔才需要的多步骤流程应放在 skill 中,按需加载。只适用于某个目录的规则应放在该目录的 AGENTS.md 中。代理可以直接从代码中读取的事实,例如目录树或依赖项列表,不应放在这两类文件中。