乐于分享
好东西不私藏

OpenClaw源码解析(十):上下文工程总览:构成、装配链、生命周期

OpenClaw源码解析(十):上下文工程总览:构成、装配链、生命周期

写在开头

最近Claude code的更新速度实在惊人,刚更新的Computer use 和 Dispatch 结合起来又有了新的体验,openclaw的热度在似乎在慢慢下降。但是系统的底层实现还是有很多值得学习的地方,如果你自己搭建过Agent,你会发现不用彻底了解上下文工程、记忆系统、Agent范式等,Vibe Coding 也能给你搭建一套可以运行的agent,但是随着功能越来越多,历史会话越来越长,你会发现暴露的问题也越来越多,所以了解底层的架构什么时候都不晚。最开始扒下源码,在AI的帮助下,我预期能分享10篇文章,但是现在看来远远不够,这个系列可能会在20-40篇左右。

值得学习的内容很多,比如上下文工程、记忆系统、多agent能力、skills机制、沙盒系统、工具、定时任务等等。也许你对于这些概念能够侃侃而谈,但是实操起来就会出现很多疑惑。希望这个函数级的源码分享能够让你了解到一些东西。

核心代码:

– src/agents/pi-embedded-runner/run.ts– src/agents/pi-embedded-runner/run/attempt.ts– src/context-engine/types.ts– src/context-engine/legacy.ts– src/agents/pi-embedded-runner/system-prompt.ts– src/agents/session-tool-result-guard.ts– src/agents/pi-embedded-runner/tool-result-context-guard.ts

1. 先记住一句话

它是“每一轮调用模型前,重新组装一次当前可用上下文”的运行时系统。

这套系统既要回答:

  • 本轮模型会看到什么
  • system prompt 里到底塞了什么
  • 哪些历史会继续保留
  • 哪些工具结果会变成后续负担

也要回答:

  • 历史坏了怎么办
  • 太长了怎么办
  • 运行中压缩了怎么办
  • 结束后这一轮如何延续到下一轮

2. 上下文到底包括什么

在 OpenClaw 里,一轮开始时真正相关的上下文至少有 4 层。

2.1 会话历史层

这是 session transcript 里的持久内容:

  • user 消息
  • assistant 消息
  • tool call / tool result
  • compaction summary
  • 部分 custom entry

这部分会跨轮保留,是下一轮装配时的原材料。

2.2 system prompt 控制层

  • 产品身份和行为规则
  • 工具说明
  • sandbox / workspace 信息
  • skills 概览
  • bootstrap/context files
  • docs / runtime / time / channel 能力

2.3 本轮临时注入层

这部分只服务当前 prompt,不一定进入长期 transcript,例如:

  • hook prepend 的临时上下文
  • 当前 prompt 中的图片
  • context engine 给的 systemPromptAddition

2.4 运行时决策层

这部分不一定直接喂给模型,但影响行为:

  • context window 大小
  • overflow 判断
  • 本轮是否已经 auto-compaction
  • prompt error
  • pre-compaction snapshot
  • tool result 是否超大

这层更像“上下文治理信息”。


3. ContextEngine 在整个系统里的位置

ContextEngine 不是一个孤立函数,而是围绕会话生命周期的抽象接口。

核心定义在 src/context-engine/types.ts,包括:

  • bootstrap
  • assemble
  • compact
  • afterTurn
  • ingestBatch
  • ingest
  • prepareSubagentSpawn
  • onSubagentEnded

其中最关键的4个是 :

3.1 bootstrap(…)

在已有 session 被重新打开时,让 engine 有机会做自己的初始化。

3.2 assemble(…)

在模型调用前,对已经整理过的消息做最后装配。

3.3 compact(…)

在上下文太大时,整体压缩。

3.4 afterTurn(…)

在这一轮结束后,处理 turn 级收尾。


4. 当前默认实现其实还是 legacy 过渡态 (2026.3.3版本)

src/context-engine/legacy.ts 非常重要,因为它说明当前 OpenClaw 的现实状态。

它的行为大致是:

  • bootstrap: 不起作用
  • ingest: 不起作用
  • assemble: 数据直接透传
  • afterTurn: 不起作用
  • compact: 桥接到老的 compaction 流程

所以你应该这样理解现在的结构:

上下文引擎接口已经独立,但真正大部分上下文预处理和运行控制,仍然在 run.ts / attempt.ts 里。

5. 一轮开始时,系统按什么顺序装配上下文

这是最核心的主线。

阶段 0:外层 run.ts 先定边界

先确定:

  • provider / model
  • auth profile
  • context window
  • 这个模型窗口是否小到不值得继续
  • 本轮允许多少次恢复性重试
  • 要用哪个 context engine

阶段 1:打开会话并修复 transcript

在 run/attempt.ts 里会做:

  • repairSessionFileIfNeeded(…)
  • 打开 SessionManager
  • guardSessionManager(…)

含义很简单:

  • 文件先得能读
  • transcript 结构先得安全
  • 工具消息写入先得有守门

阶段 2:如有需要,先 bootstrap

如果已有 session file,且 engine 实现了 bootstrap(…),会先调用它。

语义不是“构建 prompt”,而是:

这个旧会话现在要重新进入运行态了,engine 先做自己的冷启动。

阶段 3:构建 system prompt

这一轮 system prompt 会重新拼出来。

常见输入包括:

  • skills prompt
  • bootstrap/context files
  • tool summaries
  • sandbox info
  • runtime info
  • docs path
  • owner / channel / time / TTS 相关信息

这也是为什么 system prompt 在 OpenClaw 里很重。

阶段 4:清洗历史 transcript

真正送给模型前,先对历史做标准化:

  • sanitizeSessionHistory(…)
  • provider 相关 turn 校验
  • limitHistoryTurns(…)
  • sanitizeToolUseResultPairing(…)

注意这一步不是“恢复链”,而是每轮固定前处理。

阶段 5:让 context engine 做最后装配

然后调用:

  • contextEngine.assemble(…)

今天默认 legacy 实现几乎不改消息,但架构上这里已经是一个正式插点。

阶段 6:本轮级额外注入

在 prompt 真发出去前,还会发生:

  • hook 可能 prepend prompt
  • hook 可能 override / prepend / append system prompt
  • 检测当前 prompt 里的图片
  • 安装 tool-result-context-guard

这一步很关键,因为它说明:

有些治理不是改 transcript,而是改“这一轮模型将要看到的输入视图”。

阶段 7:真正调用模型

最终 activeSession.prompt(…) 看到的,是这些东西叠起来后的结果:

  • system prompt
  • 清洗后的历史
  • context engine 组装后的消息
  • hook 注入
  • 当前用户 prompt
  • 当前 prompt-local 图片

6. 检测到底是“每轮都做”,还是“有条件才做”

答案是:两种都有。

6.1 每轮固定会做的

每次 attempt 开始前,都会重新做:

  1. transcript 修复
  2. system prompt 构建
  3. history sanitize
  4. history validate
  5. history limit
  6. tool-use / tool-result repair
  7. context engine assemble
  8. tool result context guard 安装

所以“上下文检测”不是在会话开始时只发生一次,而是每轮调用模型前都会重新来一遍

6.2 按条件触发的

这些只有满足条件才会做:

  • bootstrap

条件:已有 session 且 engine 实现

  • prompt 图片检测

条件:当前 prompt 引用了图片

  • compaction wait

条件:运行中真的出现 auto-compaction 事件

  • explicit compaction

条件:外层判断是 overflow

  • tool result truncation

条件:overflow 后检测到超大 tool result


7. tool result 为什么属于上下文系统,不只是工具系统

tool result 不只是“工具临时输出一段文本”。

它会:

  • 被写进 transcript
  • 在下一轮继续成为历史
  • 占用未来 prompt 的预算

7.1 写入前治理

session-tool-result-guard.ts 会在持久化前做两类事:

  • 修结构
  • 设硬上限

也就是:

别把协议非法的工具消息写进去,也别把离谱的大结果原样写进去。

7.2 调用前治理

tool-result-context-guard.ts 会在模型调用前保护本轮上下文:

  • 单条过大就截短
  • 总体还是过大,就把旧 tool result 替换成占位符

8. 一轮结束后,上下文怎么延续

本轮结束后会调用:

  • afterTurn(…)
  • 或 ingestBatch(…)
  • 或逐条 ingest(…)

当前默认 legacy 基本还是 no-op,所以截止到 2026.3.3 版本主要还是由 SessionManager 负责持久化 transcript。

但从接口设计看,未来可以把更多“turn 结束后该如何维持上下文”的责任交给 engine。


9. 总结

  1. OpenClaw 的上下文不是单一聊天历史,而是历史、system prompt、本轮注入、运行时决策四层叠加。
  2. 每一轮开始前都会重新做上下文装配,不是开会话时做一次就结束。
  3. ContextEngine 已经是正式生命周期接口,但默认 legacy 仍然是过渡态。
  4. 当前最核心的真实控制点依然在 run.ts 和 run/attempt.ts
  5. tool result 会跨轮继续占用上下文,所以必须同时纳入工具系统和上下文系统来理解。

既然看到这了,如果觉得有帮助,随手点个关注、在看、转发三连吧,~谢谢你看我的文章,我们,下次再见。