把 AGENTS.md 写成分区合同:让代理按目录收窄改动
我见过最浪费 token 的 AGENTS.md,开头十行是“写干净代码”“注意安全”,后面两百行把前端、迁移、发布和值班手册全塞在一起。代理拿到一个改按钮的任务,也得先消化数据库回滚规则。更糟的是,根目录那份大而全的说明会把小任务推向一个默认动作:多读一点,多改一点,最后顺手碰到不该碰的目录。[unverified]
把它拆成分区合同会好很多。Codex 启动时会先读全局指令,再从仓库根目录一路读到当前工作目录;越靠近当前目录的文件排在后面,因此能覆盖前面的规则。[3] 这不是把提示词写得更有文采,而是利用实际的加载顺序,把“这里能做什么、不能做什么”放到离代码最近的地方。
根目录只保留不变量#
根目录的 AGENTS.md 不该承担百科全书的工作。它只写每个目录都必须遵守的事:包管理器、全仓验收入口、密钥禁令、变更记录格式,以及遇到跨目录影响时必须停下来的条件。规则必须可观察,别写“谨慎修改”,改成“改动 packages/api 的公开类型后运行 pnpm test:contracts”。[unverified]
原因很朴素:代理会按层合并指令,文件越深,越应该描述局部例外。[3] 根规则一旦混进“支付目录只能运行某个命令”,其他地方要么白白背着它,要么有人复制一份再慢慢漂移。真正要长期稳定的不是提示词长度,而是规则的归属。
我会把根文件压到能一屏读完,并把它当成升级协议:任何任务只要需要新依赖、数据迁移、权限变更或触及未列出的目录,就停止编辑,说明影响范围,等人确认。这里的“停止”很重要。它让不确定性浮出水面,而不是由模型自行补全。[unverified]
子目录写差异,不重复宣言#
比如 services/billing/AGENTS.md 只关心结算服务:哪些命令可以跑、哪些 fixture 可以改、迁移文件是否允许新增,以及必须由谁复核。apps/web/AGENTS.md 则写页面快照、组件目录和可访问性检查。两份文件不用重复“不要提交密钥”,因为根规则已经覆盖它。[unverified]
Codex 在每层目录只采用一个指令文件,优先顺序是 AGENTS.override.md、AGENTS.md,再到你配置的备用文件名;空文件会跳过。[3] 所以临时试验别去篡改团队共享的合同,可以在当前目录放 AGENTS.override.md,让它覆盖同层常规文件。做完就删。把临时豁免留在常规规则里,半年后通常没人知道它为什么存在。
这里有一个反直觉的边界:目录规则不是文件锁。它在给 agent 描述约束和验收路径,不会凭空替代版本控制、CI、权限系统或人工评审。高风险目录仍然要有受保护分支和真正的发布权限。指令负责让默认路径更窄,系统控制负责在越界时真的挡住。[unverified]
快速上手:三层合同,先在一个服务试跑#
下面的布局不改应用代码,只新增三份说明。先从仓库根目录开始:
mkdir -p services/billing
cat > AGENTS.md <<'EOF'
# 全仓规则
- 使用 pnpm;不要新增生产依赖,除非任务明确要求。
- 禁止读取、输出或提交任何密钥、令牌和 .env 文件。
- 改动跨出当前任务目录、涉及迁移或权限时,先停下并报告影响。
- JavaScript/TypeScript 改动后运行 pnpm test。
EOF
再给结算服务加局部合同。命令名按你的仓库替换,别把不存在的示例原样抄进生产规则:
cat > services/billing/AGENTS.md <<'EOF'
# Billing 局部规则
- 只修改 services/billing、它的测试和任务明确列出的共享类型。
- 不执行真实扣款、生产数据库命令或网络迁移。
- 修改计算逻辑后运行 pnpm --filter billing test。
- 若需要 schema 迁移,停止编辑,列出受影响表和回滚方案。
EOF
最后为一次本地探索建覆盖文件,不要提交:
cat > services/billing/AGENTS.override.md <<'EOF'
# 临时探索
- 本次只允许阅读和运行测试;不要编辑文件。
EOF
codex --cd services/billing --ask-for-approval never \
"列出你加载的指令来源,并说明本任务允许做什么。"
rm services/billing/AGENTS.override.md
官方文档建议用类似命令核对加载顺序,也说明每次新运行都会重新构建指令链,不需要手动清缓存。[3] 第一次验证别让 agent 改代码,故意给它一个会越出 services/billing 的小任务,检查它是否明确报告边界。接着再跑一个只改测试的任务,确认它给出的验收命令正是局部合同里的命令。规则的价值不在文件存在,而在越界时能否改变实际行为。
把合同当成会演化的接口#
当团队开始用子代理时,这种分层会更划算。Claude Code 的自定义子代理也能限定工具、独立权限和上下文;官方建议把可复用的项目级定义放进 .claude/agents/ 并提交到仓库。[2] 如果还要用 hook 做硬拦截,项目级配置可放在 .claude/settings.json,但别把它和 AGENTS.md 混为一谈:前者在生命周期事件上执行外部动作,后者提供任务指令。[1][3] 目录合同可以先告诉协调代理什么叫“完成”,再让只读审查代理检查 diff 和命令输出。两者别互相替代:AGENTS.md 管任务上下文,工具白名单管实际能力。
还有一个容量问题值得提前处理。Codex 会在合并的项目指令达到配置上限时停止继续加入,默认上限是 32 KiB。[3] 这不是鼓励你把阈值调大。先删掉重复的口号、过期命令和复制来的长篇规范;只有确有必要时才拆到更深目录或调整上限。代理需要的是当前路径的决定,不是整间公司的档案室。
好的 AGENTS.md 不会让代理显得更聪明,它只是让它少做自作主张的事。根目录写共同底线,子目录写局部风险,临时例外用 override 并及时删除。这样复盘一段奇怪的改动时,你至少能回答:它当时读到了什么,又是哪一条规则允许它走到这里。[unverified]
Sources#
[1] https://docs.anthropic.com/en/docs/claude-code/hooks — Claude Code Hooks reference [2] https://docs.anthropic.com/en/docs/claude-code/sub-agents — Claude Code custom subagents [3] https://developers.openai.com/codex/agent-configuration/agents-md — OpenAI Codex AGENTS.md