Hi大家好欢迎来到维元码簿。本文属于《Claude Code 源码 Deep Dive》系列专注于 Agent 执行内核中的Pipeline 与上下文压缩板块。如果你想了解整个系列可以先看系列开篇 | Claude Code 源码架构概览51万行代码的模块地图。50 轮对话后消息历史可以轻松膨胀到 10 万 token——如果不做处理不仅费钱而且可能超过模型上下文窗口。Claude Code 的解决方案不是在 API 层面优化那是 Prompt Cache 系列的主题而是在每次 API 调用前对消息执行四层压缩Snip截断、Microcompact去重、ContextCollapse折叠、AutoCompact摘要。读完全文你将能回答这几个问题Pipeline 是什么和压缩有什么关系答案Pipeline 是架构模式——数据流、条件跳站、跨站参数传递、错误恢复路径压缩是每个站承载的功能。Pipeline 不等于压缩压缩只是 Pipeline 的一种应用。四层压缩各自解决什么问题为什么它们的执行顺序不可换答案Snip 截断远的历史 → Microcompact 去重 → Collapse 折叠可恢复 → AutoCompact 摘要不可逆——每一层假设前一层已完成。什么时候压缩每一次都需要调用大模型吗答案Snip、Microcompact、ContextCollapse 都是纯本地操作不调大模型只有 AutoCompact 和 Reactive Compact 需要调用独立的 compact agent——而且有 circuit breaker 防止无限重试。AutoCompact 失败了怎么办答案circuit breaker——连续失败 3 次就放弃不再重试。还有 Reactive Compact 作为 413 错误的被动防线。压缩前后的 token 变化有多大答案一次 AutoCompact 可将 8 万 token 削减到约 1.2 万 token——削减 85%。本篇覆盖的源码范围模块核心文件核心代码行文件总行职责Pipeline 编排src/query.tsL365-543snip→microcompact→collapse→autocompact1730 行四层压缩的总调度Snipsrc/services/compact/snipCompact.tsFeature: HISTORY_SNIP––历史消息按 token 阈值截断Microcompactsrc/services/compact/microCompact.tsL1-600microcompact 主逻辑~600 行重复工具结果去重 cache_editsContextCollapsesrc/services/contextCollapse/Feature: CONTEXT_COLLAPSE––可恢复的语境折叠投影AutoCompactsrc/services/compact/autoCompact.tsL28-82阈值计算 circuit breaker352 行自动摘要压缩主逻辑Compact 核心src/services/compact/compact.tsL1-100入口函数 compactConversation~1600 行调用 compact agent 生成摘要为什么叫 Pipeline你可能会问为什么用 Pipeline 这个词有直接的代码依据。在query.tsL396-467 中消息数组messagesForQuery依次流过 4 个压缩函数——每个函数的输出就是下一个函数的输入形成标准的Pipeline流水线模式。四层压缩顺序串行、层层递进在代码结构上就是一个典型的 Pipeline。这个类比也不是本文独创的——消息上下文管理篇 中同样的messages数组在发给 API 之前还要经过另一道清洗 PipelinenormalizeMessagesForAPI()处理类型映射、Content Part 组装、缓存断点标记。两篇讲的是同一件事的不同阶段一个对消息做压缩清洗一个对消息做预发清洗。用 Pipeline 统一描述是为了让读者看到消息从用户输入到最终发出 API全程都处在 Pipeline流水线处理中。前情提要从主循环到第一阶段在姊妹篇 [主循环与状态机](./04-Claude Code深度拆解-Agent执行内核-主循环与状态机.md) 中我们建立了全局认知——while(true)循环的 4 个阶段准备→调用→执行→转移。本文聚焦第 1 个阶段——准备阶段中的 Pipeline 与上下文压缩。在query.ts中这段代码位于 L365-543——在 API 调用之前在构建完整 Prompt 之前。每次进入循环第一步就是对消息历史做瘦身。与 Prompt Cache 机制的区别Prompt Cache缓存工程深度拆解篇讲的是 Anthropic API 的 KV Cache——服务端如何复用已计算的 KV 向量来跳过前缀处理。那是让 API 少算。这里的四层压缩是让 API 少看——先砍掉不必要的 token再发给 API。两者互相独立但又协同工作压缩后的消息更短 → 前缀更稳定 → KV cache 命中率更高。Pipeline 机制详解不等于压缩而是一套架构模式你可能会有疑问标题叫“Pipeline 与上下文压缩”但读下来不就是四层压缩吗Pipeline 和压缩是什么关系它们不是同一件事。压缩是每个处理站承载的功能——截断、去重、折叠、摘要Pipeline是把这些功能串起来的架构模式——数据怎么流、哪些站跳过、参数怎么跨站传递、出错时怎么回退打个比方压缩是流水线上每台机器做的事切割、打磨、喷涂、质检Pipeline 是流水线本身的传动设计——物料怎么从上一台传到下一台、哪台机器可以跳过、质检失败怎么回炉。理解 Pipeline 的运行机制才能看懂后面每一层压缩为什么要这样编排。下面这张图展示了 Pipeline 的架构机制主数据流、条件跳站、跨站参数传递、短路重置、错误恢复双路径——注意和 Tab 4四层压缩功能的区别这里讲的是站怎么串而不是每站做什么。数据流一个数组四次变换Pipeline 的核心数据实体只有一个messagesForQueryMessage[]数组。它从getMessagesAfterCompactBoundary()初始化后依次流过四个处理站——每一站都接收数组、变换后返回原始 messages │ ▼ getMessagesAfterCompactBoundary() ── 提取压缩边界之后的消息 │ ▼ messagesForQuery [...子集] │ ① Snip ──→ messagesForQuery snipResult.messages │ ▼ ② Microcompact ──→ messagesForQuery microcompactResult.messages │ ▼ ③ ContextCollapse ──→ messagesForQuery collapseResult.messages │ ▼ ④ AutoCompact ──→ messagesForQuery buildPostCompactMessages(result) │ ▼ 最终 messagesForQuery ──→ 送往 API这是经典的 Pipeline链式处理模式——每一站的输出就是下一站的输入。条件跳站不是每站都必跑Pipeline 不是固定四站全跑。它有条件跳站机制——根据运行环境动态决定哪些站执行处理站跳站条件什么时候跳过Snipfeature(HISTORY_SNIP)未开启feature gate 关闭时整站跳过Microcompact无默认执行永不跳过ContextCollapsefeature(CONTEXT_COLLAPSE) contextCollapsefeature gate 关闭或无折叠数据时跳过AutoCompacttoken 阈值未超前三层之后 token 仍在窗口内时不触发这意味着在轻量对话中Pipeline 可能只跑 Microcompact 一站。执行路径是自适应的——不是机械的四步流程而是根据当前消息状态决定走哪条路。跨站参数传递不止是主数据流除了主数据流messagesForQueryPipeline 还有侧信道side channel传递参数。这些参数不参与数据变换但影响后续站的决策Snip → AutoCompactsnipTokensFreedSnip 释放的 token 数被传递给 AutoCompact 的阈值计算。因为 AutoCompact 的 token 估算基于“上次 API 返回的使用量”感知不到 Snip 削减了多少——需要手动补偿否则 AutoCompact 会在不该触发时触发。Compact 结果 → task_budget压缩后taskBudgetRemaining被重新计算传递给下一轮 API 调用保证服务端预算追踪在多次压缩后仍准确详见后文“Token Budget 的跨 Compact 传递”一节。这种“主数据流 侧信道”的双轨传递是 Pipeline 区别于简单函数调用链的关键设计。短路重置AutoCompact 的“核爆”前三层是增量修改——在原数组上修剪Snip、合并Microcompact、折叠Collapse。AutoCompact 完全不同它替换整个数组。buildPostCompactMessages()返回的是一个全新的消息结构摘要消息 保留的最近几轮对话尾巴完全覆盖之前的messagesForQuery。这是 Pipeline 中的“短路重置”——一旦触发前三层的增量修改被归零消息结构从零重建。这也是为什么 AutoCompact 排在最后只有增量操作都无能为力时才值得付出一次 API 调用的代价做彻底替换。错误恢复路径Pipeline 的第二条命Pipeline 不是一条直线。当 API 返回 413prompt too long时控制流会重新进入 Pipeline 的恢复路径正常路径Snip → Microcompact → Collapse → AutoCompact → API 调用 │ │ 413 错误 ▼ 恢复路径Collapse Drain → Reactive Compact → 重试 API │ │ 仍然 413 ▼ return prompt_too_longContextCollapse 的recoverFromOverflow()是第一个恢复手段——排空已折叠内容释放空间。如果失败Reactive Compact 作为被动防线。两者都失败才真正放弃——返回prompt_too_long错误。这种“正常 Pipeline 恢复 Pipeline”的双路径设计是 Agent 能长时间运行不崩溃的关键。后面讲 ContextCollapse 和 Reactive Compact 时我们还会回到这张图。四层压缩全景为什么需要四层一层压缩不够。不同的冗余需要不同的手术刀。压缩层解决的问题操作方式执行者性能可逆性Feature GateSnip远离当前的旧对话无用按 token 阈值截断本地剪裁 messages 数组微秒级0 API 调用不可逆HISTORY_SNIPMicrocompact同一 tool_use_id 的重复结果合并保留最新本地遍历比对毫秒级0 API 调用不可逆(默认开启)ContextCollapse大段完整对话占位折叠为摘要投影本地语义折叠毫秒级0 API 调用可恢复CONTEXT_COLLAPSEAutoCompact总 token 超过模型窗口调用 compact agent 生成摘要compact agentfork query()秒级1 次 API 调用/触发不可逆(默认开启)下面这张图展示了四层压缩的执行顺序和各自的效果——从上到下依次执行每一层的输出是下一层的输入。执行顺序为什么不能换因为每层假设前一层已完成。举个例子如果先做 AutoCompact1 次 API 调用再做 Microcompact0 次 API那摘要里可能还包含重复的工具结果——去重白做了API 也白调了。正确顺序是先截断远的历史Snip再去重Microcompact再折叠Collapse最后如果还是太长才调用摘要AutoCompact——这是0 成本 → 0 成本 → 0 成本 → 不得不花 1 次 API的阶梯。四层压缩的代码编排也反映了这个顺序。在query.ts中// query.ts L396-467 — 四层压缩的执行顺序letmessagesForQuery[...getMessagesAfterCompactBoundary(messages)]// 第1层SnipFeature gatedif(feature(HISTORY_SNIP)){constsnipResultsnipModule.snipCompactIfNeeded(messagesForQuery)messagesForQuerysnipResult.messages snipTokensFreedsnipResult.tokensFreed}// 第2层MicrocompactconstmicrocompactResultawaitdeps.microcompact(messagesForQuery,...)messagesForQuerymicrocompactResult.messages// 第3层ContextCollapseFeature gatedif(feature(CONTEXT_COLLAPSE)contextCollapse){constcollapseResultawaitcontextCollapse.applyCollapsesIfNeeded(...)messagesForQuerycollapseResult.messages}// 第4层AutoCompactconst{compactionResult}awaitdeps.autocompact(messagesForQuery,...)if(compactionResult){messagesForQuerybuildPostCompactMessages(compactionResult)}注意两点一是messagesForQuery被层层赋值——每层都改变了消息数组二是 Snip 的snipTokensFreed被传递给了 AutoCompact——因为 AutoCompact 的阈值判断需要考虑 Snip 已经削减了多少 token。Snip历史消息截断Snip 是最暴力的一层——直接砍掉远离当前对话的旧消息。它按 token 阈值截断保留最近的消息直到累计 token 接近某个上限更早的消息直接丢弃。被丢弃的消息不会以任何形式保留——这就是为什么它是不可逆的。Snip 不是默认开启的——它由HISTORY_SNIPfeature gate 控制。为什么需要 gate因为截断意味着 Agent “遗忘了早期的对话——如果早期对话包含关键上下文比如用户说请用 TypeScript”截断后 Agent 可能会失忆。所以这是有代价的优化。它有一个重要输出snipTokensFreed。这个数字会被传递给后续的 AutoCompact——AutoCompact 的阈值计算依赖tokenCountWithEstimation(messagesForQuery)但这个估计是基于上次 API 返回的使用量的Snip 削减的 token 它感知不到。所以需要手动减去snipTokensFreed否则 AutoCompact 会在不该触发的时候触发。Microcompact重复工具结果去重Microcompact 解决的是一个工具被多次调用结果被重复注入的问题。在 Agent 的循环中同一个工具可能被连续调用多次比如连续 Read 了 5 个文件。每次调用都会在 messages 中追加一个tool_result消息。这些结果可能在后续轮次中不再需要但它们仍然占据 token。Microcompact 的做法是按tool_use_id去重——同一个 tool_use_id 只保留最后一次结果。这比简单截断更智能——它是语义级的去重不是位置级的。另外Microcompact 还利用了 Anthropic API 的cache_edits机制。这个机制允许客户端标记哪些 tool_result 可以从缓存中删除服务端在下次请求时会报告实际删除的 token 数cache_deleted_input_tokens。Microcompact 结合这个机制不仅能减少发送的 token还能精确追踪省了多少。ContextCollapse可恢复的上下文折叠ContextCollapse 是四层压缩中唯一可恢复的一层。它和 AutoCompact 的本质区别是Collapse 是折叠而不是压缩。折叠后的内容仍然以结构化的方式存储只是读取时被投影为一个摘要。如果后续代码需要访问折叠区的内容可以通过projectView()恢复——AutoCompact 做不到这点。ContextCollapse 由CONTEXT_COLLAPSEfeature gate 控制。它的核心函数是applyCollapsesIfNeeded()——检查是否有大段对话可以折叠如果有就提交折叠日志。折叠后的messagesForQuery中大段对话被替换为一个折叠标记。这层还有一个关键用途recoverFromOverflow()。当 API 返回 413prompt too long时这是第一个被调用的恢复手段——把已折叠的内容排空释放更多空间。如果排水成功continue回到循环顶部重试如果排水失败没有可折叠的内容了就交给下一层——Reactive Compact。AutoCompact自动摘要压缩AutoCompact 是四层压缩的核武器——调用一个独立的 compact agent 来生成对话摘要。下面这张图展示了 AutoCompact 的决策流程什么时候触发、怎么计算阈值、失败了怎么办。什么时候触发AutoCompact 的触发条件是当前消息的估算 token 数超过了一个阈值。阈值的计算公式是threshold effectiveContextWindow - AUTOCOMPACT_BUFFER_TOKENS其中effectiveContextWindow是模型的上下文窗口大小减去输出预留20,000 tokensAUTOCOMPACT_BUFFER_TOKENS 13,000。这个 buffer 的存在是为了在真需要压缩之前就压缩——不是在上下文窗口满了才动手而是提前 13,000 tokens 就开始准备。还有一个CLAUDE_CODE_AUTO_COMPACT_WINDOW环境变量可以覆盖窗口大小只减不增方便测试和调试。怎么执行AutoCompact 不是直接在query.ts里写压缩逻辑——它调用一个独立的 compact agent。这个 compact agent 是另一个query()调用fork agent接收当前对话历史输出一个摘要。摘要生成有自己的 token 预算最多 20,000 个输出 token。压缩完成后messagesForQuery被替换为压缩后的精简版本——摘要消息 少量保留消息最近几个 turn 的对话尾巴。同时autoCompactTracking被重置turnId uuid()、turnCounter 0因为压缩后对话结构变了需要新的计数周期。下面这张图展示了压缩前后的消息结构对比——从 10 条完整消息变成了摘要 保留尾巴。失败了怎么办——Circuit BreakerAutoCompact 不是每次都能成功。如果 compact agent 调用失败比如 API 错误、生成的摘要太大consecutiveFailures计数器会递增。当consecutiveFailures MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES (3)时circuit breaker 熔断——不再尝试自动压缩。这个设计来自生产环境的数据BQ 2026-03-10 报告显示1,279 个 session 有 50 次连续失败最多 3,272 次每天浪费约 25 万次 API 调用。Circuit breaker 就是为这个场景设计的——与其无限重试不如 3 次后放弃。Reactive Compact被动的最后防线当四层压缩都拦不住 413 错误时Reactive Compact 上场。下面这张图展示了 Reactive Compact 的触发路径——从 413 错误被 withhold 开始到恢复成功或放弃。它的工作方式跟 AutoCompact 类似也叫 compact agent 生成摘要但有两个关键区别被动触发不是阈值判断而是 API 返回 413 后才启动。413 错误先被withhold不 yield 给用户然后依次尝试Collapse Drain → Reactive Compact。只尝试一次hasAttemptedReactiveCompact标志位保证不会死循环。如果 RC 已经尝试过且仍然 413就直接return { reason: prompt_too_long }——不再重试。这是一种防御性设计主动压缩AutoCompact尽力而为被动恢复Reactive Compact兜底。如果两者都拦不住说明是真正的不可恢复错误——比如用户在单轮中塞入了太多图片。Token Budget 的跨 Compact 传递AutoCompact 之后消息结构发生了根本性变化——从完整对话变成了摘要 尾巴。这会影响一个关键的追踪变量task_budget.remaining。task_budget是服务端用来追踪剩余预算的机制——如果对话太长服务端会限制轮次。在压缩之前服务端能看到完整对话自己数 token。压缩之后服务端只看到摘要需要客户端告诉它压缩前的最后窗口用了多少 token。taskBudgetRemaining就是这个传递变量——每次 compact 时计算压缩前的 final context tokens从 remaining 中减去。这保证了服务端的预算追踪在多次压缩后仍然准确。// query.ts L508-514 — task_budget 跨 compact 传递if(params.taskBudget){constpreCompactContextfinalContextTokensFromLastResponse(messagesForQuery)taskBudgetRemainingMath.max(0,(taskBudgetRemaining??params.taskBudget.total)-preCompactContext,)}各压缩层在模型上下文中的位置四层压缩都在操作messages部分但每一层操作的位置和粒度不同。理解它们在模型上下文中的位置有助于判断压缩带来的影响。从消息上下文管理篇的上下文全景图可知发给模型的数据分为三块┌───────────────────────────────────────────────┐ │ systemSystem Prompt ~30% ← 不压缩 │ ├───────────────────────────────────────────────┤ │ messages对话历史 ~60% ← 压缩目标 │ │ ├── 早期对话远的历史 ← Snip 截断 │ │ ├── 中间对话大段内容 ← Collapse 折叠 │ │ ├── 工具结果重复内容 ← Microcompact │ │ └── 整体超窗口时 ← AutoCompact │ ├───────────────────────────────────────────────┤ │ tools工具 Schema ~10% ← 不压缩 │ └───────────────────────────────────────────────┘Snip 操作在 messages 尾部——它按 token 阈值从数组末尾往前数砍掉超过阈值的部分。被砍掉的是最老的消息即模型视角中最不相关的上下文。代价是 Agent 可能失忆——这就是它由 feature gate 控制的原因。Microcompact 操作在 messages 的 tool_result 区域——它遍历所有tool_resultContent Part按tool_use_id合并。被压缩的是旧的工具执行结果不是整个对话。因为每个tool_result对应一个tool_use压缩后模型不会失忆只是看不到旧结果了——影响较小。ContextCollapse 操作在 messages 的大段连续对话区——它把可折叠的段落投影为摘要标记同时保留恢复能力。模型仍然知道有过这段对话但看不到细节——是四层中唯一在保记忆和省 token之间做平衡的。AutoCompact 操作在整个 messages 数组——它在三层之后如果总 token 仍然超过模型窗口就调用 compact agent 把大部分消息替换为摘要。模型只能看到压缩前发生过这些事的简述细节完全丢失。这也是为什么 AutoCompact 只在前三层都无能为力时才触发——它是核武器。与消息上下文管理篇对比消息上下文管理篇关注的是发给模型的数据由哪几块组成本文关注的是在发给模型之前对 messages 这块做了哪些预处理。两块知识合在一起才是消息从用户输入到 API 调用的完整生命周期。本章小结本文从两个维度拆解了 Agent 执行内核的 Pipeline 设计Pipeline 架构维度消息数组messagesForQuery流过四个处理站每站可选跳过条件跳站、通过侧信道传递参数跨站参数传递、AutoCompact 触发时整个替换短路重置、413 错误时进入恢复路径双路径设计。这不是简单的函数调用链而是一套有自适应能力的架构模式。压缩功能维度——四个处理站各自做了什么Snip截断按 token 阈值砍掉旧消息——代价是“失忆”由 feature gate 控制。Microcompact去重合并重复工具结果——语义级去重不是位置级。ContextCollapse折叠可恢复的语境投影——比直接删除更优雅。AutoCompact摘要调用独立的 compact agent 生成摘要——削减 85% token但有 circuit breaker 防死循环。Reactive Compact被动当四层都拦不住 413 时的最后防线——只尝试一次。这四层压缩和 Prompt Cache 机制是正交关系压缩减少发给 API 的 token 量Cache 减少API 处理这些 token 的计算量。两者叠加才是 Claude Code 能从 50 轮对话中省下 10 倍成本的原因。下一姊妹篇 [从 API 调用到安全退出](./04-Claude Code深度拆解-Agent执行内核-从API调用到安全退出.md) 将深入压缩完成后的完整链路——API 调用、流式工具执行、错误恢复、生命周期收尾。系列导航本文属于《Claude Code 源码 Deep Dive》系列中「Agent 执行内核」命题的子篇章专注于Pipeline 与上下文压缩。姊妹篇可独立阅读Claude Code 深度拆解Agent 执行内核 1 — 主循环与状态机Claude Code 深度拆解Agent 执行内核 3 — 从 API 调用到安全退出如果这篇文章对你有帮助欢迎点赞收藏支持一下。如果你对 Claude Code 源码感兴趣欢迎关注本系列后续更新。有任何想法或疑问欢迎评论区留言讨论