机制专题 · 06

上下文管理、压缩与 checkpoint

Claude Code 如何组装上下文、何时触发压缩、压缩后如何恢复,以及源码里几类 checkpoint 的边界。项目把 token/context 当成长期运行时资源管理,而不是"超过就报错"。

1. 上下文来源不是只有聊天历史#

一次模型请求的上下文大致由这些部分拼出来:

  1. system prompt:constants/prompts.ts 和 agent/system prompt 构造逻辑。
  2. system context:context.ts 中的 cwd、平台、日期、git 信息等。
  3. user context:CLAUDE.md、memory prompt、MCP/agent/skill 相关提示、hook 附加上下文等。
  4. conversation messages:用户/助手/工具结果/attachment/system 边界消息。
  5. tool definitions:tools.ts 汇总的工具 schema;启用 ToolSearch 时部分工具只以 deferred 名字出现。
  6. 动态 attachments:utils/attachments.ts 在每轮把 @file、MCP resource、deferred tools delta、agent listing delta、nested memory、relevant memories 等注入。

所以"上下文管理"实际包含三件事:控制消息历史长度、控制工具定义长度、控制外部资料/记忆/附件的注入时机。

2. auto compact 的触发阈值#

阈值仍是 effective window − 13_000(summary 预留上限 20_000)。2.1.233 多了一层窗口解析:

  1. CLAUDE_CODE_AUTO_COMPACT_WINDOW(env)
  2. settings 的 autoCompactWindow/autocompact 写入)。autoCompactEnabled 只开关自动 compact,不是窗口来源
  3. clientdata / GrowthBook 实验
  4. 模型默认窗口
  5. 未知模型可强制按当前 window 收

CLAUDE_AUTOCOMPACT_PCT_OVERRIDE 仍可把阈值压成 window 的百分比。另有 CLAUDE_CODE_BLOCKING_LIMIT_OVERRIDEDISABLE_COMPACT 关全部 compact,DISABLE_AUTO_COMPACT / autoCompactEnabled: false 只关自动 compact。

query 里还可以先吃预计算 compact(tengu_sepia_moth + precomputeCompactionEnabled)。remote 上 reactive compact 还要过 tengu_reactive_compact_remote。连续失败 3 次后本 session 不再试。

3. compactConversation 的主体流程#

services/compact/compact.tscompactConversation() 不是简单地"把历史发给模型总结":

  1. 执行 PreCompact hooks,可追加自定义 compact 指令。
  2. 设置 UI/SDK 状态:compact_startsetSDKStatus('compacting')
  3. 构造 getCompactPrompt(customInstructions)
  4. 通过 streamCompactSummary() 生成 summary。默认走 forked-agent 路径以复用主请求 prompt cache。
  5. 如果 summary 请求本身遇到 prompt-too-long,会用 truncateHeadForPTLRetry() 丢弃最老 API round group 后重试,最多 MAX_PTL_RETRIES = 3
  6. 清空/重建 read file state,生成 compact boundary、summary user message、必要 attachments、hook results。
  7. 把 discovered tool、file cache、invoked skills、plan 等状态带过 compact 边界。
  8. 执行 PostCompact hooks 和 post-compact cleanup。

compact 后的消息顺序由 buildPostCompactMessages() 固定:

compact_boundary
summaryMessages
messagesToKeep
attachments
hookResults

这说明 compact 后的会话并不是"只剩一段 summary",而是一个有边界、有保留尾部、有恢复附件的重组消息链。

4. prompt-too-long 兜底#

truncateHeadForPTLRetry() 的设计很关键:

这是 compact 自身也过长时的最后兜底,否则用户会卡在"想压缩但压缩请求也发不出去"的状态。

5. microcompact:清理旧工具结果#

services/compact/microCompact.ts 是更细粒度的上下文治理:

6. session-memory compact:用当前会话笔记替代全量总结#

session memory 的 viewer / current_session_memory attachment 还在。2.1.233 制品里没有 tengu_session_memory / tengu_sm_compact,也看不到“先 session-memory compact、再 legacy summary”这条默认路径。auto compact 走 summary compact / reactive compact / 预计算 compact。

7. "checkpoint" 至少有三种含义#

源码里 checkpoint 不是单一概念,需要区分:

  1. compact boundary checkpoint:SystemCompactBoundaryMessage,subtype 为 compact_boundary。它是压缩后的恢复边界,记录 compact metadata、pre-compact discovered tools、preserved segment 等。
  2. transcript parent chain checkpoint:utils/sessionStorage.ts 通过 parentUuid/logicalParentUuid 维护消息 DAG。compact boundary 的 parentUuid 会特殊处理,防止 resume 时旧消息链 orphan。
  3. file history checkpoint:utils/fileHistory.ts 维护文件修改快照,最多 MAX_SNAPSHOTS = 100。这是面向文件回滚/历史的 checkpoint,和上下文压缩不是同一层。

另外,profileCheckpoint 这类启动性能埋点不是用户语义上的 checkpoint。