引子:AGENTS.md 是什么

在仓库根目录放一个 Markdown 文件,告诉 AI Coding Agent 这个项目是什么、怎么构建、有哪些规矩。这个做法现在有了一个事实标准的名字——AGENTS.md

它的角色是:README.md 给人类读者看,AGENTS.md 给 AI 读者看。前者聚焦快速开始与贡献指南,后者聚焦构建命令、编码约束、验证闭环、踩坑清单,覆盖任何会影响 Agent 产出正确性的上下文。

这个概念最初由 Anthropic 的 Claude Code 以 CLAUDE.md 形式普及。随后各家工具一度各走各路:Cursor 用 .cursor/rules/*.mdc,Copilot 用 .github/copilot-instructions.md,Gemini CLI 用 GEMINI.md,Cline 用 .clinerules,Sourcegraph Amp、OpenAI Codex、Cursor、Factory、Jules(Google)等团队联合推出 AGENTS.md 作为通用入口。这场标准之争最终在 OpenAI 主导的格式上收敛——agents.md 域名指向当前规范,Linux Foundation 下属的 Agentic AI Foundation(AAIF,执行董事 Mazin Gilbert,与 MCP、Goose 同级)作为标准 steward。截至 2026 年 5 月,agents.md 主页统计 GitHub 上已有超过 6 万个开源项目使用这一格式,OpenAI Codex 自己的仓库就有 88 份按子项目就近放置的 AGENTS.md。Cursor、Aider、goose、opencode、Zed、Warp、Windsurf、Devin、JetBrains Junie、UiPath Autopilot、GitHub Copilot Coding Agent、Augment Code 等主流工具均已原生识别。Claude Code 仍以 CLAUDE.md 为主,但自 v2.107(2025 年 5 月 9 日)支持 @path/to/file.md 引用语法之后,CLAUDE.md 里只需一行 @AGENTS.md 就能完成桥接,迁移成本几乎为零。

历史脉络和段落顺序不是 AGENTS.md 真正值得讨论的地方。真正值得讨论的问题只有一个:为什么一份几百行的 Markdown,能显著改变 AI Coding 的产出质量? 答案藏在一条设计原则里:地图,而非手册。

核心设计原则:地图,而非手册

OpenAI 在 Harness Engineering 实践中把 AGENTS.md 的第一原则命名为「Map, not Manual」。Anthropic 的官方博客在 Skill 设计中也重复了同一条原则:渐进式披露(progressive disclosure)。两家把不相容的工具做到相容点上,背后的机制是同一个:大模型的注意力是有限资源,上下文窗口越大,注意力被稀释得越严重。

注意力为什么是有限资源

「Lost in the Middle: How Language Models Use Long Contexts」(Liu et al., TACL 2024,arXiv 2307.03172)给出过一个反直觉的数据点:在 20 篇文档的多文档问答任务里,把答案文档放在中间位置,准确率比放在首尾掉超过 30%。这条 U 形曲线在所有主流长上下文模型上都能复现,包括那些标榜支持几十万 token 的版本。

最近的工程实测把这条规律推得更远。一篇 2026 年的 monitor 性能评测显示,Claude Opus 4.6(开 thinking)在 100K token 转录中插入对抗指令,靠近开头的 recall 是 99.7%,移到深处之后掉到 69%。Gemini 3 标称 2M、Claude 4 标称 1M、GPT-5 标称 400K,但在 needle-in-a-haystack 测试里,几乎所有前沿模型在大约 32K 之后都会快速劣化。

这意味着「上下文窗口大」不等于「真的能用上这么大」。一份 5000 行、事无巨细的 AGENTS.md,把关键约束和普通说明同等推进窗口里,关键约束反而被淹没。一份 200 行的 AGENTS.md,只标注「要做 X 就去看 Y 文档,要写 Controller 就去看 Z 目录」,模型按需跳转,注意力花在真正当前任务相关的那一小块。

Anthropic Skills 把这一点工程化得更彻底:每个 Skill 是一个目录,分三层加载——session 启动只载入 frontmatter 元数据让模型知道有什么;正文 SKILL.md 只在被判断相关时再装进上下文;references/assets/scripts/ 推迟到执行需要时才取。AGENTS.md 是 flat-file,做不到三层精细切分,但思路是同一个:把上下文当成稀缺资源来分配,而不是当成无限堆放的仓库。

一刀切的判断标准

这条原则推导出了一个判断标准:

  • 不知道就会写出错误代码的信息,写进 AGENTS.md
  • 不知道只会写出不够好代码的信息,写进详细文档,AGENTS.md 里放链接

这条标准把「该写什么」和「不该写什么」一刀切干净。分层架构的依赖方向、异常处理的强制约定、响应体由框架统一包装,违反就直接跑不通或留下隐患,必须进 AGENTS.md。某个 Service 内部的调用顺序、某个组件 prop 的推荐写法,错了可以改,属于详细文档的领域。

反过来看,有几类东西不应该AGENTS.md,即便它们看起来重要:

  • 能机械检查出来的。让 lint 脚本说话,AGENTS.md 只放命令入口(如 make lint-arch)。把规则同时写在 AGENTS.md 和 lint 里,迟早会两边对不上,对不上时 Agent 该信谁?
  • 极少触发的边界。一年用一次的特殊路径,留在 design doc 里,AGENTS.md 提一句「特殊场景见 docs/edge-cases.md」即可。
  • 个人偏好和临时调试笔记。这些应该流向工具的 user-level 配置(如 ~/.claude/CLAUDE.md)或者 PR 评论,混进项目级 AGENTS.md 反而误导新成员。
  • secrets、PII、内部链接的明文AGENTS.md 进 Git,进 Git 就会被各种工具 index、缓存、训练。任何敏感值用 ${ENV_VAR} 引用,AGENTS.md 只声明依赖关系。

一组先行的场景

在展开具体实践之前,先看一组没有 AGENTS.md 时 AI Coding 的典型失败模式。它们解释了为什么 AGENTS.md 不是可有可无的装饰,而是工程化使用 AI Coding 工具的基础设施。

上下文割裂。前后端分属两个 Git 仓库时,AI 工具一次只能看到一半。改一个前后端联动的功能,比如后端新增接口、前端调用这个接口,需要在两个窗口之间来回切换。每次切换,模型就丢掉上一轮的上下文。

私域组件不可见。训练数据里没有的闭源组件(内部组件库、私有 SDK、商业框架),AI 既不认识也查不到文档。写出来的代码要么编译不过,要么 prop 传错,要么漏掉必要配置。

规矩活在人脑里。每个项目都有自己的潜规则:异常必须通过 BusinessException 抛出、响应体禁止手动构造、Controller 不得直接注入 Repository。这些规矩写在团队成员的默契里,不写下来 AI 就反复违反,每次纠正完下次还犯。

验证闭环断裂。AI 改完代码不知道怎么构建、怎么启动、怎么自测。本地环境配置、启动命令、验证方式散落在聊天记录和个人脚本里。AI 只能把代码改完就停下,剩下的人来接手。这意味着 Agent 无法夜间自主执行:连项目都启动不起来,谈不上自主闭环。

跨会话失忆。Agent 每开一个新会话就回到出厂状态。上一轮花了二十分钟摸清楚的目录布局、踩过的兼容性坑、定下来的命名约定,下次进来全部归零。模型本身没有「这个项目我熟」的能力,只有看到 AGENTS.md 那一刻才能瞬间穿越回相同的认知水位。

工具规则矩阵不同步。团队里同时存在用 Cursor 的、用 Claude Code 的、用 Copilot 的、用 Codex 的成员。每个人维护各自的 .cursor/rules/CLAUDE.md.github/copilot-instructions.mdAGENTS.md,规则慢慢漂移成不一致的几份。新人入职打开仓库被四五份说明文件淹没,反而不知道该信哪个。

这六种失败模式背后是同一个根因:项目的知识与约束存在于人脑或散落的角落,不存在于 AI 能读到的统一位置AGENTS.md 就是把这些隐性知识外化、并收敛到唯一入口的那个载体。

五类实践:让 AGENTS.md 真正起作用

AGENTS.md 自己一份是不够的,它需要周边基础设施配合。下面五类实践来自在多个真实项目(Spring Boot + React 的全栈管控系统、C++ 内核引擎、产品基线、文档系统)里收敛出的共同经验。

实践一:仓库聚合,消除上下文割裂

前后端分仓的项目,最直接的改造是把前端仓库放进后端的子目录下,或者进一步重构为 monorepo。两种方案的代价不同,收益方向一致:让 AI 在一个窗口里同时看到 Controller 定义和对应的前端调用。

脚本聚合是成本较低的折中:一个 setup-repos.sh 把前端仓库 clone 到 frontend/ 下,.gitignore 里把 frontend/ 屏蔽掉,不影响后端 CI/CD,不用 AI 工具的同事完全无感。monorepo 是更干净的选择:前后端代码、用户手册、参考项目、构建脚本、文档在同一棵目录树下。

1
2
3
4
5
6
7
project-root/
├── server/ # 后端
├── web/ # 前端
├── user-guide/ # 用户手册(Markdown,可由 AI 基于代码生成)
├── reference-projects/ # 参考项目(git submodule)
├── scripts/ # 构建、启动、检查脚本
└── docs/ # 架构文档、设计文档

把用户手册也放进来有个副作用:AI 改完功能代码后可以顺手把对应的用户文档也更新掉。文档和代码在同一次 commit 里共进退,文档滞后问题自然消失。

聚合方案的两个常见顾虑值得一起回答。仓库变大,clone 时间是不是会爆炸? 用 git submodule 的 ignore = all 与按需 git submodule update --init <path>,参考项目可以延迟到真正要看时再拉。前端独立 CI/CD 怎么办? 子目录方案下前端仓库依然是独立的 git 仓库,独立 push/PR/部署,只是被本地 mount 进了后端工作区——这是一个本地视图问题,不是版本管理问题。pnpm workspaces / yarn workspaces 之类的 monorepo 工具同样可以服务这个布局。

实践二:环境配置归一,让 AI 能启动项目

AI 无法验证产出的根本障碍往往不在于 AI 不会验证,而在于验证所需的环境配置散落在每个人的 .bashrc、IDE JVM 参数、临时 export 里。AGENTS.md 只要无法指向一个明确的配置文件,Agent 就没法自主启动项目。

可行的约定是:所有本地环境变量统一写在 ~/.<project>_env,纯 KEY=VALUE 格式,启动脚本自动 source。放在 ~ 下而不是项目目录的理由是避免误提交到 Git。AGENTS.md 明确写清楚优先级:

1
2
3
### 数据库连接
1. 先查 ~/.<project>_env(启动脚本自动 source,文件不存在则跳过)
2. 若文件不存在,回退到 application.yml 中的缺省值

配套一键启动脚本:

1
2
3
./scripts/start-server.sh                # 构建 + 启动 + 健康检查
./scripts/start-server.sh --quick # 服务健康则秒返回
./scripts/start-server.sh --skip-build # 跳过构建直接重启

JDK 检测、优雅关闭旧进程、健康检查轮询这些细节被封装在脚本里,AGENTS.md 只暴露命令。Agent 的认知负担被压到最低:不需要理解 nvm、fnm、JAVA_HOME 的关系,只需要知道「要启动后端就跑这个脚本」。

这条实践和 12-Factor App 的 Config 原则方向一致,但有重要差别:12-Factor 服务于云上部署,强调环境变量从外部注入;AGENTS.md 服务于本地开发与 Agent 自动化,强调启动入口的单一与默认值的可发现。两者并不冲突——~/.<project>_env 在本地补全了云上由 Secret Manager 注入的那部分,但仍然 export 成环境变量,进程拿到的接口是同一个。direnv / .envrc 是另一种风格,缺点是依赖额外工具,Agent 在不知道这个工具存在时会找不到入口;统一脚本风格的好处是它对 Agent 的可见性是闭合的——AGENTS.md → 脚本 → 进程,链路里没有黑盒。

实践三:验证闭环,改完代码不算完

验证闭环是 AGENTS.md 实践里最容易被低估的一环。它直接决定了 Agent 的产出是「看起来做完」还是「真的跑通了」。

后端的验证主力是 curl。一个可靠的 curl 验证规范有几条硬约束:每个 curl 独立执行,不串联;响应写到 /tmp/ 下的临时文件,后续用 python3 独立解析;token 获取模板化,登录写文件、提取、后续请求携带。这套啰嗦是有原因的。Agent 在 shell 里执行命令时容易踩到兼容性坑,例如 zsh 下管道加方括号 glob 会炸,curl | python3 -c "print(data['key'])" 直接报错。临时文件中转多一步,稳定性高一个量级。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
# Step 1: 登录,结果写文件
curl -s -X POST http://localhost:8080/auth/login \
-H 'Content-Type: application/json' \
-d '{"username":"admin","password":"admin"}' > /tmp/login.json

# Step 2: 提取 token(独立命令)
python3 -c "import json; print(json.load(open('/tmp/login.json'))['data']['token'])" > /tmp/token.txt

# Step 3: 业务接口调用
TOKEN=$(cat /tmp/token.txt)
curl -s -X POST http://localhost:8080/providers/list \
-H "Authorization: Bearer $TOKEN" \
-H 'Content-Type: application/json' \
-d '{"page":0,"size":10}' > /tmp/result.json

前端的验证则要靠 Agent Browser。curl 看不到页面渲染、交互、布局问题,调试前端疑难杂症时需要让 Agent 自己打开浏览器、操作页面、截屏对比(Qoder 的 agent-browser、Claude Code 接入的 computer use 都提供这个能力)。这比让 Agent 凭空猜 CSS 问题要可靠得多。

验证闭环的完整形态是:lint 和格式检查在每次代码变更后自动触发,启动脚本把应用真正拉起来,curl 或 Agent Browser 跑接口/页面验证,Spec 的 Design 文档里明确写入验证方案。最后这一点尤其关键,它告诉 Agent「写完代码不算完,自测过功能才算完」。这套端到端验证是夜间自主执行能跑通的前提。睡前设计好 Spec 提交给 Agent,第二天早上验收结果,是只有在闭环完整时才成立的工作模式。

更进一步的方向是把验证本身也基础设施化。一种做法是写出一份 harness/config/environment.json,由 AGENTS.md 指向它:里面声明启动命令、依赖服务、必需环境变量、关键功能场景;每次任务结束由「verifier 子 Agent」基于环境契约和本次改动动态生成 verify.json,跑一遍冒烟。这种 environment.json 与 verify.json 分离的模式来自 OpenAI Harness Engineering 的实践,好处是验证策略不需要每次手写——你定义一次环境,验证逻辑在每次任务时按上下文重新生成。

另一个值得提的方向是 eval 化:把每次出现过的 bad case 留下来作为 eval task。验证闭环不只是「这次跑通了」,而是「这次跑通的能力下次还在」。

实践四:自动化检查,给规则装上执行力

AGENTS.md 里写的规则,如果没有自动化检查,AI 和人都会违反。规则的优先级天然是:能自动化检查的 > 写在 AGENTS.md 中的 > 口头约定的

典型例子是分层依赖检查。项目里定义严格的层级约束:

1
2
3
4
5
6
L0 - entity/          → 只允许依赖 common
L1 - repository/ → 只允许依赖 entity, common
L2 - core/ → 横切关注点,不允许依赖业务包
L3 - config/ → 允许依赖 core, service
L4 - service/ → 业务核心层
L5 - controller/ → 只允许依赖 service, core, common

光把这段贴进 AGENTS.md 是不够的。需要一个 shell 脚本扫描所有 Java 文件的 import 语句,按包路径判断层级,检测是否违反依赖方向。违规时输出的错误信息必须按 WHAT / WHY / HOW 三段式组织:

1
2
3
✗ service/gateway/impl/SomeService.java 导入了 entity.SomeEntity
原因: gateway 客户端实现禁止直接依赖业务 Entity,须通过 DTO 传递数据
修复: 在编排层完成 Entity→DTO 转换,客户端只接收 DTO

这三段不只是给人看的,更是给 AI 看的。Agent 读到这条错误信息后可以直接按 HOW 的指引去修,不需要额外上下文。集成到 make lint-arch,一条命令完成检查。改完代码 Agent 自己跑一遍检查,形成「改 → 检 → 修」的闭环。

错误信息的 agent-actionable 工艺值得单独说一句。同样是层级违规,下面两种写法对 Agent 的价值差一个数量级:

1
2
3
4
5
6
7
8
✗ 错误:Forbidden import in core/types/user.go

✓ 信息丰富:core/types/user.go:15 imports core/config(layer 0 → layer 2)。
Layer 0 包必须没有任何内部依赖。
修复方案:
1. 把依赖 config 的逻辑移到更高层(如 core/service/user.go)
2. 把 config 值作为参数传入而非直接 import
3. 在 layer 0 定义接口,由 layer 2 实现注入

第一种 Agent 看完只知道「错了」,会进入瞎试;第二种 Agent 直接挑选选项 1/2/3 之一执行。差别不在 lint 写得有多复杂,而在你愿不愿意把「为什么」和「怎么修」也写进去。

通过 Makefile 把质量检查命令收敛成统一入口:

1
2
3
4
5
lint-arch:    ./scripts/lint-deps.sh      # 分层依赖检查
lint-format: mvn spotless:check # 格式检查
format: mvn spotless:apply # 格式修复
build: mvn package -DskipTests # 构建
test: mvn test # 测试

Agent 不需要记住每个检查命令的具体写法,AGENTS.md 只暴露 make lint-archmake lint-format

这一条还有一个被忽视的延伸:把架构图也机械化生成。Mermaid 包依赖图、组件关系图、序列图都可以由 go list -json ./... 之类的导入分析自动产出,AGENTS.md 里写图本身没有意义——写「这张图由 make arch-diagram 生成」才有意义,因为这意味着图永远不会和代码失同步。

实践五:引入参考项目源码,替代永远滞后的文档

这一条是反直觉但效果最显著的一条。AI 不认识的东西——闭源私域组件、内部网关内核、公有云项目、竞品架构——靠写使用文档补上,成本高、覆盖不全、还总是滞后于实现。换一个思路:不写文档,直接把源码引进来。

1
2
3
4
5
6
7
reference-projects/
├── higress/ # 开源网关内核源码
├── nacos/ # 开源注册配置中心源码
├── teamix-pro/ # 闭源组件库源码(TypeScript)
├── apigo/ # 公有云 AI 网关后端(Go)
├── apigw/ # 公有云 AI 网关前端(React)
└── himarket/ # 开源 AI 开放平台(Spring Boot)

每个参考项目通过 git submodule 引入,配合 ignore = all 避免影响 CI/CD。本地按需拉取:

1
2
git submodule update --init                                # 首次拉取全部
git submodule update --init reference-projects/teamix-pro # 只拉取单个

源码永远不会过时,它本身就是最准确的文档。 AI 不会写私域组件时,直接读源码里的 TypeScript 定义和实现;要对接网关内核时,直接查看路由和插件的实际代码。代价是仓库体积变大和 submodule 的管理负担,换来的是 Agent 写代码的准确性。对于训练数据中不存在的内部项目,这个交易划算。

仅有源码还不够。每个参考项目配一份架构说明文档(docs/design-docs/ref-*.md),作为参考项目的地图:

文档 说明
ref-higress.md 网关内核:路由模型、插件机制、CRD 结构
ref-nacos.md Nacos:配置中心对接、服务发现集成
ref-teamix-pro.md Teamix Pro:ProTable/ProForm 使用模式、TS 类型速查
ref-apigo.md 公有云后端:目录结构、分层架构、核心模块
ref-apigw.md 公有云前端:页面结构、组件体系、路由设计
ref-himarket.md HiMarket:多模块结构、领域模型

ref 文档告诉 Agent「这个参考项目的整体结构和关键模块在哪」,reference-projects 提供细节。这些 ref 文档本身也可以由 AI 基于参考项目源码生成,又一个「代码生成文档」的闭环。生成 prompt 大致是「读 reference-projects/<name>/ 下的目录结构与几个核心文件,产出一份不超过 200 行的 AGENTS.md 风格地图,标注每个核心模块的入口位置和关键接口」,跑一遍即可。

引入这么多参考仓库会不会让 Agent 无所适从?实测下来不会。AGENTS.md 的项目结构树标注了每个目录的用途,ref 文档提供架构概览,参考优先级规则明确「什么时候该看哪个项目」。现在的大模型知道什么时候该去参考项目里翻实现,什么时候该在本项目里改动,不会因为仓库里多了几个参考项目就迷失方向。

AGENTS.md 的骨架

把上面五类实践沉淀成一份 AGENTS.md,大致的章节结构是这样的:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
# AGENTS.md

## 1. 项目概述
一段话:项目定位、技术栈、仓库结构。
前 10 行必须让 AI 建立项目心智模型。

## 2. 快速命令
构建、启动、格式化、质量检查的命令速查表。
环境变量配置说明(env 文件位置、启动脚本自动 source)。

## 3. 后端架构
包结构树(ASCII)+ 每个包的用途注释。
核心子系统简要说明 + 详细文档链接。

## 4. 前端架构
技术栈、路由方案、API 层约定、组件库规范。
详细文档链接。

## 5. 关键约定
5-10 条硬性编码规则(违反会直接导致问题的)。
每条规则附详细文档链接。

## 6. 本地开发与验证流程
「改 → 构建 → 启动 → 验证」完整闭环。
curl 验证模板、Token 获取、日志路径。

## 7. 质量检查
lint、format、build、test 命令矩阵。

## 8. 参考项目约定
参考项目列表 + 优先级规则。

## 9. 文档导航
所有详细文档的索引表。

控制在 200 行以内。超过这个范围说明有细节该拆到 docs/ 下的专题文档里。

为什么是 200 行而不是 500、不是 1000?回到前面 Lost-in-the-Middle 的曲线——一份 200 行的 AGENTS.md 配合现代模型,几乎全部落在「首尾区间」,每条规则都能被注意到;500 行就开始有相当一部分内容滑入中段衰减区;1000 行以上,关键规则的命中率和写在某处源码注释里没有本质差别。这是一个工程取舍,不是审美偏好。

每个一级标题加序号(## 1. 项目概述 而不是 ## 项目概述),是为了让 detail 文档在跨文档引用时有稳定 anchor:docs/architecture.md 里写 “依赖方向参见 AGENTS.md §5” 就不会因为顺序调整而失效。

子项目嵌套是另一个关键技巧。OpenAI Codex 自己的仓库里有 88 份 AGENTS.md,每份对应一个子项目目录,最近原则生效——Agent 在 packages/foo/ 下工作时优先读 packages/foo/AGENTS.md,再叠加根目录的全局 AGENTS.md。这套就近原则把项目根的全局规则和子项目的具体细节解耦:根 AGENTS.md 不必为每个子项目维护章节,子项目的 AGENTS.md 也不必重复全局约定。前提是工具本身支持就近读取——Codex 和 opencode 原生支持,Claude Code 通过 CLAUDE.md 的目录树机制天然就有,Cursor 用 .mdc 的 glob 实现类似效果。

AGENTS.md 与同类文件的兼容关系

虽然 AGENTS.md 在收敛成事实标准,但每个工具仍有自己的偏好与扩展。理解这些差异,决定了一份 AGENTS.md 能不能在多种工具下都生效。

工具 默认文件 加载方式 嵌套语义
OpenAI Codex / opencode AGENTS.md 自动;最近原则 多级嵌套,子目录覆盖父目录
Claude Code CLAUDE.md(默认) 自动;从 cwd 向上层走目录树拼接 支持 @path/to/file.md 引用(v2.107+)
Gemini CLI GEMINI.md 自动;项目级 单一项目根
GitHub Copilot .github/copilot-instructions.md 自动 单文件
Cursor .cursor/rules/*.mdc(也读 AGENTS.md 部分自动、部分按 glob 匹配 规则按 glob 作用域
Aider AGENTS.md 自动 项目根
Hermes-agent AGENTS.md 全部子目录组合 + SOUL.md 总是加载 自动 全部组合

几个值得注意的细节:

  • Codex / opencodeAGENTS.md 当作 system prompt 直接拼进上下文,最近的覆盖最远的;这意味着「每个子目录写自己的小 AGENTS.md」是被原生支持的工作模式。
  • Claude Code 默认读 CLAUDE.md,不读 AGENTS.md。最干净的兼容方案是 CLAUDE.md 里只写一行 @AGENTS.md,让 Claude Code 通过 @importAGENTS.md 当成内容引入。这条 import 是 v2.107(2025-05-09)才加上的,更早的 Claude Code 需要用软链 ln -s AGENTS.md CLAUDE.md 来桥接。
  • Cursor.mdc 规则系统更复杂——可以按文件 glob 设置作用域、也可以标 always-apply。Cursor 也会读 AGENTS.md,但语义上和 .mdc 不完全等价;如果 Cursor 是团队主力,把核心约定放 .mdc 里、把通用项目地图放 AGENTS.md,是比较稳的写法。
  • Copilot.github/copilot-instructions.md 是单文件、单作用域、自动加载。把它配置成「include AGENTS.md 内容」是一种实践,但 Copilot 本身不支持 @import,只能复制粘贴或用脚本 sync。

实操层面,团队最少需要做三件事:

  1. AGENTS.md 是源。把项目级所有约束、命令、文档导航写在这里。
  2. 其他工具的入口文件作为薄壳CLAUDE.md = @AGENTS.mdGEMINI.md 同;.cursor/rules/main.mdc 写一段引用。
  3. 承认 Copilot / Cursor 的扩展能力。如果团队真的依赖 Cursor 的 .mdc glob 作用域或者 Copilot 的特定语法,那它们是 AGENTS.md 之外的补充而不是替代——补充在工具特定文件里,绝不复制规则文本,复制就埋下漂移。

记忆层级是另一个值得对照的概念。Claude Code 提供四层记忆:Enterprise managed policy → User Global(~/.claude/CLAUDE.md)→ Project(./CLAUDE.md)→ Local 嵌套,加上 @import 递归。AGENTS.md 模型故意放弃了这种层级——它不区分 user global 和 project,所有内容都进项目仓库。这是一个有意识的取舍:单文件、可移植、可被任何工具读到,代价是没法表达「我个人偏好用 vim 风格快捷键」这种用户级配置。后者本来也不该写进项目共享文件,留给工具的 user-level 配置即可。

AGENTS.md 与 Skill / Slash command 的取舍

Anthropic 在 2025 年 10 月推出 Skills,把 AI 助手的「能力」做成可独立分发的目录,引出了一个新问题:什么应该写进 AGENTS.md,什么应该做成 Skill?

两者的根本差别是加载时机

  • AGENTS.md 永远在场。每次会话启动,工具把 AGENTS.md 内容拼进 system prompt 或上下文头部。它是「项目是什么样子」的常驻说明。
  • Skill 按需加载。Anthropic Skills 用三层渐进式披露——session 启动只载入 frontmatter 元数据(一行 description),SKILL.md 正文要等模型判断「这个 skill 跟当前任务相关」时才加载,references/scripts/ 推迟到执行需要时才取。一个仓库可以挂几十个 Skill,绝大多数永远不会被加载,token 成本接近零。

这条加载差异决定了归属判断:

内容 应该在哪 原因
项目结构、技术栈、构建命令 AGENTS.md 每个任务都要用
分层架构约束、命名规范 AGENTS.md 永远开
常用 lint / build / test 入口 AGENTS.md 高频
验证 PR 是否准备好合并 Skill 只有在「我做完了」这种节点用
把 Markdown 报告渲染成 PPTX Skill 偶发任务
跑一份安全审计 Skill 按场景触发
把日志切片再喂给一个分析子 agent Skill 工作流式、需要 scripts
团队特定的 git commit message 风格 可两可——日常用就 AGENTS.md,特殊场景才用就 Skill 看频率

一条简单的判断启发:如果省略它会立刻写出错代码,进 AGENTS.md;如果只是某些任务需要的 workflow / 工具组合,进 Skill。

Skills 的另一个好处是可独立版本与可分发harness-creatorpdf-extractorsecurity-reviewer 这种通用能力做成 Skill 之后,可以跨项目复用;AGENTS.md 是项目自己的描述,没有跨项目复用的语义。社区已经出现 SKILL.md 包目录、Skill marketplace 等基础设施,把 AGENTS.md 也朝这个方向推不太合适——AGENTS.md 越通用化越没用,它的价值恰恰在于"对这个项目而言"。

Slash command 是 Skill 的一种轻量形态:固定流程、用户主动触发,不依赖模型判断相关性。当一个流程足够刚性、又总是由人类发起时,Slash command 比 Skill 干净。

怎么开始写

从零写一份 AGENTS.md 的门槛比想象中低。

第一种起步方式:大多数 AI Coding 工具提供 /init 或等价命令(Claude Code 的 /init、Qoder 的 qoder init),扫描项目结构生成初始版本。自动生成的版本覆盖项目概述和基本构建命令,作为骨架够用。

第二种起步方式:用更完整的 Skill 起步。社区里有几个开源的 harness-creator 风格 Skill,能一次性生成 AGENTS.md、分层架构 lint 脚本、Makefile、验证脚本、参考文档骨架,相当于把本文五类实践打包成一键产出。本文末尾给出一份脱敏过、可直接拷进 ~/.claude/skills/ 或本地 Skill 目录的版本。

起步之后的迭代是 bad case 驱动 的:

  • 某次 AI 用了错误的命名风格 → 思考「如果 AGENTS.md 里多一条 XX 规则,是不是就不会犯」→ 补一条
  • 某次 AI 在错误的层级引入了依赖 → 是全局规则,加到 AGENTS.md;或者补一个 lint 脚本自动拦
  • 某次 AI 在某个模块用错了某个 prop → 是模块细节,补到对应的 docs/ 文档里
  • 某次 AI 改完没自测,PR 里漏了关键路径 → 是验证闭环漏洞,补 harness/config/environment.json 里的 functional scenario

判断改哪里的标准始终是一条:全局规则进 AGENTS.md,模块细节进 docs,能 lint 的让 lint 说话。如果所有细节规则都往 AGENTS.md 里怼,上下文会膨胀到重要规则被淹没。

更结构化的迭代节奏是把 bad case 沉淀成两种产物:第一种是 AGENTS.md 的新规则(如果是项目级、高频、机械可识别的违规);第二种是 eval task(如果是某个具体场景,留下来作为回归测试)。两条流水线并行——AGENTS.md 防止下次同类 bad case 再出现,eval 检查这次的修复是否真的生效并守得住后续修改。

AGENTS.md 不腐烂

写一份 AGENTS.md 不难,难的是它一年后还有用。文档腐烂在 AI Coding 场景比传统场景更危险——一条过期规则给人类只是误导,给 Agent 就是 30 个 tool call 之后才发现走错方向。社区里几种已知的腐烂姿态:

链接腐烂AGENTS.md 引用 docs/foo.md,文档被重命名或删除后链接失效。Agent 顺着断链走会得到 404,更糟的情况是中间还过了一层别名重定向,最后落到一个看似相关其实不对的页面。检测办法:一份 50 行的 Python 脚本扫描所有 markdown 链接,对内部相对路径用 os.path.exists 校验,外部链接用 HEAD 请求探活,CI 跑一遍。

Interface driftAGENTS.md 写「核心接口 UserServiceinternal/core/user.go:18」,半年后 UserService 重命名为 UserManager、文件也挪到 internal/users/service.go,但 AGENTS.md 没更新。Agent 读到这条线索去找 UserService 找不到,可能就编造一个出来。检测办法:对 AGENTS.md 里出现的所有类型名 grep 代码,找不到的标记为 P0 警告。

陈旧规则压过新规则。Reddit 上一种常见抱怨:AGENTS.md 写「不能直接 import L2 包」,但项目两个月前已经决定打通 L2,AGENTS.md 没改,结果 Agent 严格遵守一条已经废弃的规则。这一种比缺信息更糟糕——比起「不知道」,「知道一条错的」更难纠偏。

100 本书塞进一个文件AGENTS.md 涨到 2000 行,包含每个团队成员的偏好、每次 retro 的结论、所有踩过的坑。结果是没人读,Agent 也读不进去。

当成安全边界。把 “不要读 production env” 写进 AGENTS.md 是一回事,把它当作真正的访问控制是另一回事。AGENTS.md 是建议,不是 sandbox。关键约束的兜底必须在 lint、execpolicy、shell sandbox 里——harness-creator 提到的 environment.json 把所有 secret 标记为 ${VAR} 引用、绝不内联值,是一个相同思路的具体化。

对应的工程化对策:

  1. 链接 / interface drift 检测脚本作为 CI 的一部分。每个 PR 跑一遍,发现新增断链或类型不匹配就 fail。
  2. AGENTS.md 的最小行数 / 最大行数硬性预算。低于 60 行通常说明骨架未完成,高于 250 行通常说明该往 docs/ 拆。CI 里加 wc -l AGENTS.md 的硬阈值。
  3. 维护节奏挂在 release 节奏上。每次 release 前的 review checklist 加一项:AGENTS.md 是否需要更新。比 “某天定期 review” 这种口号有用得多。
  4. 关键规则双写。重要约束在 AGENTS.md 写明的同时,必须在 scripts/lint-* 里有机械检查兜底。前者是给 Agent 看的「为什么」,后者是给系统看的「不许做」,两者只缺一个就会出事。
  5. 每条进入 AGENTS.md 的规则必须能溯源。理想做法是写明「来自 PR #1234 的 retro」「来自 incident 2026-04-12」。当规则失去 context 时,半年后没人敢删。

谁来读这些文件

推广 AGENTS.md 时,明确标注每个文件的读者有助于降低团队的理解成本:

文件 读者 说明
README.md 项目介绍、快速开始,人类入口
AGENTS.md AI 为主,人可浏览 AI 工具自动读取的项目指令
docs/*.md AI 为主,人可参考 各模块的开发手册
scripts/*.sh 人和 AI 共用 构建、启动、部署脚本
setup-repos.sh 人执行 一键环境搭建
harness/config/environment.json AI / Verifier 子 Agent 运行时契约:依赖、启动、env vars
~/.claude/CLAUDE.md 或类似 user-global 个人 个人偏好、不进项目仓库

README 和 AGENTS.md 是互补的。README 聚焦快速开始和贡献指南,AGENTS.md 聚焦构建命令、编码规范、验证流程。内容可能有少量重叠(比如项目概述),侧重点不同,不需要合并。脚本是人和 AI 共用的部分,因此脚本本身的可读性和健壮性格外重要,其中的每一行注释都可能成为 Agent 决策的依据。

为什么是 AGENTS.md

工具不统一的现状下,AGENTS.md 作为核心入口的价值来自几个方面:

  • 足够通用:被大多数主流工具识别,一份文件覆盖多数场景
  • 零配置成本:工具打开项目自动读取,不需要安装插件或配置 hook
  • 维护负担低:不用为每种工具维护一份规则文件
  • 兼容性好:不识别 AGENTS.md 的 Claude Code 可以通过 CLAUDE.md 内的 @AGENTS.md 引用接入,迁移成本接近零
  • 有标准 steward:AAIF 在 Linux Foundation 旗下托管,有 backing 而不是某家厂商的私货

和特定工具绑定的 rules、hook、Skill 作为补充,核心规则全部收敛到 AGENTS.md 一个入口。换工具时迁移成本趋近于零。

作为知识沉淀的副作用

AGENTS.md 的初衷是给 AI 看,它的维护过程本身是一次团队知识梳理。过去散落在 Wiki、聊天记录、口头约定里的编码规约,现在被结构化地写进了一份 Markdown。新人入职读完 AGENTS.md,就能拿到过去需要摸索很久的「潜规则」。给 AI 写 AGENTS.md 的过程,客观上也是给人写了一份项目使用手册。

这和 Code as Documentation 的思路一脉相承:隐性知识外化成可执行、可校验的文本,才能跨越时间和人员传承。AGENTS.md 加上 lint 脚本、启动脚本、验证规范,本质是在构建一个反馈回路:Agent 读 AGENTS.md 理解项目,写代码,自动检查,启动验证,根据结果修正。人类的角色从回路中的每一步执行者,变成回路本身的设计者。

更进一步看,AGENTS.md 标记了一种工程文化的转折:从「文档是给人看的副产品」变成「文档是给 Agent 用的主输入」。一旦文档有了机械读者,它就被迫保持准确——因为 Agent 不会自动忽略矛盾、不会主动修补遗漏,每一处不准都会在某次 tool call 里付出代价。某种意义上,AI Coding 让我们第一次有了一支严格执行 RFC 的工程团队,前提是我们愿意把 RFC 写得让它能用。

一份可参考的起步 skill

把上面所有实践打包成一键产出的工具叫 harness-creator——一个 Anthropic Skill 风格的目录,扫描你的仓库后产出 AGENTS.mddocs/ 架构骨架、scripts/lint-depsharness/config/environment.json、Makefile、CI 模板。作者把它脱敏后放在了 claude-configurations/skills/harness-creator/ 下,可以直接 cp -R~/.claude/skills/ 用。

它的工作流大致是:Phase 1 探测项目状态(空仓库 / 已有代码 / 部分 harness / 完整 harness),Phase 2 并行跑三个分析子 Agent(架构 / 现状 / 环境),Phase 3 计算 delta,Phase 4 并行跑创作子 Agent 产出文档、lint、配置,Phase 5 跑验证。所有 prompt 模板和 reference 文档都在仓库里可读。

如果你打算自己起步而非用现成 Skill,这份目录里的 references/documentation-templates.mdreferences/linter-templates.mdreferences/environment-config-guide.md 也可以单独当作素材库参考——本文五类实践的所有具体格式(AGENTS.md 章节、Mermaid 包依赖图模板、layer linter 错误信息样式、environment.json schema)都在里面有可复制的版本。

参考资料