夜雨聆风学习资料网

ARTICLE · 1141480

AGENTS.md 不是普通文档:一次真实项目里的 Agent Context 治理

AGENTS.md 不是普通文档:一次真实项目里的 Agent Context 治理

我在 DeepSeek Harness 上做了一个叫 dsh-knit 的插件,开发过程中把项目里的AGENTS.md治理了一遍。做完最想纠正的一个认知是:它不是一份文档,而是一份有运行时预算的 Context 资产。

我原来以为它是文档,直到发现它按字节被切

我的项目里有一份AGENTS.md,是给 AI 干活的执行规则:怎么改代码、哪些不能动、发布怎么做、界面数值是多少。它一度是一个文件、65,092 B(约 63.6 KiB)。这里说的 KiB 是按 1024 进位的二进制单位,我不跟十进制的 KB 混着算。

DeepSeek Harness 给"指令链"——每一轮都会拼进提示词的那些规则文件——的预算是64 KiB(65,536 B)。超了会怎样,我把源码翻出来看明白了:先整体丢弃较宽泛的指令文件;如果剩下的"最具体"那个还是装不下,就对它做截断。它不是智能摘要,而是保留前面的字节,直接截去后面的内容。

而且 64 KiB 并不全部属于正文:注入的时候还有固定的系统包装和文件路径信息。在我这次具体的场景里(文件就在会话工作目录下,显示路径就是AGENTS.md),扣掉这些之后折算出来的正文空间约为 65,232 B——超过它,后面的字节就开始被切掉。(具体拆解我放在文末小注里。)

问题就在这里:被切掉的是后面。而我的禁止事项、附录、恢复手册,全都写在后面。

这里我要如实补一句,因为它比"我抓到了一个事故"更重要:我给自己定的警戒线是 65,100 B,当时文件 65,092 B,看起来只剩 8 B 余量。但把渲染外壳算清楚之后,它距真正会开始切字节的地方还有 140 B——那条警戒线既没有留档来源,还比实际更保守。

所以这个故事真正的转折不是"我抓到了一个 Context 截断事故",而是我查完源码,推翻了自己原来的判断;然后我发现,真正的问题是我一直把 Agent Instructions 当成普通文档在管理。这一条比一个 bug 更值得写。

拆层的标准不是主题,而是"什么时候必须到场"

我的第一反应是"太大了,删一点"。我确实先手写压过一轮:65,092 → 60,387 B。但那只是把句子改短,结构没变——再发三个版本,它又会顶回来。

真正有效的那一刀,标准不是"这段属于哪个主题",而是"这段内容什么时候必须进入 Agent 的当前 Context":

每轮都在场:跨任务的硬规则、当前状态、禁止事项、以及"遇到什么事去读哪个文件"。

相关作用域被宿主发现后进入 Context:构建约束、颜色与几何数值、测试写法。这是宿主已有的机制:一次成功的文件读写(read/write/edit,shell不算)之后,它才会去发现那个目录下的指令文件。

做某件事才读:发布剧本、真机验收清单、插件恢复手册。

历史与取证,默认不进:逐版过程、被否方案、完整的踩坑取证。

要说清楚这四层的身份:它们都是我在这个项目里人工定义的规则层,不是宿主的架构。宿主提供的只有"从会话目录逐层向上找同名指令文件,预算不够就先丢宽泛的、再截断最具体的"这一套机制;分层只是把它真的用起来。

拆完的账:常驻层15,936 B(15.56 KiB,自定上限 16 KiB = 16,384 B)、作用域层40,186 B(39.24 KiB,自定上限 40 KiB = 40,960 B),合计 54.81 KiB。治理前那一个文件占掉了 64 KiB 预算的99.3%;治理后,常驻层这一层约占预算的24%。这两个数是不同口径——一个是"单文件对预算",一个是"分层之后的一层对预算"——我不把它们伪装成同一个指标的前后对比。

做完这一步我才发现,Context 治理不只是压缩率问题。它至少有三件事:放多少、什么时候放、以及凭什么知道去哪里找。预算解决"放多少",分层解决"什么时候到场",路由解决"做什么事去哪里找";再往后,断言和指纹解决的是"这份规则到底是不是那一版"。

从"权威分工"到"做什么事读哪个文件"

分层之后还有一层没解决:Agent 怎么知道该去读哪一份?

原来的做法是一张"权威分工"表,本质上是要求它记住"某类内容在哪份文件里"。我改成按任务触发词的路由:

「发布 / 打 tag / 对校验和」→ 发布剧本

「改完界面 / 真机验收」→ 验收清单

「插件在界面里全不见了」→ 恢复手册

边界要说清楚:这是"我告诉它做什么事的时候去读哪个文件",是给 Agent 的检索提示,不是系统自动把内容递到它面前,也不是宿主保证它一定读。路由降低的是"找到正确上下文"的成本。

有意思的是改完常驻层反而小了 253 B:我加了三条路由,同时把一整段版本史下移到了历史层。省下的不是算法,是放错了地方的内容。

把预算和声明变成闸门

治理不能停在一次性优化上,否则它一定会长回去。我给它装了三个闸门。

预算闸门:常驻层 ≤ 16 KiB、作用域层 ≤ 40 KiB、合计 ≤ 56 KiB(在宿主的 64 KiB 之上留出空间)。现在常驻层只剩 448 B,作用域层只剩 774 B。每次都会打出两层的实际字节、占比和余量,余量低于地板线就直接失败。写这篇文章时我还把渲染外壳算进去了:两层正文 56,122 B,实际注入是 56,463 B,所以真实余量是 9,073 B(约 8.86 KiB)——比我工具里那个只算正文的 9.19 KiB 更紧。这个差值不大,但它决定了我以后加内容的判断依据。

完整性闸门:把容易漂移的文档声明变成机器可检查的断言——常驻层里写的版本号必须等于包里的版本号;写的"N 个测试文件"必须等于测试脚本里真实的文件个数;注入文本里所有"N/N"必须只有一个值。再给每个来源文件记下 sha256 和修改时间,我把这一层叫Context Provenance(上下文来源追溯):它能确认当时依据的是哪一版源规则文件,减少"到底是哪一版规则"靠回忆判断的问题——它记的是源文件的版本,不是最终注入载荷的快照。

这个闸门第一次跑就抓到一个真 bug,而且抓的是校验器自己:它把样式表里的aspect-ratio:1/1当成了测试数,报出"出现了多个测试数:641 / 1"。我收紧正则,又补了一次反向验证(故意传一个错的测试数,它必须报错),现在它是绿的。

发布闸门:版本一致性、发布前检查、打包校验和——这一层我本来就有,现在它和上面两个一起跑。当前测试641 项 / 26 个测试文件全绿。

我特意没做的四件事

这一节不是"偷懒清单",是这次治理里最重要的工程判断。想先说一句:Context 治理不是压缩率竞赛。对于工程规则来说,可解释性本身就是一种工程能力。

所以我更看重"我读到的 ≈ Agent 实际被注入的"这个不变量,而不是理论上的最大压缩率:

不做运行时的 section compiler(把长文件按小节在运行时重编一遍再注入)。那样我读的和我以为它收到的就不是同一份东西,我还得再建一个面板去解释"它其实收到了什么"。

不用正则给规则自动打分、排优先级。我的规则已经人工分过级;用关键词打分只会把重要的降下去,还会反过来教我"把词写成全大写"。

不让同一个文件在不同时间切出不同片段。那会让每一轮注入的内容都在变,上下文一直抖——这正是我上一版刚从产品里删掉的那类东西。

不自建第二套检查面板。检查上下文该由宿主提供,塞进我的项目路线图只会让边界变模糊。

把这些抽成七条可以搬走的原则

先量预算,再谈压缩。不知道自己离上限多远,"太大了"就只是一个感觉。

按"何时必须到场"分层,而不是只按主题拆文件。主题决定归档,时机决定注入。

给延迟加载的内容配任务触发词路由。别指望它记住一张分工表。

把容易漂移的说明变成机器可检查的断言。版本号、测试数、计数自洽,都该有退出码。

建立 Context Provenance(上下文来源追溯)。事后能说清当时依据的是哪一版源规则文件。

不要为了压缩率牺牲可解释性。人读到的和 Agent 收到的最好是同一份东西。

区分"机制变好了"和"Agent 行为变好了"。前者现在就能证明,后者要靠几十个真实会话去统计。

这也是我对自己这次治理最老实的总结:能证明的是五件机制上的事——注入载荷在测量口径下距上限还有余量;路由从静态分工变成了任务提示;一部分可验证声明已经能用退出码检查;每个来源文件有了指纹;宿主本身还有"内容没变就不重复注入"的机制。不能证明的是"它更听话了"。

所以如果只留一句话:Agent 规则不是写下来的那一刻就对 Agent 产生作用,而是进入当前 Context 后,才真正获得被看到和使用的机会。后半句我特意不写"就一定生效"——进入上下文只是拿到了机会。

如果你的AGENTS.md已经写到几十 KB,我建议今晚只做一件事:量一下它有多少字节,再问一句"离上限还有多远"。如果只剩几百字节,那你以为已经交代过的规则,很可能已经不是它实际收到的那一份。

面向Deepseek Harness AI Coding Agent 的任务感知工作区上下文检索与生命周期追踪:按当前任务找到、组织并持续追踪最相关的文档、代码与媒体。纯本地、零模型调用、零网络。安装:dsh plugin --profile web add dsh-knit

相关学习资料