Claude Code 上下文压缩系统深度解析
Claude Code 上下文压缩系统深度解析
基于源码分析,揭秘 Claude Code 如何通过五层机制让对话”永不超限”。
目录
- 系统全景
- 上下文窗口检测
- Token 计数机制
- 第一层:时间触发微压缩
- 第二层:缓存微压缩(ant 专属)
- 第三层:自动压缩
- 第四层:会话记忆压缩
- 第五层:完全压缩
- 压缩后的上下文重建
- 技能内容的跨压缩存活
- 警告与阻断系统
- 查询循环中的触发顺序
- 所有关键常量速查表
系统全景
所有 LLM 都有上下文窗口限制。Claude Code 的解法不是单一机制,而是五层递进式压缩架构,按照代价从低到高依次尝试:
1 | Token 压力上升 |
相关文件分布在 src/services/compact/ 目录下:
| 文件 | 职责 |
|---|---|
compact.ts |
核心压缩逻辑(~1700 行) |
autoCompact.ts |
自动压缩阈值、熔断器 |
microCompact.ts |
微压缩(时间触发 + 缓存路径) |
apiMicrocompact.ts |
API 原生上下文管理(ant 专属) |
sessionMemoryCompact.ts |
会话记忆压缩 |
prompt.ts |
NO_TOOLS_PREAMBLE、总结提示词 |
grouping.ts |
API 轮次分组(用于 PTL 重试) |
compactWarningState.ts |
警告抑制状态 |
postCompactCleanup.ts |
压缩后缓存重置 |
上下文窗口检测
文件:src/utils/context.ts
Claude Code 通过多重检测确定当前模型的上下文窗口大小(第 51–98 行):
1 | export const MODEL_CONTEXT_WINDOW_DEFAULT = 200_000 // 默认 200K |
压缩系统使用的是有效上下文窗口,需减去为总结输出预留的 token(autoCompact.ts 第 28–49 行):
1 | const MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000 // p99.99 实测为 17,387 tokens |
Token 计数机制
文件:src/utils/tokens.ts
系统使用两种计数策略:
精确计数(基于 API 返回值)
tokenCountWithEstimation()(第 226–261 行):从最近一次 API 响应的 usage 字段读取精确值,再加上此后新增消息的估算值:
1 | // 从 usage 中读取的字段 |
注意:token 计数包含缓存消耗,这是正确的——缓存内容虽然便宜,但确实占据了上下文窗口。
粗略估算(无 API 调用)
roughTokenCountEstimation()(src/services/tokenEstimation.ts 第 203 行):
1 | Math.round(content.length / 4) // 每 4 字节约等于 1 个 token |
更精确时可调用 countMessagesTokensWithAPI,使用 anthropic.beta.messages.countTokens(...)。若主路径失败,兜底为 countTokensViaHaikuFallback——真实发起一个 max_tokens: 1 的 Haiku API 调用,从返回的 usage 中读取实际 token 数。
第一层:时间触发微压缩
文件:src/services/compact/microCompact.ts
设计动机
Anthropic API 的 Prompt Cache TTL 约为 60 分钟。超时后,服务端缓存失效,所有历史工具结果都需要重新发送。此时已有大量 token 被”无效”内容占用——何不趁机清空?
触发条件
1 | // 第 446–530 行 |
执行动作
- 保留最近
keepRecent(默认 5 个)可压缩工具结果 - 其余旧工具结果内容替换为:
1 | export const TIME_BASED_MC_CLEARED_MESSAGE = '[Old tool result content cleared]' |
- 调用
resetMicrocompactState()清空缓存-MC 状态
可被压缩的工具类型:file_read、Bash/shell 工具、grep、glob、web_search、web_fetch、file_edit、file_write。
关键细节:时间触发微压缩直接修改本地消息数组。因为缓存已冷,不存在服务端缓存失效问题。
第二层:缓存微压缩(ant 专属)
文件:src/services/compact/microCompact.ts,第 305–399 行
功能标志:CACHED_MICROCOMPACT(仅 Anthropic 内部)
与第一层的本质区别
时间触发微压缩修改本地消息;缓存微压缩发送 cache_edits 指令到服务端,在不破坏缓存前缀的前提下静默删除旧内容。
1 | 普通清空:本地改 → 下次发送时服务端缓存全部失效(昂贵) |
consumePendingCacheEdits() 在每次 API 请求前被 claude.ts 调用,将待处理的编辑指令附加到请求中。
外部用户的降级
1 | // 第 288–292 行注释 |
对外部用户而言,前两层微压缩均不生效,上下文压力完全由第三层自动压缩处理。
第三层:自动压缩
文件:src/services/compact/autoCompact.ts
触发阈值的精确计算
“87%”并非代码中的字面常量,而是从 buffer 减法推导而来:
1 | export const AUTOCOMPACT_BUFFER_TOKENS = 13_000 // 第 62 行 |
可通过环境变量 CLAUDE_AUTOCOMPACT_PCT_OVERRIDE(百分比)覆盖阈值。
熔断器:三次失败停止
1 | // 第 70 行 |
1 | // 第 257–265 行 |
成功时 consecutiveFailures 重置为 0;失败时递增。三次失败后该 session 不再尝试自动压缩。
完整触发决策链
自动压缩在以下情况下跳过(autoCompact.ts 第 160–239 行):
querySource === 'session_memory'或'compact'(递归防护)querySource === 'marble_origami'(上下文折叠 Agent,CONTEXT_COLLAPSE特性开启时)- 设置了
DISABLE_COMPACT或DISABLE_AUTO_COMPACT环境变量 - 用户配置
autoCompactEnabled === false REACTIVE_COMPACT特性 +tengu_cobalt_raccoon标志为 true(响应式专属模式)CONTEXT_COLLAPSE特性开启且isContextCollapseEnabled()为 true- 最终检查:
tokenCountWithEstimation(messages) - snipTokensFreed >= threshold
第四层:会话记忆压缩
文件:src/services/compact/sessionMemoryCompact.ts
这是完全压缩的轻量替代方案:利用后台维护的运行时摘要,无需额外的 API 调用来生成总结。
默认配置
1 | export const DEFAULT_SM_COMPACT_CONFIG = { |
工作原理
- 找到
lastSummarizedMessageId(会话记忆系统最近已总结到的消息) - 保留从该消息点到当前的消息(必要时向前扩展以满足最小阈值)
- 使用已有的会话记忆内容作为历史摘要
- 无需调用 API 生成摘要
安全门控
如果压缩后(摘要 + 保留消息)的 token 数仍超过自动压缩阈值,则返回 null,回退到完全压缩。
启用条件:GrowthBook 标志 tengu_session_memory 和 tengu_sm_compact 同时为 true,或设置了 ENABLE_CLAUDE_CODE_SM_COMPACT 环境变量。
第五层:完全压缩
文件:src/services/compact/compact.ts(~1700 行)
NO_TOOLS_PREAMBLE:为什么这么严厉
文件:src/services/compact/prompt.ts,第 19–26 行
1 | const NO_TOOLS_PREAMBLE = `CRITICAL: Respond with TEXT ONLY. Do NOT call any tools. |
代码注释(第 12–18 行)解释了为什么措辞如此强硬:
“在 Sonnet 4.6+ 自适应思考模型上,即使有较弱的指令,模型有时仍会尝试工具调用。使用
maxTurns: 1的缓存共享 fork 路径时,被拒绝的工具调用意味着没有文本输出 → 触发流式降级(4.6 上失败率 2.79%,4.5 上仅 0.01%)。”
API 层同样强制执行(compact.ts 第 1125–1134 行):
1 | export function createCompactCanUseTool(): CanUseToolFn { |
<analysis> 草稿模式
压缩提示词要求模型先写 <analysis> 块(思考草稿,提升总结质量),再写 <summary> 块。生成完成后,formatCompactSummary() 完全删除 analysis 块,只保留 summary 注入回上下文。
9 节总结提示词
压缩提示词要求总结以下 9 个方面:
- 主要请求与意图
- 关键技术概念
- 文件和代码段(含完整代码片段)
- 错误及修复方案
- 问题解决过程
- 所有用户消息
- 待处理任务
- 当前工作状态
- 可选的下一步(含最近对话的直接引用)
双路径流式架构
路径 A:缓存共享 fork(默认)
由 GrowthBook 标志 tengu_compact_cache_prefix(默认 true)控制,通过 runForkedAgent 运行,继承主对话的缓存前缀。
- 不设置
maxOutputTokens(设置会导致 thinking 配置分歧,破坏缓存键) skipCacheWrite: true,防止总结过程污染缓存
路径 B:直接流式降级
路径 A 失败时使用。设置 maxOutputTokensOverride: Math.min(20_000, getMaxOutputTokensForModel(...))。工具仅提供 [FileReadTool](若启用工具搜索则额外提供 ToolSearchTool 和 MCP 工具)。
PTL(Prompt Too Long)重试
压缩调用本身也可能超限。遇到 prompt_too_long 错误时:
1 | const MAX_PTL_RETRIES = 3 // compact.ts 第 227 行 |
摘要注入回上下文的格式
getCompactUserSummaryMessage()(prompt.ts 第 337 行):
1 | This session is being continued from a previous conversation that ran out of context. |
若为自动压缩(非用户手动触发),还会追加:
1 | Continue the conversation from where it left off without asking the user any further questions. |
压缩后的上下文重建
压缩完成后,buildPostCompactMessages() 按严格顺序重建上下文(第 330–338 行):
1 | boundaryMarker |
Attachments 注入顺序(compactConversation 函数):
- 文件重读(最多 5 个,总计 ≤50K tokens,每个 ≤5K)
- 异步 Agent 状态消息
- Plan 文件 attachment(若处于 plan 模式)
- Plan 模式指令
- 已调用技能内容(若本 session 使用过技能)
- 工具集 delta(
callSite: 'compact_full',从头重新声明全部工具) - Agent 列表 delta
- MCP 指令 delta
- SessionStart hook 结果(CLAUDE.md 重新注入等)
技能内容的跨压缩存活
文件:src/bootstrap/state.ts,第 1500–1540 行
1 | export type InvokedSkillInfo = { |
压缩时的技能恢复(createSkillAttachmentIfNeeded,compact.ts 第 1494–1534 行):
- 按
invokedAt降序排列(最近使用的优先保留) - 每个技能最多
POST_COMPACT_MAX_TOKENS_PER_SKILL = 5,000tokens,超出部分用 marker 替换:1
[... skill content truncated for compaction; use Read on the skill path if you need the full text]
- 总技能预算
POST_COMPACT_SKILLS_TOKEN_BUDGET = 25,000tokens
postCompactCleanup.ts 的关键注释(第 17–20 行):
1 | // Note: We intentionally do NOT clear invoked skill content here. |
技能内容会跨越多次压缩持久保留——这是刻意设计,不是遗漏。
警告与阻断系统
文件:src/services/compact/autoCompact.ts,第 93–145 行
系统维护四个阶梯式阈值:
1 | ├─ WARNING_THRESHOLD(threshold - 20,000 tokens):显示上下文窗口使用警告 |
1 | export function calculateTokenWarningState(tokenUsage, model) { |
isAtBlockingLimit 阻止新用户输入。可通过 CLAUDE_CODE_BLOCKING_LIMIT_OVERRIDE 环境变量覆盖。
查询循环中的触发顺序
文件:src/query.ts,第 393–468 行
每次用户发送消息,压缩层按固定顺序依次检查:
1 | 1. HISTORY_SNIP(feature('HISTORY_SNIP')) |
所有关键常量速查表
| 常量 | 值 | 文件 | 行号 |
|---|---|---|---|
MODEL_CONTEXT_WINDOW_DEFAULT |
200,000 | context.ts |
9 |
MAX_OUTPUT_TOKENS_FOR_SUMMARY |
20,000 | autoCompact.ts |
30 |
AUTOCOMPACT_BUFFER_TOKENS |
13,000 | autoCompact.ts |
62 |
WARNING_THRESHOLD_BUFFER_TOKENS |
20,000 | autoCompact.ts |
63 |
ERROR_THRESHOLD_BUFFER_TOKENS |
20,000 | autoCompact.ts |
64 |
MANUAL_COMPACT_BUFFER_TOKENS |
3,000 | autoCompact.ts |
65 |
MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES |
3 | autoCompact.ts |
70 |
POST_COMPACT_MAX_FILES_TO_RESTORE |
5 | compact.ts |
122 |
POST_COMPACT_TOKEN_BUDGET |
50,000 | compact.ts |
123 |
POST_COMPACT_MAX_TOKENS_PER_FILE |
5,000 | compact.ts |
124 |
POST_COMPACT_MAX_TOKENS_PER_SKILL |
5,000 | compact.ts |
129 |
POST_COMPACT_SKILLS_TOKEN_BUDGET |
25,000 | compact.ts |
130 |
MAX_COMPACT_STREAMING_RETRIES |
2 | compact.ts |
131 |
MAX_PTL_RETRIES |
3 | compact.ts |
227 |
DEFAULT_SM_COMPACT_CONFIG.minTokens |
10,000 | sessionMemoryCompact.ts |
57 |
DEFAULT_SM_COMPACT_CONFIG.minTextBlockMessages |
5 | sessionMemoryCompact.ts |
58 |
DEFAULT_SM_COMPACT_CONFIG.maxTokens |
40,000 | sessionMemoryCompact.ts |
59 |
TIME_BASED_MC_CONFIG.gapThresholdMinutes |
60 分钟 | timeBasedMCConfig.ts |
31 |
TIME_BASED_MC_CONFIG.keepRecent |
5 | timeBasedMCConfig.ts |
33 |
TOOL_RESULT_CLEARED_MESSAGE |
'[Old tool result content cleared]' |
toolResultStorage.ts |
34 |
TIME_BASED_MC_CLEARED_MESSAGE |
'[Old tool result content cleared]' |
microCompact.ts |
36 |
roughTokenCountEstimation 精度 |
4 字节/token | tokenEstimation.ts |
205 |
| API native MC trigger | 180,000 tokens | apiMicrocompact.ts |
— |
| API native MC target | 40,000 tokens | apiMicrocompact.ts |
— |
设计哲学总结
这套系统体现了几个精妙的工程取舍:
1. 代价递进:从”直接清空旧工具结果”(近乎零代价)到”fork 子 Agent 生成摘要”(一次额外 API 调用),代价逐层提升,每层只在必要时触发。
2. 缓存感知:时间触发微压缩专门利用缓存冷却窗口,在”反正要重发”的时机顺手清理,不额外增加成本。
3. 熔断器防护:BQ 数据显示没有熔断器时每天全局浪费 25 万次 API 调用——用 3 次失败上限解决了一个真实的生产问题。
4. 技能内容持久化:技能内容刻意跨越多次压缩保留,确保 AI 始终能”记住”本 session 使用过哪些技能的完整指令,而不是在每次压缩后茫然。
5. 摘要与工具硬隔离:NO_TOOLS_PREAMBLE + API 层 deny 双重保险,源于真实的 2.79% 失败率数据,不是过度防御。