Claude Code 06:Context Compact——三层压缩换无限会话

在真实的软件工程中,一个残酷的物理事实是:上下文窗口终究是有限的。

读取一个包含 1,000 行代码的文件,就会消耗约 4,000 个 Token;在排查大型系统的复杂 bug 时,读取 20 个模块、运行 15 次构建与单测,活跃上下文会轻松突破 100k 甚至 180k Token。如果不对上下文施加治理,Agent 很快就会因超出模型物理上下文上限而直接崩溃。

但直接丢弃历史消息(硬截断)会导致 Agent 遗忘最初的用户约束与前序排查结论。为了在物理限制下实现“无限会话”的工程假象,Claude Code 构筑了一套由轻到重、按需触发的三层上下文压缩体系(Context Compact)。

本文将拆解这三层机制的触发边界、代码实现以及工业级生产规则。


三层压缩拓扑全景

这套设计遵循一条核心原则:能用纯代码规则解决的绝不调用模型(Layer 1),必须调用模型时采用精炼提示词保障高保真度(Layer 2)。


Layer 1:Microcompact——零 LLM 调用的静默清理

在 src/services/compact/microCompact.ts 中,Microcompact 是成本最低的一级防御。它完全不调用大模型,仅通过纯算法在内存消息栈上执行就地修改(In-place Mutation)。

核心逻辑:清除陈旧的工具执行大结果

当 Agent 在第 3 轮调用 read_file 读取了 800 行代码,并在第 4 轮完成了重构,那么到了第 10 轮时,第 3 轮读取的那 800 行源码对当前决策已经毫无参考价值,留着只会空耗 Token。

Microcompact 会遍历历史,将早期的工具大结果替换为轻量占位符,仅保留最近 $N$ 个工具结果:

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
KEEP_RECENT_TOOLS = 3

def micro_compact(messages: list) -> list:
"""
遍历消息栈,将陈旧的工具输出就地替换为轻量占位符
"""
# 1. 搜集所有历史上的 tool_result 索引
tool_results = []
for m_idx, msg in enumerate(messages):
if msg["role"] == "user" and isinstance(msg.get("content"), list):
for p_idx, part in enumerate(msg["content"]):
if isinstance(part, dict) and part.get("type") == "tool_result":
tool_results.append((m_idx, p_idx, part))

# 若工具调用总数未超出保留阈值,无需处理
if len(tool_results) <= KEEP_RECENT_TOOLS:
return messages

# 2. 将除了最近 N 个之外的陈旧结果替换为极简占位符
for m_idx, p_idx, part in tool_results[:-KEEP_RECENT_TOOLS]:
raw_text = str(part.get("content", ""))
# 仅压缩体积较大(如超 150 字符)的内容
if len(raw_text) > 150:
part["content"] = "[系统提示: 早期工具执行输出已归档,内容已清除以节省上下文空间]"

return messages

时间维度的缓存衰减触发

除了轮次触发,Claude Code 源码中还包含 timeBasedMCConfig.ts:当检测到用户离开终端超过 60 分钟再回来时,服务端 Anthropic Prompt Cache 的 TTL(生存时间)基本已经失效。此时系统会自动触发一次更激进的 Microcompact,防止冷启动时带着陈旧的上下文拉高首次推理延迟。


Layer 2:Full Compact——结构化深度压缩与标识符保留

当 Microcompact 清理完毕后,如果总 Token 数仍然逼近警戒线(源码中设定为 context_window - 13,000),系统就会激活重型武器:Full Compact。

Full Compact 会派生一个独立的子调用,利用专门针对代码压缩调优的提示词,将整段漫长历史浓缩为结构化大纲。

致命陷阱:模糊摘要的灾难

如果使用普通的摘要提示词:“请总结上述对话”,模型通常会产出如下废话:

“开发者此前分析了用户鉴权模块,发现了一些安全漏洞并进行了修复。”

一旦上下文被替换成这种模糊概括,Agent 会在下一轮直接瘫痪——它完全不知道修改了哪个文件、哪一行、哪个函数,也丢掉了测试框架的运行指令。

工业级准则:严格保留标识符(Strict Identifier Preservation)

Claude Code 在 src/services/compact/prompt.ts 中给出了极为严苛的结构化提取约束:

1
2
3
4
5
6
7
你是一个高保真度的代码会话压缩引擎。在生成摘要时,必须严格保留以下物理锚点:
1. 涉及的所有物理文件路径、脚本路径;
2. 涉及的所有精确函数名、类名、变量名与错误码;
3. 具体修改涉及的代码行号范围(如 src/auth.ts:45-80);
4. 明确记录已经做出的架构决策和用户强制约束;
5. 当前未完成的任务与下一步计划。
严禁使用泛泛而谈的概括性描述!

压缩后的标准输出范式:

1
2
3
4
## 上下文提炼摘要
- [已确认架构]: 项目采用 pnpm monorepo,使用 vitest 进行单元测试。
- [已完成改动]: 在 `src/auth/jwt.ts:50-75` 重构了 `verifyToken()` 函数,补充了过期校验。
- [未决问题]: `src/routes/login.ts:32` 处的异常处理尚未闭环,单测 `tests/jwt.test.ts` 尚待运行。

Layer 3:Auto Compact 与压缩后状态回贴(Reseeding)

在执行完 Full Compact 并将旧消息清空后,如果只给模型留下一段摘要,模型容易产生“冷启动失忆”。

Claude Code 会在压缩后的纯净上下文头部,执行状态回贴(Reseeding):

通过这种回贴,Agent 既释放了前序几十轮对话产生的数万 Token 堆积,又瞬间找回了核心文件的物理结构和当前任务指针,真正实现了“无限推进而不失忆”。

磁盘 Transcript 才是绝对事实源:
压缩移出上下文并不意味着数据被彻底销毁。Claude Code 会将所有原始事件流无损追加写入本地磁盘文件(Transcript)。排查问题或断点续传时,完整的证据链永远有据可查。


总结

Claude Code 的 Context Compact 展现了极致的工程克制:

  1. 多级防御:先用零成本的代码过滤清理陈旧工具日志(Microcompact),最后才调用模型(Full Compact);
  2. 代码级高保真:强约束文件路径、函数名与精确行号,杜绝模糊摘要导致的执行瘫痪;
  3. 状态回贴:压缩不是单纯的删减,而是将“摘要 + 焦点文件 + 任务图”重新打包,赋予模型全新动力。

上下文压缩解决了单会话内 Token 膨胀的难题。但在真实的工程推进中,复杂的任务不仅需要被拆解,还需要表达依赖关系(例如任务 B 必须等待任务 A 完成),并且在进程崩溃后能从磁盘完整恢复。

下一篇我们将深入架构底层:Task System 磁盘持久化任务图与 DAG 依赖驱动。