布鲁斯的技术小屋

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):

  1. 扫描所有 assistant message,找出可裁剪工具的 tool_use block。
  2. 按 tool name 分组。
  3. 同一个 tool 存在多次调用时,删除所有中间调用,只保留最后一次。
  4. 同时删除对应的 tool_result message。
  5. 计算 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 左右触发。

执行流程

  1. 先尝试 Session Memory Compaction 这种轻量路径,复用已持久化的 session memory 摘要。
  2. 如果失败,再执行 Full Compaction:fork 一个子 agent,把整个对话喂给 summarization model 生成摘要。
  3. 最终结果大致变成:
    [compact_boundary_message, summary_message, ...last_N_messages]

熔断器:连续失败 3 次后停止重试,防止超限会话反复白白调用 API。

被压制的情况

  • Context Collapse 启用时
  • Reactive Compact 模式时
  • querySourcesession_memory / compact / marble_origami 时,防止递归触发

四级递进逻辑总结

阶段策略粒度触发条件代价
Snip删除冗余工具调用单条 message同工具重复调用零(纯本地)
MicroCompact清空旧 tool_result 内容block 级计数/时间零或 cache edit API
Collapse归档 + 摘要一段消息消息段90% / 95% fill1 次 LLM 调用
AutoCompact全量对话摘要整个对话约 93% fill1 次 LLM 调用

整个设计哲学其实非常克制:能省则省,从低代价到高代价逐级升级。

  • 先做免费的本地裁剪:Snip
  • 再做低成本的内容清除:MicroCompact
  • 然后做增量摘要:Collapse
  • 最后才动用全量摘要这颗“核弹”:AutoCompact

这套分层压缩策略的价值,不只是省 token,更重要的是它尽量把“上下文还原度”保留在每个阶段都足够高。只有在不得不动手的时候,才逐步牺牲细节换取容量。