Claw Code 源码解析(四):Agent 如何管理上下文与压缩历史

Published on
10 mins read

这是这组 Claw Code 源码解析的最后一篇主文。

整套源码里,如果只让我挑一个最值得单独写成文章的主题,我会选上下文管理。

原因很简单:很多人一提到 agent 的 memory,就默认想到向量数据库、长期记忆、embedding 检索;但 Claw Code 当前主线解决的问题并不是这个。

它更关心的是:在一个持续执行的 agent runtime 里,什么信息该进 prompt,什么时候会超预算,超预算后怎么保持任务连续性。

这套策略可以压缩成一条很清楚的主线:

  • Session transcript
  • ProjectContext
  • Prompt budget
  • PromptCache
  • Session compaction

这套系统的上下文管理分 5 层

我会把它拆成下面五层:

  1. Session
  2. SystemPromptBuilder
  3. ProjectContext
  4. PromptCache
  5. compact_session()

这个拆分说明一个很重要的事实:这里的 context management 不是“把所有资料都塞进去”,而是一个明确的预算和压缩系统。


Session:上下文的底座不是纯文本,而是结构化 transcript

这一步很关键。

在这个系统里,会话不是一串单纯文本,而是结构化消息序列。消息块里不仅有:

  • user text
  • assistant text

还有:

  • tool use
  • tool result
  • compaction 元数据
  • fork 信息

这会直接改变后面的上下文管理方式。因为一旦 transcript 本身是结构化的,系统就能知道:

  • 之前执行过哪些工具
  • 工具返回了什么
  • 哪些消息已经被压缩过

换句话说,后续 compaction 做的不是“随便把聊天记录缩短”,而是基于真实执行历史做摘要。


System prompt 不是静态模板,而是动态组装

第二层是 SystemPromptBuilder

这里最值得注意的地方是:system prompt 并不是一段固定文案,而是由静态部分加动态部分组成。动态部分会注入:

  • 当前工作目录
  • 当前日期
  • git 状态和 diff
  • 发现到的指令文件
  • 部分配置和环境上下文

这意味着系统不是只靠“历史消息”理解任务环境,环境本身也会进入 prompt。

这很像一个真正的 coding agent,而不是普通聊天机器人。


ProjectContext:它注入的是环境快照,而不是仓库全文

这里还有一个很值得注意的点:ProjectContext 做的是 环境信息发现与筛选,不是把工程所有内容一股脑塞进去。

典型注入内容包括:

  • cwd
  • today
  • git status
  • git diff
  • instruction files

这说明它的设计目标非常克制:

  • 给模型足够的任务环境
  • 但不试图把整个仓库全文变成 prompt

这种做法很现实,因为真正限制 agent 的不是“有没有更多信息”,而是上下文预算是否可控。


指令文件也有预算,不会无限塞

这套实现里一个很成熟的信号是:连 instruction files 都有字符预算。

这件事看起来不起眼,但其实说明整个系统的思路很一致:

  • prompt 是预算受限资源
  • 即使是高优先级上下文,也要做上限控制
  • 不允许某一类上下文无限膨胀

如果你做过真实 agent 系统,就会知道这是非常重要的工程判断。因为很多项目不是模型能力不够,而是 prompt 管理失控。


PromptCache:从 provider 视角管理上下文复用

PromptCache 解决的是另一个常被忽略的问题:provider 视角下,哪些 prompt 前缀是稳定的,哪些会导致缓存失效。

这和“系统内部怎么记上下文”不是一回事。

它更像是在问:

  • 这轮请求和上一轮相比,前缀有没有大幅变化
  • 哪些动态注入让缓存命中率下降
  • 长会话里如何减少重复成本

所以 PromptCache 其实处在“上下文管理”和“成本优化”的交叉地带。

这类设计非常像成熟 runtime,而不是单次调用脚本。


什么时候触发 compaction

真正让这套系统有意思的,是 compaction。

核心逻辑并不复杂:当累计输入接近预算阈值时,系统会触发压缩,把旧消息摘要化,保留最近消息原文继续运行。

这比“直接截断历史”成熟很多,因为它承认了两个事实:

  1. 旧消息并非完全没用
  2. 但也不值得全部原文保留

于是系统采用了一个折中方案:

  • 旧历史压成 summary
  • 最近消息保留原文 tail
  • 然后继续当前任务

这和很多聊天产品简单丢前文,完全不是一回事。


token 估算并不花哨,但足够实用

这里还有一个我很喜欢的工程取舍:token 估算并没有追求极致精确,而是采用相对朴素的近似估算。

这类策略的价值在于:

  • 实现简单
  • 运行时成本低
  • 足以支持压缩阈值判断

这其实是很典型的 runtime 工程判断。很多时候,系统不需要“理论最优”,而需要“足够稳定并且可解释”。


compaction 不是裁掉旧消息,而是“摘要旧消息 + 保留最近 tail”

这是整套实现里最值得记住的一点。

如果只是把旧消息硬裁掉,系统会立刻丢失:

  • 之前做过的决策
  • 调用过的工具
  • 遇到过的错误
  • 用户明确给过的约束

而摘要旧消息再保留最近 tail,意味着系统仍然保住了两层东西:

  • 长期任务记忆的摘要版
  • 当前局部执行上下文的原文版

这个策略对 coding agent 特别重要,因为任务连续性往往比对话自然度更重要。


自动压缩和手动压缩是两条路径

这一点也很成熟。

如果一个系统只有自动 compaction,用户很难在关键时刻主动整理上下文;如果只有手动 compaction,长任务又容易自己失控。

所以更合理的方式就是两条路径同时存在:

  • 自动压缩:当输入规模超过阈值时由 runtime 触发
  • 手动压缩:给用户显式入口,在需要时主动收束上下文

这种设计说明它既考虑了系统自治,也保留了人类干预能力。


它解决的不是“长期记忆”,而是“长任务连续性”

这是我对这套设计最重要的判断。

Claw Code 当前主线的 context management,并不是在做一个通用 AI memory system。它真正解决的是:

  • 当前任务上下文怎么进入 prompt
  • 长会话怎么控制预算
  • 超长之后怎么不丢失执行连续性

所以它的核心不是向量检索,而是:

  • transcript
  • budget
  • cache
  • compaction

这是一条非常工程化,也非常务实的路线。


小结

  • 上下文管理可以拆成 SessionSystemPromptBuilderProjectContextPromptCachecompaction 五层
  • 这里的主线不是向量记忆,而是长任务 prompt 管理
  • Session 是结构化 transcript,不只是聊天记录
  • compaction 的核心策略是“摘要旧消息 + 保留最近 tail”
  • 自动压缩和手动压缩并存,说明系统同时考虑了自治和人工控制

如果你在做 coding agent 或长任务 agent,这一套思路非常值得借鉴。

如果你想补前面的仓库定位、扩展系统和验证体系,建议回到第一篇开始:

Claw Code 源码解析(一):它为什么不是普通聊天 CLI,而是一个 Agent Runtime