Appearance
第 12 章 上下文的分层与渐进式加载
本章要解决的问题
第 11 章解决了"代码从哪里来"。还有一类开销出现得更早:会话还没开始,项目指令、工具 schema、Skill 说明就已经全部进了上下文——不管这个任务用不用得上。 本章回答三个问题:什么内容可以按目录层级组合(只加载当前工作目录的祖先链)、什么内容可以渐进式加载(先给目录,命中再给正文)、这样做的代价是什么。压缩(第 13 章)是内容进了上下文之后的补救;本章讲的是让它一开始就别进去。
12.1 全量注入为什么撑不住
一个生长中的 Agent 会不断"变胖":多接一个 MCP server,它的工具 schema 就进 tools 数组;多写一份 Skill,它的说明就进系统提示词;项目从一个仓库变成 monorepo,每个子项目的指令都往里塞。第 6 章把这部分叫固定开销——还没处理任务就先占掉、且每轮请求都重复支付的部分。
全量注入的问题不只是贵:
| 问题 | 具体表现 |
|---|---|
| 成本 | 每轮请求都携带;按第 2 章的结论,只有完全不变的前缀才能命中缓存,一边加新工具一边改前缀,连缓存优势也保不住 |
| 干扰 | 关键信息淹没在无关内容里;第 6 章提到长上下文的中间位置召回会下降,内容越多越明显 |
| 选错率 | 名称相近的工具多了,模型选错的概率随之上升(第 10 章展开过) |
一条实用的判断标准:这个任务十有八九用不到的内容,不该在第一轮就进上下文。剩下的问题是——怎样让模型"用到时才拿到"。答案是把加载拆成两层以上:先给一层常驻的、很小的"目录和判断依据",命中后再加载正文。
12.2 分层加载:项目指令的层级
最容易做的分层,是顺着文件系统的目录结构切。
pi 的做法是逐层发现指令文件:启动时先加载全局 Agent 目录的指令,再从文件系统根目录一路查到当前工作目录。每个目录最多选一份文件,同目录下覆盖文件优先,其次才是 AGENTS.md、CLAUDE.md 等候选;找到的各层内容按从外到内的顺序拼接,不是只保留离当前目录最近的一份。
这解决的是 monorepo 场景的固定开销:十个平级子项目各有一份指令,全量注入就是十份;按祖先链加载后,只会带上全局约定和当前子项目所在路径上的规则,不会把九个无关兄弟目录一起塞进来。
自建时的两个设计点:
- 查找顺序与组合语义必须确定:全局 → 外层目录 → 当前目录,同层候选谁优先,跨层是就近覆盖还是全部拼接?这些选择都能实现,但要固定下来并且可预测——模型需要知道"哪条规矩说了算"。pi 选择同目录候选优先级 + 跨目录全部拼接。
- 外层只保留真正全局的内容:构建命令、代码风格、目录约定;子项目的细节留给子目录。判断标准和代码组织一样:如果一段指令只在某个子目录下成立,它就不该放在仓库根。
代价在启动阶段:运行时要遍历祖先目录并读取命中的文件;目录很深、各层规则都很长时,拼接后的固定开销仍会增长。若会话中途切换工作目录,还要明确重新发现和加载的时机。分层省掉的是无关分支,不是让祖先规则自动消失。
12.3 渐进式加载:先给目录,命中再给正文
分层解决"放哪一层";渐进式加载解决"正文什么时候进"。同一个模式有三种常见形态。
形态一:Skill 的渐进披露。 每个 Skill 只在上下文里占几百 token 的名称和简介;模型判断当前任务命中某个 Skill 后,再用读文件工具把完整步骤取进来。这套机制四家都在用,它本质上是把"写进系统提示词"变成"写成可检索的文件"——说明和正文的分离,让 Skill 数量增长不再线性推高每轮开销。
形态二:工具的延迟发现。 第 10 章比较过四家的工具呈现策略,这里只强调它与上下文的关系:tools 数组本身也是上下文。codex 把工具分成三种状态——直接提供、可搜索但不直接提供、完全隐藏——第二类只给一个搜索入口,模型用 tool_search 命中后才拿到完整 schema。dsh 按 preset 和 scope 装配当轮工具集,pi 靠扩展注册。三条路的共同点是:常驻的工具清单保持小而稳定,长尾工具按需进入会话。
安全边界在这里必须重申一次:一个工具从"隐藏"变成"可调用",意味着它的审批策略、超时和输出限制都要进入当前会话(第 10 章的注册冲突规则)。渐进加载改变的是呈现时机,不是审查标准——动态加载后要重新生成当轮工具集,不能让模型调用一个策略层还没认识的名字。
形态三:检索即渐进。 第 11 章的 grep → read 多跳,本身就是渐进式加载:模型先拿到的是文件名和匹配行(目录),确认相关后才读入完整文件(正文)。把这个视角带回第 6 章,上下文的三段式布局就多了一层含义:稳定前缀要小,不只是为了缓存,更是为了给"渐进加载进来的内容"留出空间。
三种形态共用一条经验:目录信息必须足够模型做判断。Skill 简介写不清,模型就不会命中;工具搜索入口的描述太含糊,该被发现的工具就不会被发现。渐进式加载把一部分"写系统提示词"的工作变成了"写目录"——目录的质量直接决定加载的对不对。
12.4 更彻底的一步:隔离
分层和渐进加载仍在主会话的上下文里做文章。最激进的手段是把工作整个挪出去:子 Agent 带着独立上下文执行大块中间工作,只把结论回传主会话。探索一个陌生目录、批量阅读几十个文件、跑一轮长测试——这些过程的中间产物都不进主上下文,主会话只看到一段结论。
代价同样明确:结论是有损压缩,主 Agent 失去了过程中的细节;父子两个会话的协调(怎么下发任务、怎么控制预算、怎么停止)本身就是一个需要设计的协议。这些内容在第 16 章展开,本节只需要记住它在加载谱系上的位置:分层和渐进是"少放进来",隔离是"放外面算完再拿结果"。
12.5 自建起步:按什么顺序上这些手段
这些手段不是并列的选项,而是有一个从便宜到昂贵的顺序:
text
第 1 步 固定开销瘦身:系统提示词去重,子项目指令下放到子目录(第 6 章)
第 2 步 分层加载:指令文件按目录层级发现与覆盖
第 3 步 Skill 渐进披露:说明进上下文,正文按需读
第 4 步 工具延迟发现:长尾工具先给搜索入口(需要注册表与策略配合,见第 10 章)
第 5 步 检索:grep → read 的多跳(第 11 章)
第 6 步 子 Agent 隔离:大块中间工作挪出主上下文(第 16 章)
最后 压缩:内容已经进了上下文,才轮到它补救(第 13 章)前面每做一步,后面一步的压力就小一分。常见的反模式是跳过前面五步,直接把压缩做得很复杂——那是在用一个昂贵的补救机制,去对付一个本可以不制造的问题。
【对比小结】
| 常驻上下文的内容 | 按需加载的内容 | 主要代价 | |
|---|---|---|---|
| codex | 系统提示词、直接提供的工具 | 可搜索工具经 tool_search 命中后加载 | 维护搜索入口的描述质量 |
| grok-build | 系统提示词、宽工具集 | Skill 按需读取;MCP 工具动态接入 | 工具多,常驻清单仍比别家大 |
| dsh | preset 装配的核心工具与说明 | scope 子作用域、注入插件按位置追加 | 装配规则本身需要设计与测试 |
| pi | 当前工作目录祖先链上的指令文件、Skill 简介、小工具集 | Skill 正文、扩展注册的工具 | 祖先链过深时固定开销仍会增长;扩展一致性交给作者 |
没有一家把所有内容都做成渐进式:高频、小而稳定的核心能力常驻,长尾能力按需加载——这条分界线在每个产品里位置不同,但都存在。
本章小结
- 分层加载按目录边界排除无关内容;渐进式加载先给足以判断的目录,命中后再给正文或 schema。
- 动态加载不降低审查标准;隔离能进一步减少主上下文负担,代价是信息损失和协调成本。
- 实施顺序是瘦身 → 分层 → 渐进 → 检索 → 隔离;压缩只处理已经进入上下文的内容。
渐进加载能把"进上下文之前"的问题解决大半,但已进上下文的内容仍会随任务积累:旧的工具结果、过时的检索记录、几轮之前的调试输出。下一章处理这个补救性问题:上下文接近上限时,先裁掉什么、何时压缩、压缩后怎样继续。