用 dox 自动保持 AGENTS.md 最新
三周后 AGENTS.md 可能已错误却仍被代理信任。用 dox 从仓库重新生成文件,再像审查代码一样检查差异,避免代理执行过时命令并修改错误文件。
三周后,为什么你的 AGENTS.md 已经过时
AGENTS.md 会过时,是因为它没有与代码建立任何关联。你在仓库处于某种状态的那天手动编写一次。之后测试运行器发生变化,软件包被重命名,服务被删除,但文件仍然描述着 June 的状态。没有任何步骤会失败,因为没有构建步骤读取它。
代理会读取它,并相信其中的内容。这才是代价所在。没有 AGENTS.md 的仓库会让编码代理在执行操作前先检查周围环境。包含错误 AGENTS.md 的仓库则会让它停止检查,因为它已经得到了答案。它会运行文件中列出的命令,shell 返回 Missing script: "test",然后代理开始猜测。它通常会编辑 package.json,加入文档承诺存在的脚本。过时的文件并不是静默失效。它导致了你并不想要的修改。
dox 是一种解决方案。它是一组为代理编写的规则,要求将更新文档作为完成工作的组成部分。这样,文件就会与导致其内容失效的代码在同一个提交中发生变化。
dox 是什么,以及它不是什么
dox 是一个 Markdown 文件。该仓库位于 agent0ai/dox,采用 MIT 许可证。截至 2026 年 8 月 11 日,整个项目只有一个 3906 字节的 AGENTS.md、一个 README、一个 LICENSE 和两张图片。无需安装软件包,也没有运行时。
这一点很重要,因为“生成器”这个词容易让人以为它会解析代码。实际上,没有任何组件会解析代码。dox 是编码代理读取的一份契约:代理负责生成内容,而 dox 是指令集,用于规定代理何时读取文档、何时重写文档,以及每份文档应采用什么结构。
该文件包含十个部分,其中两个部分负责实际工作。“编辑前读取”要求代理从仓库根目录开始,沿计划修改的每条路径逐级查找,并在当前会话中读取每条路径上的所有 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 能看到什么,以及它无法知道什么
构建树状结构的代理会读取仓库,因此仓库中的任何内容都可以纳入清单:目录结构、软件包清单和锁定文件、package.json、Makefile 或 pyproject.toml 中的脚本、CI 工作流文件、Dockerfile、入口点,以及(如果有)CODEOWNERS。根据这些内容构建的清单确实可以自行维护。软件包移动后,下一次处理会同步移动描述该软件包的条目。
以下内容不在仓库中,代理无法读取,因此应由您明确说明:
- 规则存在的原因。这可以防止代理将其当作不必要的复杂性而删除。
- 两条可用路径中哪一条受支持,哪一条计划删除。
- 仓库之外的任何信息,例如预发布环境,或某个依赖项为何固定在两个版本之前。
- 您计划在下周执行的工作。这决定了文件只是当前有效,还是确实有用。
dox 了解自身的这一限制。其自身规则规定,Work Guidance 必须反映项目当前的标准或用户指令;如果两者都不存在,则将该部分留空。Verification 必须反映现有检查;因此,如果仓库中没有测试框架,该部分应保持为空,直到添加测试框架为止。凭空编造标准的生成文件比空白部分更糟,因为代理随后会强制执行这个虚构的标准。
让手写意图不被生成的清单覆盖
这是最容易让人放弃生成式文档的问题。你写了一段说明,解释作业队列必须保持单消费者。三周后,一次重新生成会改写该文件,你的段落就消失了。它被埋在包含 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 注释不会渲染到页面上,但代理仍会读取它。现在让这个代码块的保留状态可检查,以便覆盖它的操作明确失败。在每个 pull request 上通过 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,因为“编辑前先阅读”要求代理在每项任务中读取完整链路。这就是需要承担的代价。如果您已经在统计代理运行的成本,就值得持续关注这一点。
单体仓库:多个契约,一个索引
在包含 40 个软件包的仓库中,只维护一个根目录 AGENTS.md,会产生没人阅读的再生成差异,而且其中大部分内容与代理当前执行的任务无关。dox 提供的解决方案是子级 DOX 索引:根文件保存整个仓库的规则,并指向子文件;每个持久边界维护自己的文件。如何组织这棵目录树,以及哪些工具会读取嵌套文件,详见 适用于单体仓库的嵌套 AGENTS.md 文件。
dox 改变的是评审范围。修改 packages/api 的拉取请求应只在 packages/api 内产生文档差异,不应出现在其他位置:
git diff --stat -- '*AGENTS.md'如果该命令针对只涉及一个软件包的变更列出 6 个文件,说明目录树设计不正确。可能是边界划分过粗,也可能是属于根文档的规则被复制到了每个子文件。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,该仓库不提供任何 package,也没有 releases。您只需将其内容复制到项目的 AGENTS.md 中,coding agent 就会按照其中的规则执行。请固定所复制内容对应的 commit,即撰写本文时的 f34ec7ad1055d3393887e5a2670e8cb7320c9165,并在 commit message 中注明它。这样,您之后就能确定当前代码树基于哪个版本的规则构建。
如何防止重新生成内容时删除我手写的规则?
将意图与清单分开保存。将持久性的说明写入单独的文档;必须保留在 AGENTS.md 中的内容则放入标记块。然后在 CI 中检查该代码块:使用 sed 从分支和 origin/main 中提取它,使用 diff 比较两者。如果存在任何差异,就让构建失败。之后由人员批准或还原更改,而不是让更改隐藏在大型 diff 中并无提示地通过。
我应该多久重新生成一次 AGENTS.md?
在使其内容失效的 pull request 中立即重新生成。结构变更及其文档应放在同一个 diff 中,因为只有此时审阅者才同时拥有两者的上下文。每周执行一次的计划任务可用于处理漏过分支检查而产生的偏差,并且应创建 pull request,而不是直接提交到 main。
构建命令应放在根目录的 AGENTS.md 中,还是子目录中的文件中?
放在离命令所属范围最近的文档中。整个仓库适用的规则和子文档索引放在根目录。仅适用于某个 package 的命令放在该 package 的 AGENTS.md 中。dox 按距离解决冲突:距离更近的文档控制本地细节,子文档不得削弱父文档规则。将相同命令复制到每个子目录,才会导致例行处理重写整个代码树。
对于小型仓库,使用 dox 值得吗?
通常不值得。只有一个 package、一个测试命令和一个 20 行的 AGENTS.md 时,内容老化得很慢,您发现问题后可以在几分钟内修复。仓库存在多个具有不同规则的边界,或贡献者缺少相关背景时,dox 才能体现价值,因为此时文档链承担了没有任何单个人员能够独自完成的工作。