开源项目 ticket-tracker 将 Agent 所需操作上下文集中到一份 6245 行的 Markdown,其中约 1800 行手写,其余从代码生成。部署流程会阻止文档与代码不一致的版本上线,并提供索引、网页和集成清单入口。
加入我的任务跟踪系统后,Agent 只需读取一个 URL:/llms-full.txt。这份 Markdown 文档有 6,245 行,其中约 1,800 行是我手写的,其余内容都从代码生成。如果文档与代码不一致,部署流程就会拒绝发布。
这里的约束是:Agent 应该只需要读一个页面,不必逛遍整个文档站;而这个页面也不能成为另一份事实副本,写完一周就开始过时。
我是 Panth,负责 Oizom 的软件团队。这套做法来自我的开源看板项目 ticket-tracker,我自己的 Agent 也通过它领取和处理工作。项目 Agent 计划的 O 节标题是“只需给 orchestrator 指向一个页面”。这篇文章介绍的,就是这个页面如何构建。
应用同时提供以下四个入口:
/llms.txt the short index: what this is, the URLs, how to authenticate
/llms-full.txt the one page: the whole operating context, Markdown
/integrate the same content as a readable web page, for people
/integrate.json a small manifest: api, mcp, sdk, openapi and llms URLs + version
/llms.txt 有 85 行,主要内容就是告诉你“去读 /llms-full.txt”。完整页面包含九章:软件介绍、Agent 如何接入、三种入口(SDK、REST、MCP)、以完整可运行的 orchestrator 展示工作循环、参考文档、使用规则、实用示例、产物,以及版本与变更日志。
这四个文件都由同一个脚本 scripts/gen-integration.mjs 生成。它还负责构建 Claude Code plugin,其中的 skill 会将 llms-full.txt 原样作为参考文件打包,因此 plugin 里也不会存在另一份独立维护的副本。
正文放在 scripts/integration-source/ 中,每章一个 Markdown 文件。这是唯一手写的部分。如果正文需要一张表,而表里的信息代码已经知道,就在对应位置放一个标记:
## 5. Reference
...
{{gen:contracts}}
...
{{gen:rest}}
...
{{gen:mcp}}
...
{{gen:sdk}}
生成器会将每个标记替换成相应内容块,构建这些内容块所用的数据源,与软件运行时使用的数据源完全相同:
仅参考文档一章就有 3,218 行,涵盖 72 个 REST endpoint 和 36 个 MCP tool。这些都不需要人手敲。新增 route 后,下次构建时它就会出现在页面里,因为 route 的 schema 就是页面的数据源。
标记展开采用严格校验。只要遇到生成器不认识的标记,就会停止:
.replace(/\{\{gen:([a-z]+)\}\}/g, (_, name) => {
if (!(name in blocks)) fail(`unknown generated block {{gen:${name}}} in the prose`);
return blocks[name];
})
有些表格需要在每个生成的名称旁边补上一句解释,比如拥有 commenter 角色的 Agent 可以做什么。这些解释写在脚本里,以名称为键关联。如果共享包新增了一个角色,却没人写明它的含义,构建就会失败,而不是输出一行空白说明。
node scripts/gen-integration.mjs --check 会重新生成所有内容,并与已经提交的副本比较。部署脚本会在 firebase deploy 之前运行这项检查,确保不会带着过时的页面发布。如果我改了 schema,却忘记执行 pnpm integrate:gen,部署就会停下来。
这套机制成立的前提是:相同的输入必须生成完全相同的字节。因此,所有列表都按固定顺序输出,生成过程也不会读取当前时间。页面上的“更新”日期取自最新一条变更日志;当契约发生变化时,由作者更新这条记录。如果用 new Date() 打时间戳,这道检查每天午夜都会失败。
这道检查机制本身也有测试。端到端测试套件先将内容生成到临时目录,再依次向每个文件追加一行,检查是否只将被修改的那个文件报告为过时。plugin zip 则按字节比较,所以测试会翻转文件中间的一个字节,并验证同样能检测出变化。
这个页面的用途是回答“有没有什么变化?”,缓存副本会让这个目的落空。而模型期待拿到 Markdown,结果却收到 HTML,处境甚至比没有这个页面更糟。托管服务为 /llms*.txt 设置 text/plain; charset=utf-8,并附带 no-store 和 access-control-allow-origin: *。一项测试会读取 firebase.json,断言这些响应头配置正确;另一项独立检查则通过托管模拟器验证实际返回的响应头。
用户创建一个 Agent,将它加入看板,再给它一个 token。Agent 拿到的就是这个 token 和一个 URL。通过这个页面,它会学到如何不靠轮询等待工作、发布任务列表、每分钟发送一次 heartbeat、遇到阻碍时提问,以及提交结果并附上文件。完成这些操作所需的所有 endpoint 和 tool,都在同一个文件里。
文末的变更日志可以追溯到 2026-09-26 发布的 1.0.0 版本,因此 Agent 能判断上周学到的内容是否仍然有效。
你的 Agent 从哪里学习你的 API?你又靠什么防止这些内容过时?
如需进一步采取行动,你可以考虑屏蔽此人和/或举报滥用行为。