源码:
src/services/compact/autoCompact.ts(352 行) 导出函数:autoCompactIfNeeded,作为deps.autocompact被query.ts调用
1. 概述
Autocompact 是 Claude Code 上下文管理的最后一道防线 。当 microcompact 和 context collapse 都无法将上下文压缩到有效窗口内时,autocompact 触发全量消息压缩------调用 LLM 将历史消息批量总结为摘要。
执行位置(query.ts)
scss
microcompact (line 414) → 只清理 tool result 内容
context collapse (line 441) → 消息区间折叠为 LLM 摘要
autocompact (line 454) → 全量消息压缩为单个摘要 ← 这里
2. 关键常量(第 28-70 行)
MAX_OUTPUT_TOKENS_FOR_SUMMARY(第 30 行)
typescript
const MAX_OUTPUT_TOKENS_FOR_SUMMARY = 20_000
压缩摘要的最大 token 数。基于 p99.99 的统计------压缩摘要的输出极少超过 17,387 tokens,取整为 20,000。
Buffer Tokens(第 62-65 行)
typescript
export const AUTOCOMPACT_BUFFER_TOKENS = 13_000
export const WARNING_THRESHOLD_BUFFER_TOKENS = 20_000
export const ERROR_THRESHOLD_BUFFER_TOKENS = 20_000
export const MANUAL_COMPACT_BUFFER_TOKENS = 3_000
| 常量 | 值 | 用途 |
|---|---|---|
AUTOCOMPACT_BUFFER_TOKENS |
13,000 | autocompact 触发的预留缓冲(给 model 回复留空间) |
WARNING_THRESHOLD_BUFFER_TOKENS |
20,000 | token 警告阈值 |
ERROR_THRESHOLD_BUFFER_TOKENS |
20,000 | token 错误阈值 |
MANUAL_COMPACT_BUFFER_TOKENS |
3,000 | blocking limit 的预留缓冲(/compact 手动触发时更激进) |
MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES(第 68-70 行)
typescript
const MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES = 3
熔断器阈值。注释说明(第 68-69 行):
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.
在引入熔断器之前,某些会话会无意义地重试 autocompact 多达 3000+ 次,每天浪费约 25 万次 API 调用。熔断器在 3 次连续失败后停止重试。
3. 有效上下文窗口计算(第 33-49 行)
typescript
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
}
公式:有效窗口 = min(模型上下文窗口, 环境变量覆盖) - min(模型最大输出, 20,000)
- 从完整上下文窗口中扣除压缩摘要的输出空间
- 环境变量
CLAUDE_CODE_AUTO_COMPACT_WINDOW允许用户限制用于压缩的窗口大小 - 被
getAutoCompactThreshold(第 72 行)调用,减去AUTOCOMPACT_BUFFER_TOKENS(13k)后得到实际触发阈值
调用链
scss
getEffectiveContextWindowSize(model)
└─ getAutoCompactThreshold(model) (autoCompact.ts:72)
└─ 减掉 AUTOCOMPACT_BUFFER_TOKENS (13k) → autocompact 触发阈值
└─ 直接调用 → blocking limit 计算
4. Autocompact 阈值计算(第 72-91 行)
typescript
export function getAutoCompactThreshold(model: string): number {
const effectiveContextWindow = getEffectiveContextWindowSize(model)
const autocompactThreshold = effectiveContextWindow - AUTOCOMPACT_BUFFER_TOKENS
// ...
return autocompactThreshold
}
触发阈值 = 有效窗口 - 13,000 tokens
这 13,000 tokens 的缓冲是为了让 model 在压缩后还有空间生成回复。
测试覆盖(第 78-88 行)
typescript
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)
}
}
环境变量 CLAUDE_AUTOCOMPACT_PCT_OVERRIDE 允许设置阈值为有效窗口的百分比,用于测试场景。例如设为 90 则阈值为有效窗口的 90%。
5. Token 警告状态计算(第 93-145 行)
typescript
export function calculateTokenWarningState(tokenUsage: number, model: string): {
percentLeft: number
isAboveWarningThreshold: boolean
isAboveErrorThreshold: boolean
isAboveAutoCompactThreshold: boolean
isAtBlockingLimit: boolean
}
多级阈值
lua
有效窗口: |←·································································→|
触发阈值: |←--- 13k buffer ---→|
↓ autocompact 触发点
警告阈值: |←------- 20k -------→|
↓ 黄色警告
错误阈值: |←------- 20k -------→|
↓ 红色警告
Blocking Limit: |←-- 3k buffer --→|
↓ 阻塞用户输入
关键逻辑(第 103-106 行)
typescript
const autoCompactThreshold = getAutoCompactThreshold(model)
const threshold = isAutoCompactEnabled()
? autoCompactThreshold
: getEffectiveContextWindowSize(model)
const percentLeft = Math.max(0, Math.round(((threshold - tokenUsage) / threshold) * 100))
如果 autocompact 被禁用,warning/error 阈值回退到有效窗口大小而非触发阈值------用户仍然能看到警告,但 autocompact 不会自动触发。
百分比计算(第 108-110 行)
percentLeft 的计算分母是 threshold 而非完整上下文窗口。这意味着告警百分比反映的是相对可用空间的占比,而非相对模型最大能力的占比。
6. Autocompact 启用检查(第 147-158 行)
typescript
export function isAutoCompactEnabled(): boolean {
if (isEnvTruthy(process.env.DISABLE_COMPACT)) return false // 完全禁用
if (isEnvTruthy(process.env.DISABLE_AUTO_COMPACT)) return false // 仅禁用自动
const userConfig = getGlobalConfig()
return userConfig.autoCompactEnabled // 用户设置
}
两级禁用:
DISABLE_COMPACT:同时禁用 autocompact 和手动/compactDISABLE_AUTO_COMPACT:只禁用自动触发,保留/compact命令
7. 是否应该压缩的判断(第 160-239 行)
typescript
export async function shouldAutoCompact(
messages: Message[],
model: string,
querySource?: QuerySource,
snipTokensFreed = 0,
): Promise<boolean>
7.1 递归守卫(第 170-183 行)
typescript
if (querySource === 'session_memory' || querySource === 'compact') {
return false
}
if (feature('CONTEXT_COLLAPSE')) {
if (querySource === 'marble_origami') {
return false
}
}
三个禁止触发 autocompact 的来源:
| querySource | 原因 |
|---|---|
session_memory |
这是 forked agent(子进程),触发 autocompact 会导致死锁 |
compact |
/compact 命令本身就在做压缩,不应该再递归触发 |
marble_origami |
这是 context collapse 的 spawn agent,如果它触发 autocompact,runPostCompactCleanup 的 resetContextCollapse() 会销毁主线程的 committed log(模块级状态在所有 fork 间共享) |
7.2 功能开关检查(第 185-223 行)
三层门控:
isAutoCompactEnabled()--- 用户设置 + 环境变量- Reactive-only 模式 (
REACTIVE_COMPACTfeature flag):当 GrowthBook flagtengu_cobalt_raccoon为 true 时,抑制主动 autocompact,改为依赖 API 返回 413 后的被动压缩(reactive compact) - Context collapse 模式 :当 context collapse 激活时,抑制 autocompact,因为 collapse 的 90%/95% 阈值系统拥有上下文管理权
7.3 为什么 collapse 要抑制 autocompact(第 201-223 行注释)
erlang
Context-collapse mode: same suppression. Collapse IS the context
management system when it's on --- the 90% commit / 95% blocking-spawn
flow owns the headroom problem. Autocompact firing at effective-13k
(~93% of effective) sits right between collapse's commit-start (90%)
and blocking (95%), so it would race collapse and usually win, nuking
granular context that collapse was about to save.
三个阈值的位置(相对有效上下文窗口):
erlang
collapse 提交开始: 90%
autocompact 触发: ~93% ← 正好在中间
collapse blocking: 95%
如果没有抑制,autocompact 会在 ~93% 时抢先触发,把 collapse 准备折叠的上下文一次性压缩成单个摘要,破坏了 collapse 的粒度保持策略。
设计选择 :抑制放在 shouldAutoCompact 而非 isAutoCompactEnabled 中,是为了保留 reactive compact(413 后备,内部调用 shouldAutoCompact)和手动 /compact 以及 session memory 压缩的能力。
7.4 最终 token 检查(第 225-238 行)
typescript
const tokenCount = tokenCountWithEstimation(messages) - snipTokensFreed
const threshold = getAutoCompactThreshold(model)
const { isAboveAutoCompactThreshold } = calculateTokenWarningState(tokenCount, model)
return isAboveAutoCompactThreshold
snipTokensFreed是 snip 操作(删除已归档消息的引用)释放的 token 数,从总计数中减掉- 最终判断:当前 token 数是否超过 autocompact 触发阈值
8. 核心函数:autoCompactIfNeeded(第 241-351 行)
typescript
export async function autoCompactIfNeeded(
messages: Message[],
toolUseContext: ToolUseContext,
cacheSafeParams: CacheSafeParams,
querySource?: QuerySource,
tracking?: AutoCompactTrackingState, // 跟踪状态(跨 turn)
snipTokensFreed?: number,
): Promise<{ wasCompacted: boolean; compactionResult?: CompactionResult; consecutiveFailures?: number }>
8.1 前置检查
全局禁用(第 253-255 行):
typescript
if (isEnvTruthy(process.env.DISABLE_COMPACT)) {
return { wasCompacted: false }
}
首先检查,不占用熔断器计数。
熔断器(第 257-265 行):
typescript
if (
tracking?.consecutiveFailures !== undefined &&
tracking.consecutiveFailures >= MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES
) {
return { wasCompacted: false }
}
连续失败 ≥ 3 次 → 跳过本次尝试。熔断状态通过 AutoCompactTrackingState.consecutiveFailures 跨 turn 传递。
shouldAutoCompact(第 267-277 行):
typescript
const model = toolUseContext.options.mainLoopModel
const shouldCompact = await shouldAutoCompact(messages, model, querySource, snipTokensFreed)
if (!shouldCompact) return { wasCompacted: false }
8.2 重压缩信息(第 279-285 行)
typescript
const recompactionInfo: RecompactionInfo = {
isRecompactionInChain: tracking?.compacted === true, // 是否链式重压缩
turnsSincePreviousCompact: tracking?.turnCounter ?? -1, // 距上次压缩的轮数
previousCompactTurnId: tracking?.turnId, // 上次压缩的 turn ID
autoCompactThreshold: getAutoCompactThreshold(model), // 触发阈值
querySource,
}
这些信息传递给 compactConversation,帮助 LLM 理解它正在对已压缩过的上下文做二次压缩。
8.3 路径 A:Session Memory 压缩优先(第 287-310 行)
typescript
const sessionMemoryResult = await trySessionMemoryCompaction(
messages, toolUseContext.agentId, recompactionInfo.autoCompactThreshold,
)
if (sessionMemoryResult) {
setLastSummarizedMessageId(undefined)
runPostCompactCleanup(querySource)
if (feature('PROMPT_CACHE_BREAK_DETECTION')) {
notifyCompaction(querySource ?? 'compact', toolUseContext.agentId)
}
markPostCompaction()
return { wasCompacted: true, compactionResult: sessionMemoryResult }
}
先尝试 Session Memory 压缩(一种更智能的压缩方式,不是直接给 LLM 全部总结,而是利用之前抽取的结构化记忆)。如果成功:
- 重置最后总结的消息 ID(因为消息列表已变)
- 执行压缩后清理(重置 collapse 状态、清除 microcompact 缓存等)
- 通知缓存断裂检测器(避免将 post-compact 的缓存 miss 误报为断裂)
- 标记已压缩
Session Memory Compaction(会话记忆压缩)机制分析
8.4 路径 B:传统压缩(第 312-333 行)
typescript
const compactionResult = await compactConversation(
messages, toolUseContext, cacheSafeParams,
true, // suppressUserQuestions
undefined, // customInstructions
true, // isAutoCompact
recompactionInfo,
)
setLastSummarizedMessageId(undefined)
runPostCompactCleanup(querySource)
return {
wasCompacted: true,
compactionResult,
consecutiveFailures: 0, // 重置失败计数
}
调用 compactConversation(来自 compact.ts)执行实际的 LLM 压缩。参数说明:
suppressUserQuestions = true:自动触发,不询问用户isAutoCompact = true:标记为自动压缩(区别于手动/compact)
成功后 consecutiveFailures 重置为 0。
8.5 错误处理与熔断器(第 334-350 行)
typescript
catch (error) {
if (!hasExactErrorMessage(error, ERROR_MESSAGE_USER_ABORT)) {
logError(error)
}
const prevFailures = tracking?.consecutiveFailures ?? 0
const nextFailures = prevFailures + 1
if (nextFailures >= MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES) {
logForDebugging(
`autocompact: circuit breaker tripped after ${nextFailures} consecutive failures`,
{ level: 'warn' },
)
}
return { wasCompacted: false, consecutiveFailures: nextFailures }
}
- 用户中止(
ERROR_MESSAGE_USER_ABORT)不记入错误日志(不logError),但仍然会计入熔断器计数 - 每次失败递增计数,通过返回值传递给 caller,caller 存入
tracking状态以保持跨 turn 持久性
9. Tracking State(第 51-60 行)
typescript
export type AutoCompactTrackingState = {
compacted: boolean // 本轮是否已压缩
turnCounter: number // turn 计数器
turnId: string // 当前 turn 的 UUID
consecutiveFailures?: number // 连续失败次数(熔断器)
}
这个状态在 query.ts 的 query loop 中维护,每轮更新,传递给下一次 autoCompactIfNeeded 调用。
10. 完整执行流程图
kotlin
autoCompactIfNeeded()
│
├─ DISABLE_COMPACT? → return
│
├─ 熔断器 ≥ 3 次连续失败? → return
│
├─ shouldAutoCompact()? → false → return
│ │
│ ├─ querySource 递归守卫 → false
│ ├─ isAutoCompactEnabled → false
│ ├─ reactive-only 模式 → false
│ ├─ context collapse 激活 → false
│ └─ token 未超阈值 → false
│
├─ trySessionMemoryCompaction() ← 路径 A
│ └─ 成功? → postCompactCleanup → return
│
└─ compactConversation() ← 路径 B
├─ 成功 → postCompactCleanup → 重置熔断器 → return
└─ 失败 → 递增熔断器 → return
11. 与 microcompact 和 context collapse 的关系
| 机制 | 对消息的影响 | 缓存影响 | 频率 |
|---|---|---|---|
| microcompact | 清空 tool result 内容 | 不影响(Cached MC)或缓存 miss(Time-based) | 每次 API 调用前 |
| context collapse | 消息区间替换为 <collapsed> 摘要 |
不影响(只替换占位符) | 每次 API 调用前 |
| autocompact | 所有消息替换为单个 summary message | 缓存 miss(prompt 完全改变) | 极少触发(前两者都无效后) |
autocompact 是最重量级的压缩方式------一旦触发,它使用 LLM 总结整个对话历史,替换为单条 summary message,后续对话将在压缩后的上下文中继续。