SSD Nodes Learn Hosting plans →
指南 Matt Connor作者: Matt Connor · 更新于 2026-08-22

单体仓库如何设计嵌套 AGENTS.md 文件

根目录 AGENTS.md 容易过时并浪费上下文。本文给出按服务拆分的嵌套布局,涵盖 200 行建议、32 KiB 限制及规则冲突问题。

单体仓库中的嵌套 AGENTS.md 含义

单体仓库中的嵌套 AGENTS.md,指的是在仓库根目录放置一个简短文件,并在每个服务目录中再放置一个文件。根目录文件包含适用于整个仓库的少量规则,以及其他文件所在位置的索引。每个服务目录中的文件只包含该目录适用的命令和约定。代理编辑 services/worker/queue.py 时,会先读取根目录文件和 worker 文件,不会将任何上下文用于它不会修改的前端。

无需安装任何内容。AGENTS.md 是一种约定,上游项目对此有明确说明:

AGENTS.md 只是标准 Markdown 文件。标题可以任意使用;代理只会解析您提供的文本。

因此,值得认真掌握这项技术。该格式不会悄然发生变化。真正容易出问题的是文件位置和维护方式,而这两项都需要由您负责。

为什么一个大型根目录 AGENTS.md 会失效?

在包含 Web 应用、后台 worker 和 Terraform 目录的代码库根目录放置一个 600 行的 AGENTS.md,会导致 4 个问题。

它会逐渐过时,因为没有明确的维护者。 在 apps/web 中重命名测试脚本的工程师,实际修改的是 apps/web 下的文件。根目录中的 AGENTS.md 不在该变更中,因此没有审阅者会看到不一致。6 周后,文件仍在描述一个已经不存在的构建步骤,而导致问题的人也早已忘记了这次修改。

每个任务都会消耗上下文。 这些文件会在会话开始时加载,此时代理还不知道您要提出什么请求。Claude Code 文档给出了具体建议:“每个 CLAUDE.md 文件控制在 200 行以内。文件越长,消耗的上下文越多,遵循程度越低。” Codex 在指令文件的总大小达到 32 KiB(默认值为 project_doc_max_bytes)后会停止合并文件。根目录文件记录 4 个服务的信息,因此每个任务都会为其中另外 3 个服务消耗上下文预算。

指令会开始相互矛盾。 Web 目录需要 pnpm test。worker 需要 pytest -q。将这些规则写入同一个文件后,每条规则只在部分场景中有效,代理必须猜测当前适用哪一条。Claude Code 文档说明了结果:“如果两条规则相互矛盾,Claude 可能会任意选择其中一条。” 为每个目录单独设置文件可以消除这种猜测,因为上下文中始终只会出现两条规则中的一条。当您确定自己已经清楚写出规则,但规则仍被跳过时,排查指令始终未生效的原因比第 4 次重写措辞更有效。

它会充满代理可以从代码中读取的事实。 例如目录树、依赖项列表,以及每个软件包的功能摘要。Claude Code 的 /doctor 检查正是为了清除这些内容。该检查会“删除 Claude 可以从代码库推导出的内容,例如目录布局、依赖项列表和架构概览”,并保留“工具默认行为不同的陷阱、原因和约定”。这是我所知道的判断某一行是否应写入文件的最佳标准。

代理会读取根目录文件,还是只读取最近的文件?

这是最容易被误解的地方。因此,与其转述,不如直接引用上游约定:

在每个 package 中放置一个 AGENTS.md。代理会自动读取目录树中最近的文件,因此最近的文件优先,每个子项目都可以提供定制化说明。

关于冲突:

距离被编辑文件最近的 AGENTS.md 优先;明确的用户聊天提示会覆盖所有其他内容。

对很多人来说,“优先”意味着“根目录文件会被忽略”。事实并非如此。在实现这一约定的工具中,会读取从仓库根目录到工作目录路径上的每个文件,并将其合并。只有当两个文件对同一主题给出不同说明时,最近的文件才会优先。

Codex 明确说明了这一机制:“Codex 会从根目录开始拼接文件,并使用空行连接。距离当前目录更近的文件会覆盖较早的指导。”Claude Code 也会沿相同路径查找其对应的文件名。工作目录上方目录层级中的文件“会在启动时完整加载”,并且“所有发现的文件都会拼接到上下文中,而不是相互覆盖”。工作目录下方的目录则不同:Claude Code 会在需要时加载这些文件,即“当 Claude 读取这些目录中的文件时”。

这会带来两个实际结果。根目录文件会成为仓库中每个会话的前缀,因此其中的每一行都应视为每周要付出一百次成本的内容。代理在其他位置工作时,不会产生目录级文件的成本。因此,详细说明适合放在对应目录的文件中。

本文行为已根据 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、consumer 必须保持幂等的原因,以及测试通过前必须运行的迁移。infra 文件用于编写防止代理执行破坏性操作的规则。绝不要运行 terraform apply。只运行 terraform plan,然后停止,并写明已配置的状态后端,避免代理尝试初始化新的状态后端。

请注意,这些文件中都没有包含每个服务用途的说明。这部分内容应由人类维护。上游项目也作出了相同区分,说明“README.md 文件面向人类:快速入门、项目说明和贡献指南”,而 AGENTS.md 包含“编码代理所需的额外信息,有时还包括详细上下文:构建步骤、测试和约定”。AGENTS.md 与面向人类的 README 之间的划分逐句说明了这一边界;记录代码设计原因的 DESIGN.md介绍了第 3 个文件,即用于解释设计决策而非记录命令的文件。

代码发生变化时,谁来更新文件?

只需要一条规则,并将其写入根目录文件:谁修改了某个目录中的代码,谁就必须在同一个提交中更新该目录的 AGENTS.md。

这条规则依靠的是机械机制,而不是团队文化。目录级文件会与代码出现在同一个差异中,因此拉取请求的审阅者可以同时看到两者。根目录文件属于所有人,也就意味着没有人真正负责;而且它通常不会出现在任何人正在查看的差异中。

在拉取请求上增加检查来落实这条规则。该检查会查找每个已修改文件上方最近的 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")"
done
apps/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

文档日期比代码日期早六个月,并不能证明文件内容错误。它只能告诉您应先阅读哪个文件;对于一项只需 1 秒的检查来说,这就足够了。

查找已经不存在的路径。 文档腐化有一种非常典型的方式:继续描述已经删除的代码。这些文件中的每个路径都用反引号括起,因此很容易提取并检查。

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,因为它们都包含斜杠,而且都不是磁盘上的文件。

会话中的症状。 agent 读取该文件后,按文件指示尝试打开 src/api/client.ts,但工具返回:

No such file or directory

于是它会采取合理的做法,编写自己的 fetch wrapper。这就是文件过时的实际代价。agent 并不是忽略您的文档,而是遵循文档,访问一个三个月前已删除的路径,然后重新编写您已经拥有的代码。使用 Ponytail,它会让 agent 遵循能够奏效的最小改动 之类的 skill,可以减少这种重写倾向,但它无法找到文件错误指向的 helper。

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.md

成功时,ln 不会输出任何内容,因此请检查目录列表: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,并在拉取请求中审核。任何克隆该仓库的人看到的内容都相同。agent memory 由 agent 编写,存储在仓库之外,并且只对一台机器有效。Claude Code 文档也作了相同区分:CLAUDE.md 保存由你编写的“Instructions and rules”,自动记忆保存由 Claude 编写的“Learnings and patterns”,而 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。将文件放在代码旁边可以使这条规则真正落实,因为修改会进入人工已经在查看的同一个拉取请求差异中。添加 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 中。代理可以直接从代码中读取的事实,例如目录树或依赖项列表,不应放在这两者中的任何一个文件中。