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_referencecache_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 cachepromptCacheSharingEnabled = true
// 实验确认:不共享 cache 的路径是 98% cache miss
// 浪费约 0.76% 的 fleet cache_creation (~38B tokens/day)
const promptCacheSharingEnabled = growthbook.getFeature(
  'tengu_compact_cache_prefix', true
)

2. 工具结果是上下文膨胀的主要元凶

一个 grep -rcat 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 通过三层控制:

  1. 单工具上限:每个工具声明自己的 maxResultSizeChars
  2. 聚合预算:所有工具结果总量不能超过阈值
  3. 过期清理: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 路径,不能假设它总能成功。