Claude Code 长上下文管理策略分析
基于
@anthropic-ai/claude-code@2.1.88源码还原项目
核心文件:services/compact/,utils/toolResultStorage.ts,query.ts,services/contextCollapse/
概述
Claude Code 不是简单依赖 LLM 的 256K/512K/1M 上下文窗口硬抗,而是设计了一套多层、递进式的上下文管理系统。共 6 层防线,由轻到重依次触发,确保在任何交互长度下都能保持 AI 响应的质量和速度。
核心架构:Query 循环中的执行顺序
每次 AI API 调用前,按以下顺序执行:
1. applyToolResultBudget() — 裁剪超大工具结果(零 API 开销)
2. Snip Compact — 移除旧的 API round groups
3. Microcompact — 清理旧工具结果(缓存编辑 or 时间触发)
4. Context Collapse — 实验性细粒度管理(可选,替代 autocompact)
5. Auto Compact — 如果以上都不够,AI 摘要化整个对话
↓
发送 API 请求
每层策略的关键特征对比:
| 层级 | 策略 | 触发方式 | API 调用 | 压缩比 | 保留精度 |
|---|---|---|---|---|---|
| 1a | 缓存编辑 Microcompact | 工具数量阈值 | 否(服务端编辑) | 低 | 高(只删过期工具结果) |
| 1b | 基于时间的 Microcompact | 时间间隔 | 否 | 低 | 高 |
| 2 | Snip Compact | 阈值 | 可能 | 中 | 中(裁剪整段对话) |
| 3 | 聚合工具结果预算 | 每次调用前 | 否 | 中 | 中(大结果→文件+预览) |
| 4 | Auto Compact | Token 阈值 | 是(1次 API) | 高 | 低(AI 摘要化) |
| 5 | Context Collapse | 百分比阈值 | 可能 | 可调 | 高 |
第一层:Microcompact(微压缩)
最轻量操作,不请求额外 API 调用,纯本地或服务端编辑完成。
1a. 缓存编辑 Microcompact(Cached Microcompact)
利用 Anthropic API 的 cache_edits 功能,在服务端标记某些 token 为”已删除”,不破坏 prompt cache 前缀。这是最高效的一层。
只对特定工具做清理:
const COMPACTABLE_TOOLS = new Set([
'Read', 'Bash', 'Grep', 'Glob', 'WebSearch', 'WebFetch', 'Edit', 'Write'
])
触发逻辑: 按计数器触发——保留最近 N 个工具结果,超出阈值的旧结果通过 cache_edits API 删除。
关键特征:
- 不修改本地消息内容
- 使用来自 GrowthBook 的远程配置(trigger threshold, keep recent)
- 只在主线程运行,防止 forked agent 污染全局状态
- 返回的消息内容不变,
cache_reference和cache_edits在 API 层添加
源码路径: services/compact/microCompact.ts
1b. 基于时间的 Microcompact(Time-based Microcompact)
当用户长时间无交互(超过配置的 gapThresholdMinutes),说明之前的工具结果已经过时,系统清空除最后 N 个之外的所有工具结果内容,替换为 [Old tool result content cleared]。
触发条件:
function evaluateTimeBasedTrigger(messages, querySource) {
// 需要:enabled + 主线程来源 + 最后一个 assistant 消息的时间间隔 > 阈值
const gapMinutes = (Date.now() - lastAssistant.timestamp) / 60_000
return gapMinutes >= config.gapThresholdMinutes
}
清除逻辑:
// 清除旧工具结果,只保留最近的 keepRecent 个
const keepRecent = Math.max(1, config.keepRecent)
const keepSet = new Set(compactableIds.slice(-keepRecent))
const clearSet = new Set(compactableIds.filter(id => !keepSet.has(id)))
// 将清除的内容替换为固定文本
return { ...block, content: '[Old tool result content cleared]' }
关键设计:
- 至少保留 1 个结果(
Math.max(1, keepRecent)),防止清除全部导致零上下文 - 清除后重置 cached-MC 状态(因为服务端 cache 已失效)
- 通知 prompt cache break detection 预期 token 下降,避免误报
源码路径: services/compact/microCompact.ts
第二层:工具结果持久化与聚合预算
大工具结果不直接放入消息上下文,而是持久化到磁盘。
单工具结果持久化
每个工具声明自己的 maxResultSizeChars,超过阈值就写到磁盘文件而非注入上下文:
// 解析每个工具的持久化阈值
function getPersistenceThreshold(toolName, declaredMax) {
// Read 工具 = Infinity(不自持久化,避免 Read→file→Read 循环)
if (!Number.isFinite(declaredMax)) return Infinity
// GrowthBook 远程配置可按工具名覆盖阈值
const overrides = growthbook.getFeature('tengu_satin_quoll', {})
if (overrides?.[toolName]) return overrides[toolName]
// 默认:min(工具声明值, 50_000 chars)
return Math.min(declaredMax, DEFAULT_MAX_RESULT_SIZE_CHARS)
}
持久化结果格式:
<persisted-output>
Output saved to: /path/to/.claude/sessions/{sessionId}/tool-results/{toolUseId}.txt
Preview (first 2000 chars): ...
</persisted-output>
聚合工具结果预算(Tool Result Budget)
除了单个工具上限,还有跨工具、跨 session 的总预算控制:
// 状态必须稳定以保证 prompt cache 前缀不变
type ContentReplacementState = {
seenIds: Set<string> // 已经见过的 tool_use_id,命运已锁定
replacements: Map<string, string> // 被替换成预览的映射
}
每次 query 循环开始前调用 applyToolResultBudget():
// 计算所有 tool_result 的总 token 数
const totalTokens = calculateTotalToolResultTokens(messages)
// 超出预算时,按先入先出策略替换最旧的结果为预览
if (totalTokens > effectiveMaxTokens) {
// 找到可替换的最旧工具结果
// 将其内容替换为 <persisted-output> 标签和预览
// 记录到 replacements map 中(保证下次调用时一致)
}
关键设计:
seenIds确保已经做过替换决策的结果不会被二次处理- 通过
ContentReplacementState跨子 agent 共享缓存决策 - 支持 subagent 从 sidechain records 重建父 agent 的替换状态
源码路径: utils/toolResultStorage.ts
第三层:Snip Compact(历史裁剪)
通过 feature('HISTORY_SNIP') 控制的裁剪机制。Snip 不是摘要,而是直接移除旧的 API-round groups(assistant message + 对应 tool_result 对),减少 token 占用。
// snipTokensFreed 传递给 autocompact,使阈值检查反映 Snip 移除的 token
// tokenCountWithEstimation 本身看不到节省(因为它读的是 API usage)
Snip 在 autocompact 之前运行——如果 Snip 已经把 token 数降到了阈值以下,autocompact 就不会触发,避免不必要的 API 调用。
源码路径: services/compact/snipCompact.ts
第四层:Auto Compact(自动摘要压缩)
这是核心的上下文压缩机制,也是最重量级的一层。
触发阈值
// 各层级缓冲值
const AUTOCOMPACT_BUFFER_TOKENS = 13_000 // 保留 13K 余量 → 触发自动压缩
const WARNING_THRESHOLD_BUFFER_TOKENS = 20_000 // 警告线(UI 提示)
const ERROR_THRESHOLD_BUFFER_TOKENS = 20_000 // 错误线(阻断新请求)
const MANUAL_COMPACT_BUFFER_TOKENS = 3_000 // 手动压缩极限
// 有效上下文窗口 = 总窗口 - 摘要预留
function getEffectiveContextWindowSize(model) {
const reservedTokensForSummary = min(getMaxOutputTokens(model), 20_000)
return contextWindow - reservedTokensForSummary
}
// 触发阈值 = 有效窗口 - 13K 缓冲
function getAutoCompactThreshold(model) {
return getEffectiveContextWindowSize(model) - AUTOCOMPACT_BUFFER_TOKENS
}
以 200K 模型为例:
- 有效窗口 ≈ 200K - 20K(摘要预留)= 180K
- 自动压缩阈值 = 180K - 13K = 167K token
压缩流程
compactConversation():
1. 执行 pre_compact hooks(用户可注入清理逻辑)
2. 先尝试 Session Memory Compact
└── 用 AI 生成记忆摘要代替完整压缩(更经济)
3. 调用 compactConversation 核心逻辑:
a. 从消息中剥离图片/文档块(它们不是摘要所需)
b. 启动 forked agent,发送压缩 prompt
c. AI 生成对话摘要
d. 如果摘要请求本身 prompt_too_long → 截断最旧的 round 重试(最多 2 次)
4. 压缩成功后:
a. 清除 fileStateCache
b. 并行生成 post-compact attachments:
- 最近修改的文件(最多 5 个,每个 5K token)
- 异步 Agent 状态
- Plan 文件
- 已调用的技能内容(25K token 预算,每个技能 5K)
- MCP 工具增量
c. 写入 compact boundary marker
d. 写入摘要消息
5. 执行 post_compact hooks 恢复关键上下文
6. 重置 cache read baseline
压缩后的结果大小: 约 20-40K token(摘要 + 系统 prompt + 工具 schema + re-injected 关键上下文),远低于原始的 128-256K。
熔断机制
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3
// 连续 3 次压缩失败后放弃,避免浪费 API 调用
// 注释记录:BQ 数据显示有 1,279 个 session 连续失败 50-3272 次
// 每天浪费约 25 万次 API 调用
Session Memory Compact(优先于完整压缩)
在触发完整压缩前,先尝试生成 Session Memory 摘要。如果 session memory 系统能生成足够的摘要来降低 token 压力,就不需要完整的 AI 摘要化。
// 先试 session memory compact,成功了直接返回
const sessionMemoryResult = await trySessionMemoryCompaction(
messages, agentId, threshold
)
if (sessionMemoryResult) return { wasCompacted: true, ... }
// 不行再走完整压缩
Partial Compact(局部压缩)
除了完整压缩整个对话,还支持局部压缩:
enum PartialCompactDirection {
'from', // 保留 pivot 之前的,摘要之后 → 保留 prompt cache 前缀
'up_to', // 保留 pivot 之后的,摘要之前 → 需清理旧 cache
}
用户可以选择压缩对话的某个部分而不影响其他部分。
压缩后重新注入关键上下文
Compact 不是一刀切清掉。压缩成功后,通过 attachment 重新注入关键信息:
| 注入内容 | 预算 | 说明 |
|---|---|---|
| 最近修改的文件 | 最多 5 个 × 5K = 25K | 让 AI 知道当前代码状态 |
| 异步 Agent 状态 | ≤ 50K | 仍在运行的 worker 状态 |
| Plan 文件 | 按需 | 当前执行的计划 |
| 已调用技能 | 25K 总预算,每个 5K | 让 AI 知道已有哪些技能指令 |
| MCP 工具增量 | 按需 | 连接的 MCP 服务器信息 |
| 已发现的工具 | 按需 | ToolSearch 发现的工具列表 |
源码路径: services/compact/compact.ts, services/compact/autoCompact.ts
第五层:API 原生上下文管理
Anthropic API 提供 context_management 功能,客户端通过 getAPIContextManagement() 配置策略:
// 工具结果清除策略
{
type: 'clear_tool_uses_20250919',
trigger: { type: 'input_tokens', value: 180_000 }, // 超过 180K 触发
clear_at_least: { type: 'input_tokens', value: 140_000 }, // 至少清到剩 40K
exclude_tools: ['Edit', 'Write', 'NotebookEdit'], // 编辑器结果保留
}
// Thinking 块清除策略
{
type: 'clear_thinking_20251015',
keep: 'all' // 正常保留所有 thinking
// 或:{ type: 'thinking_turns', value: 1 } // 长时间闲置后只保留最后一个
}
关键设计:
- 这是服务端策略,与客户端配合工作
clear_tool_uses清除工具调用和结果,但保留exclude_tools中的编辑器操作clear_thinking保留 thinking 上下文(思考链对推理很重要)- 默认阈值:触发 180K,目标 40K(保留最后 40K token)
源码路径: services/compact/apiMicrocompact.ts
第六层:Context Collapse(上下文坍缩)
feature('CONTEXT_COLLAPSE') 控制的内部实验性功能。这是一个更细粒度的上下文管理系统:
- 90% 上下文使用率 时开始 commit 保存 granular 上下文
- 95% 时阻塞式 spawn 保存
- 当启用时,autocompact 被抑制(避免 race condition — autocompact 在 ~93% 触发,刚好在 collapse 的 90-95% 之间)
- Collapse IS the context management system when enabled(它是主系统,不是一个补充层)
源码路径: services/contextCollapse/
关键设计原则
1. Prompt Cache 命中率是核心指标
所有压缩策略都考虑 prompt cache:
- Cached Microcompact 之所以最优,是因为它通过
cache_edits删除内容不破坏 cache 前缀 partialCompact('from')保留 prompt cache 前缀- 压缩后重置 cache read baseline,避免将合法的 cache drop 误报为 break
- 压缩自身使用 forked agent 共享父级 prompt cache(
promptCacheSharingEnabled = true)
// 实验确认:不共享 cache 的路径是 98% cache miss
// 浪费约 0.76% 的 fleet cache_creation (~38B tokens/day)
const promptCacheSharingEnabled = growthbook.getFeature(
'tengu_compact_cache_prefix', true
)
2. 工具结果是上下文膨胀的主要元凶
一个 grep -r 或 cat large_file 就能吃掉 5-10K token。Claude Code 的策略:
- 单工具结果有上限(
maxResultSizeChars),默认 50K 字符 - Read 工具
maxResultSizeChars = Infinity(通过maxTokens自身限流,避免 Read→persist→Read 循环) - 跨工具结果有聚合预算(
applyToolResultBudget) - 超大的写到文件,只给预览
- 过期的通过 microcompact 批量清空
3. 自动压缩可能失败,需要熔断
从实际数据看,有 session 连续压缩失败 3000+ 次,浪费 25 万次 API 调用/天。熔断器设计:
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3
// 每次压缩失败计数+1,成功后重置为 0
// 达到 3 次后直接返回 false,不再尝试
if (tracking.consecutiveFailures >= 3) {
logWarn('autocompact: circuit breaker tripped — skipping future attempts')
return { wasCompacted: false }
}
4. 压缩不是丢失信息,而是重组信息
Compact 成功后通过多层 re-injection 恢复关键上下文:
压缩前 (128K+) 压缩后 (~30K)
───────────────── ─────────────────
系统 prompt (~15K) 系统 prompt (~15K)
工具 schema (~10K) 工具 schema (~10K)
对话历史 (~80K) → 对话摘要 (~3K)
工具结果 (~20K) 关键文件快照 (~10K)
附件 (~3K) MCP 指令 (~2K)
技能内容 (~5K)
Boundary marker (~1K)
Session Start hooks (~1K)
5. 多级禁用控制
用户可按需关闭不同层次:
DISABLE_COMPACT // 关闭所有压缩
DISABLE_AUTO_COMPACT // 只关自动压缩,保留手动 /compact
USE_API_CLEAR_TOOL_RESULTS // 启用 API 侧工具结果清除
USE_API_CLEAR_TOOL_USES // 启用 API 侧工具调用清除
对低代码开发智能体团队的启示
启示 1:不要赌单一策略,设计多层防线
Claude Code 不是”满了就压缩”,而是 6 层由轻到重的策略。最轻的(microcompact)是纯本地的零成本操作,最重的(auto compact)才请求 AI 做全对话摘要。
应用: 低代码平台中的长对话/长工作流也应该有轻重量级的清理(删除过期中间结果)+ 中等重量(时间触发清空)+ 重量级(AI 摘要压缩)的分级策略。
启示 2:工具结果是上下文膨胀的主要元凶
5 个 shell 命令的长输出就能吃掉 5-10K token。Claude Code 通过三层控制:
- 单工具上限:每个工具声明自己的
maxResultSizeChars - 聚合预算:所有工具结果总量不能超过阈值
- 过期清理:microcompact 批量清除旧结果
应用: 低代码平台中每个节点的输出应该有上限机制,并且跨节点的总输出量也要有预算控制。不同节点类型(读/写/查询)应有不同上限。
启示 3:Prompt Cache 是不可忽略的成本变量
所有压缩策略都考虑 prompt cache 命中率。Cached Microcompact 之所以成为最优路径,就是因为它在删除内容的同时不破坏 cache 前缀。
应用: 如果系统有 cache 机制,压缩策略的设计必须把 “cache 命中率” 作为核心指标,不只是 “token 数量”。破坏一次 cache 的成本可能比保留一些冗余 token 更贵。
启示 4:任何自动化 AI 操作都需要熔断器
从生产数据看,自动化压缩可能在不可恢复的状态下浪费大量 API 调用。3 次失败熔断是合理的起点。
应用: 低代码平台中任何自动化的 AI 操作(压缩、摘要、重试、重新路由)都需要 circuit breaker,设定最大连续失败次数后停止尝试。
启示 5:压缩后必须 re-inject 关键上下文
Claude Code 压缩后不会让 AI 完全重新开始。它精心设计了 post-compact re-injection 策略:
- 最近修改的文件状态(让 AI 知道当前代码)
- Plan 文件(让 AI 知道要做什么)
- 已调用技能(让 AI 知道已有的指令)
- MCP 工具增量(让 AI 知道可用的工具)
应用: AI 摘要化对话或压缩工作流后,不能让它”失忆”。需要设计 post-compact re-injection 策略,把关键上下文以结构化附件的形式重新注入。
启示 6:时间作为压缩触发器
time-based microcompact 是一个简单而优雅的设计:用户长时间没交互 = 之前的中间结果不再需要。这比基于 token 数量的触发更自然。
应用: 低代码工作流中,如果一个任务暂停时间过长,其中间结果(缓存、临时数据)很可能不再需要,可以安全清理。
启示 7:压缩自身的容错
compactConversation 本身也可能遇到 prompt_too_long(即需要压缩的对话已经长到连压缩请求都发不出去)。它通过截断最旧的 API round 并重试来解决:
// 压缩请求本身也 prompt_too_long?截断最旧的内容重试
for (ptlAttempts = 0; ; ptlAttempts++) {
summary = await callCompactAPI(messages)
if (!summary.startsWith('Prompt is too long')) break
if (ptlAttempts >= MAX_PTL_RETRIES) throw new Error('不可恢复')
messages = truncateOldestRounds(messages)
}
应用: 压缩/摘要操作本身也需要有 fallback 路径,不能假设它总能成功。