Claude Code 源码解析 Part 5:上下文压缩系统,Agent 为什么不会越聊越糊

Claude Code 源码解析 Part 5:上下文压缩系统,Agent 为什么不会越聊越糊

前面几篇我们已经看过 Claude Code 的多 Agent 编排、权限系统、主循环和 Task Runtime。
但如果只把这些部分拼起来,一个真正的难题还没有解决:

当对话越来越长、工具结果越来越多、任务跨越多个小时之后,系统如何避免上下文失控?

很多 agent 产品在这里会突然退化。刚开始几轮很聪明,跑到后面就越来越糊,越来越容易忘记自己刚才做过什么。Claude Code 之所以值得研究,正是因为它不是在上下文快炸的时候临时做一次 summary,而是把“压缩”做成了一整套持续运行的治理系统。

我想先给出这一篇的核心判断:

Claude Code 的上下文管理不是一次 summarize,而是分层、持续、带运行时条件的 context governance。

如果你只看 src/query.ts,可能会以为这里是一个普通的 query loop;但只要把 src/services/compact/autoCompact.tssrc/services/compact/microCompact.tssrc/utils/toolResultStorage.ts 和 context collapse 相关逻辑一起看,就会发现它已经形成了一套层次分明的压缩架构。

一、Claude Code 先做的不是“总结”,而是“控制上下文进入主循环的方式”

很多人对上下文压缩的第一反应是:历史太长了,就做个摘要。
Claude Code 的源码不是这么思考的。

src/query.ts 的主循环里,进入模型前会先经过几层处理:

  • applyToolResultBudget
  • history snip
  • microcompact
  • context collapse
  • autocompact

这几个步骤不是同义词,也不是互斥替代。它们对应的是不同层级的问题:

  • 工具结果是否过大
  • 历史里是否有不再值得保留的长尾内容
  • 某些结果是否可以轻量替换或清空
  • 是否可以用 collapse view 重投影上下文
  • 是否已经逼近窗口上限,必须触发正式 summary compaction

换句话说,Claude Code 不是把“压缩”理解成一个动作,而是理解成一个逐级升级的治理管线。

这很关键。因为上下文失控通常不是一个时刻发生的,而是一个渐进过程。如果系统只有最终 summary 这一种武器,那么它实际上是在用重锤解决所有问题。

二、第一层:先限制工具结果本身,而不是等它们把上下文拖爆

这一层很容易被忽略,但其实很重要。

src/query.ts 里,Claude Code 会在 microcompact 之前先运行 applyToolResultBudget。这意味着它优先处理的是一个很现实的问题:

很多上下文膨胀,不是来自自然语言,而是来自工具结果。

比如:

  • 文件读取太长
  • shell 输出太长
  • 抓取网页太长
  • 搜索结果太多

Claude Code 在工具定义层已经给很多工具声明了 maxResultSizeChars,而在 query 阶段又做了一层 aggregate budget 处理。两层叠加起来,形成的是一种很务实的策略:

先在结果产生的地方设边界,再在消息进入模型前做统一治理。

这和很多系统“先把所有结果都塞进历史,等超限了再想办法”的做法完全不同。
Claude Code 更像是在说:不要把垃圾先放进上下文,然后再花更高代价去清理它。

三、第二层:Snip 与 Microcompact,本质上是“轻量整理”,不是重写历史

如果继续看 src/query.ts,会看到一个很有意思的顺序:

  • snip
  • microcompact
  • 再决定是否需要更重的 compact

这背后的设计非常成熟。

1. Snip 的作用

Snip 解决的是明显冗余的历史尾部问题。
源码里甚至把 snipTokensFreed 继续传给 autocompact,用来影响后续阈值判断。这说明 Snip 不是一个 UI 层的修饰,而是会真实进入 runtime 决策链。

2. Microcompact 的作用

src/services/compact/microCompact.ts 比很多人想象中更重要。
它不是做“大规模摘要”,而是对一些高频、可压缩的工具结果做轻量处理,尤其是那些会频繁占据上下文、但并不总需要完整保留原文的结果。

这层的核心思想是:

能用轻量替换解决的问题,就不要上来做整段总结。

这一点对长期运行的 agent 很重要。因为 summary compaction 虽然有效,但代价高,而且会损失颗粒度。Microcompact 则更像手术刀,优先保留局部信息结构。

四、第三层:Context Collapse 不是 summary,而是“投影视图”

src/query.ts 里有一段注释非常值得读:context collapse 被描述成一个“read-time projection”,而不是对 REPL 全历史的直接重写。

这意味着它的逻辑不是“把原始历史抹掉,换成总结”,而是:

  • 全量历史依然存在
  • 压缩后的 summary 消息存在于 collapse store
  • 主循环在进入时,根据 collapse log 投影出一个更适合当前推理的视图

我认为这是 Claude Code 压缩设计里最有启发的一层。

因为它背后体现的是一种比“摘要替换原文”更高级的思路:

上下文不一定要被销毁,也可以被投影。

这是 runtime 思维,不是聊天思维。聊天系统默认历史就是线性 append;runtime 则允许你对历史构建更适合当前执行的视图。

五、第四层:Autocompact 是最后防线,而且是有门槛、有熔断的

src/services/compact/autoCompact.ts 说明 Claude Code 对 autocompact 的态度其实很克制。

它不是“只要长了就压”。这里至少能看到几类限制:

  • 根据模型窗口和输出保留计算有效窗口
  • 用 buffer token 计算阈值,而不是贴着上限跑
  • 允许通过配置和 feature gate 禁用
  • 对某些 querySource 做递归保护,避免死锁或互相打架
  • MAX_CONSECUTIVE_AUTOCOMPACT_FAILURES 这种 circuit breaker

这个 circuit breaker 我认为特别重要。
它说明 Claude Code 不是把 compact 当成必然成功的操作,而是承认:

有些上下文已经坏到无法挽回,如果一直重试 compaction,只会浪费 API 调用和系统资源。

这是非常典型的生产系统思维。
不是“多试几次总会好”,而是“失败到一定程度就必须停,不能让系统无限自耗”。

六、为什么说 Claude Code 的压缩系统本质上是治理系统

到这里可以看出,Claude Code 的 context compaction 并不是一个单点功能,而是一组协同的机制:

  • 工具结果预算控制
  • snip
  • microcompact
  • collapse 投影
  • autocompact
  • 失败熔断
  • 特定 querySource 的递归防护

把这些组合起来之后,Claude Code 才真正具备了“长会话能力”。

这也是我认为行业里一个经常被低估的事实:

长上下文能力不等于长窗口能力。

模型给你 200K、500K 甚至更大的窗口,不代表系统就自动变成长期稳定的 agent。
如果没有 runtime 层的上下文治理,窗口只会被更快填满,混乱只会在更大的空间里发生。

七、Claude Code 这套压缩系统最值得学的地方

最后做个收束。

如果你在做 agent runtime,我认为 Claude Code 的上下文压缩系统至少有四个地方值得抄:

  1. 把压缩拆成多个层次,不要只有“做摘要”这一种手段。
  2. 先限制工具结果,再治理消息历史,控制膨胀源头。
  3. 引入 context projection 思维,而不是一味重写原始历史。
  4. 给 autocompact 加熔断与递归保护,承认 compaction 也是会失败的。

Claude Code 真正强的地方,不是它“能压缩”,而是它知道不同阶段该用什么力度治理上下文。
这背后体现的不是 prompt 技巧,而是 runtime engineering。

下一篇我们会进入另一块同样决定系统边界的区域:工具执行平面。Claude Code 的 tools 为什么不是普通的 function calling?它是怎么把权限、并发、安全、执行结果和流式工具调用整合成一层 execution plane 的?

相关阅读:

深求社区(DeepSeek.club)出品,国内领先的AI大模型及应用开源社区!