上下文预算与压缩
Agent 的上下文会随工具回合不断增长。如果只在到达模型硬上限后做一次全文总结,既浪费已经可以清理的大块工具结果,又可能因为 Prompt 已经太大而连总结请求都无法发出。
Claude Code 因此使用一套从便宜、细粒度到昂贵、高粒度的分层上下文管理。
请求前的固定顺序
flowchart LR
A["工具结果大小预算"] --> B["历史截剪"]
B --> C["微压缩"]
C --> D["细粒度上下文折叠"]
D --> E["自动完整压缩"]
E --> F["请求模型"]
F --> G["响应式压缩<br/>只在真实过长后恢复"]
顺序很重要:先让丢失最小的机制尝试释放空间,如果已经足够,就不必过早把整段对话替换成摘要。
第一层:限制工具结果的成本
文件读取、搜索、Shell 和网页结果往往是上下文中增长最快的部分。它们的原始内容对刚完成的决策很重要,但不一定需要永久保留。
因此运行时可以对工具结果设置预算,把过大内容截断、清理或替换为稳定占位说明。目标是保留“曾经执行过并得到某类结果”的协议骨架,而不是永久重复全量数据。
第二层:历史截剪与微压缩
微压缩不是完整对话摘要,而是对可压缩工具结果和思考数据做局部处理。当前源码快照可确认三类边界:
按时间清理旧结果
条件启用时,当主线会话经历长时间空闲且 Prompt cache 已经变冷,可以清理更旧的可压缩工具结果,保留最近若干个。
这个设计的精妙点是利用了“缓存已经冷却”这个时机:既然下一次本来就很难命中旧前缀,便可用更小历史重新开始。
使用 API 缓存编辑
在支持的模型与主线路径中,微压缩可以不改写本地 transcript,而是在下一次 API 请求中要求删除缓存里某些工具结果。只有 API 确认实际删除量后,本地才记录压缩边界。
它把“审计 transcript”和“本次模型实际需要的缓存内容”进一步分开。
API 端上下文管理
条件启用时,请求可携带清理旧思考或工具结果的策略。这与本地微压缩是不同层次:一个决定本地如何投影历史,另一个请求服务器管理已缓存上下文。
某些缓存微压缩与上下文折叠的内部模块不在当前源码快照中,因此本教程只陈述可验证的调用条件、输出契约和状态转移,不推测其内部删取算法。
第三层:自动完整压缩
当便宜清理仍不足以释放空间时,主线可以在达到硬上限之前主动进行完整压缩。
压缩阈值不能等于模型全部上下文窗口。它还要为压缩摘要本身、下一次输出和安全缓冲预留空间。
自动完整压缩还有多个保护:
- 用户可以关闭自动压缩;
- 专用压缩或 Session Memory 查询自身不再递归自动压缩;
- 响应式专用模式或上下文折叠可以抑制主动完整压缩;
- 连续压缩失败会触发断路器,避免每次迭代反复消耗注定失败的 API 请求。
压缩的输出不只是一段摘要
完整压缩后的上下文大致由下列部分重建:
压缩边界 → 摘要消息 → 必须保留的近期消息 → 恢复的附件 → Hook 结果
需要恢复的不只是文件路径,还包括:
- 最近的关键文件上下文;
- 当前 Plan 与 Plan Mode;
- 已调用且仍有效的 Skills;
- 异步 Agent 与任务状态;
- 动态工具、Agent 列表和 MCP 变化。
压缩因此不是“把聊天记录换成一段总结”,而是一次运行状态重建。
压缩摘要也尽量复用主线 Prompt cache
条件允许时,压缩摘要会使用一个受限 Fork,复用主线已渲染的 system、工具和历史前缀。该 Fork 不执行工具,只运行一轮摘要,并且避免为它的私有尾巴写入新缓存。
这里甚至要避免随意改变最大输出,因为输出上限可能间接改变思考预算,从而让本来相同的前缀无法命中缓存。
第四层:真实过长之后的响应式压缩
主动阈值是根据本地使用量预估,可能与提供方的真实接受边界有差异。因此当 API 实际返回 prompt too long 时,条件启用的响应式压缩作为最后恢复层。
它只在真实错误后运行,并且同一用户回合仅尝试一次,防止“压缩 → 仍然过长 → 再压缩”的无限螺旋。
当前源码快照没有包含该模块的内部删取算法,因此只能确认其进入条件、结果形状、单次保护和失败类型。
Prompt cache 与压缩是同一设计问题
压缩会改变历史,而 Prompt cache 依赖字节前缀稳定。因此上下文管理必须同时考虑:
- system 稳定段与动态段的缓存作用域;
- 消息级只保留一个有效 cache marker;
- 普通请求把 marker 放在最后消息,不写入缓存的 Fork 放在最后一个共享前缀点;
- 某些 beta、快速模式和缓存策略在会话内使用粘性快照,避免中途翻转破坏 cache key;
- 动态 Agent 列表、日期变化和 MCP 变化尽量以尾部 delta 表达,而不回头改写旧前缀。
这是整个压缩架构最精妙的地方:它不是单纯追求“Token 更少”,而是在语义连续、协议合法、缓存稳定和恢复成本之间取得平衡。
条件启用的任务 Token 预算如何穿过压缩
当调用方提供 API 任务 Token 预算时,压缩摘要会把大量旧历史隐藏在更小文本后面。如果任务预算只根据压缩后可见 Token 重新计算,就会“忘记”压缩前已经消耗的预算。
因此运行时会在压缩时记住压缩前最后一次 API 给出的权威上下文使用量,从剩余预算中扣除,再在后续请求中显式携带。多次压缩会继续累计,不会重置任务消耗。
压缩后的连续性是重建出来的
自动压缩后仍能继续 Plan Mode、Skills 和异步 Agent,不是因为它们的所有原始文本都还在模型窗口。而是因为运行时在压缩后重新附加仍有效的状态和指令。
这是理解“长会话连续性”的关键:连续性来自被精心选择并重建的状态,不是无限上下文。
源码定位
- 压缩调度顺序:
src/query.ts - 微压缩边界:
src/services/compact/microCompact.ts、src/services/compact/apiMicrocompact.ts - 自动完整压缩:
src/services/compact/autoCompact.ts、src/services/compact/compact.ts - 压缩后消息重建:
src/services/compact/compact.ts - Prompt cache 与缓存编辑:
src/services/api/claude.ts、src/utils/api.ts