Claude Code 上下文压缩一Snip Compact 消息精简化删除这是 Claude Code 源码学习系列的第一篇。Snip Compact 是四级压缩体系中最先执行的一级——让 AI 模型主动删除对话中不再需要的消息。一、它在哪一层Claude Code 的上下文压缩是一个四级递进式防护体系。每轮对话开始前按顺序执行Snip Compact删除整条消息 ← 本文 ↓ Microcompact清除工具结果内容 ↓ Context Collapse异步折叠历史上下文 ↓ AutoCompactLLM 兜底总结二、它解决什么问题2.1 Compact 做不到的事传统的 Compact对话总结只能做一件事把整个对话前缀替换为一段摘要。Compact 必须做的事: 原始: [msg1][msg2][msg3][msg4][msg5][msg6] 结果: [boundary][summary][msg5][msg6] 问题msg5 之前的消息全部被 summary 替代 即使 msg4 很重要、不应被总结Snip 可以从对话中间删除任意消息不影响前后的消息Snip 可以做的事: 原始: [msg1][msg2][msg3][msg4][msg5][msg6] 结果: [msg1][msg2][msg5][msg6] msg3 和 msg4 被干净地移除 前后消息保持原样parentUuid 链重新连接2.2 场景举例场景Snip 的作用早期方案被废弃删除讨论废弃方案的那段对话调试过程不再需要删除echo 111之类的调试操作重复操作删除第一次失败的尝试只保留最终成功的错误探索方向删除走错方向的对话分支三、核心机制AI 模型主动删除Snip 和所有其他压缩方式的最大不同是 AI 模型自己决定删什么。1. 系统: 在每条发往 API 的用户消息末尾注入 [id:xxx] 短标签 → 模型能看到每条消息的 ID 2. 模型: 调用 SnipTool({ message_ids: [a3k7x2, b9f4y1] }) → 告诉系统删除这两条消息 3. 系统: 删除消息 生成 snip boundary 修复消息链3.1 标签注入// utils/messages.ts:1620-1625functionappendMessageTagToUserMessage(message:UserMessage):UserMessage{consttag\n[id:${deriveShortMessageId(message.uuid)}]// 将标签追加到消息最后一个 text block 的末尾}// deriveShortMessageId: UUID前10位hex → base36 → 6字符短ID// a1b2c3d4-e5f6-... → a3k7x2模型看到的消息示例我需要修复 foo.ts 的 login 函数之前的方案不对请重新分析。 [id:a3k7x2]3.2 模型主动调用 SnipTool模型推理: 前面的对话 [id:x7b2m1] 是我第一次失败的尝试 那个方案已经被用户否决了可以删掉。 → snip({ message_ids: [x7b2m1] })3.3 系统执行删除SnipTool 执行: 1. 从 mutableMessages 数组移除 msg-x7b2m1 2. 生成 snip boundary 记录: { type: system, subtype: snip_boundary, snipMetadata: { removedUuids: [uuid-of-x7b2m1] } } 3. boundary 推入 mutableMessages 4. 后续 API 请求: projectSnippedView() 过滤掉被删消息四、核心机制二内存删除 vs 磁盘保留4.1 三份数据两种处理方式┌──────────────────────────────────────────┐ │ mutableMessages (内存数组) │ │ m1 → m2 → boundary → m5 → m6 │ │ 被删的 m3、m4 已不存在 │ │ │ │ 作用作为真相源提供 API 请求的消息快照 │ └──────────────────┬───────────────────────┘ │ │ recordTranscript() 记录 boundary ▼ ┌──────────────────────────────────────────┐ │ JSONL 磁盘文件 (append-only不可改写) │ │ m1, m2, m3!, m4!, boundary, m5, m6 │ │ m3 和 m4 的 JSON 行永远留在磁盘上 │ │ boundary 记录了 removedUuids: [u3, u4] │ └──────────────────┬───────────────────────┘ │ │ Resume 时 applySnipRemovals() ▼ ┌──────────────────────────────────────────┐ │ Resume 加载后的视图 │ │ 1. 读 JSONL → MapUUID, Message │ │ 2. 根据 boundary 删除 m3、m4 │ │ 3. 修复 parentUuid 链跳过间隙 │ │ 4. buildConversationChain() 重建 │ └──────────────────────────────────────────┘4.2 parentUuid 链修复删除消息后链中有缺口。applySnipRemovals()修复// utils/sessionStorage.ts:1982-2038functionapplySnipRemovals(messages:MapUUID,TranscriptMessage):void{// 1. 收集被删除的 UUIDconsttoDeletenewSetUUID()for(constentryofmessages.values()){constremovedUuidsentry.snipMetadata?.removedUuidsif(removedUuids)for(constuuidofremovedUuids)toDelete.add(uuid)}// 2. 删除消息for(constuuidoftoDelete)messages.delete(uuid)// 3. 修复 parentUuid 链for(const[uuid,msg]ofmessages){if(msg.parentUuidtoDelete.has(msg.parentUuid)){// 向上追溯找到第一个非删除祖先messages.set(uuid,{...msg,parentUuid:resolve(msg.parentUuid)})}}}修复示例修复前: m6.parentUuid → m5 → m4 → m3 → m2 ↑ m5和m4/m3都被删了 修复后: m6.parentUuid → m2 直接跳到第一个非删除祖先五、核心机制三双重视图投影层在对话进行中REPL 需要保留完整历史供用户滚动回看但发给 API 的必须是删除后的精简版。mutableMessages (完整REPL 用): [m1][m2][m3][m4][boundary][m5][m6] projectSnippedView(): ↓ 根据 boundary.removedUuids 过滤 messagesForQuery (精简API 用): [m1][m2][boundary][m5][m6]projectSnippedView()是纯函数——不修改 mutableMessages只返回过滤后的新数组。六、语义标签 (Nudge)当 token 快超限时系统还会注入一个提示引导模型考虑主动清理// utils/messages.ts:4148-4158casecontext_efficiency:{const{SNIP_NUDGE_TEXT}require(../services/compact/snipCompact.js)returnwrapMessagesInSystemReminder([createUserMessage({content:SNIP_NUDGE_TEXT,isMeta:true})])}模型会看到类似这样的 meta 消息[注意上下文窗口接近上限。你可以使用 Snip 工具删除已完成 任务中不再需要的消息来节省空间。]七、与 QueryEngine 的协作SDK 模式下QueryEngine 通过snipReplay回调来本地重放 snip 操作// QueryEngine.ts:1278-1282snipReplay:(yielded:Message,store:Message[]){if(!snipProjection.isSnipBoundaryMessage(yielded))returnundefinedreturnsnipModule.snipCompactIfNeeded(store,{force:true})}// 当收到 snip boundary → 在本地 mutableMessages 上重放删除// boundary 本身不 push 到数组重放结果替换整个数组八、小结Snip Compact 的设计巧妙之处在于模型自主决策— 不是启发式规则而是让模型判断语义冗余双重视图— REPL 保留完整历史API 层按需过滤磁盘不可变— boundary 记录删除意图resume 时重建链修复— parentUuid 追溯跳过被删消息保证链完整零 API 费用— 纯客户端操作不增加 API 调用下一章将介绍第二级压缩Microcompact。本文全部来自博主学习 Claude Code 源码时的笔记和与 AI 的问答整理。