跳到正文
山月行
返回

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

发布于

AGENTS.md 是给编程 Agent 看的项目说明书。人读 README.md,看怎么 clone、怎么跑起来;Agent 每次会话都从空上下文开始,它需要另一份东西:这个仓库的约定、坑,以及「做完长什么样」。agents.md 把这件事说成 README for agents,由 Agentic AI Foundation 托管,没有强制字段,纯 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 的工具调用 里,官方说明在 How Claude remembers your project。

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

AGENTS.md 总览:新会话上下文从零开始,读入 AGENTS.md 这份开放约定,Claude、Codex、Cursor 各按自己的规则加载,再按约定改代码,少试错、省 token

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

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

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

Claude Code(默认)CodexCursor
主文件无项目自有 CLAUDE.md 时读 AGENTS.md每层 AGENTS.md根目录和子目录的 AGENTS.md
覆盖文件不认 AGENTS.override.md / AGENTS.local.md每层先 AGENTS.override.md 再 AGENTS.md以 Rules 文档 为准
多层怎么合从 cwd 沿祖先目录加载一路上的 AGENTS.md;Read 子路径时再补该层从 git 根合并到 cwd,越近越优先子目录与父目录合并,更具体的优先
体积文档未给单独字节上限合计默认 32 KiB文档未给字节上限

Codex 的规则写在 Custom instructions with 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 超限截断

有一个链接容易点错:code.claude.com/docs/en/agents.md 讲的是并行 agents(同时跑多个子 agent),不是这份项目说明书。看 AGENTS.md 文件怎么加载,走 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 那篇 里读过这份源码,逻辑比多数教程短。

默认模式叫 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

怎么确认加载了: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 打开子路径时会再补该层

大 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

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

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

少写否定式的两条路:平时直接写「用 pnpm 安装」;硬红线才写否定,并紧跟肯定目标,两条路最后都让上下文里留下目标行为

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 里已有的脚本再抄一遍:

# 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

本站仓库根上的 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 导入,或软链过去,让一份说明两处生效

@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」可以检查。

自检四步:每行作用在任务上,环境能查的留给环境,否定改成目标并写清指针触发条件,完成条件可以检查

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 那篇 的 agents-md 一节进源码最快。约定本身以 agents.md 和 Codex 指南 为准,Claude 的加载表以 memory 文档 为准。


分享本文:

上一篇
用 mods 改写 Claude Code 的工具调用
下一篇
Claude Code skill:四条规则与两个模式