第 6 篇:上下文管理 — 无限对话的秘密

本篇是《深入 Claude Code 源码》系列的第 6 篇。我们将深入分析 Claude Code 如何在有限的 context window 内支撑无限长度的对话——从 token 预算管理、多级压缩策略,到文件状态缓存与 compact 后恢复的设计。

范围说明:上下文的”构建”(System Prompt 组装、CLAUDE.md 注入、git status 获取)已在第 4 篇中覆盖,缓存策略的横切视角将在第 7 篇中展开。本篇聚焦于上下文构建完成后的压缩、清理与恢复——即当 context window 不够用时,Claude Code 如何在不中断对话的情况下释放空间并重建必要的工作上下文。

为什么上下文管理如此重要?

LLM 的 context window 是有限的。Claude 的模型通常有 200K token 的窗口(部分模型支持 1M),看起来很大,但在实际使用中消耗极快:

  • 一次 System Prompt 可能占 5K-15K token
  • 用户附带的 CLAUDE.md 文件可能占 3K-10K token
  • 每次工具调用的结果(如读取一个 500 行文件)可能占 2K-5K token
  • 一次完整的 agentic loop(读文件 → 分析 → 编辑 → 运行测试)可能消耗 30K-50K token

在一个真实的编程会话中,用户可能连续工作数小时,产生上百个工具调用。如果不做任何上下文管理,context window 很快就会耗尽,对话将被迫中断。

Claude Code 的解决方案是一套多层次的上下文压缩与恢复体系——从最轻量的 Microcompact(清理工具结果)到最重量级的 Full Compact(用模型总结整个对话),形成了一个完整的上下文压力梯度响应系统。


一、Token 预算管理:三个关键函数

上下文管理的基础是精确的 token 预算计算。三个核心函数定义了整个系统的运行边界。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
graph LR
CW["Context Window<br/>如 200K tokens"] --> EW["getEffectiveContextWindowSize()<br/>= CW - 输出预留 (20K)"]
EW --> ACT["getAutoCompactThreshold()<br/>= EW - 缓冲区 (13K)"]
ACT --> CTWS["calculateTokenWarningState()<br/>判定当前 token 用量的危险等级"]

CTWS --> W["⚠️ Warning<br/>剩余 < 20K"]
CTWS --> E["🔴 Error<br/>剩余 < 20K"]
CTWS --> AC["🔄 AutoCompact<br/>超过阈值"]
CTWS --> BL["🚫 Blocking<br/>剩余 < 3K"]

style CW fill:#e3f2fd
style EW fill:#e8f5e9
style ACT fill:#fff3e0
style CTWS fill:#fce4ec

1.1 getEffectiveContextWindowSize() — 实际可用空间

文件services/compact/autoCompact.ts:33-49

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
const MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000

export function getEffectiveContextWindowSize(model: string): number {
const reservedTokensForSummary = Math.min(
getMaxOutputTokensForModel(model),
MAX_OUTPUT_TOKENS_FOR_SUMMARY,
)
let contextWindow = getContextWindowForModel(model, getSdkBetas())

// 支持通过环境变量覆盖(用于测试)
const autoCompactWindow = process.env.CLAUDE_CODE_AUTO_COMPACT_WINDOW
if (autoCompactWindow) {
const parsed = parseInt(autoCompactWindow, 10)
if (!isNaN(parsed) && parsed > 0) {
contextWindow = Math.min(contextWindow, parsed)
}
}

return contextWindow - reservedTokensForSummary
}

这个函数计算的是实际可用于输入的 token 空间。关键逻辑:

  • 从模型的 context window(如 200K)中减去输出预留(MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000
  • 20K 的预留值来源于 p99.99 统计:compact 总结输出的最大值为 17,387 token

以一个 200K context window 的模型为例:effectiveContextWindow = 200,000 - 20,000 = 180,000

1.2 getAutoCompactThreshold() — 自动压缩触发线

文件services/compact/autoCompact.ts:72-91

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
export const AUTOCOMPACT_BUFFER_TOKENS = 13_000

export function getAutoCompactThreshold(model: string): number {
const effectiveContextWindow = getEffectiveContextWindowSize(model)
const autocompactThreshold =
effectiveContextWindow - AUTOCOMPACT_BUFFER_TOKENS

// 支持百分比覆盖(用于测试)
const envPercent = process.env.CLAUDE_AUTOCOMPACT_PCT_OVERRIDE
if (envPercent) {
const parsed = parseFloat(envPercent)
if (!isNaN(parsed) && parsed > 0 && parsed <= 100) {
const percentageThreshold = Math.floor(
effectiveContextWindow * (parsed / 100),
)
return Math.min(percentageThreshold, autocompactThreshold)
}
}

return autocompactThreshold
}

Auto-compact 触发线 = 有效窗口 - 13K 缓冲。以 200K 模型为例:167,000 = 180,000 - 13,000

这个 13K 缓冲区是刻意留出的——它确保在检测到需要 compact 后,仍有足够空间完成当前 turn 的工具调用和模型响应。

1.3 calculateTokenWarningState() — 四级告警体系

文件services/compact/autoCompact.ts:93-145

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
export const WARNING_THRESHOLD_BUFFER_TOKENS = 20_000
export const ERROR_THRESHOLD_BUFFER_TOKENS = 20_000
export const MANUAL_COMPACT_BUFFER_TOKENS = 3_000

export function calculateTokenWarningState(
tokenUsage: number,
model: string,
): {
percentLeft: number
isAboveWarningThreshold: boolean
isAboveErrorThreshold: boolean
isAboveAutoCompactThreshold: boolean
isAtBlockingLimit: boolean
} {
const autoCompactThreshold = getAutoCompactThreshold(model)
const threshold = isAutoCompactEnabled()
? autoCompactThreshold
: getEffectiveContextWindowSize(model)

const warningThreshold = threshold - WARNING_THRESHOLD_BUFFER_TOKENS
const errorThreshold = threshold - ERROR_THRESHOLD_BUFFER_TOKENS
const blockingLimit =
getEffectiveContextWindowSize(model) - MANUAL_COMPACT_BUFFER_TOKENS

return {
percentLeft: Math.max(0, Math.round(((threshold - tokenUsage) / threshold) * 100)),
isAboveWarningThreshold: tokenUsage >= warningThreshold,
isAboveErrorThreshold: tokenUsage >= errorThreshold,
isAboveAutoCompactThreshold:
isAutoCompactEnabled() && tokenUsage >= autoCompactThreshold,
isAtBlockingLimit: tokenUsage >= blockingLimit,
}
}

以 200K 模型为例的四级告警(auto-compact 启用时):

级别 阈值 Token 值 含义
Warning threshold - 20K ~147K UI 显示黄色警告
Error threshold - 20K ~147K UI 显示红色警告
AutoCompact threshold ~167K 触发自动压缩
Blocking effective - 3K ~177K 禁止新查询,必须手动 /compact

值得注意的是,Warning 和 Error 阈值在当前源码中完全相同(都是 20K 缓冲),这意味着两者总是同时触发。在 TokenWarning.tsx 中,UI 使用 isAboveErrorThreshold ? "error" : "warning" 判断颜色,但由于两个阈值相等,实际上总是直接显示红色(error)。分开定义这两个常量是为未来独立调整留下了扩展空间。


二、Microcompact — 最轻量的上下文清理

在 auto-compact 触发之前,系统会先尝试更轻量的 Microcompact。Microcompact 不会调用模型来总结对话,而是直接清理旧的工具调用结果来释放空间。

2.1 设计思路

工具调用结果(如文件内容、命令输出、搜索结果)在刚返回时对模型理解上下文至关重要,但随着对话推进,它们的价值递减——模型已经”消化”了这些信息并做出了决策。Microcompact 的策略就是保留最近的 N 个工具结果,清理更早的

2.2 可清理的工具类型

文件services/compact/microCompact.ts:41-50

1
2
3
4
5
6
7
8
9
10
const COMPACTABLE_TOOLS = new Set<string>([
FILE_READ_TOOL_NAME,
...SHELL_TOOL_NAMES,
GREP_TOOL_NAME,
GLOB_TOOL_NAME,
WEB_SEARCH_TOOL_NAME,
WEB_FETCH_TOOL_NAME,
FILE_EDIT_TOOL_NAME,
FILE_WRITE_TOOL_NAME,
])

这些是允许清理其历史结果的高占用工具。列表中包含读取类(FileRead、Grep、Glob)、Shell 命令、网络请求,也包含写入类(FileEdit、FileWrite)——它们的 tool_result 往往包含大量操作反馈文本,在后续 turn 中信息价值递减。注意 AgentTool 等工具的结果不在此列——它们的输出通常是高度浓缩的总结,清理后信息损失大。

2.3 本地 Microcompact 的两条路径

microcompactMessages() 函数(services/compact/microCompact.ts:253-293)内部有两条本地路径,按优先级短路选择:

路径 1:Time-based Microcompact

文件services/compact/microCompact.ts:446-530

当用户离开一段时间后回来(gap > 阈值),服务端的 prompt cache 已经过期,此时无需保护缓存,直接构造新的消息对象替换旧的工具结果内容:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
function maybeTimeBasedMicrocompact(
messages: Message[],
querySource: QuerySource | undefined,
): MicrocompactResult | null {
const trigger = evaluateTimeBasedTrigger(messages, querySource)
if (!trigger) return null

// ... 收集可清理的工具 ID,保留最近 N 个
const keepSet = new Set(compactableIds.slice(-keepRecent))
const clearSet = new Set(compactableIds.filter(id => !keepSet.has(id)))

// 构造新的消息对象,替换内容为占位文本
const result: Message[] = messages.map(message => {
// ...
return { ...block, content: TIME_BASED_MC_CLEARED_MESSAGE }
// '[Old tool result content cleared]'
})
}

这种方式通过 { ...block, content: ... }{ ...message, message: { ...message.message, content: newContent } } 构造新的 Message / content 对象返回,而非原地修改(mutate)原始消息。因为 cache 已经冷了,没有需要保护的前缀,所以可以放心地替换内容。

路径 2:Cached Microcompact(cache_edits API)

文件services/compact/microCompact.ts:305-399

当 prompt cache 仍然温热时,不能替换消息内容(那会改变 prompt 前缀,破坏缓存命中),而是通过 Anthropic API 的 cache_edits 机制,在 API 层面删除指定工具的 tool_result

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
async function cachedMicrocompactPath(
messages: Message[],
querySource: QuerySource | undefined,
): Promise<MicrocompactResult> {
// ... 注册工具结果到追踪状态
const toolsToDelete = mod.getToolResultsToDelete(state)

if (toolsToDelete.length > 0) {
// 创建 cache_edits 指令,由 API 层发送
const cacheEdits = mod.createCacheEditsBlock(state, toolsToDelete)
pendingCacheEdits = cacheEdits

// 本地消息不变!cache_reference 和 cache_edits 在 API 层添加
return {
messages, // 未修改
compactionInfo: { pendingCacheEdits: { ... } },
}
}
}

关键区别:这种路径不修改本地消息。删除操作通过 API 的 cache_edits 参数传递,由服务端在缓存层执行。这保留了 prompt cache 的命中率。

2.4 API-level Context Management — 独立的并行机制

文件services/compact/apiMicrocompact.ts

除了上述两条本地 microcompact 路径外,还有一套独立的 API 层上下文管理机制。它不在 microcompactMessages() 的选择链中,而是通过 services/api/claude.ts 在构建 API 请求时注入 context_management 配置参数,将上下文清理策略声明式地委托给 Anthropic API 服务端执行:

1
2
3
4
5
6
7
// services/api/claude.ts:1633 — 在 API 请求构建时调用
const contextManagement = getAPIContextManagement({
hasThinking,
isRedactThinkingActive,
clearAllThinking,
})
// 作为请求参数传递给 API

getAPIContextManagement() 返回一组声明式策略:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
export function getAPIContextManagement(options?: {
hasThinking?: boolean
isRedactThinkingActive?: boolean
clearAllThinking?: boolean
}): ContextManagementConfig | undefined {
const strategies: ContextEditStrategy[] = []

// 清理旧的 thinking 块
if (hasThinking && !isRedactThinkingActive) {
strategies.push({
type: 'clear_thinking_20251015',
keep: clearAllThinking
? { type: 'thinking_turns', value: 1 }
: 'all',
})
}

// 清理旧的工具结果(ant-only)
if (useClearToolResults) {
strategies.push({
type: 'clear_tool_uses_20250919',
trigger: { type: 'input_tokens', value: triggerThreshold },
clear_at_least: { type: 'input_tokens', value: triggerThreshold - keepTarget },
clear_tool_inputs: TOOLS_CLEARABLE_RESULTS,
})
}

return { edits: strategies }
}

与本地 microcompact 的关系:这两套机制并行存在,互不排斥。本地 microcompact 在客户端发送请求前清理或标记消息;API-level context management 则是在请求参数中声明清理策略,由服务端在处理请求时执行。它们可以同时生效——客户端做一轮粗清理,服务端再做一轮精细清理。


三、Full Compact — 用模型总结对话

当 Microcompact 不够用,token 用量达到 auto-compact 阈值时,系统触发 Full Compact——用模型自己来总结之前的对话

3.1 触发流程

文件services/compact/autoCompact.ts:241-351

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
export async function autoCompactIfNeeded(
messages: Message[],
toolUseContext: ToolUseContext,
cacheSafeParams: CacheSafeParams,
// ...
): Promise<{ wasCompacted: boolean; compactionResult?: CompactionResult }> {
// 1. 熔断器:连续失败 3 次后停止尝试
if (tracking?.consecutiveFailures >= MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES) {
return { wasCompacted: false }
}

// 2. 检查是否需要 compact
const shouldCompact = await shouldAutoCompact(messages, model, querySource)
if (!shouldCompact) return { wasCompacted: false }

// 3. 优先尝试 Session Memory Compact
const sessionMemoryResult = await trySessionMemoryCompaction(...)
if (sessionMemoryResult) return { wasCompacted: true, ... }

// 4. 回退到传统的 Full Compact
const compactionResult = await compactConversation(messages, ...)
return { wasCompacted: true, compactionResult, consecutiveFailures: 0 }
}

几个重要的工程细节:

熔断器(Circuit Breaker):连续失败 3 次后停止尝试。注释揭示了这个设计的背景:

BQ 2026-03-10: 1,279 sessions had 50+ consecutive failures (up to 3,272) in a single session, wasting ~250K API calls/day globally.

没有熔断器时,context 不可恢复地超限的 session 会在每个 turn 都发起注定失败的 compact 请求,全球每天浪费约 25 万次 API 调用。

递归保护shouldAutoCompact() 会检查 querySource,防止 compact 自身('compact')或 session memory('session_memory')触发新的 compact,避免无限递归。

3.2 Session Memory Compact — 免调用的压缩

文件services/compact/sessionMemoryCompact.ts

这是 compact 的一个创新路径:不调用模型生成总结,而是直接使用 Session Memory 系统已有的对话记忆作为压缩后的总结。

Session Memory 是一个独立的后台系统(将在第 23 篇详述),它在对话过程中持续异步提取关键信息到磁盘文件。当 compact 触发时,如果 Session Memory 已经有内容,就直接用它作为总结,跳过昂贵的 API 调用:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
export async function trySessionMemoryCompaction(
messages: Message[],
agentId?: AgentId,
autoCompactThreshold?: number,
): Promise<CompactionResult | null> {
// 等待正在进行的 session memory 提取完成
await waitForSessionMemoryExtraction()

const sessionMemory = await getSessionMemoryContent()
if (!sessionMemory || await isSessionMemoryEmpty(sessionMemory)) {
return null // 回退到传统 compact
}

// 计算保留多少最近的消息
const startIndex = calculateMessagesToKeepIndex(messages, lastSummarizedIndex)
const messagesToKeep = messages.slice(startIndex)

// 直接用 session memory 作为总结,无需 API 调用
return createCompactionResultFromSessionMemory(
messages, sessionMemory, messagesToKeep, ...
)
}

calculateMessagesToKeepIndex 使用一组可配置的阈值决定保留多少最近消息:

  • 最少保留 10K token5 条有文本的消息(取较大者)
  • 最多保留 40K token(硬上限)
  • 还会确保不拆分 tool_use / tool_result 配对

3.3 传统 Full Compact — compactConversation()

文件services/compact/compact.ts:387-586

当 Session Memory Compact 不可用时,执行传统的 Full Compact:

  1. 执行 PreCompact hooks——允许用户自定义的 hook 脚本在 compact 前运行
  2. 构建总结请求——将对话历史和总结 prompt 发给模型
  3. 流式获取总结——模型生成对话总结
  4. 处理 prompt_too_long——如果连 compact 请求本身都超限,会截断最旧的消息重试
  5. 重建上下文——清理文件缓存,重新注入关键附件

3.4 Compact Prompt — 模型如何被指导生成总结

文件services/compact/prompt.ts

Compact prompt 的设计非常讲究。它要求模型按 9 个维度进行结构化总结:

1
2
3
4
5
6
7
8
9
1. Primary Request and Intent — 用户的请求和意图
2. Key Technical Concepts — 关键技术概念
3. Files and Code Sections — 涉及的文件和代码片段
4. Errors and fixes — 遇到的错误和修复
5. Problem Solving — 问题解决过程
6. All user messages — 所有用户消息(非工具结果)
7. Pending Tasks — 待完成的任务
8. Current Work — 当前进行的工作
9. Optional Next Step — 可选的下一步

一个精妙的设计是 <analysis> 标签——模型被要求先在 <analysis> 中整理思路,然后在 <summary> 中给出正式总结。之后,formatCompactSummary() 函数会剥离 <analysis> 部分,只保留 <summary> 注入到后续上下文中:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// services/compact/prompt.ts:311-335
export function formatCompactSummary(summary: string): string {
let formattedSummary = summary
// 剥离 analysis — 它是提升总结质量的草稿,正式总结完成后无信息价值
formattedSummary = formattedSummary.replace(
/<analysis>[\s\S]*?<\/analysis>/, '',
)
// 提取并格式化 summary
const summaryMatch = formattedSummary.match(/<summary>([\s\S]*?)<\/summary>/)
if (summaryMatch) {
formattedSummary = formattedSummary.replace(
/<summary>[\s\S]*?<\/summary>/,
`Summary:\n${summaryMatch[1]?.trim()}`,
)
}
return formattedSummary.trim()
}

这实质上是一种 chain-of-thought 然后剥离 的技巧:让模型在生成最终总结前先深入思考,但不把思考过程注入后续上下文(节省 token)。

另一个值得注意的设计:prompt 开头有一段强力的 NO_TOOLS_PREAMBLE,反复强调模型不要调用工具:

1
2
CRITICAL: Respond with TEXT ONLY. Do NOT call any tools.
- Tool calls will be REJECTED and will waste your only turn — you will fail the task.

注释解释了原因:compact 使用 maxTurns: 1 的 forked agent 执行,模型如果尝试调用工具会被拒绝,导致白白浪费 API 调用。在 Sonnet 4.6 上这个问题的发生率从 0.01% 上升到了 2.79%,所以增加了这个前置强调。

3.5 Compact 后的上下文重建

Compact 不仅仅是压缩——压缩完后需要重建模型继续工作所需的上下文

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
// compact.ts:531-585 (简化)
// 1. 清理文件缓存
context.readFileState.clear()
context.loadedNestedMemoryPaths?.clear()

// 2. 并行生成后续附件
const [fileAttachments, asyncAgentAttachments] = await Promise.all([
createPostCompactFileAttachments(preCompactReadFileState, context, 5),
createAsyncAgentAttachmentsIfNeeded(context),
])

// 3. 恢复关键上下文
// - 最近读取的文件(最多 5 个,每个最多 5K token)
// - Plan 附件(如果在 plan mode 中)
// - 已调用的 Skill 内容(每个最多 5K token)
// - Deferred Tools / Agent / MCP 指令的增量附件

文件恢复有严格的 token 预算控制:POST_COMPACT_TOKEN_BUDGET = 50_000POST_COMPACT_MAX_TOKENS_PER_FILE = 5_000POST_COMPACT_MAX_FILES_TO_RESTORE = 5。这确保 compact 后的上下文不会因为恢复附件而再次膨胀。


四、FileStateCache — 文件读写安全状态追踪

文件utils/fileStateCache.ts

FileStateCache 的核心职责不是”避免重复读取”,而是追踪文件的读取状态与可编辑性,并在 compact 后作为文件恢复的索引依据。它回答的核心问题是:模型当前对哪些文件有什么程度的认知?是否可以安全地编辑它们?

1
2
3
4
5
6
7
8
9
10
11
12
export type FileState = {
content: string
timestamp: number
offset: number | undefined
limit: number | undefined
// True when this entry was populated by auto-injection (e.g. CLAUDE.md) and
// the injected content did not match disk (stripped HTML comments, stripped
// frontmatter, truncated MEMORY.md). The model has only seen a partial view;
// Edit/Write must require an explicit Read first. `content` here holds the
// RAW disk bytes (for getChangedFiles diffing), not what the model saw.
isPartialView?: boolean
}
1
2
3
4
5
6
7
8
9
10
11
12
export class FileStateCache {
private cache: LRUCache<string, FileState>

constructor(maxEntries: number, maxSizeBytes: number) {
this.cache = new LRUCache<string, FileState>({
max: maxEntries,
maxSize: maxSizeBytes,
sizeCalculation: value => Math.max(1, Buffer.byteLength(value.content)),
})
}
// ...
}

几个设计要点:

  1. 读写安全守卫isPartialView 标记是这个缓存最关键的语义。当文件内容是被截断注入的(如 CLAUDE.md 去掉 HTML 注释、截断的 MEMORY.md),这个标记告诉 FileEdit/FileWrite 工具必须先做一次完整的 Read,不能基于缓存的部分内容进行编辑。源码注释明确说明:content 字段存储的是原始磁盘内容(用于 getChangedFiles 差异比对),不是模型看到的内容。

  2. Compact 后文件恢复索引:Full Compact 后,readFileState.clear() 清除所有缓存,然后 createPostCompactFileAttachments()services/compact/compact.ts:1415-1464)从 compact 前的 preCompactReadFileState 中挑选最近的文件重新注入。FileStateCache 在这里充当了”恢复索引”——它知道哪些文件是模型最近读取过的,按时间戳排序后选择最重要的恢复。

  3. 双重限制max 限制条目数(默认 100),maxSize 限制总大小(默认 25MB)。这防止大量大文件导致内存膨胀。

  4. 路径标准化:所有 key 在存取时都经过 normalize(),确保 /foo/../bar/bar 命中同一条缓存。

  5. Agent 隔离createSubagentContext()cloneFileStateCache(),确保子 Agent 的文件读取不污染父级的缓存状态。


五、compactWarningState — 用 Store 模式管理告警状态

文件services/compact/compactWarningState.ts

这是一个有趣的小模块——它复用了第 3 篇介绍的极简 Store 来管理 compact 告警的抑制状态:

1
2
3
4
5
6
7
8
9
10
11
import { createStore } from '../../state/store.js'

export const compactWarningStore = createStore<boolean>(false)

export function suppressCompactWarning(): void {
compactWarningStore.setState(() => true)
}

export function clearCompactWarningSuppression(): void {
compactWarningStore.setState(() => false)
}

为什么需要抑制?因为 compact 成功后,我们不再有准确的 token 计数(要等下次 API 响应才知道),如果继续显示告警会造成误导。所以 compact 成功后设置抑制,直到下次 microcompact 开始时清除。

这个模块同时展示了 Store 模式的复用性——同一个 35 行 Store 实现,既用于管理全局 AppState,也用于管理局部的 UI 状态。


六、postCompactCleanup — 压缩后的缓存清理

文件services/compact/postCompactCleanup.ts

Compact 后需要清理大量缓存和追踪状态。这个清理函数揭示了 Claude Code 中有多少模块级缓存

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
export function runPostCompactCleanup(querySource?: QuerySource): void {
const isMainThreadCompact =
querySource === undefined ||
querySource.startsWith('repl_main_thread') ||
querySource === 'sdk'

resetMicrocompactState() // Microcompact 追踪状态
clearSystemPromptSections() // System Prompt 分段缓存
clearClassifierApprovals() // 分类器审批缓存
clearSpeculativeChecks() // 推测性权限检查缓存
clearBetaTracingState() // 遥测追踪状态
clearSessionMessagesCache() // Session 消息缓存

// 只有主线程 compact 才清理这些(子 Agent 共享模块级状态)
if (isMainThreadCompact) {
getUserContext.cache.clear?.() // 用户上下文缓存
resetGetMemoryFilesCache('compact') // CLAUDE.md 文件缓存
}
}

isMainThreadCompact 的判断尤其重要——子 Agent 和主线程运行在同一个进程中,共享模块级状态。如果子 Agent compact 时清理了主线程的缓存(如 getUserContext 缓存),会导致主线程状态损坏。注释中记录了这个教训。


七、可迁移的设计模式

模式 1:多级压力梯度响应

不要等到资源耗尽才采取行动。设计一组从轻量到重量级的干预措施,在不同的压力等级触发不同的响应:

  • 低压:Time-based 清理(几乎零成本)
  • 中压:Cached Microcompact(利用 API 原生能力)
  • 高压:Full Compact(用模型总结,昂贵但有效)
  • 极限:Blocking(禁止新请求,强制用户手动干预)

适用场景:任何有资源限制的系统——内存管理、数据库连接池、磁盘空间、API 配额。

模式 2:熔断器 + 连续失败计数

当某个操作连续失败超过阈值时,停止重试。这避免了”在已知无法成功的操作上浪费资源”的陷阱。

1
2
失败 → 计数器 +1 → 超过阈值 → 熔断(停止尝试)
成功 → 计数器归零

Claude Code 的实践:3 次连续 compact 失败后熔断,因为统计显示有些 session 会连续失败数千次,每天浪费 25 万次 API 调用。

适用场景:任何可能连续失败的重试逻辑——API 调用、数据库重连、任务队列。

模式 3:Chain-of-Thought 然后剥离

让模型在生成最终输出前先在指定标签(如 <analysis>)中进行详细推理,然后在后处理中剥离推理过程,只保留最终结果(<summary>)。这兼顾了输出质量和 token 经济性。

适用场景:任何需要模型生成高质量摘要/总结的场景,特别是当总结会被注入后续 prompt 时。


下一篇预告

第 7 篇:Prompt Cache — 跨模块的缓存策略如何降低 API 成本

我们将从横切视角审视 Prompt Cache 如何贯穿 System Prompt、对话循环和上下文管理三个模块。你会看到 CacheSafeParams 如何让 fork agent 与 parent 共享缓存、saveCacheSafeParams() 如何实现跨 turn 复用,以及 fork subagent 的 byte-exact prompt threading 极致优化。


全部内容请关注 https://github.com/luyao618/Claude-Code-Source-Study (求一颗免费的小星星)