Claude Code 上下文压缩(三):Context Collapse 异步折叠

这是 Claude Code 源码学习系列的第三篇。Context Collapse 是四级压缩中最具创新性的设计——它将传统的"同步全文总结"进化为"后台异步多段折叠",在不阻塞用户的情况下持续维护上下文健康。

一、它在哪一层?

Snip Compact(删除整条消息)
    ↓
Microcompact(清除工具结果内容)
    ↓
Context Collapse(异步折叠)    ← 本文
    ↓
AutoCompact(LLM 兜底总结)

二、设计哲学:从「一刀切」到「精细折叠」

2.1 Compact 的局限

传统 AutoCompact 的模型很简单:对话太长 → LLM 总结一切 → 一份摘要替代全部历史。

Compact:
  整个对话前缀 ──► [一篇大总结]

  问题:
  - 所有细节丢失,变成一段"面条式"总结
  - 你无法保留"这段很重要,那段可以折"
  - 同步执行,用户必须等待 LLM 总结完成

2.2 Collapse 的答案

Context Collapse 把"一次大总结"变成了"多段小折叠":

Context Collapse:
  对话 ──► [修复 foo.ts: done ✓]    ← 一段折叠
           [讨论架构: done ✓]       ← 又一段折叠
           [最新工作: 保留原始消息]    ← 不折叠

每段被折叠的区间变成一行自然语言总结。未被折叠的消息保持原样。多段折叠可以在整个对话中的任意位置。


三、核心架构:异步代理 + 读写分离

3.1 ctx-agent — 后台分析引擎

Context Collapse 的核心是一个独立运行的 ctx-agent(上下文代理):

主线程(用户对话):
  [用户输入] → [压缩管线] → [API 请求] → [模型回复] → [用户看见]
      │                                                  │
      │  并行运行,互不阻塞                                │
      │                                                  │
ctx-agent(后台):
  [分析对话] → [识别已完成的任务区间] → [LLM 生成总结] → [stage 到队列]
   ↑                                                      │
   └──── 在空闲时重新 spawn,持续分析新对话 ───────────────┘

关键设计:ctx-agent 的 LLM 调用在后台进行,用户等待的是下一个毫秒的 API 响应,而不是等待 ctx-agent 的总结完成。

3.2 两级状态:Staged → Committed

Stage(暂存队列)
  ├─ ctx-agent 分析完成、生成 summary 的折叠
  ├─ 存在 collapseStore.stagedQueue 中
  ├─ 持久化到磁盘 snapshot(last-wins)
  └─ 尚未应用到实际对话中

      │ applyCollapsesIfNeeded()  ← 主线程调用,瞬间完成
      ▼

Commit(已应用)
  ├─ 折叠正式生效
  ├─ projectView() 实际替换消息
  └─ 持久化到磁盘 commit 日志(append-only)
// query.ts:440-447 — 核心集成点
if (contextCollapse) {
  const collapseResult = await contextCollapse.applyCollapsesIfNeeded(
    messagesForQuery, toolUseContext, querySource
  )
  messagesForQuery = collapseResult.messages  // 折叠后的投影
}

applyCollapsesIfNeeded() 本身不调用 LLM——它只是检查 staged 队列,把已分析好的折叠标记为 committed。毫秒级完成。


四、读时投影(Read-Time Projection)

4.1 为什么 Summary 不进 REPL

┌───────────────────────────────────────────────┐
│ REPL (mutableMessages) — 用户可滚动回看         │
│ [m1][m2][m3][m4][m5][m6][m7][m8][m9][m10]    │
│ 完整历史,用户看到所有消息                       │
└───────────────────────┬───────────────────────┘
                        │
        collapseStore — 只在内存中,管理折叠元数据
        commit 1: { first: m1.uuid, last: m4.uuid,
                    summary: "完成了auth模块重构: ..." }
        commit 2: { first: m8.uuid, last: m9.uuid,
                    summary: "讨论了缓存策略: ..." }
                        │
                        │ projectView() — 每次 API 请求时动态投影
                        ▼
┌───────────────────────────────────────────────┐
│ API 视图                                       │
│ [summary_1][m5][m6][m7][summary_2][m10]       │
│ 被折叠的消息被 summary 文本替代                  │
└───────────────────────────────────────────────┘

如果 summary 进了 REPL,用户滚动历史时会看到一段突兀的总结文字。放在 collapseStore 中,用户看到的始终是完整对话,只有 API 请求中才是投影后的视图。

4.2 跨轮持久

// query.ts:432-439
// Nothing is yielded — the collapsed view is a read-time projection
// over the REPL's full history. Summary messages live in the collapse
// store, not the REPL array. This is what makes collapses persist
// across turns: projectView() replays the commit log on every entry.

每一轮对话的 projectView() 都从 commit 日志重放所有折叠——所以一旦折叠被 commit,它在后续所有轮次中都持续生效。


五、持久化设计

5.1 两类磁盘条目

// types/logs.ts

// Commit — append-only 日志
type ContextCollapseCommitEntry = {
  type: 'marble-origami-commit'
  collapseId: string           // 折叠 ID
  summaryUuid: string          // summary 占位符 UUID
  summaryContent: string       // 完整的 XML 折叠标签
  summary: string              // 纯文本总结
  firstArchivedUuid: string    // 折叠起点
  lastArchivedUuid: string     // 折叠终点
}

// Snapshot — last-wins
type ContextCollapseSnapshotEntry = {
  type: 'marble-origami-snapshot'
  staged: Array<{
    startUuid, endUuid, summary, risk, stagedAt
  }>
  armed: boolean               // spawn 触发器状态
  lastSpawnTokens: number      // 上次触发时的 token 数
}

Commit 是 append-only(和消息一样),按顺序重放。Snapshot 是 last-wins(只有最新的重要),记录 ctx-agent 的恢复状态。

5.2 Resume 恢复

// sessionStorage.ts:3694-3697
else if (entry.type === 'marble-origami-commit') {
  contextCollapseCommits.push(entry)        // 逐条收集
} else if (entry.type === 'marble-origami-snapshot') {
  contextCollapseSnapshot = entry           // 只保留最后一条
}

// 加载后
restoreFromEntries(contextCollapseCommits, contextCollapseSnapshot)

Resume 后的 collapseStore 恢复至崩溃前的状态——已 committed 的折叠还在,staged 的待命折叠也在。


六、阈值与触发

6.1 多级阈值

有效上下文窗口 (effectiveWindow)
│
├─ 90% ─── 提交开始 (commit-start)
│          applyCollapsesIfNeeded() 开始把 staged fold commit
│
├─ 93% ─── 这是 autocompact 的老位置
│          collapse 启用时 autocompact 在此被禁
│          (否则两者会竞争,compact 胜出但损失粒度)
│
└─ 95% ─── 阻断 spawn (blocking-spawn)
           强制触发 ctx-agent 进行紧急折叠

6.2 与 AutoCompact 的互斥

// autoCompact.ts:215-222
if (feature('CONTEXT_COLLAPSE')) {
  const { isContextCollapseEnabled } = require('../contextCollapse/index.js')
  if (isContextCollapseEnabled()) {
    return false  // ← AutoCompact 直接退场
  }
}

Collapse 和 Compact 不是叠加关系,是替代关系。Collapse 是更精细的替代品。如果 Collapse 启用了,Compact 不再主动触发(只在 API 413 时作为最后兜底)。


七、PTL 恢复路径

当对话超长导致 API 返回 413(prompt too long)时,Collapse 有自己的恢复链:

// query.ts:1089-1117
if (contextCollapse && transitionReason !== 'collapse_drain_retry') {
  const drained = contextCollapse.recoverFromOverflow(messages, querySource)
  
  if (drained.committed > 0) {
    // 成功 drain 了 staged fold → 用折叠后的视图重试
    state = { ...state, messages: drained.messages }
    continue  // 回到 query loop 开头
  }
  
  // drained.committed === 0 → staged queue 空或过期
  // fall through → reactiveCompact → 传统 LLM 总结
}

恢复优先级:

  1. Drain staged fold → 如果 ctx-agent 已经分析好了
  2. Reactive compact → 紧急 LLM 总结
  3. 用户错误 → “对话过长,请手动 /compact”

八、UI 反馈

// TokenWarning.tsx — CollapseMode 下的实时进度
function CollapseLabel() {
  const { collapsedSpans, stagedSpans, totalErrors } = getStats()
  const total = collapsedSpans + stagedSpans
  
  if (totalErrors > 0) {
    return <Text color="warning">collapse errors: {totalErrors}</Text>
  }
  
  return <Text dimColor>{collapsedSpans} / {total} summarized</Text>
}

用户看到的不是 “Compacting…” 的等待,而是一个安静的进度指示器。


九、小结

Context Collapse 是 Claude Code 上下文管理中最具工程创意的设计:

  1. 异步代理 — LLM 分析对话在后台进行,不与用户交互争时间
  2. 多段折叠 — 不像 Compact 一刀切,而是精确折叠已完成的任务区间
  3. 读时投影 — 消息不变,API 请求时动态投影;用户看到完整历史
  4. 两级状态 — stage(暂存)→ commit(激活),延迟应用,随时可调整
  5. 替代而非叠加 — 与 AutoCompact 互斥,是更精细的替代方案
  6. 优雅降级 — PTL 时 drain staged → reactive compact → 用户手动

下一章将介绍第四级(兜底级):AutoCompact。


本文全部来自博主学习 Claude Code 源码时的笔记和与 AI 的问答整理。

Logo

汇聚全球AI编程工具,助力开发者即刻编程。

更多推荐