OpenClaw源码解析(八):OpenClaw 的 Agent 范式:内部求解循环、外层恢复循环
建议结合这些文件一起读:
– src/agents/pi-embedded-runner/run.ts– src/agents/pi-embedded-runner/run/attempt.ts– src/agents/pi-embedded-subscribe.ts– src/context-engine/types.ts– src/agents/tools/memory-tool.ts
这一篇的目标
上一篇主要讲的是真实执行链,这一篇我们的视角再往上提一层,回答以下问题:
-
OpenClaw 的主 agent 到底属于哪种范式。 -
为什么你在代码里看到两层 loop,但它们不属于同一个分析层级。 -
一个复杂任务在 OpenClaw 里是如何被“持续推进”的。
先给一个准确定位
OpenClaw 当前的主 agent 是一个 ReAct-like single-agent execution loop,外面包了一层 runtime recovery loop,再由 context 和 memory 系统为它提供可持续工作记忆。
拆开看就是:
-
single-agent
主决策者通常只有一个,没有显式的 planner / critic / judge 多角色。
-
ReAct-like
模型会根据当前上下文决定是直接回答还是调用工具,工具结果再回流到同一条 transcript。
-
runtime recovery loop
环境失败时不是直接结束,而由 run.ts 外层重试、切换账号、切换模型、压缩上下文。
-
context + memory support
attempt 级重放能不能继续任务,不取决于“断点恢复”,而取决于上下文和记忆是否把任务状态保留下来了。
一定要分清两种“多轮”
这是理解 OpenClaw 时最容易出错的地方。
第一种:任务求解多轮
这是 agent 真正“做事”的轮次。
例如:
读文件-> 发现还不够-> 再搜一处-> 调命令验证-> 修改文件-> 再跑测试-> 最终回答
这种多轮发生在一次 attempt 背后的内部 session runtime 中。
第二种:运行恢复多轮
这是外层 run.ts 在做的事。
例如:
这次 attempt 因上下文溢出失败-> 先 compact-> 再发起一个新的 attempt
或者:
这次 attempt 用的账号失效-> 换 profile-> 再发起一个新的 attempt
这两种“轮次”在源码里都表现为“会再次调用模型”,但它们根本不是一回事。
前者是任务推进,后者是运行修复。
OpenClaw 为什么更像 ReAct,而不是 Planner-Executor
OpenClaw 当前主链没有一个清晰独立的“先做完整计划,再逐步执行”的硬边界。
它更接近:
看当前上下文-> 先决定下一步动作-> 执行动作-> 把结果写回上下文-> 再决定下一步
这就是 ReAct 范式的的核心。
具体体现
-
不预先生成完整执行图
没有显式 DAG 或多阶段 workflow 先展开再执行。
-
工具结果直接进入同一条 transcript
下一轮模型读取的是“自己刚刚行动后的最新状态”。
-
是否继续调工具由模型即时决定
不是外层流程引擎硬编码“必须先 search 再 read 再 edit”。
-
停止条件通常由内部 loop 自然收敛
当模型判断信息足够或任务完成,它就停止继续调用工具并输出最终答复。
因此从方法论上说:
OpenClaw 的主 agent 不是“先规划后执行”,而是“边观察边行动边更新工作记忆”。
为什么说 attempt.ts 是“执行容器”而不是“推理器”
attempt.ts 做了很多事,但它本身不扮演“决策者”。
它更像一个容器层,负责:
-
准备工作记忆 -
准备工具空间 -
准备系统约束 -
准备 hooks / skills / bootstrap -
接住底层事件 -
在结束后把结果整理出来
真正“下一步该做什么”的决策,仍然由底层模型在 activeSession.prompt(…) 驱动的内部循环中完成。
所以你可以把三层关系记成:
run.ts = 运行恢复控制器attempt.ts = 单次执行容器session runtime = 真正的模型-工具求解循环
这套范式为什么天然依赖 transcript
如果一个系统采用的是“每一步都重新算完整计划”,它对 transcript 的依赖反而可以弱一点。
但 OpenClaw 不是。
它依赖的是:
上一步做了什么+ 得到了什么观察+ 现在还缺什么-> 决定下一步
这意味着 transcript 在这里不是“聊天记录”,而是:
当前任务的在线工作记忆。
里面承载的不是只有 user / assistant 文本,还有:
-
tool call -
tool result -
reasoning 相关块 -
图片 -
自定义条目 -
compaction summary
一旦 transcript 被裁坏、配对关系坏掉、tool result 太长、历史结构非法,任务推进能力会直接下降。
为什么这套范式还需要外层恢复 loop
如果只有内部 ReAct-like loop,没有外层恢复层,这套系统会非常脆弱。
原因很简单:agent 的失败不止是“想错了”,还有大量基础设施失败:
-
provider 超载 -
timeout -
auth/profile 失效 -
模型能力不兼容 -
context overflow -
工具结果把上下文顶爆
这些问题单靠“模型再想一轮”没有意义。
所以 OpenClaw 把恢复权收回到 run.ts:
-
内层 loop 负责求解任务 -
外层 loop 负责修复运行环境
这其实是一种非常典型的 agent runtime 分层。
“为什么外层不直接接管任务推进”
因为如果外层也去管任务步骤,就会把系统变成另一种形态:
-
需要显式的 planner -
需要显式的 task state machine -
需要定义每种工具的编排规则 -
需要更多框架层调度逻辑
OpenClaw 当前显然没有选择这条路线。
它选择的是:
把任务推进留给模型,把运行恢复留给 runtime,把工作记忆维护交给 context/memory。
这条路线的优点是灵活,代价是对上下文质量要求非常高。
一次 attempt 内,什么在变,什么相对稳定
理解这一点很关键。
相对稳定的东西
-
当前 attempt 的基本执行容器 -
当前 attempt 内构建出来的 system prompt 主体 -
当前 attempt 可用的工具集合 -
当前 attempt 绑定的 provider / model / profile
持续变化的东西
-
transcript 内容 -
tool result 数量 -
usage 累积 -
compaction 状态 -
当前任务所处阶段
外层重试时,什么会被保留,什么会被重建
这也是理解“attempt 级重放”必须知道的。
会被保留的
-
session 中已经写入并保留下来的 transcript -
上下文压缩后的结果 -
某些 prompt-error 自定义条目 -
memory flush 后落入长期记忆的内容
会被重建的
-
本次 attempt 的 session runtime 容器 -
system prompt 的完整装配 -
hook 运行结果 -
history sanitize / validate / limit / repair 的过程 -
订阅器和流式状态
所以新的 attempt 不是“完全从零”,也不是“沿用上一个 attempt 的内存现场”。
它是一种中间态:
保留工作结果,重建执行容器。
一个复杂任务在这套范式下是怎么完成的
拿一个代码修复任务举例:
用户:修复 webhook 重复发送
真实推进方式更接近:
-
attempt 启动,组装 prompt、tools、context。 -
内部 agent loop 先搜索和读文件。 -
transcript 写入搜索结果和文件内容摘要。 -
模型根据新观察形成假设。 -
调命令验证假设。 -
修改代码。 -
运行测试。 -
如果通过,输出最终答复。 -
如果中途 context overflow,外层 run.ts 才介入做恢复。
注意:第 2 到第 8 步通常都在同一个 attempt 内发生。
这说明 OpenClaw 的主能力不是“把任务拆成很多外层流程节点”,而是:
给同一个 agent 一个足够好的连续工作空间,让它自己把链条走完。
这套范式最值得学习的设计点
1. 将“求解失败”和“运行失败”分层
模型不会因为 auth 失效这种基础设施问题直接承担恢复责任。
2. 将 transcript 当作在线工作记忆,而不是聊天日志
这让工具密集型任务能在一个长链里持续推进。
3. 允许 attempt 级重放,而不是依赖 fragile 的中间断点恢复
这提高了跨 provider、跨 compaction、跨 profile 恢复的健壮性。
4. 通过 context/memory 系统给重放提供“状态连续性”
否则 attempt 级重放会变成低质量重复劳动。
5. 把 system prompt 当成控制平面,而不是人格设定
这使得每次 attempt 都能重新绑定运行约束,而不是纯粹继承旧现场。
这一篇的最终结论
-
OpenClaw 当前最接近“单智能体 ReAct-like 执行 + 外层运行恢复”的范式。
-
两层 loop 不是一个层级的问题。
内层 loop 解决任务,外层 loop 修运行环境。
-
attempt 级重试是这套设计的关键。
它不恢复中间断点,而是依赖 transcript、context 和 memory 让新的 attempt 继续接近原任务状态。
如果您觉得有帮助,感谢您的关注和转发。
夜雨聆风