Lazy loaded image
Claude Code 上下文压缩算法深度分析
字数 5508阅读时长 14 分钟
2026-4-1
2026-4-1
type
Post
status
Published
date
Apr 1, 2026
slug
cc3
summary
基于 Claude Code 源码(2026-03-31 快照,512K 行 TypeScript)逆向分析 核心文件:src/services/compact/ 目录下 11 个文件
tags
人工智能
上下文工程
category
技术文档
icon
password
Description

1. 架构总览

Claude Code 的上下文压缩不是单一算法,而是一个 4层递进的压缩体系,每层解决不同层面的问题:
核心原则: 尽可能用廉价的规则操作延迟昂贵的 LLM 调用,只在不得已时丢弃信息。

涉及的源文件

文件
行数
职责
microCompact.ts
~400
微压缩:规则清理旧工具结果
apiMicrocompact.ts
API 层缓存编辑集成
timeBasedMCConfig.ts
时间触发微压缩配置
autoCompact.ts
~350
自动压缩:阈值判断 + 断路器
compact.ts
~600+
传统压缩:Fork Agent 摘要
prompt.ts
~375
压缩提示词模板
sessionMemoryCompact.ts
~630
Session Memory 压缩路径
grouping.ts
~63
消息按 API 轮次分组
postCompactCleanup.ts
压缩后清理
compactWarningHook.ts
压缩警告钩子
compactWarningState.ts
压缩警告状态

2. 第1层:微压缩(Microcompact)

源文件: microCompact.ts

核心思想

不调用 LLM,纯规则操作——清理旧的、大块的工具输出结果,保留语义信息。这是每轮查询前都会执行的最轻量操作。

可压缩工具白名单

不在白名单中的工具(如 Agent、Skill、MCP 等)的结果不会被微压缩。

两个子路径

子路径 A:时间触发微压缩

子路径 B:缓存编辑微压缩(Cached MC)

这是更精巧的路径——在缓存仍然有效时工作:
缓存编辑的状态管理:

Token 估算辅助


3. 第2层:自动压缩(Auto-Compact)

源文件: autoCompact.ts

阈值计算

其他阈值

断路器机制

shouldAutoCompact 决策树

autoCompactIfNeeded 执行流程


4. 第3层:传统压缩(Full Compact)

源文件: compact.ts + prompt.ts

核心机制:Fork Agent

传统压缩使用一个 Fork Agent——创建当前会话的一个分支,让它生成摘要。关键优势是共享主会话的 prompt cache

预处理管线

摘要输出格式

Fork Agent 被要求生成两个 XML 块:

防止工具调用的强力前导词

为什么需要这么强力?注释解释了:

后处理(formatCompactSummary)

压缩后消息序列

Prompt-Too-Long 重试机制

当压缩请求本身超过上下文限制时:

部分压缩(Partial Compact)

支持两个方向:
方向
提示词
用途
from(默认)
PARTIAL_COMPACT_PROMPT
保留旧消息,仅摘要"最近的消息"
up_to
PARTIAL_COMPACT_UP_TO_PROMPT
摘要旧消息,保留新消息。摘要会放在开头,后续消息跟在后面
up_to 模式的摘要包含一个特殊的第 9 章节 "Context for Continuing Work",专门为后续消息提供上下文。

5. 第4层:Session Memory 压缩

源文件: sessionMemoryCompact.ts

核心思想

不调用 LLM 生成新摘要,而是直接使用已经通过后台记忆提取(extractMemories)积累的 Session Memory 作为"摘要"。

优势

  • 速度快:不需要 API 调用,<10ms
  • 质量可预测:Session Memory 是在每轮查询后渐进更新的,不是一次性压缩
  • 保留近期消息:不像传统压缩那样替换所有消息

配置参数

calculateMessagesToKeepIndex 算法

这是 Session Memory 压缩的核心算法——决定保留哪些最近的消息:

adjustIndexToPreserveAPIInvariants 算法

这个算法解决一个棘手的问题——流式传输时,一个 API 响应会产生多条消息(thinking、tool_use 等),它们共享同一个 message.id。如果在中间切断,normalizeMessagesForAPI 合并时会丢失 thinking 块。
源码注释中的真实 bug 场景:

trySessionMemoryCompaction 完整流程


6. 消息分组算法

源文件: grouping.ts
设计细节:
  • 同一 API 请求的流式块共享同一个 message.id
  • StreamingToolExecutor 在流式输出期间交错插入 tool_result,但它们属于同一轮次
  • 只要 message.id 不变,所有消息都在同一组内
  • 不跟踪未解决的 tool_use ID——让分组边界自然形成,由 ensureToolResultPairing 在 API 层修复残留的配对问题

7. Token 估算算法

贯穿所有压缩路径的核心辅助:

粗略估算

消息级估算

精确计算


8. 压缩提示词工程

源文件: prompt.ts

三种提示词模板

模板
变量名
用途
完整压缩
BASE_COMPACT_PROMPT
摘要整个对话
部分压缩(from)
PARTIAL_COMPACT_PROMPT
只摘要最近的消息
部分压缩(up_to)
PARTIAL_COMPACT_UP_TO_PROMPT
摘要旧消息,作为后续消息的前导

提示词结构

<analysis> 块的作用

压缩后的摘要消息


9. 5层错误恢复中的压缩角色

query.ts 的 5 层错误恢复机制中,压缩系统承担了前 2 层:
响应式压缩 vs 主动压缩
属性
主动压缩(Auto-Compact)
响应式压缩(Reactive Compact)
触发
token 数超过阈值
API 返回 prompt-too-long 错误
时机
API 调用前
API 调用失败后
路径
autoCompact.tscompact.ts
query.ts 错误恢复层
回退
SM → 传统压缩 → 放弃
传统压缩 → 裁剪最旧消息

10. 各层对比与设计哲学

横向对比

属性
微压缩
Session Memory
传统压缩
PTL Recovery
调用 LLM
是(Fork Agent)
信息损失
最小(仅删工具输出)
中等(靠已有摘要)
中等(9章摘要)
高(丢弃最旧消息)
延迟
<1ms
<10ms
5-30s
~0
触发
每轮自动
自动压缩优先路径
自动/手动
压缩自身超限
prompt cache
保留(缓存编辑)
破坏
共享(Fork)
破坏
最大输出
20K tokens
断路器
有(3次)
有(3次)
token 预算
40K 最大保留
50K 文件重注入
按组裁剪

设计哲学总结

  1. 渐进式降级:从无损操作(微压缩)到有损操作(传统压缩)到丢弃操作(PTL Recovery),每一层都比上一层代价更高
  1. 缓存优先:微压缩的缓存编辑路径专门设计为不破坏 prompt cache,传统压缩通过 Fork Agent 共享 cache
  1. 安全性保证
      • 永远不切断 tool_use/tool_result 配对
      • 断路器防止失败时无限重试
      • 递归保护防止压缩代理触发自身压缩
  1. 可观测性:每个压缩操作都记录 logEvent(如 tengu_compact, tengu_cached_microcompact),包含压缩前后的 token 数、触发原因、重试次数等
  1. 可配置性:几乎所有阈值都支持环境变量覆盖(用于测试)或 GrowthBook 远程配置(用于动态调整)
基于 Claude Code 源码逆向分析,2026-03-31