# AGENTS.md 怎么写：让 Agent 读懂项目

> AGENTS.md 是给编程 Agent 看的项目说明书。Claude Code 自 v2.1.277 起才直接读它；仓库里已有项目自有 CLAUDE.md 时，默认仍然不读。

- 发布于: 2026-10-13
- 原文: https://shanyue.tech/posts/agents-md-guide/
- 标签: ai, agents-md, claude-code, codex, ai-engineering

---

`AGENTS.md` 是给编程 `Agent` 看的项目说明书。人读 `README.md`，看怎么 clone、怎么跑起来；`Agent` 每次会话都从空上下文开始，它需要另一份东西：这个仓库的约定、坑，以及「做完长什么样」。[agents.md](https://agents.md/) 把这件事说成 *README for agents*，由 [Agentic AI Foundation](https://aaif.io) 托管，没有强制字段，纯 Markdown。

我以前写这份文件，主要是给 `Codex`、`Cursor` 用的。`Claude Code` 这边长期更认 `CLAUDE.md`，对 `AGENTS.md` 的支持很弱，常见做法是在 `CLAUDE.md` 里 `@AGENTS.md` 导入，或者直接软链过去。

这个局面在 **2026 年 9 月 18 日的 v2.1.277** 变了：原生读取由内置 `mod` `cc-plugin-agents-md`（源码在 `mods/agents-md`）接上。实现细节我写在 [用 mods 改写 Claude Code 的工具调用](https://shanyue.tech/posts/claude-code-mods/) 里，官方说明在 [How Claude remembers your project](https://code.claude.com/docs/en/memory)。

写这份说明书，和写给 `Agent` 读的 skill 是同一套手感。Matt 的 `writing-for-agents` 就是干这个的：创建或编辑 skill，或改 `AGENTS.md` / `CLAUDE.md`。这篇长文的结构也借了它：环境里查得到的不重复、分层用指针、规则先写目标行为，后面几节会落到具体句子上。

![AGENTS.md 总览：新会话上下文从零开始，读入 AGENTS.md 这份开放约定，Claude、Codex、Cursor 各按自己的规则加载，再按约定改代码，少试错、省 token](https://shanyue.tech/images/26-10-13/agents-md-overview.webp)

图里灰色是一次会话怎么开始，紫色是指令文件，橙色是各工具自己的加载规则，绿色是你让 `Agent` 去做的事。后面几张图沿用这套颜色：绿是你写的，红是做决定或检查，灰是事件来源，紫是指令和上下文，橙是工具规则或传递执行。

## 一、各工具怎么读这份文件

开放约定只规定文件名和「冲突时离编辑文件最近的优先」，各工具的加载规则并不一样。协作时把差异写清楚，比假装有一份统一行为更省事。

| | `Claude Code`（默认） | `Codex` | `Cursor` |
| --- | --- | --- | --- |
| 主文件 | 无项目自有 `CLAUDE.md` 时读 `AGENTS.md` | 每层 `AGENTS.md` | 根目录和子目录的 `AGENTS.md` |
| 覆盖文件 | **不认** `AGENTS.override.md` / `AGENTS.local.md` | 每层先 `AGENTS.override.md` 再 `AGENTS.md` | 以 [Rules 文档](https://cursor.com/docs/rules) 为准 |
| 多层怎么合 | 从 cwd 沿祖先目录加载一路上的 `AGENTS.md`；`Read` 子路径时再补该层 | 从 git 根合并到 cwd，**越近越优先** | 子目录与父目录合并，更具体的优先 |
| 体积 | 文档未给单独字节上限 | 合计默认 **32 KiB** | 文档未给字节上限 |

`Codex` 的规则写在 [Custom instructions with AGENTS.md](https://learn.chatgpt.com/docs/agent-configuration/agents-md)。它启动时建一条指令链：先读 `~/.codex` 里的全局文件（有 `AGENTS.override.md` 就用它，否则用 `AGENTS.md`），再从 git 仓库根往当前工作目录走，每层同样先 override 再普通文件，最后用空行拼起来。越靠近 cwd 的内容出现得越晚，覆盖前面的指导。空文件跳过，拼到 `project_doc_max_bytes`（默认 32 KiB）就停。

`Cursor` 把 `AGENTS.md` 写成 `.cursor/rules` 的简单替代：纯 Markdown，没有 `alwaysApply` / `globs` 那套 frontmatter。官方文档明确支持根目录和子目录，子目录的说明会和父目录合并，更具体的优先。需要按文件类型精确挂上规则时，仍然用 `.cursor/rules` 里的 `.mdc`。各工具文件名不同，加载细节以官方为准；我没有逐行对过 `Cursor` 的源码。

![Codex 从全局 ~/.codex 拼到仓库根 AGENTS.md，再走到 cwd，越近越优先，合计默认 32 KiB 超限截断](https://shanyue.tech/images/26-10-13/agents-md-codex-merge.webp)

有一个链接容易点错：`code.claude.com/docs/en/agents.md` 讲的是并行 `agents`（同时跑多个子 agent），不是这份项目说明书。看 `AGENTS.md` 文件怎么加载，走 [memory 文档](https://code.claude.com/docs/en/memory) 里的 `AGENTS.md` 一节。

## 二、Claude Code 会不会读 AGENTS.md

会。以前不太会，现在会。分界就是 **v2.1.277（2026-09-18）**。

更早的版本里，项目指令几乎等于 `CLAUDE.md`。仓库里已经有一份给 `Codex`、`Cursor` 用的 `AGENTS.md` 时，要让 `Claude` 看见，得自己搭桥：`CLAUDE.md` 里写 `@AGENTS.md`，或者 `ln -s AGENTS.md CLAUDE.md`，再不然用 `SessionStart` hook 把文件打进上下文。这些办法现在仍然可用，但不再是唯一入口。

v2.1.277 起，读取由内置插件 `cc-plugin-agents-md`（源码目录 `mods/agents-md`）完成。它挂两个事件：`prompt.context` 在构建上下文时把找到的 `AGENTS.md` 交给引擎，当作项目指令；`tool.call` 挂在 `Read` 上，读到子目录文件时，把项目根到该文件之间、还没进过上下文的 `AGENTS.md` 补进来，做法接近引擎处理嵌套 `CLAUDE.md`。

这一层若已有 `CLAUDE.md`，就用它，不补 `AGENTS.md`。补层只发生在文本 `Read`：IDE 里打开的文件、prompt 里的 `@` 提及、图片和 PDF 的读取，都走引擎自己的嵌套 `CLAUDE.md` 逻辑。我在 [mods 那篇](https://shanyue.tech/posts/claude-code-mods/) 里读过这份源码，逻辑比多数教程短。

默认模式叫 `claude-md-or-agents-md`。规则是一条决策，不是「两份都读」。

查找范围是 cwd 及其所有祖先目录，一直到文件系统根，可以高过 git 仓库根。这一路上出现下面任意一份，这个项目就交给引擎读 `CLAUDE.md`，`AGENTS.md` 整份跳过：

- `CLAUDE.md`
- `.claude/CLAUDE.md`
- `CLAUDE.local.md`

下面这些**不算**「项目自有」，挡不住 `AGENTS.md`：

- 用户级 `~/.claude/CLAUDE.md`
- 组织托管的 `CLAUDE.md`
- `.claude/rules/` 下的规则文件
- 额外 `--add-dir` 目录里的 `CLAUDE.md`

一路上都没有项目自有 `CLAUDE.md` 时，加载这一路上的 `AGENTS.md` 和 `.claude/AGENTS.md`。所以仓库外的父目录里放着一份 `CLAUDE.md`，同样会让 `AGENTS.md` 失效，排查时记得往上看。

![Claude Code 默认模式：构建上下文时从工作目录沿祖先目录查找项目自有 CLAUDE.md，用户级 ~/.claude 与托管文件不算；一份都没有才加载 AGENTS.md，有则只读 CLAUDE.md](https://shanyue.tech/images/26-10-13/agents-md-claude-mode.webp)

怎么确认加载了：memory 文档写交互会话的对话里会出现 `no CLAUDE.md found; AGENTS.md loaded: …` 这一行；mod README 写的是这行只进 debug log，转录里没有。两处说法不同，对话里看不到这行，不代表没加载。最直接的办法是让模型复述项目指令，或者打开 debug log 看。

仓库内的 `@path` 导入会展开，每条跟在文件后面，和 `CLAUDE.md` 的导入一样。指向仓库外的路径，要先通过引擎的「外部导入」批准；那个批准对话框只为 `CLAUDE.md` 的导入弹出，没批准过时，`AGENTS.md` 里的外部导入会被静默丢掉。

`Claude Code` 当作项目指令加载的只有上面这几份；`AGENTS.override.md`、`AGENTS.local.md`，以及 `.agents/` 下的文件，都不在其中（skill 发现是另一条路径，和这里无关）。

这一点和 `Codex` 正好相反：`Codex` 把 override 当作正式覆盖层。团队如果学环境变量的思路，搞一份共享 `AGENTS.md` 再加个人 `AGENTS.local.md`，在 `Codex` 里说得通；放到 `Claude Code` 里，那份 local 不会进上下文。

更麻烦的是：项目里若为了个人偏好加了 `CLAUDE.local.md`，它算「项目自有」，默认模式下会把 `AGENTS.md` 整份挡住。

要两份一起读，把 **Project instructions** 改成 `claude-md-and-agents-md`。主路径是 `/config` 里改。

也可以在 settings 里给插件配 `instructionFiles`：v2.1.285 起键是 `cc-plugin-agents-md@builtin`，更早的版本用 `agents-md@builtin`（旧版会忽略新键）。这个选项只认用户 settings、`--settings` 和托管 settings，项目级 `.claude/settings.json` 里写了不生效。

还要注意版本窗口：v2.1.277 之前的会话只读 `CLAUDE.md`；v2.1.277 的发行说明不含 Bedrock、Vertex、Foundry，这类会话要到 v2.1.281 才能加载；用 `/plugin` 关掉这个内置插件，行为同样退回去。

升级后的第一场会话有时也还没接上，下一场才读 `AGENTS.md`。这些情况下，`CLAUDE.md` 里留一行 `@AGENTS.md` 仍然是最稳的桥。

## 三、可以嵌套：子目录各放一份

`AGENTS.md` 可以放在子目录里，不必全堆在仓库根。`Agent` 沿目录树往上找，父级那份仍然在上下文里，冲突时离当前编辑文件更近的优先；兄弟目录那份不会读进来。这和前端 `import` 时就近解析 `node_modules` 是一个道理。

![嵌套 AGENTS.md：正在改前端文件时沿目录树向上找，更近的优先，父级仍在，兄弟目录的后端那份不读；Claude 用 Read 打开子路径时会再补该层](https://shanyue.tech/images/26-10-13/agents-md-nested.webp)

大 monorepo 尤其明显。根目录放仓库级约定（怎么发 PR、密钥怎么处理），`apps/web` 放前端约定，`apps/api` 放后端约定。你在前端改按钮样式时，不必把后端那套测试矩阵也读进来，省的是 token，贴的是上下文。

`Claude Code` 在会话开始时加载的是「根到 cwd」这一路上的文件；真正改到子目录里的文件时，`Read` 还会把那一层的 `AGENTS.md` 再补进来（该层已有 `CLAUDE.md` 时用它）。所以「嵌套」有两拍：启动时按工作目录走一遍，干活时按打开的文件再补一拍。

内容上也值得嵌套。`writing-for-agents` 把这叫 **progressive disclosure**：主文件只留每条路径都要用的步骤，分支细节放到指针后面，触发时再加载。目录树上的嵌套（子目录各一份）和内容上的嵌套（`AGENTS.md` 里一行指向 `docs/frontend-conventions.md`）是同一件事的两个方向。OpenAI 自己的主仓库在约定站点举例时，就有 88 份嵌套的 `AGENTS.md`。

Skill 里的 `references/`、`rules/` 也是这个结构：先靠 description 决定要不要打开，打开后再按需读某一条。把所有最佳实践塞进根目录一份 `AGENTS.md`，注意力会稀薄——那是 sprawl，不是完整。

## 四、写什么：环境里有的不必再写，少写否定式

`writing-for-agents` 有一句我每次写 `AGENTS.md` 都会拿出来用：**环境也是 source of truth。** `package.json` 的 `scripts`、锁文件、目录布局、命令的 `--help`，`Agent` 自己能查。你再抄一遍，得到的是一份会过期的缓存。技术栈写在 `package.json` 里，`AGENTS.md` 里就不必再列「本项目用 Astro、Tailwind、pnpm」。

该缓存的是 `Agent` 查不到的东西：没写进配置的约定、某个选择背后的原因、配置文件不肯坦白的坑。查找昂贵时才值得缓存；一文件、一命令能查到的，留给环境。

![环境是真相：package.json 的脚本和布局 Agent 自己能查到就留给环境；查不到的约定、原因、坑才写入 AGENTS.md](https://shanyue.tech/images/26-10-13/agents-md-env-truth.webp)

另一条是 **少写否定式**。否定会把被禁的行为拖进上下文，让它更可用，而不是更不可用。*Don't think of an elephant*，脑子里只剩大象。写「用 `pnpm` 安装依赖」，比写「不要用 npm」干净：目标行为直接出现，被禁的那个甚至不必开口。

红线有时必须否定——「密钥禁止提交」这类，正面改写会发虚。即便如此，否定后面要紧跟肯定目标：「密钥禁止提交；密钥放环境变量或本地未跟踪文件。」注意力落在「做什么」上。

![少写否定式的两条路：平时直接写「用 pnpm 安装」；硬红线才写否定，并紧跟肯定目标，两条路最后都让上下文里留下目标行为](https://shanyue.tech/images/26-10-13/agents-md-positive.webp)

`writing-for-agents` 的 description 写得很短：*Writing documents for agents. Use when creating or editing skills, or modifying AGENTS.md or CLAUDE.md.*

我写这篇时借了它的分层：先给总览指针（开头那张图），再按工具、嵌套、写法、模板、共存、自检分层；环境里的命令只在「`Agent` 查不到」时出现。前两节讲加载差异时用了不少「不读」「不认」，那是在描述工具事实，给 `Agent` 写规则时才用上面这套肯定式。没有可靠的个人主页链接，文中只称 skill 名。

还有几个杠杆，写 `AGENTS.md` 时够用就点到：

- **Context pointer**：主文件里一行指向外置文档，并写清什么情况下去读。指针的措辞决定会不会被打开，目标文件本身决定打开之后好不好用。
- **Completion criteria**：每一步写清「做完长什么样」。本站 `AGENTS.md` 里「完成的标志：文件在对应目录、文件名是 kebab-case、frontmatter 通过 schema、正文满足该集合 skill」就是一条可检查的完成条件。
- **Single source of truth**：同一条约定只放一处。根目录和 `apps/web` 都写一遍格式化命令，改的时候一定会漏。
- **No-op**：模型默认就会做的事，整句删掉。写「请认真完成任务」几乎改变不了行为。

## 五、一份能用的最小模板，以及本站 AGENTS.md 拆解

开放约定没有必填字段。我自己的起步文件大概长这样，刻意不把 `package.json` 里已有的脚本再抄一遍：

```markdown
# AGENTS.md

先选定一个内容集合，再按该集合的写法写完一篇。
完成的标志：文件在对应目录、文件名是 kebab-case、frontmatter 通过 schema。

## 集合

- 站点长文：`src/content/blog/`。写法见 `.agents/skills/blog-article/SKILL.md`。
- 公众号短图文：`src/content/wechat/`。写法见对应 skill。

## 约定

- 安装依赖用 `pnpm`。
- 正文从导语或 `##` 开始，标题由 frontmatter 的 `title` 渲染。
- 配图放 `public/images/YY-MM-DD/`，文中用根路径引用，统一 WebP。
```

模板求短。第一节用完成条件把「写完」钉死；第二节用指针把分支推到 skill 里，根文件保持短；第三节只写环境不肯说的约定。

![最小模板的三段：完成条件把写完钉死，集合指针一行指向 skill，约定只写环境查不到的；细节在触发时按需读 skill](https://shanyue.tech/images/26-10-13/agents-md-template.webp)

本站仓库根上的 `AGENTS.md` 就是按这个形状写的。它先让 `Agent` 选定集合（`blog` / `wechat-articles` / `rednotes` / `wechat`），再给每个集合一个指针，指向对应 skill。正文与配图那一节写的是 frontmatter 时区、图放哪、archify 怎么出图——这些在 `package.json` 里找不到。提交那一行只写 commit message 的格式，因为 Conventional Commits 不会出现在 `astro.config` 里。

有一句表面上是否定式：「正文不写 `# H1`」。它后面立刻跟了肯定目标：「标题只由 frontmatter 的 `title` 渲染；正文从导语或 `##` 开始。」这就是红线配对。若只写「不要写 H1」，模型仍然可能在第一节用 `#`；把「标题从哪来、正文从哪起」写出来，目标行为才进上下文。

整份文件大约几十行。集合多了就加一行指针，细节进 skill，根文件保持能一口气读完。这是 information hierarchy，让每一行都在干活。

## 六、和 CLAUDE.md 共存，以及团队共享 vs 个人

默认模式下，`Claude Code` 遇到项目自有 `CLAUDE.md` 就读它，`AGENTS.md` 被挡在外面。要让 `Claude Code` 和其他工具共用一份说明书，官方给了两条桥：

1. 在 `CLAUDE.md` 里写 `@AGENTS.md`，下面再追加只对 `Claude Code` 有意义的段落（比如 plan mode、hooks）。
2. 不需要 Claude 专属段落时，软链：`ln -s AGENTS.md CLAUDE.md`。

![仓库已有 CLAUDE.md 时默认不读 AGENTS.md；在 CLAUDE.md 里写 @AGENTS.md 导入，或软链过去，让一份说明两处生效](https://shanyue.tech/images/26-10-13/agents-md-coexist.webp)

`@AGENTS.md` 导入在「两份都读」的模式下也不会重复加载，官方说引擎会跳过已经读过的那份。以前用 `SessionStart` hook 把 `AGENTS.md` 打进上下文的，原生支持接上之后把 hook 拿掉，避免上下文里出现第二份拷贝。

团队协作常见另一种分层：仓库里提交共享的 `AGENTS.md`，个人规则放 `AGENTS.local.md`，local 进 `.gitignore`。思路像 `.env` 和 `.env.local`。

**这里必须标工具差异**：`Codex` 认 `AGENTS.override.md`（同层覆盖）；`Claude Code` 不读 `AGENTS.override.md` 和 `AGENTS.local.md`。

Claude 侧的个人项目偏好，官方入口是 `CLAUDE.local.md`，但如前所述，它在默认模式下会挡住 `AGENTS.md`。更稳的做法是：共享约定进 `AGENTS.md`；Claude 个人偏好放用户级 `~/.claude/CLAUDE.md`（不挡项目 `AGENTS.md`），或把 Project instructions 改成两份都读之后再用 `CLAUDE.local.md`。

## 七、写完用 writing-for-agents 自检一遍

把 `AGENTS.md` 当作给 `Agent` 读的文档来审，用下面几条过一遍：

1. **每一行还作用在任务上吗？** 过期的、从不触发的，整句删。
2. **环境能查到的，还在重复吗？** `package.json`、锁文件、目录名，留给环境。
3. **否定式能改成目标行为吗？** 能改就改；必须保留的红线，后面写上肯定目标。
4. **指针写清触发条件了吗？** 「详见 docs/」太弱；「改 `src/content/blog/` 下的长文时读 blog-article skill」才是指针。
5. **完成条件能判断做完没做完吗？** 「理解清楚」会诱使提前收工；「`pnpm build` 通过、产物含该 slug」可以检查。

![自检四步：每行作用在任务上，环境能查的留给环境，否定改成目标并写清指针触发条件，完成条件可以检查](https://shanyue.tech/images/26-10-13/agents-md-selfcheck.webp)

`Claude Code` 2.1.283 起还可以在会话里跑 `/doctor prompt-audit`，让它自己找过期、冲突、指向不存在文件的指令，默认覆盖 `CLAUDE.md`、`CLAUDE.local.md`、`AGENTS.md`，以及 `.claude/` 与 `~/.claude/` 下的规则、skill、命令、子 agent 和 output style。改不改由你决定，它只出报告。

`AGENTS.md` 能在多个工具之间共用，是因为它足够笨：一份 Markdown，没有 schema。真正要小心的是各工具的加载差异——`Claude Code` 默认优先项目自有 `CLAUDE.md`，有它就跳过 `AGENTS.md`，override / local 也不加载；`Codex` 从根合并到 cwd，认 override，还有 32 KiB 预算。把差异写进团队文档，比让每个人撞一次要便宜。

实现层面想看 `Claude` 到底怎么读，从 [mods 那篇](https://shanyue.tech/posts/claude-code-mods/) 的 `agents-md` 一节进源码最快。约定本身以 [agents.md](https://agents.md/) 和 [Codex 指南](https://learn.chatgpt.com/docs/agent-configuration/agents-md) 为准，`Claude` 的加载表以 [memory 文档](https://code.claude.com/docs/en/memory) 为准。
