Claude Code系列一——上下文四层压缩
Claude Code系列一——上下文四层压缩
Claude Code 在长对话里并不是只靠一次“全量摘要”来控制上下文,而是设计了一条四级递进的上下文压缩流水线。执行顺序在 query.ts:396-468 中串联,每一级都运行在上一级的输出之上。
整体思路很清晰:能本地免费裁剪就先裁剪,只有在上下文继续逼近上限时,才逐步升级到更昂贵的摘要策略。
Stage 1: Snip Compact(剪枝压缩)
Feature gate: HISTORY_SNIP
做什么:删除同一只读工具的中间冗余调用,只保留最后一次。
原理:如果 agent 用 Grep 连续搜了 5 次,而第 5 次已经包含最终需要的信息,那么前 4 次的结果基本不再有价值。Snip 会直接从 message 列表里物理删除这些中间的 tool_use + tool_result 对。
处理范围:只处理三类只读探索型工具:
- Bash
- Grep
- Glob
算法(参考 compact/snip.go):
- 扫描所有 assistant message,找出可裁剪工具的
tool_useblock。 - 按 tool name 分组。
- 同一个 tool 存在多次调用时,删除所有中间调用,只保留最后一次。
- 同时删除对应的
tool_resultmessage。 - 计算
tokensFreed,传给后续 autocompact 作为阈值修正参考。
特点:这是唯一一个真正“物理删除 message”的阶段。其他阶段更多是清空内容或者替换成摘要。
Stage 2: MicroCompact(微压缩)
做什么:清除旧的 tool_result 内容,保留 message 骨架,但把内容体掏空。
这里有两条路径:
Path A: Cached MicroCompact
主要面向 Anthropic 内部用户。
- 不修改本地 message 内容
- 通过 Anthropic API 的
cache_edits机制,在服务端删除已缓存的tool_result - 好处是不破坏 prompt cache prefix,能继续省钱
- 基于计数触发:累计注册的
tool_result数量超过阈值时,删除最老的,只保留最近keepRecent个
Path B: Time-based MicroCompact
这是通用回退路径。
- 检测距上次 assistant 回复的时间间隔
- 如果超过阈值,说明 cache 已经冷了,就直接清空旧
tool_result文本 - 替换为
[Old tool result content cleared] - 因为缓存已经过期,所以直接改内容不会产生额外成本
可压缩的工具范围 比 Snip 更广:
- Read
- Bash
- Grep
- Glob
- WebSearch
- WebFetch
- Edit
- Write
和 Snip 的核心区别:
- Snip 删除整条 message
- MicroCompact 保留 message 结构,但清空内容
Stage 3: Context Collapse(上下文折叠)
Feature gate: CONTEXT_COLLAPSE
做什么:将一段连续消息归档并替换为 LLM 生成的摘要,类似 git squash。
这个阶段的关键设计有三个:
- 非破坏性:不修改 REPL 原始 message 数组,摘要保存在独立的 collapse store 里。
- 增量式:每次只折叠新增部分,而不是全量重做。
- 持久化:折叠记录以 commit log 的形式存储,每次进入 query 时通过
projectView()重放。
它还设计了两个阈值:
- 90% context fill:主动触发折叠提交
- 95% context fill:阻塞提交,不允许继续提交新请求,直到折叠完成
它和 AutoCompact 的关系也很关键:
// autoCompact.ts:215-223
if (contextCollapse?.isContextCollapseEnabled()) {
return false
}
也就是说,一旦 Context Collapse 启用,AutoCompact 会被压制。原因很简单:两者阈值太接近,容易互相竞争,而 Collapse 保留了更细粒度的上下文。
另外还有一个恢复机制:如果 API 返回 413(payload too large),会调用 contextCollapse.recoverFromOverflow() 排空已暂存的折叠内容。
Stage 4: Auto Compact(自动全量压缩)
做什么:当 token 总量超过阈值时,用 LLM 生成整个对话的摘要,替换掉全部历史。
阈值计算(autoCompact.ts:72-91):
effectiveContextWindow = contextWindow - 20,000
autoCompactThreshold = effectiveContextWindow - 13,000
例如 Opus 的 200k window:
200,000 - 20,000 - 13,000 = 167,000
也就是大约在 167k token 左右触发。
执行流程:
- 先尝试 Session Memory Compaction 这种轻量路径,复用已持久化的 session memory 摘要。
- 如果失败,再执行 Full Compaction:fork 一个子 agent,把整个对话喂给 summarization model 生成摘要。
- 最终结果大致变成:
[compact_boundary_message, summary_message, ...last_N_messages]
熔断器:连续失败 3 次后停止重试,防止超限会话反复白白调用 API。
被压制的情况:
- Context Collapse 启用时
- Reactive Compact 模式时
querySource为session_memory/compact/marble_origami时,防止递归触发
四级递进逻辑总结
| 阶段 | 策略 | 粒度 | 触发条件 | 代价 |
|---|---|---|---|---|
| Snip | 删除冗余工具调用 | 单条 message | 同工具重复调用 | 零(纯本地) |
| MicroCompact | 清空旧 tool_result 内容 | block 级 | 计数/时间 | 零或 cache edit API |
| Collapse | 归档 + 摘要一段消息 | 消息段 | 90% / 95% fill | 1 次 LLM 调用 |
| AutoCompact | 全量对话摘要 | 整个对话 | 约 93% fill | 1 次 LLM 调用 |
整个设计哲学其实非常克制:能省则省,从低代价到高代价逐级升级。
- 先做免费的本地裁剪:Snip
- 再做低成本的内容清除:MicroCompact
- 然后做增量摘要:Collapse
- 最后才动用全量摘要这颗“核弹”:AutoCompact
这套分层压缩策略的价值,不只是省 token,更重要的是它尽量把“上下文还原度”保留在每个阶段都足够高。只有在不得不动手的时候,才逐步牺牲细节换取容量。