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){constcollapseResult=awaitcontextCollapse.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 日志typeContextCollapseCommitEntry={type:'marble-origami-commit'collapseId:string// 折叠 IDsummaryUuid:string// summary 占位符 UUIDsummaryContent:string// 完整的 XML 折叠标签summary:string// 纯文本总结firstArchivedUuid:string// 折叠起点lastArchivedUuid:string// 折叠终点}// Snapshot — last-winstypeContextCollapseSnapshotEntry={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-3697elseif(entry.type==='marble-origami-commit'){contextCollapseCommits.push(entry)// 逐条收集}elseif(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-222if(feature('CONTEXT_COLLAPSE')){const{isContextCollapseEnabled}=require('../contextCollapse/index.js')if(isContextCollapseEnabled()){returnfalse// ← AutoCompact 直接退场}}Collapse 和 Compact 不是叠加关系,是替代关系。Collapse 是更精细的替代品。如果 Collapse 启用了,Compact 不再主动触发(只在 API 413 时作为最后兜底)。
七、PTL 恢复路径
当对话超长导致 API 返回 413(prompt too long)时,Collapse 有自己的恢复链:
// query.ts:1089-1117if(contextCollapse&&transitionReason!=='collapse_drain_retry'){constdrained=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 总结}恢复优先级:
- Drain staged fold → 如果 ctx-agent 已经分析好了
- Reactive compact → 紧急 LLM 总结
- 用户错误 → “对话过长,请手动 /compact”
八、UI 反馈
// TokenWarning.tsx — CollapseMode 下的实时进度functionCollapseLabel(){const{collapsedSpans,stagedSpans,totalErrors}=getStats()consttotal=collapsedSpans+stagedSpansif(totalErrors>0){return<Text color="warning">collapse errors:{totalErrors}</Text>}return<Text dimColor>{collapsedSpans}/{total}summarized</Text>}用户看到的不是 “Compacting…” 的等待,而是一个安静的进度指示器。
九、小结
Context Collapse 是 Claude Code 上下文管理中最具工程创意的设计:
- 异步代理— LLM 分析对话在后台进行,不与用户交互争时间
- 多段折叠— 不像 Compact 一刀切,而是精确折叠已完成的任务区间
- 读时投影— 消息不变,API 请求时动态投影;用户看到完整历史
- 两级状态— stage(暂存)→ commit(激活),延迟应用,随时可调整
- 替代而非叠加— 与 AutoCompact 互斥,是更精细的替代方案
- 优雅降级— PTL 时 drain staged → reactive compact → 用户手动
下一章将介绍第四级(兜底级):AutoCompact。
本文全部来自博主学习 Claude Code 源码时的笔记和与 AI 的问答整理。