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 开始前,都会重新做:
-
transcript 修复 -
system prompt 构建 -
history sanitize -
history validate -
history limit -
tool-use / tool-result repair -
context engine assemble -
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. 总结
-
OpenClaw 的上下文不是单一聊天历史,而是历史、system prompt、本轮注入、运行时决策四层叠加。 -
每一轮开始前都会重新做上下文装配,不是开会话时做一次就结束。 -
ContextEngine 已经是正式生命周期接口,但默认 legacy 仍然是过渡态。 -
当前最核心的真实控制点依然在 run.ts 和 run/attempt.ts。 -
tool result 会跨轮继续占用上下文,所以必须同时纳入工具系统和上下文系统来理解。
既然看到这了,如果觉得有帮助,随手点个关注、在看、转发三连吧,~谢谢你看我的文章,我们,下次再见。
夜雨聆风