当 Agent Instructions 从几 KB 长到几十 KB,它就不再只是 Markdown 文档,而是一份有运行时预算、有作用域、有到场时机和生命周期的 Context 资产。
本文实验项目
这篇文章不是为了说明问题而构造的 Demo。
整个治理过程来自我正在维护的一个 DeepSeek Harness 插件:
dsh-knit
它解决的是另一个 Context 问题:根据当前任务,从项目工作区找到更相关的文档、代码和其他上下文,并将这些上下文组织给人和 Agent 使用。
它本身采用本地、确定性的方式工作,不依赖模型调用,也不依赖网络。
而这次 AGENTS.md 治理,恰恰就是在维护 Knit 的过程中发生的。
本文中的 L0 / L1 / L2 / L3、预算闸门、任务触发词路由、Claims 与文件指纹,全部来自这个真实项目。
项目:PolinniZhong/dsh-knit
一、我原来以为 AGENTS.md 是文档,直到发现它其实有 Runtime Budget
我的项目里有一份 AGENTS.md,是给 AI 干活的执行规则:
怎么改代码、哪些不能动、发布怎么做、界面数值是多少。
它一度是:
65,092 B
≈ 63.6 KiB
而我使用的 DeepSeek Harness 宿主,对 Agent Instructions 的 maxBytes 是:
65,536 B
= 64 KiB
也就是说,这一份文件已经占掉了:
65,092 / 65,536
≈ 99.3%
这里先说明一个单位问题:
本文统一使用 KiB,即 1024 进位的二进制单位,不把 KB 和 KiB 混用。
宿主到底怎么处理超预算?
我直接读了 dsh-agent-instructions 的实现。
它不是智能摘要。
也不是把 Markdown 理解以后重新压缩。
它的核心流程更接近:
flowchart TD
A[Instruction Files] --> B[按层级收集]
B --> C{是否超过 maxBytes}
C -->|否| D[正常注入]
C -->|是| E[先丢弃较宽泛的文件]
E --> F{最具体文件仍超预算?}
F -->|否| G[剩余文件正常注入]
F -->|是| H[保留前面的字节]
H --> I[截掉后面的内容]
I --> J[加入截断提示]
所以本质上不是:
“模型帮我总结规则。”
而是:
预算不够,就裁。
而且它保留的是前面的内容,后面的字节被截掉。
64 KiB 也不是全部留给正文
这里还有一个很容易被忽略的细节。
实际注入的文本除了正文,还会有:
- system reminder 框架
- 固定 intro
Instructions from: <relative path>文件头- 空行等渲染开销
文件路径长度还会影响文件头大小。
在我这次具体的单文件场景里,文件就在会话工作目录下,显示路径就是:
AGENTS.md
因此扣掉渲染开销以后,对应的正文空间大约是:
65,232 B
这个数字不是所有项目、所有路径的通用常数。
路径越长,可用于正文的空间还会进一步减少。
但这里发生了一个很重要的事实纠正
我原来以为:
65,092 B的 AGENTS.md 已经发生过静默截断。
于是我去查证据。
结果发现:
65,092 B < 65,232 B
所以在当时的单文件场景下:
65,092 B
+ 渲染开销
= 65,396 B
65,396 B < 65,536 B
它并没有触发截断。
我甚至把自己项目里原来的 65,100 B 警戒线也重新查了一遍。
最终只能确认:
65,100 B是项目自己设定的一条保守线,但没有找到可靠的历史留档来证明它为什么是这个数字。
所以我没有继续把它写成:
“我抓到了一个 Context 截断事故。”
因为没有证据。
真正被证实的是另外一件事情:
我曾经让 Agent Instructions 长期贴着宿主运行时预算上限运行,而且没有一个可靠的预算治理机制。
这已经足够构成一个工程问题。
二、真正的压缩,不是删句子,而是控制 Context 的到场时机
我第一反应当然是删。
实际上,我确实手工压过一轮:
65,092 B
↓
60,387 B
但这只能解决一个问题:
这一版更短了。
它没有解决:
哪些内容根本不应该每一轮都进入 Context?
只要继续维护几版,这个文件还会重新膨胀。
所以我把问题换成:
这段内容,什么时候必须进入 Agent 当前 Context?
这成为整个治理过程中最重要的一刀。
我最后把项目里的规则人工分成四层:
flowchart TB
S[Agent Instructions Source]
S --> L0[L0 常驻规则<br/>Every Turn]
S --> L1[L1 作用域规则<br/>Relevant Scope]
S --> L2[L2 任务资料<br/>Relevant Task]
S --> L3[L3 历史 / 取证<br/>Historical / Evidence]
L0 --> A[跨任务硬约束<br/>当前状态 / 禁止事项]
L1 --> B[代码纪律<br/>测试写法 / 几何 / 颜色]
L2 --> C[发布剧本<br/>真机验收 / 故障恢复]
L3 --> D[版本历史<br/>决策记录 / 被否方案]
L0:每轮都要到场
包括:
- 跨任务硬规则
- 当前状态
- 禁止事项
- 遇到什么事情应该去读什么资料
这是最贵的一层。
因此,它不应该承担“整个项目百科全书”的职责。
L1:进入作用域后再到场
我的实现代码都在 knit/ 下面。
例如:
- 构建约束
- 测试写法
- UI 几何参数
- 特定代码纪律
这些内容并不需要在处理项目中其他事情时一直存在。
宿主本身已经提供了嵌套 instruction 的发现机制。
我用探针实测后确认:
read / write / edit
↓
成功访问相关文件
↓
发现对应目录的 AGENTS.md
↓
后续 Context 获得相关作用域规则
而 shell 并不属于同样的触发路径。
这个细节让我意识到:
Context 什么时候出现,本身就是工程设计的一部分。
L2:做某件事情才读取
例如:
发布 / 打 tag / 对校验和
→ 发布剧本
改完界面 / 真机验收
→ 验收清单
插件在界面里全不见了
→ 恢复手册
这些内容不是没用,而是:
没必要每一轮都存在。
L3:历史、决策与取证
例如:
- 每个版本发生过什么
- 为什么没有采用方案 B
- 完整的故障取证
- 被否方案
- 历史踩坑
它们的价值在于:
需要时可追溯。
而不是:
每一轮都必须进入模型。
这里必须说清楚:
L0 / L1 / L2 / L3 是我在 Knit 项目里人工定义的治理层,不是 DeepSeek Harness 官方的四种 Context Mode。
宿主真正提供的是:
层级发现
+
作用域机制
+
预算限制
+
去重
+
增量更新
我只是把这些能力组织成了一套更明确的 Context 治理方式。
三、Context 治理不是压缩率,而是“放多少、什么时候放、去哪里找”
拆完以后,我才发现一个更有意思的问题:
Context Governance 并不是一个压缩率问题。
至少有三个问题:
flowchart LR
A[Agent Context Governance]
A --> B[放多少?]
A --> C[什么时候放?]
A --> D[去哪里找?]
B --> B1[Budget]
C --> C1[Layer / Scope]
D --> D1[Task Routing]
B1 --> E[可控 Context]
C1 --> E
D1 --> E
预算解决:
放多少?
分层解决:
什么时候到场?
路由解决:
做什么事情应该去哪里找?
然后还有两个更后面的问题:
这份规则是不是最新的?
我现在依据的是哪一版?
这对应:
Integrity / Claims
+
Provenance / Fingerprint
于是,一个原本看起来很简单的 Markdown 文件,开始出现了完整的工程治理维度:
Budget
Scope
Timing
Routing
Integrity
Provenance
这才是我后来开始用:
Agent Context Governance
来描述这件事情的原因。
四、从“权威分工”到 Context Routing
这是这次改动里,我最喜欢的一条。
原来的规则是一张:
“哪些问题去哪些文件看”的权威分工表。
看起来很清楚。
但它隐藏了一个问题:
Agent 仍然需要记住这张表。
于是我把它改成任务触发词路由。
发布 / 打 tag / 对校验和
→ 发布剧本
改完界面 / 真机验收
→ 验收清单
插件在界面里全不见了
→ 恢复手册
也就是从:
Knowledge
↓
File
变成:
Task
↓
Trigger
↓
Reference
这个区别很小,但我认为更符合 Agent 工作方式。
因为 Agent 面对的通常不是:
“我现在需要知道某类知识。”
而是:
“我现在正在做一件事情。”
但它不是自动检索
这里一定要把边界说清楚。
这次实现的是:
当前任务
↓
触发词
↓
告诉 Agent 去哪里找
不是:
当前任务
↓
系统自动读取文档
↓
直接注入 Context
所以它降低的是:
Context Retrieval Cost
不是:
Context Retrieval Success
它提高的是找到正确上下文的概率和效率,而不是保证 Agent 一定会执行读取。
有意思的是,这一步改完以后,常驻层反而少了:
253 B
我增加了三条路由,同时把一整段版本史移到了历史层。
所以:
真正省下来的不是压缩算法,而是放错了地方的内容。
五、让 Context 治理进入工程流水线
如果只做一次拆分,几个月以后它还是会长回来。
所以我给项目增加了几个治理闸门。
5.1 Budget Gate
我增加了一套:
knit/tools/agents-budget.mjs
项目预算暂时定义成:
L0 ≤ 16 KiB
L1 ≤ 40 KiB
L0 + L1 ≤ 56 KiB
为什么不直接把预算放到宿主的 64 KiB?
因为我要给未来的 Context 注入留出空间。
当前数据是:
| 层 | 正文 | 项目预算 | 余量 |
|---|---|---|---|
| L0 | 15,936 B | 16,384 B | 448 B |
| L1 | 40,186 B | 40,960 B | 774 B |
| 合计 | 56,122 B | 57,344 B | — |
而实际注入时还有渲染外壳。
本次测量:
正文
56,122 B
实际载荷
56,463 B
宿主上限
65,536 B
因此:
65,536
-
56,463
=
9,073 B
≈ 8.86 KiB
这才是这次场景下真正意义上的剩余空间。
注意:
这 9,073 B 不是“压缩节省出来的空间”,而是当前实际 payload 距宿主上限的剩余预算。
5.2 Integrity Gate
规则文件还有一个问题:
它会过期。
例如:
版本:0.20.0
测试:641 项
测试文件:26 个
这些数字写在 Markdown 里以后,代码继续变化,它们就可能变成假话。
所以我给预算工具加入了:
node knit/tools/agents-budget.mjs --claims
检查:
版本号
测试文件数量
测试计数
N/N 自洽
然后发生了一件很有意思的事情。
第一次运行时,校验器自己先出 Bug 了。
它看到:
641 / 1
认为测试计数出现了多个值。
结果一查,是:
aspect-ratio: 1/1;
它把 CSS 的比例值识别成了测试数。
于是我收紧了正则,然后又故意传一个错误的测试数做反向验证:
真实值 641
→ 通过
故意传入 999
→ 必须失败
最后才算通过。
这个过程让我更加确认一件事情:
治理工具自己也必须被治理。
5.3 Provenance
我还给两层指令文件增加了:
sha256
mtime
这里不做完整的 Context Snapshot。
它解决的是一个更加实际的问题:
以后回头看发布记录,我能知道当时依据的是哪一版源规则文件。
所以我把它称作:
Context Provenance
可以简单理解成:
classDiagram
class Context {
+sourcePath
+loadMode
+reason
}
class SourceFile {
+path
+sha256
+mtime
}
Context --> SourceFile : derived from
它并不能还原最终模型收到的完整 payload。
但是它已经比:
“我记得当时应该是那个版本。”
可靠很多。
5.4 Release Gate
版本一致性、发布前检查、打包校验这一层本来就有。
现在只是和:
Budget Gate
+
Integrity Gate
+
Release Gate
一起进入发布前的治理流程。
当前项目测试:
641 项
26 个测试文件
全部通过。
六、为什么其他 Coding Agent 也在往类似方向走?
这次治理不是一个孤立的项目技巧。
不同 Agent 的实现完全不同,但它们都已经在暴露同一个事实:
Project Instructions 本身就是 Runtime Context。
Codex:Instruction 本身存在明确 Budget
OpenAI 当前 Codex agent loop 文档明确描述了 AGENTS.md 的聚合方式,并设置默认 32 KiB 的项目指令限制,同时根据目录层级合并更具体的 instructions。
这意味着一个非常重要的事情:
AGENTS.md 从产品语义上已经不是“普通 Markdown”,而是 Agent Runtime 的输入。
Gemini CLI:Hierarchy + JIT
Gemini CLI 现在支持:
Global
↓
Workspace / Project
↓
Subdirectory
↓
JIT Context
当工具访问某个目录或文件时,它还可以进一步发现对应目录中的 GEMINI.md。
这个思路与我在项目里的 L1 作用域治理非常接近:
相关 Context 在真正进入相关工作范围后再出现。
Claude Code:把项目规则和其他知识拆开
Claude Code 也采用项目级规则文件、用户级记忆和引用机制,把真正需要长期保留的规则和更大范围的知识组织开。它代表的也是同一个方向:
不是所有知识都应该进入每一轮的主 Context。
所以我现在越来越倾向于把这件事情叫:
Context Supply Chain
规则不是孤立的一份 Markdown。
它有:
Source
↓
Scope
↓
Routing
↓
Injection
↓
Validation
↓
Provenance
整个链路都需要治理。
七、我特意没有做“智能 Context Compiler”
到这里,可能有人会问:
既然都开始治理 Context 了,为什么不直接让 LLM 把 AGENTS.md 总结成更短的版本?
因为我认为:
工程规则和普通知识的容错率不是一个级别。
如果做:
AGENTS.md
↓
LLM Summary
↓
Compiled Context
确实可能得到更短的内容。
但同时会带来:
- 规则遗漏
- 语义变化
- 优先级误判
- 每次编译结果不一致
- Source 与 Context Payload 不一致
然后你还要再回答一个问题:
Agent 到底收到了什么?
所以这次我刻意没有做四件事。
1. 不做 Runtime Section Compiler
不在运行时重新把人工规则编译成另一份模型专用规则。
2. 不用关键词给规则自动打优先级
规则已经人工分层。
再让正则自动打分,反而可能产生误判。
3. 不让同一个文件动态切出不同 Context 片段
否则每一轮进入 Agent 的内容都可能发生变化,Context 会变得很难解释。
4. 不在项目里再造第二套 Context Inspector
最终:
Agent 到底收到了什么 Context?
应该由宿主提供透明度。
项目只负责把自己的规则治理好。
八、把这次实践抽成七条可以搬走的原则
走到这里,我觉得这件事已经不只属于 dsh-knit。
如果换成其他 Coding Agent,我仍然会建议:
1. 先量预算,再谈压缩
不知道距离宿主上限还有多少,“太大了”只是感觉。
2. 按“何时必须到场”分层
不要只是按主题拆文件。
主题决定归档,时机决定注入。
3. 给延迟内容配任务触发词路由
不要让 Agent 记一张“什么知识在哪个文件”的表。
4. 把容易漂移的说明变成机器可检查的断言
能计算的东西,不要只写在 Markdown 里。
5. 给 Context Source 建立 Provenance
至少能够知道:
Source Path
sha256
mtime
6. 不要为了压缩率牺牲可解释性
对于工程规则来说:
可解释性本身就是一种工程能力。
7. 区分“机制变好了”和“Agent 行为变好了”
这次治理能证明的是:
预算可测
规则可分层
任务可路由
声明可检查
来源可追溯
宿主可去重
但不能证明:
Agent 因此已经更听话了。
要证明行为收益,还需要后续几十个真实 Coding Session 的前后对比,例如:
规则违规率
返工次数
错误资料读取率
人工介入次数
任务完成率
写在最后
我以前把 AGENTS.md 当成项目说明书。
现在更愿意把它看成:
Agent 的 Runtime Context 输入。
一旦这么看,很多以前觉得不重要的问题就会出现:
它有多大?
什么时候进入?
为什么进入?
应该去哪里找?
是不是最新?
有没有重复?
还能再长多少?
这也是我这次治理最后留下来的一个判断:
Agent 规则不是写下来的那一刻就对 Agent 产生作用,而是进入当前 Context 后,才真正获得被看到和使用的机会。
但这里故意不加一句:
“进入 Context 就一定会生效。”
因为这不是同一个问题。
Context Governance 解决的是:正确的规则能不能以可控、可解释、可验证的方式到场。
至于 Agent 最终有没有按照这些规则行动,那属于下一层——Context Effectiveness。
而这可能才是 AI Coding 从“模型会写代码”进入“Agent 能持续做工程”之后,真正值得继续研究的问题。