写好 AGENTS.md
如果你打开过一份 AGENTS.md,里面是一堆旧 command、半记得的偏好,还有一条明显和 repo 冲突的规则,你已经懂这个问题了。这个文件本来应该是 Codex 的长期 instruction layer。现实里,它常常变成大家把上周对 agent 的不满全贴进去的地方。那不是记忆。那是沉积物。
AGENTS.md 重要,是因为 Codex 做事前会读它。OpenAI 的 Codex docs 把它描述成给项目的 custom instructions 和 context,会从 global 和 project scope 发现,再按 precedence 合并。这让它和 Claude Code 里的 CLAUDE.md 很像:它不是整个 harness,但它是 agent 最先看到的 harness state 之一。它如果锋利,会话就从正确约束开始。它如果模糊或过期,agent 一开始看的就是错地图。
这篇讲的是怎样写一份顶得住压力的 AGENTS.md:它是干什么的,哪三个属性最重要,每份好文件都需要哪五个章节,我在 audit 里反复看到哪些坏模式,还有一个你可以直接偷走的 before/after 例子。
AGENTS.md 是用来做什么的
AGENTS.md 不是 README。README 是给人看的,人可以追问、跳读、忽略旧章节。AGENTS.md 是给一个马上要跑 command、改文件、甚至在 harness 允许时做不可逆决定的 agent看的。
这个区别会改变写法。好的 README 可以讲故事。好的 AGENTS.md 更像 boot sequence。它告诉 agent:现在在哪个世界里,哪些 command 真实存在,哪些规则是硬规则,哪些假设危险,以及更深的资料在哪里。
harness 这个框架在这里很有用。Mitchell Hashimoto 对 harness engineering 的说法、OpenAI 关于 Codex repository knowledge 的写法、还有 LangChain 最近关于 agent harness 的文章,都指向同一件事:model 本身不是产品。model 外面的系统很重要。tools、rules、memory、feedback loops、CI、docs、permissions 都会改变结果。AGENTS.md 就是这个系统里 always-loaded 的 prose layer。
但这不代表 AGENTS.md 应该装下所有东西。OpenAI 自己的 harness engineering lesson 也很清楚:巨大的 instruction file 会按固定方式失败。它挤掉当前 task,让每条规则看起来同样重要,会腐烂,也很难验证。更好的模式是短 AGENTS.md:足够给 agent 定位,足够严格避免常见错误,也清楚写出更深 source-of-truth documents 在哪里。
“好”是什么样子
好的 AGENTS.md 有三个属性,顺序不能换:正确、一次会话能读完、被 prose 之外的层执行。
正确性排第一,因为一条假规则会污染其他规则。如果 AGENTS.md 说 “use pnpm”,但 package-lock.json 和 CI 证明项目用 npm,这个文件就是错的。如果它说 “run make test”,但没有 Makefile,这个文件也是错的。agent 最后会发现不匹配。一旦发现,文件里其他每条规则都会没那么可信。过期 guidance 不是中性的。它会教 agent 把 instructions 当建议。
可读性排第二。Codex 要把 instructions 和用户任务、代码、command output、tool results 一起塞进 working context。900 行 AGENTS.md 通常说明没人决定什么才重要。root AGENTS.md 一个实用目标是 200 行以内。只有当某个 subdirectory 真的有不同规则时,才加 nested AGENTS.md。如果项目需要 40 页知识,把知识放进 docs/,再从短 map 里链接过去。
执行排第三。AGENTS.md 可以说 “no direct pushes to main”。它拦不住 git push origin main。AGENTS.md 可以说 “run typecheck before merge”。它不能让 typecheck 通过。规则只有在 CI、hooks、permissions、review gates 里被执行时才变成事实。prose 告诉 agent 要做什么。harness 证明它有没有做。
每份 AGENTS.md 都需要的五个章节
我见过那些真正撑得住的文件,反复出现同样五个章节。名字可以变,工作不变。
第一节是 project facts。一小段。项目做什么,用什么 stack,是什么类型产品,主要代码在哪。不要写 pitch。它应该让 agent 分清这是 marketing site 还是 CLI,是 monorepo 还是单 app,是 library 还是内部 service。
第二节是 commands。今天真的能跑的 command:install、build、lint、unit test、integration test、dev server、code generation。包管理器边界也要写。如果 CI 用 npm,就写 npm。如果 repo 用 uv,就写 uv。不要列你希望存在的 command。也不要列所有可能 script。只列 agent 被期望会跑的那些。
第三节是 working rules。branch、commit、PR 期待、review policy、dependency policy、generated files、lockfiles、secrets。这些规则会影响每一次 edit。它们必须可证伪。“Keep changes small” 很弱。“One logical change per commit; if the commit message needs 'and', split it” 更强。
第四节是 architecture and boundaries。什么不能重写。哪一层负责什么。哪些目录是 generated。哪些 API 稳定。哪些 migration 是单向的。这个章节避免最贵的一类 agent 错误:看起来合理、但违反本地 architecture 的 refactor。
第五节是 deeper references。指向真实 docs,不要复制它们。docs/auth.md 放 auth invariants。docs/release.md 放 release procedure。.github/workflows/ci.yml 是 CI truth。package.json 是 script truth。这样 AGENTS.md 保持短,agent 需要细节时也有路可走。
常见问题
第一个常见失败是 模糊。“Write clean code” 不是规则。“Follow best practices” 不是规则。“Be careful with migrations” 也不是规则。agent 没法验证。改成有明显 pass/fail 的规则:
- Database migrations must be reversible unless the PR description says why not.
- Public API changes require updates to docs/api.md and at least one integration test.
- New React components must live under src/components/ and use existing UI primitives before adding new styling primitives.
第二个失败是 冲突。这经常发生在 global 和 project instructions 打架时。global AGENTS.md 说 “use pnpm”。repo AGENTS.md 说 “use npm”。如果 project rule 是故意覆盖 global rule,这可以没问题,但文件要写出来。“Use npm in this repo; overrides global pnpm preference because CI and lockfile are npm-based.” 这样冲突就不是 bug,而是声明过的 override。
第三个失败是 过期引用。path 会搬。script 会改名。CI job 会被替换。AGENTS.md 里的过期 command 比没有 command 更糟,因为 agent 会浪费时间跑它,然后开始自己猜。每次改 AGENTS.md,都应该顺手检查 path 和 command 还在不在。
第四个失败是 没有边界的章节。“Guidelines” 一开始有五条好 bullet。三个月后变成四十条。有些是偏好。有些是规则。有些是解释。有些互相重复。章节一旦无边界,就没人维护。给它预算。如果 “Principles” 超过七条,就删掉或搬走。
第五个失败是 语言漂移。文件一开始是英文,后来塞进中文聊天摘录,又加了复制来的 CI logs。或者把给人看的 commentary 和给 agent 的 commands 混在一起。agent 也许还能 parse,但这种漂移是维护味道。给文件选一种语言。code、commands、file names 保持 literal。解释保持短。
一个完整例子
这是一个基于真实 audit 模式的 before:
# AGENTS.md
## Notes
- This is a React app.
- Use good TypeScript.
- Tests are important.
- Do not break production.
- Use the right package manager.
- Ask before big changes.
- There are docs in the docs folder.
- Follow the existing style.
意图没问题。文件几乎没用。“Good TypeScript” 没边界。“Tests are important” 没说哪些 tests。“Right package manager” 让 agent 自己从 filesystem 猜。“Big changes” 是 model 当时觉得大的东西。“Docs in the docs folder” 指向所有地方,也等于没有指向。
同一个文件可以这样重写:
# AGENTS.md
## Project facts
This is a Vite + React + TypeScript marketing site for a developer tool.
The app code lives in src/. Static content lives in src/content/.
## Commands
- Install: npm ci
- Local build: npm run build
- Unit tests: npm test
- Lint: npm run lint
## Working rules
- Use npm only. Do not add pnpm/yarn/bun lockfiles.
- Keep blog content in English unless the file is under src/content/blog/zh/.
- Do not edit generated dist/ output by hand.
- No direct pushes to main. Open a PR for all changes.
## Architecture and boundaries
- Route metadata is centralized in src/lib/seo.ts and scripts/generate-sitemap.ts.
- Blog slugs are derived from markdown filenames after stripping numeric prefixes.
- If you add a blog post, update related posts, sitemap routes, and OG metadata in the same change.
## References
- package.json is the source of truth for commands.
- scripts/generate-sitemap.ts is the source of truth for static routes.
- src/content/blog/ contains editorial style examples.
它还是很短。但每条重要规则都能测试。你能检查 lockfile。你能跑 command。你能看 route script。你能验证 blog change 有没有一起更新旁边的 metadata。agent 要读的字更少,拿到的约束更真实。
为什么 linter 应该进循环
就算 AGENTS.md 写得好,它也会漂。repo 会变,package manager 会变,global file 会变,services/payments/ 下面还会加 nested override。正确的维护习惯不是“第一次写到完美”。正确习惯是每次关键时刻都把文件和 repo 对一遍。
AgentLint 抓的是会让 AGENTS.md 悄悄不可靠的失败模式:模糊规则、冲突、过期 path、过期 command、缺少执行、没有边界的章节、语言漂移。目的不是让文件漂亮。目的是确保 Codex 做事前读到的 instruction layer 仍然是真的。