一、一个真实的崩溃现场
半年前,我们团队的前端仓库根目录多了一个文件:AGENTS.md。
一开始它只有 40 行,写着技术栈和几条禁止事项。后来变成 120 行、300 行、600 行。Agent 每犯一次错,我们就往里加一条规则;每次有人问"为什么这么写",我们就把解释也塞进去。
它并没有因此变得更听话。该漏的请求封装照样漏,废弃的组件写法被当成现行规范,明明写了"修改前先确认",写操作还是直接执行了。
问题不在规则太少,而在我们把所有东西塞进了同一个容器。
掘金上那篇《别再堆 AGENTS.md 了》有个说法很准:AGENTS.md 不是 Agent 的大脑,更像它的宪法。一份什么都写的宪法,最后什么都管不好。
下面是我们踩坑半年后的落地总结,核心就一句话:让 Agent 记得更少,但在需要的时候拿到更准确的信息。
二、先想清楚:AGENTS.md 到底该放什么
在动手之前,先接受一个判断标准:
如果这条内容只对某一次任务有效,就不应该写进常驻的 AGENTS.md。
AGENTS.md 只适合放三类东西:
- Agent 在这个仓库里的角色
- 必须遵守的安全和协作边界
- 入口级的工作约定(比如"改完必须跑哪些检查")
除此之外的一切------架构说明、接口语义、发布流程、历史踩坑------都不该常驻在这里。
判断一条内容该去哪,问自己一个问题:它的生命周期有多长?
| 内容类型 | 生命周期 | 正确归属 |
|---|---|---|
| 技术栈、硬性禁止事项 | 数月~数年 | AGENTS.md |
| 当前任务需求 | 数天~数周 | Spec |
| 可复用的操作流程 | 数月~数年 | Skill |
| 架构、接口、术语 | 长期 | Wiki / docs |
| 平台查询与写操作 | 长期 | CLI / MCP |
| 质量验证 | 长期 | CI / Review |
上下文分层就是从这个判断开始的。
三、六层上下文分层体系
完整的分层体系包含六层,每层只回答一个问题:
| 层级 | 核心职责 | 回答的问题 | 生命周期 |
|---|---|---|---|
| AGENTS.md | 身份与边界 | 你是谁,什么不能做 | 数月~数年 |
| Spec | 任务合同 | 这次要完成什么,怎样算完成 | 数天~数周 |
| Skill | 可复用流程 | 这类任务具体怎么做 | 数月~数年 |
| CLI / MCP | 系统交互 | 如何安全地碰外部系统 | 长期 |
| Wiki / Reference | 长期知识 | 这个项目是什么,为什么这样设计 | 长期 |
| CI / Review | 质量验证 | 如何证明做对了 | 长期 |
一句话:AGENTS.md 管身份和边界,Spec 管当前任务,Wiki 管长期知识,Skill 管动作,CLI/MCP 管系统交互,Review 和 CI 管质量。
每一层都不完美,但每一层都知道自己不该负责什么。
四、目录约定:借鉴 dotagents 草案
光有分层还不够,还得有地方放。先说清楚现状:.agents/ 目录约定没有权威标准 ,社区里主要是些草案性质的提案,其中一个是 dotagents------个人提案,Draft 状态,讨论还不多,远算不上规范。我们参考它,不是因为它"标准",而是因为它的思路和我们踩坑半年后的结论对得上,值得借鉴。
它的思路是三条:
- 共享知识留在人类熟悉的位置(
README.md、CONTRIBUTING.md、docs/),不重复 - Agent 专用资源集中在
.agents/下(skills/、personas/、settings/) - 根
AGENTS.md作为路由器,按需引导加载
工具兼容性:先泼一盆冷水
AGENTS.md 是开放格式(见官方规范),Codex、Cursor 等工具会直接读取;但 Claude Code 读的是 CLAUDE.md,需要用符号链接保持两边一致:
bash
ln -s AGENTS.md CLAUDE.md
另外要清楚一点:AGENTS.md 里的 Context Routing 不是自动生效的机制,它依赖 Agent 主动去读被路由的文件。不同工具对"按需加载"的支持程度不一样,所以第六节的验证步骤不能省------路由写了没用上,等于白写。
dotagents 的核心主张
用一份简洁的根 AGENTS.md 作为路由器,只按需引导 Agent 读取相关资源,避免单文件膨胀。
它明确区分两类信息。
共享项目知识(人类和 Agent 都需要),保持原有位置:
README.md--- 项目目的、安装CONTRIBUTING.md--- 贡献流程、编码规范docs/--- 架构、术语、决策记录
Agent 专用资源,放入 .agents/:
.agents/personas/--- 专家角色.agents/skills/--- 任务特定技能.agents/settings/--- 厂商中立配置.agents/memory/、.agents/logs/--- 本地状态(通常 gitignore)
推荐目录结构
yaml
project/
├── AGENTS.md # 路由器:上下文路由 + 硬边界
├── README.md # 人类:项目目的、安装
├── CONTRIBUTING.md # 人类:贡献流程、编码规范
├── docs/ # 人类 + Agent 共享
│ ├── architecture/ # 架构、接口、术语
│ └── decisions/ # 决策记录(ADR)
├── specs/ # 当前任务 Spec(短期,用完归档)
│ ├── 2025-06-user-profile-refactor.md
│ └── archive/ # 已完成任务的 Spec 归档
├── .agents/ # Agent 专用
│ ├── personas/ # 专家角色
│ │ └── standards-reviewer.md
│ ├── skills/ # 任务 Skills
│ │ ├── feature-check/
│ │ │ ├── SKILL.md
│ │ │ └── scripts/
│ │ ├── api-change/
│ │ │ └── SKILL.md
│ │ └── release-check/
│ │ └── SKILL.md
│ ├── settings/ # 厂商中立配置
│ ├── memory/ # 本地状态(gitignore)
│ └── logs/ # 执行摘要(gitignore)
└── .github/
└── workflows/
└── agent-quality.yml # 质量门禁
注意三点:memory/ 和 logs/ 通常应被版本控制忽略,且不得包含机密、个人数据或隐藏推理内容;Spec 生命周期只有几天到几周,所以不放在 docs/(长期知识)里,任务结束后移入 specs/archive/;这套结构是我们借鉴 dotagents 思路后自己定的,目录约定本身没有标准,不必照搬,适合团队就好。
五、实战案例
下面是我们团队正在用的真实文件。可以直接抄。
案例 1:一份 80 行的 AGENTS.md
错误示范(我们曾经的写法):
bash
# AGENTS.md
本项目使用 React,请遵循最佳实践。
组件要写测试。
样式建议用 CSS Modules,但也可以用 less。
接口请求要用封装好的方法,不要直接用 fetch。
...
(以下省略 500 行)
问题:全是"建议"和"可以",没有硬边界;把长期知识和入口约定混在一起;Agent 无法判断哪条必须遵守。
正确示范:
markdown
# AGENTS.md --- 前端项目宪法
## 角色
你是本前端仓库的 Coding Agent,负责在明确的边界内完成代码修改、测试和文档更新。
## 技术栈
- React 19 + TypeScript 5.x
- 包管理器:pnpm(禁止 npm / yarn)
- 样式:Less + CSS Modules
- UI 库:antd v5
- 状态:Zustand
- 请求:@/utils/request(基于 axios 封装)
## 目录约定
- 组件:src/components/{ComponentName}/index.tsx
- 页面:src/pages/{PageName}/index.tsx
- 服务:src/services/{domain}.ts
- 类型:src/types/{domain}.ts
- 测试:与源文件同目录,后缀 .test.tsx
## 禁止事项
- 禁止修改 src/utils/request.ts 的拦截器逻辑
- 禁止执行 git push --force
- 涉及生产配置变更,必须先输出 dry-run 方案待确认
- 代码层面的禁令(内联样式、直接 fetch)不写在这里,
由 ESLint 硬约束,见下方 lint 配置
## 修改后必须执行
- pnpm type-check
- pnpm lint
- 新增组件必须附带 .test.tsx
## Context Routing
- 修改文档前,先读 CONTRIBUTING.md
- 做架构决策时,查阅 docs/decisions/
- 执行发布流程时,加载 .agents/skills/release-check/SKILL.md
- 涉及接口变更时,加载 .agents/skills/api-change/SKILL.md
注意最后一段 Context Routing:这就是"路由器"的写法,告诉 Agent 何时加载什么,而不是把所有内容都塞在这里。
还有一个细节值得单独说: "禁止内联样式""禁止直接用 fetch"这类代码级禁令,我们刻意不写进 AGENTS.md,而是放进 lint 规则。 Prompt 里的禁令是概率性的,Agent 可能忘;lint 报错是确定性的,想绕都绕不过去:
json
// .eslintrc 片段
{
"rules": {
"react/forbid-component-props": ["error", {
"forbid": [{ "propName": "style", "message": "禁止内联样式,请使用 CSS Modules" }]
}],
"no-restricted-globals": ["error", {
"name": "fetch", "message": "请使用 @/utils/request"
}]
}
}
原则就一句:能被工具契约拦截的行为,就不要只写在 Prompt 里。 AGENTS.md 只保留真正无法用工具表达的约束------比如"先 dry-run 待确认"这种需要人来判断的事。
整份文件 80 行,没有一句"建议",全是可执行的约束。
案例 2:一个可验证的 Skill
.agents/skills/feature-check/SKILL.md(frontmatter 遵循 Agent Skills 开放规范:name + description,触发条件写在 description 里,工具靠它判断何时加载):
yaml
---
name: feature-check
description: 新增或修改前端功能后,执行标准化检查流程。当任务涉及新增组件、页面或修改现有功能逻辑时使用。
---
# Feature Check
## 执行步骤
1. 确认改动范围符合 Spec 中声明的"可修改范围"
2. 检查是否使用了 @/utils/request,而非直接 fetch
3. 检查样式是否使用 CSS Modules,而非内联 style
4. 确认新增组件附带 .test.tsx
5. 运行 `pnpm type-check`
6. 运行 `pnpm lint`
7. 运行 `pnpm test -- --coverage`
8. 输出检查报告,包含:改动文件、通过项、失败项
## 失败处理
- 若 type-check 或 lint 失败,必须修复后重新执行,不得跳过
- 若测试覆盖率低于 80%,必须补充测试用例
- 若涉及接口契约变更,停止执行,转而加载 api-change Skill
这个 Skill 的每一步都有明确的成功或失败判定,不存在"尽量做到"。
案例 3:一份任务 Spec
specs/2025-06-user-profile-refactor.md:
markdown
# Spec: 用户中心页面重构
## 背景
现有用户中心页面使用 class 组件,需重构为函数组件 + Hooks。
## 可修改范围
- src/pages/UserProfile/**
- src/components/UserAvatar/**
- src/services/user.ts(仅限 getUserProfile 方法)
## 明确不可修改
- src/utils/request.ts
- src/components/Layout/**
- 任何 package.json 依赖版本
## 验收标准
- [ ] 页面功能与重构前完全一致
- [ ] 所有组件改为函数组件
- [ ] 测试覆盖率不低于 80%
- [ ] pnpm type-check 和 pnpm lint 全部通过
## 需人工确认的操作
- 涉及 user.ts 中其他方法的改动
- 需要新增依赖
- 需要修改路由配置
Spec 的关键是边界清晰:能改什么、不能改什么、什么情况必须停下来问人。任务完成后移入 specs/archive/------三个月前的 Spec 还在被 Agent 加载,是常见的上下文污染源。
案例 4:CLI/MCP 与 CI 层怎么落地
前三个案例覆盖了 AGENTS.md、Skill、Spec。剩下两层不靠文件,靠机制:
- CLI/MCP 层:写操作走带确认的通道------MCP 工具的 human-in-the-loop 确认、git hooks 拦截 force push、数据库变更只给只读账号。原则同 lint:把"不要碰生产"从一句 Prompt 变成物理上碰不到。
- CI/Review 层:第六节的 agent-quality.yml 就是入口,加上正常的人类 code review。Agent 产出和人写代码走同一条门禁,不搞特殊通道。
六、如何验证这套体系有效
"可验证"是这套体系区别于"写文档"的关键。我们团队用以下方式做自检:
验证 1:AGENTS.md 是否失控
bash
wc -l AGENTS.md
# 期望:≤ 100
我们把这条写进了 CI:
bash
# .github/workflows/agent-quality.yml
name: Agent Quality
on: [pull_request]
jobs:
check-agents-md:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- name: Check AGENTS.md size
run: |
lines=$(wc -l < AGENTS.md)
if [ "$lines" -gt 100 ]; then
echo "AGENTS.md 超过 100 行(当前 $lines 行),请将内容迁移到 Spec/Wiki/Skill"
exit 1
fi
行数是最粗的口径,一段长文本压成一行就能绕过;更稳的算法是按 token 或顶层小节数计,但实现成本高。实践中行数检查挡住了绝大多数自然膨胀,剩下靠 review 兜底,性价比最高。
验证 2:规则是否重复
每条硬规则应该只出现在一个地方。用 grep 检查:
ini
# 检查"禁止内联样式"是否在多处重复
grep -rn "禁止内联样式" . --include="*.md" --exclude-dir=node_modules
# 期望:只出现在 AGENTS.md 中
这个检查只覆盖字面重复------规则换个说法写第二遍,grep 查不到。把它当抽查手段,不要当保证。
验证 3:Skill 是否真的可触发
让 Agent 执行一次真实任务,观察它是否按预期加载了 Skill:
markdown
任务:给 UserProfile 页面新增一个"编辑昵称"功能
观察点:
1. Agent 是否读取了 specs/ 下对应的 Spec?
2. Agent 是否加载了 .agents/skills/feature-check/SKILL.md?
3. Agent 是否在改动 user.ts 前停下来确认?
不建议靠人盯屏。多数工具的 hooks 或会话日志能记录 Agent 的文件读取行为(比如 Claude Code 的 hooks),把"有没有读 Spec"变成一条可查的日志,验证成本会低很多。
验证 4:上下文消耗对比
在分层前后,记录一次典型任务的 token 消耗:
| 指标 | 分层前 | 分层后 |
|---|---|---|
| 常驻上下文行数 | 600+ | 80 |
| 单次任务加载行数 | 600+ 全量 | 80 + 当前任务相关的 Spec/Skill |
| Agent 首次执行正确率 | ~60% | ~90% |
先说清楚:这张表是我们团队的内部体感,样本小,没做严格对照实验,正确率一列尤其如此。社区的对照研究(如 2026 年《Evaluating AGENTS.md》一类工作)甚至显示上下文文件并不保证普遍提升表现------收益高度取决于内容质量。拿这张表当起点,在自己的仓库跑几个同类任务的 A/B,得到你自己的数字,比信我们的重要。
另外,分层后单次加载行数未必比 600+ 少多少(Spec 加 Skill 可能几百行),真正的收益是"每次加载的内容都和当前任务相关",而不是总量变小。
七、渐进式落地路线
不要一开始就把所有目录建齐。分三步走:
第一步:从三个文件开始
- 一份只写边界、不写百科的 AGENTS.md(60~100 行)
- 一份当前任务 Spec(定义范围、验收标准和确认条件)
- 一份能被验证的 Skill(封装一个重复动作,比如 feature-check)
第二步:引入 .agents/ 目录
当 Skill 超过 3 个、需要多角色审查时,再建立完整的 .agents/ 结构:
.agents/
├── personas/
├── skills/
└── settings/
第三步:建立知识升级机制
知识沉淀必须有升级条件。不是每次 Agent 犯错都值得新增规则。稳妥的流程是:
一次问题 → 记录现象 → 判断是否可复现 → 验证解决方式 → 决定进入 Wiki、Skill 还是硬规则
"硬规则"也有优先级:先看 lint / CI / hooks 能不能拦,能拦就不进 AGENTS.md。否则团队会得到一套越来越长、越来越互相矛盾的"经验集合"。
八、常见陷阱
| 陷阱 | 表现 | 规避方法 |
|---|---|---|
| AGENTS.md 膨胀 | 从 40 行涨到 600 行 | 设硬上限,CI 自动检查 |
| 规则多层重复 | 同一条约定在 AGENTS.md 和 Skill 里各写一遍 | 每条规则只存在于一个层 |
| Spec 长期驻留 | 三个月前的任务 Spec 还在被加载 | 任务结束移入 specs/archive/ |
| Skill 过于宽泛 | "帮我写好代码"这种 Skill | 触发条件明确、步骤可验证 |
| 知识无序沉淀 | 每次犯错就加一条规则 | 走"复现→验证→升级"流程 |
| 工具边界缺失 | 只靠 Prompt 说"不要改生产配置" | 用 CLI/MCP 的写操作确认机制硬约束 |
| 多工具环境踩坑 | Claude Code 读不到 AGENTS.md | symlink CLAUDE.md,并实测路由是否生效 |
最后一条原则尤其重要:能通过工具契约限制的行为,就不要只写在 Prompt 里。
九、总结
这套体系的核心不是"写更多文档",而是让每一层都知道自己不该负责什么:
- AGENTS.md 管身份和边界 ------ 保持精简,只做路由
- Spec 管当前任务 ------ 用完即弃
- Skill 管动作 ------ 可复用、可验证
- Wiki 管长期知识 ------ 人类和 Agent 共享,不重复
- CLI / MCP 管系统交互 ------ 用工具契约硬约束
- CI / Review 管质量 ------ 自动化验证
掘金那篇文章的判断标准,我们团队已经刻进了流程:
如果这条内容只对某一次任务有效,就不应该写进常驻的 AGENTS.md。
半年下来最大的体会是:Agent 不是知道得越多越好,而是在需要的时候,能拿到越准确的信息越好。
从今天开始,把你的 AGENTS.md 砍到 100 行以内试试。用过一段时间,你就回不去了。
相关阅读: