Agents.md 写得越全 ≠ Agent越听话
OpenAI 内部有个项目,他们用 Codex 从零搭了一个内部产品:百万行代码,零行人写,全部由 Agent 生成。三个工程师,五个月,1500 个 PR。这件事被他们写成了一篇文章,叫 Harness Engineering。
项目初期,他们做了一件几乎所有人都会做的事:写了一个巨大的 AGENTS.md,把所有规则、约束、架构原则、编码规范全塞进去。逻辑上这很合理——Agent 不是每次启动都失忆吗,那我把它需要知道的一切都写在开机必读文件里,总没错吧。
结果,用他们自己的原话说,"it failed in predictable ways"——以一种可以预见的方式失败了。
下面我们来聊聊,为什么这样做会导致Agent不听话?说白了就两件事:
注意力被稀释了。前沿模型可靠遵循的指令上限大约 150~200 条,而 Claude Code 自身的系统提示已经占掉约 50 条,留给你的只有 100~150 条。超过这个阈值后发生的事很反直觉——不只是新加的规则被忽略,是所有规则的遵循质量一起下降。上下文窗口就那么大,你塞两百条规则进去,Agent 真正该关注的东西——当前任务、相关代码、需要参考的文档——反而被挤到了角落。更麻烦的是,两百条规则全都摆在"开机自启动"的位置,对 Agent 来说等于全部没有优先级。它分不清哪条是此刻必须遵守的、哪条是完全没用的。
上下文腐烂(Context Rot)。代码天天在变,架构在演进,但那个几百行的文件没人想去更新——写的时候人人都觉得"以后会维护的",三周之后它就成了屎山文件。Agent 不知道哪些还有效、哪些早就过时了,人也搞不清楚。到这个阶段,这个文件从资产变成了负债:它还在每次会话里消耗上下文,但提供的信息已经开始出错。
你发现没有,这两个问题有一个共同的根源:试图在启动时把所有信息一次性灌给 Agent。信息越多,稀释越严重,腐烂越快。所以解法的方向也就清楚了——问题并不出在信息本身,出在信息出现的时机上。
你想:如果信息本身就是错的,不管放在哪个文件里 Agent 都会用错。上下文腐烂的根源也不是"放在 AGENTS.md 里",而是它每次启动时都被加载——即使这条规则早已过时、跟当前任务完全无关。
同样,注意力稀释也不是因为"文件大",而是因为所有规则都在同一时间、同一优先级被喂给 Agent。
所以真正的共同根源是:把"Agent 需要知道的一切"等同于"Agent 每次启动时必须知道的一切"。
最佳做法:渐进式披露
OpenAI 团队做了一个关键转变:把 AGENTS.md 从"百科全书"改成了"目录"。新的 AGENTS.md 只有大约 100 行。它不再试图把所有规则写进去,只干一件事:告诉 Agent "你需要知道的东西在哪里"。真正的知识放在一个结构化的 docs/ 目录里——设计文档、架构文档、产品规格、安全规范,每个都是独立文件,有清晰的分类和索引。
这个模式叫 Progressive Disclosure(渐进式披露)——Agent 从一个小的、稳定的入口开始,在需要的时候才去读更深层的信息。
真正的杠杆从来不在于你给 Agent 的信息量多大,而在于在对的时间,只给它需要的那一份。“正如爱情一样,对的时间遇到错误的人注定无法走到最后”。
说了这么多,到底该如何落地?那么好,接着看...
根 AGENTS.md 不是一个"保存其他规则的目录"——它是一个索引,而不是容器。 规则本身不放在根文件里,放在外部结构化目录中;根文件只留路径和触发条件。
核心结构如下:

两个关键设计:
一、索引用"条件+路径",而不是裸 @ 引用
错误写法(会在每次启动时把整个文件嵌进上下文):
PLAINTEXT
@docs/auth-patterns.md正确写法(Agent 只在碰对应模块时才去读):
PLAINTEXT
涉及认证相关的改动,先读 docs/auth-patterns.md
改数据库 schema 或 migration 时,先读 docs/db-conventions.md这就是渐进式披露在项目里的最小实现——信息没有被删除,只是从"每次必读"变成了"用到才读"。
二、四层作用域自带渐进式披露
CLAUDE.md 本身就是层级系统,不需要你额外设计:
位置 | 作用范围 | 放什么 |
|---|---|---|
| 全局,所有项目生效 | 个人偏好("回复用中文") |
| 当前项目,提交到 git | 团队共享的入口索引 |
子目录 | 只在 Agent 碰到该目录的文件时才加载 | monorepo 里各子项目的规范 |
| 个人私有,加入 .gitignore | 你个人的调试偏好、本地路径 |
规模再大一点,用 .claude/rules/ 目录做模块化——每个规则文件带 YAML frontmatter 的 paths 字段,用 glob 限定作用范围:
YAML
---
paths: src/auth/**
---
# 认证模块规范
所有 token 操作必须通过 lib/auth.ts 的封装,不要直接操作 cookie。这样 Agent 只在碰 src/auth/ 下的文件时才加载这条规则,其他时候完全不占上下文。
举个例子:
拿一个典型的 Web 项目来说——假设你的项目有认证、数据库、部署三块复杂逻辑。拆完之后的文件布局:
PLAINTEXT
项目根目录/
├── CLAUDE.md # ~80 行,索引 + 常驻规则
├── docs/
│ ├── auth-patterns.md # 认证规范(token 管理、权限模型)
│ ├── db-conventions.md # 数据库约定(schema 变更流程、migration 禁区)
│ └── deploy-checklist.md # 部署清单
└── .claude/
└── rules/
└── security.md # 安全规则,paths: src/** (全局生效的模块规则)
而 CLAUDE.md 的内容长这样:
MARKDOWN
# 项目名:一句话说明
Next.js 14,App Router + TypeScript + Prisma。
## 常用命令
- `pnpm dev`:启动开发服务器
- `pnpm test src/xxx.test.ts`:跑单个测试,不要跑全量(全量要 4 分钟)
- `pnpm typecheck`:改完代码必须跑一次
- `pnpm db:migrate`:生成 migration,文件不要手动改
## 项目结构
- `/app` - 页面和路由
- `/lib` - 跨模块共享的工具函数
- `/prisma` - 数据库 schema,migration 文件不要手动改
## 重要约定
- 不要用 --force 推送,用 --force-with-lease
- 环境变量在 .env.local,不要提交
## 按需参考的文档
- **涉及认证相关改动** → 先读 `docs/auth-patterns.md`
- **改数据库 schema 或 migration** → 先读 `docs/db-conventions.md`
- **涉及部署或 CI/CD** → 先读 `docs/deploy-checklist.md`
注意几个设计决策:
常驻的只有 Agent 猜不到的东西:用 pnpm 还是 npm、测试命令要精确到文件路径、哪些目录绝对不能碰——这些东西 Agent 从代码里推不出来。
索引条目是"条件 + 路径",不是裸路径:
涉及认证相关改动 → 先读 docs/auth-patterns.md而不是@docs/auth-patterns.md。前者是线索,后者会在启动时把整个文件嵌进上下文。分层的直觉:根文件本身只有约 80 行,大部分信息在外部文件中,只有触发条件命中时才加载。
关键规则可以用强调语法加权:某条规则 Agent 老是不遵守的话,试试在前面加
IMPORTANT:或者YOU MUST:——Anthropic 官方文档明确说过这类关键词能提升模型对特定指令的遵循程度。
现在你知道架构怎么设计了,但是又出现了一个问题,根文件里留什么,不能凭感觉,到底哪些该放在根目录下的Agent.md ,哪些该拆出去放在docs/下。
什么信息值得常驻
用HumanLayer 团队提出过一个三维度框架——WHAT / WHY / HOW——来帮你判断一条信息该不该留在根文件里:
维度 | 回答的问题 | 例子 |
|---|---|---|
WHAT | 项目是什么、技术栈、目录结构 | Next.js 14 + App Router |
WHY | 为什么选这个方案、模块职责 | "那个看起来冗余的逻辑是给老用户留的兼容" |
HOW | 怎么跑测试、用什么包管理器 |
|
HOW 决定了 Agent 能不能正确执行。WHY 决定了它会不会理解你的决策背景——比如知道"这个目录不能碰是因为历史上出过事故",就不会自作聪明地去"优化"。WHAT 是入口索引。

说白了,"写得好" ≠ "放得对"。是常驻还是拆出你看这个规则是每轮对话都需要还是只有在特定条件下才会触发。编写指令的核心不是“告诉它做什么”,而是“消除它猜的空间”。
那么新的问题来了,既然决定了要把规则拆出去:怎么拆才算真的“按需加载”?
如何按需加载
@ 引用不等于渐进式披露——它会在启动时把全文嵌进上下文,换了个方式做全量灌注。真正的按需加载是条件指引("涉及认证时读 docs/auth.md"),配合子目录 CLAUDE.md(管整个目录)和 .claude/rules/ + paths glob(管跨目录文件模式)。
子目录 CLAUDE.md → 管整个目录
当一个目录下的所有文件都共享同一套规范,放子目录 CLAUDE.md 最干净。典型场景:
PLAINTEXT
frontend/
CLAUDE.md ← "所有组件用 React 函数组件 + TypeScript,状态管理用 Zustand"
backend/
CLAUDE.md ← "所有 API 用 Express + Prisma,异常统一走 errorMiddleware"
Agent 只要碰 frontend/ 下的任何文件,前端那套规则自动加载;碰 backend/ 的文件,后端规则自动加载。互不污染。
.claude/rules/ + paths glob → 管特定文件模式
当规则不覆盖整个目录,只对某些文件或某些模式生效,用 paths glob 更精确:
YAML
# .claude/rules/auth-constraints.md
---
paths: "src/auth/**"
---
- 不要直接操作 session,走 AuthService 封装的接口
- 密码字段禁止 log 输出
YAML
# .claude/rules/test-efficiency.md
---
paths: "**/*.test.ts"
---
- 只跑当前改动的测试文件:pnpm test <文件路径>
- 禁止跑全量,除非我明确要求
Agent 改 src/order/create.ts 时,认证规则不会加载;改 src/auth/login.ts 时,测试效率规则也不会加载。每条规则的加载范围被精确限定了。
一句话区分
方案 | 适用范围 | 适合场景 |
|---|---|---|
子目录 | 整个目录 | monorepo 里每个子项目有自己的技术栈和编码规范 |
| 特定文件模式 | 横切关注点(认证、测试、日志),只对部分文件生效 |
简单记:管一个目录的所有东西 → 子目录 CLAUDE.md;管一类文件(不管它们在哪个目录)→ paths glob。
如何维护?
你学会了怎么拆、怎么按需加载。但拆完不是终点——文件跑了一周后,Agent 又踩了坑:它把老用户兼容逻辑当成冗余代码删掉了。这件事恰好说明:文件拆干净只是前半程,后半程是让它持续进化。
活了文档的两个动作:一增一减
增:错题本模式
Claude Code 创始人 Boris Cherny 的做法很值得学:他自己的 CLAUDE.md 只有 2.5k token(一百多行),提交到 git 里。每次 Agent 犯了一个他没拦住的错,就往里加一条。
回到你说的那个事故——Agent 删了老用户兼容逻辑。正确的反应不是发火,而是立刻打开 CLAUDE.md,加一条:
MARKDOWN
## 兼容逻辑
- 看到带 `// LEGACY: 兼容 2023 年前注册用户` 注释的代码,不要删除或重构,
先确认是否还有用户依赖该逻辑
这条规则天然满足我们前面讲过的所有标准:它来自真实翻车、Agent 猜不到、有具体的行为指令。
减:定期做减法
反过来,每隔几周做一次清理。方法很简单——对文件里每一条规则问一句话:删掉这行,Agent 会不会再犯同样的错? 不会就删。
还可以让 Agent 自己审一遍:"这个文件里有没有过时的内容?有没有冗余的规则?" 过时的(比如项目已经迁移到 pnpm 但文件里还写着 npm 命令)、linter 能管的(2 空格缩进)、README 里重复的,全部砍掉。
为什么错题本模式天然正确
每条规则都来自真实事故,所以每条都是 Agent "猜不到的"
按需增长,不会一上来就写成百科全书
每条规则有事故背书,三周后回头看也知道它为什么存在——不会腐烂
一增一减,文件才能一直保持高信噪比。
