如何用 dox 自动保持 AGENTS.md 最新
AGENTS.md 三周后就会过时,代理仍会相信它。用 dox 从仓库重新生成文件,再像审查代码一样检查差异,避免代理执行失效命令并产生多余修改。
为什么您的 AGENTS.md 三周后就不正确了
AGENTS.md 会过时,因为没有任何机制将它与代码关联起来。您在仓库处于某种状态的当天手动编写了它。随后,测试运行器发生变化、软件包被重命名、服务被删除,但文件仍在描述6月的状态。没有任何步骤会失败,因为构建流程不会读取它。
代理会读取它,并相信其中的内容。这才是代价所在。没有 AGENTS.md 的仓库会让编码代理在执行操作前先检查环境。有错误 AGENTS.md 的仓库则会让代理停止检查,因为它已经得到了答案。它会运行文件中指定的命令,shell 返回 Missing script: "test",然后代理开始猜测。它通常会修改 package.json,加入文档中承诺存在的脚本。过时的文件并没有悄无声息地失效,而是导致了您并不需要的修改。
dox 是一种解决方案。它是一组为代理编写的规则,要求将文档更新作为完成工作的一部分,因此文件会与导致其内容失效的代码在同一个提交中发生变化。
dox 是什么,以及它不是什么
dox 是一个 Markdown 文件。该仓库位于 agent0ai/dox,采用 MIT 许可证。截至 2026 年 8 月 11 日,整个项目由一个 3906-byte 的 AGENTS.md、一个 README、一个 LICENSE 和两张图片组成。项目无需安装软件包,也没有运行时。
这一点很重要,因为 “generator” 这个词会让人以为它是一个解析代码的程序。实际上,没有任何程序会解析您的代码。dox 是编码代理读取的一份契约:您的代理负责生成内容,dox 则是一组指令,规定代理何时读取文档、何时重写文档,以及每份文档应采用什么结构。
该文件包含 10 个部分,其中两个部分负责实际工作。“编辑前阅读”要求代理从仓库根目录开始,遍历计划修改的每条路径,并在当前会话中读取沿途的每个 AGENTS.md,不得依赖记忆。“编辑后更新”要求代理在每次有意义的修改后执行一次 DOX 流程,也就是在任务完成前执行文档更新步骤。当用途、结构、工作流、权限或用户偏好发生变化时,该流程会更新负责归属的最近文档。
其余部分规定文档结构。子目录中的 AGENTS.md 默认按以下顺序组织:用途、归属、本地契约、工作指南、验证和子 DOX 索引。根目录文件包含项目范围的规则和顶层子 DOX 索引,代理通过该索引发现子文档。“收尾”是代理在任务结束时执行的检查清单:根据文档链重新检查已修改的路径,更新最近的归属文档,刷新所有受影响的索引,删除相互矛盾的内容,运行现有验证,并报告哪些文档是其有意未修改的。
将 dox 固定到一个提交,而不是 main
该仓库没有标签和发布版本,因此没有可用于固定的版本号。请改为固定提交。当前的 AGENTS.md 对应提交 f34ec7ad1055d3393887e5a2670e8cb7320c9165,日期为 2026 年 8 月 1 日。
mkdir -p .agent
curl -fsSL -o .agent/dox-f34ec7a.md \
https://raw.githubusercontent.com/agent0ai/dox/f34ec7ad1055d3393887e5a2670e8cb7320c9165/AGENTS.md
wc -c .agent/dox-f34ec7a.mdwc -c 应输出 3906。如果输出了其他数字,说明您没有获取本指南所描述的文件,因此请先阅读该文件,再信任其中的内容。如果您输错提交哈希,-f 会使 curl 停止并输出 curl: (22) The requested URL returned error: 404,且不会写入任何内容;随后 wc -c 会输出 0。文件内容被截断比没有文件更糟,因为 agent 会在不知情的情况下只遵循部分约定。
cp .agent/dox-f34ec7a.md AGENTS.md
git add AGENTS.md .agent/dox-f34ec7a.md
git commit -m "Add DOX rules (agent0ai/dox @ f34ec7a)"该 cp 适用于尚不存在 AGENTS.md 的仓库。如果已有 AGENTS.md,请勿覆盖它。将 dox 部分放在现有内容上方,将您自己的规则保留在下方,然后从头到尾通读一次结果。两个相互矛盾的文档会导致 agent 遵循它最后读到的那条规则。
然后在仓库内要求您的 agent 执行第一轮处理。README 提供了确切措辞:
Initialize DOX tree for this project now.它会创建子目录中的 AGENTS.md 文件,以及指向这些文件的索引。请先检查其结果,再确认处理正确:
git status --short
find . -name AGENTS.md -not -path './.git/*' | sortfind 输出中的每个文件都应出现在上方某个 Child DOX Index 中。索引未提及的子文档可能会被 agent 遗漏,因为 agent 正是通过索引找到不在其当前遍历路径上的文档。
dox 能看到什么,以及它无法知道什么
构建目录树的 agent 会读取代码仓库,因此仓库中的任何内容都可以写入清单:目录结构、包清单和锁定文件、package.json、Makefile 或 pyproject.toml 中的脚本、CI 工作流文件、Dockerfile、入口点,以及(如果有)CODEOWNERS。根据这些内容构建的清单确实可以自行维护。包移动后,下一次扫描会移动描述该包的条目。
下面的内容都需要由您明确说明,因为仓库中没有这些信息可供读取:
- 规则存在的原因。它可以防止 agent 将规则当作不必要的复杂性而删除
- 两条可用路径中哪一条受支持,哪一条正等待删除
- 仓库之外的任何信息,例如预发布环境,或某个依赖项为何固定在落后两个版本的版本
- 您计划下周执行的工作。这决定了文件是当前有效,还是确实有用
dox 了解自身的这一限制。它自己的规则规定,Work Guidance 必须反映项目当前的标准或用户的指示;如果两者都没有,则将该部分留空。Verification 必须反映现有检查,因此如果仓库中没有测试框架,该部分会一直留空,直到仓库中加入测试框架。凭空生成并编造标准的文件,比空白部分更糟,因为 agent 随后会强制执行这个虚构的标准。
让手写意图不出现在生成的清单中
这是最容易让人放弃生成文档的问题。您写了一段说明,解释作业队列必须保持单消费者模式。三周后,一次处理重新生成了文件,您的段落消失了。它被埋在包含40行变更的差异中,而这些变更大多只是调整文件名,结果没有人发现。
需要同时采用两种机制。
首先,将持久化的意图移到其他文件中。设计决策及其依据应写入供代理使用的 DESIGN.md;面向人的说明应放在您从 AGENTS.md 中拆分出的 HUMAN.md中。AGENTS.md 则保存清单和本地约定,这正是代码变化时应随之更新的部分。
其次,为必须保留在 AGENTS.md 中的意图设置保护边界。将其包裹在标记中,并将该代码块视为人工维护内容:
## User Preferences
<!-- dox:keep start -->
The jobs queue stays single consumer. Ordering is the reason this service exists.
Deploys ship on Tuesday. A Friday deploy is a human decision, not an agent decision.
<!-- dox:keep end -->Markdown 注释不会渲染到页面上,但代理仍会读取它。现在让该代码块的保留状态可验证,这样删除它的处理就会明确失败。在每个拉取请求中于 CI(持续集成)中运行以下命令:
git fetch -q origin main
sed -n '/dox:keep start/,/dox:keep end/p' AGENTS.md > /tmp/keep.head
git show origin/main:AGENTS.md | sed -n '/dox:keep start/,/dox:keep end/p' > /tmp/keep.base
diff -u /tmp/keep.base /tmp/keep.head如果代码块未被改动,diff不会输出任何内容并以0退出。任何输出都表示处理改写了人工维护的文本,因此应由人员批准或回滚。这样检查就能持续生效,无需任何人记住它。
在拉取请求中重新生成,而不是按定时任务生成
刷新文档的最佳时机,是使文档失效的那次提交。将 DOX 检查放在包含结构变更的同一个拉取请求中,这样差异足够小,便于实际审阅。
用于强制执行此要求的阻塞式检查:
#!/usr/bin/env bash
set -euo pipefail
git fetch -q origin main
base=$(git merge-base origin/main HEAD)
changed=$(git diff --name-only "$base" HEAD)
if grep -qE '^(src|apps|packages)/' <<<"$changed" && ! grep -q 'AGENTS\.md$' <<<"$changed"; then
echo "Code changed but no AGENTS.md was touched. Run a DOX pass, or say why not."
exit 1
fi请根据您的代码仓库调整路径。这样做的价值在于:检查会在分支上失败,此时修复成本较低;同时失败原因也能让审阅者采取相应措施。
定时任务是备用机制,而不是主要机制。每周运行一次,可以捕获分支上无人注意到的问题:通过 rebase 移动的文件、合并时删除的软件包,以及引用已不存在目录的文档。请在一台小型主机上运行它,可以使用同一台用于在 VPS 上运行编码代理的主机,并让它创建拉取请求,而不是直接推送到 main。
#!/usr/bin/env bash
set -euo pipefail
cd /srv/src/myapp
git fetch -q origin
git switch -c "dox/refresh-$(date +%Y%m%d)" origin/main
# Your agent CLI goes on the next line, in whatever non-interactive mode it offers.
# Prompt: "Run a DOX pass over this repository. Change AGENTS.md files only."
git add '*AGENTS.md'
git commit -m "dox: refresh AGENTS.md tree" || { echo "nothing to refresh"; exit 0; }
git push -q -u origin HEAD
gh pr create --fill这里的注释特意作为占位符。每个代理都有自己的 CLI(命令行界面)和非交互式标志。网页上复制的命令如果与您的版本不匹配,就会在 cron 中执行失败,而且无人能看到错误。请补充该命令,并在设置定时任务前手动运行一次。|| exit 0也很重要:当代码树已经是最新状态时,git commit会通过 nothing to commit, working tree clean 以非零状态退出;在 set -e 下,这会将正常运行报告为失败。
每次检查都会消耗 token,因为“编辑前先阅读”要求代理在每项任务中读取完整链路。这就是需要付出的代价。如果您已经在统计代理运行的成本,就值得持续关注这一点。
Monorepo:多个契约,一个索引
在包含四十个软件包的仓库中,根目录只有一个 AGENTS.md 会产生没人阅读的再生成差异,而且其中大部分内容与代理当前处理的任务无关。dox 的解决方案是子级 DOX 索引:根文件保存适用于整个仓库的规则,并指向各个子级文件;每个持久边界拥有自己的文件。如何组织这棵目录树,以及哪些工具会读取嵌套文件,参见适用于 monorepo 的嵌套 AGENTS.md 文件。
dox 改变的是审查范围。修改 packages/api 的拉取请求,应只在 packages/api 内产生文档差异:
git diff --stat -- '*AGENTS.md'如果该命令为只涉及一个软件包的修改列出六个文件,说明目录树设计有问题。可能是边界划分过粗,也可能是本应放在根文件中的规则被复制到了每个子文件。dox 直接说明了修复方式:通用规则放在父级文档中,具体细节放在子级文档中。重复规则会导致一次常规处理重写所有内容。如果相同规则确实适用于多个独立仓库,那是另一个问题;此时,在多个仓库之间共享代理技能是更合适的工具。
像审查代码一样审查差异
生成的文档差异很容易在未阅读的情况下直接批准。错误文件就是这样发布的。应像审查生成的代码一样谨慎阅读差异,并检查以下四项。
- 文件现在新增的命令。合并前应自行运行该命令。虚构的构建说明是最常见的问题。
- 承载原有意图的已删除行。新增内容通常容易补回,删除内容才会造成信息丢失。
- 绝对路径、主机名、内部 URL,或任何类似凭据的内容。
- 已不存在对象的清单条目。使用
ls可以在一秒内确认这一点。
然后使用 wc -l AGENTS.md 检查大小。根文件超过 200 行就应考虑拆分,因为这条链路的全部价值在于代理只读取相关的小段内容,而不是读取全部内容。
发生故障时
该流程删除了您的意图块。 上方的 diff 检查会输出被删除的行。使用 git restore --source=origin/main AGENTS.md 从分支起点恢复文件,然后使用更具体的指令重新运行流程,并明确指定它可以修改的章节。
两个分支都重新生成了文件。 您会看到 CONFLICT (content): Merge conflict in AGENTS.md,文件中还会出现冲突标记 <<<<<<< HEAD。不要手动编辑这些标记。该文件是生成文件,因此正确的解决方法是对合并后的工作树重新执行一次完整流程。
代理完全忽略了该文件。 检查工具实际读取的文件名。如果它读取的是另一个文件,请使用 ln -s AGENTS.md CLAUDE.md 将其指向相同内容,并提交该符号链接。这样可以保留一个源文件,避免两个文档逐渐产生差异。
目录树中出现了未建立索引的子文档。 将 find . -name AGENTS.md 的输出与父文档中的索引条目进行比较。索引中没有提及的子文档,代理可以直接跳过。
生成器何时没有必要
如果只有一个软件包、一个测试命令,而且两位维护者都熟悉该代码库,就手动编写这20行内容。20行的 AGENTS.md 不会快速过时,不值得为此维护目录树、索引、CI 检查和每周任务。修改构建流程时重新阅读即可。这就是全部维护成本,而且低于维护相关工具链的成本。
当代码库包含没有任何一个人能完整掌握的边界时,使用 dox 才值得:例如多个规则不同的软件包,或没有相关背景的新贡献者。价值不在于生成的文本,而在于文档会成为拉取请求必须通过的检查项。只有这样,代码库中的文件才有理由持续保持最新。
FAQ
使用 dox 需要安装任何东西吗?
不需要。dox 是一个采用 MIT 许可证的 Markdown 文件。截至 11 August 2026,该仓库不提供任何软件包或发行版本。将其内容复制到项目的 AGENTS.md 中,编码代理即可按照其中的规则执行。固定复制内容对应的提交版本;截至本文撰写时,该版本为 f34ec7ad1055d3393887e5a2670e8cb7320c9165。同时在提交消息中注明该版本,以便以后确认当前代码树基于哪个规则版本构建。
如何阻止重新生成过程删除我手写的规则?
将意图与清单分开保存。持久性的说明放在单独的文档中,必须保留在 AGENTS.md 内的内容则放入带标记的代码块。然后在 CI 中检查该代码块:从分支和 origin/main 中分别提取代码块,使用 sed,再通过 diff 比较两者。如果存在任何差异,就让构建失败。随后由人员批准或还原更改,而不是让更改隐藏在大型差异中并悄然通过。
应多久重新生成一次 AGENTS.md?
在使其内容变得不正确的拉取请求中立即重新生成。结构性更改及其文档应放在同一个差异中,因为此时审查人员才同时具备审查两者所需的上下文。每周一次的计划任务用于处理漏过分支检查的内容漂移,并且应创建拉取请求,而不是直接提交到 main。
构建命令应放在根目录的 AGENTS.md 中,还是放在子目录中?
放在拥有这些命令的最近层级文档中。仓库范围的规则和子目录索引放在根目录。仅适用于某个软件包的命令放在该软件包的 AGENTS.md 中。dox 按距离解决冲突:距离更近的文档控制本地细节,子文档不得削弱父文档规则。将相同命令复制到每个子目录中,正是例行处理会重写整个代码树的原因。
对于小型仓库,使用 dox 是否值得?
通常不值得。一个软件包只有一个测试命令,AGENTS.md 只有二十行时,内容老化得很慢;发现问题后,通常可以在一分钟内修复。仓库存在多个具有不同规则的边界,或贡献者缺少相关背景时,dox 才能体现价值,因为这时文档链承担了单个人无法承担的工作。