乐于分享
好东西不私藏

OpenClaw源码解析(七):Agent Runner 全执行链 | 上集

OpenClaw源码解析(七):Agent Runner 全执行链 | 上集

核心文件:

– src/agents/pi-embedded-runner/run.ts
– src/agents/pi-embedded-runner/run/attempt.ts
– src/agents/pi-embedded-subscribe.ts


读完这篇你会了解以下问题

  • OpenClaw 到底有几层 loop,它们分别负责什么。
  • run.ts 的 while (true) 和 agent 真正执行任务的 loop 是不是一回事。
  • 一个复杂任务正常执行时,代码主要停留在哪一层。
  • 如果任务在“第 3 轮工具调用之后”出错,外层 retry 是从第 3 轮继续,还是重新来一遍。
  • attempt.ts 返回以后,run.ts 到底是在做兜底,还是在继续任务。

如果你只记一句话,先记这个:

run.ts 管的是“这次请求是否需要重新发起一个新 attempt”,attempt.ts 管的是“一个 attempt 内部如何让 agent 持续推进任务直到停下”。


先给总图

用户发送一条消息-> runEmbeddedPiAgent(...) in run.ts-> 解析 provider / model / auth profile / context window / context engine-> 进入 run.ts 外层 while(true)   -> 发起一次 runEmbeddedAttempt(...)      -> 打开 session / tools / memory / prompt / hooks / context      -> 调 activeSession.prompt(...)      -> 底层 agent 在这一次 prompt 里进入内部模型-工具-模型循环      -> attempt 收集 streaming 事件、tool result、compaction 事件      -> attempt 等待 compaction 收敛      -> attempt 返回结构化诊断结果   -> run.ts 判断:      -> 成功:直接收束并返回      -> 可恢复失败:修改状态后 continue,发起一个“新的 attempt”      -> 不可恢复失败:构造最终错误并返回

理解这张图以后,很多细节都会自动清楚。


第 1 层:一次用户请求不是一次模型调用,而是一次“run”

入口在 src/agents/pi-embedded-runner/run.ts 的 runEmbeddedPiAgent(…)

这里的基本运行单位不是“prompt”,而是“run”。

一个 run 的职责是:

  1. 为这次用户请求选择模型、provider、账号、上下文窗口。
  2. 建立这次请求允许使用的恢复策略。
  3. 在失败时判断能不能修复,然后决定是否再发起一个新 attempt。
  4. 最终把结果收束成 payload + meta 返回给上层。
一条消息= 一次 run= 1 到多次 attempt 的调度过程

第 2 层:run.ts 的外层 while (true) 是恢复循环,不是 agent 思考循环

最关键的代码结构在 src/agents/pi-embedded-runner/run.ts:792

while (true) {  ...  const attempt = await runEmbeddedAttempt(...)  ...}

2.1 这个 loop 的真实职责

这个 loop 不负责让模型一轮轮思考。

它负责的是:

  • retry limit 控制
  • auth profile 轮换
  • failover 到其他 provider/model
  • thinking level 降级
  • context overflow 后的 compaction / truncation
  • timeout / overload / billing / auth 错误恢复

换句话说,这层 loop 的语义不是:

第 1 轮推理 -> 第 2 轮推理 -> 第 3 轮推理

而是:

第 1 次完整执行尝试失败-> 调整运行条件-> 第 2 次完整执行尝试

2.2 这一层为什么必须存在

因为 agent 运行时并不总是“逻辑错了”,更多时候是“环境失败了”:

  • 账号 token 过期
  • 当前 provider 暂时超载
  • 当前模型不支持某个 thinking level
  • 本轮 transcript 过大
  • 某个 tool result 把上下文顶爆了

这些失败都不是 agent 内部“下一轮思考”能解决的,而必须由外层运行器介入。

所以:

run.ts 的 loop 是“运行环境恢复状态机”,不是“任务求解循环”。


第 3 层:attempt.ts 是单次执行容器,不是简单的 API 封装

run.ts 每次循环都会调用一次 runEmbeddedAttempt(…),调用点在 src/agents/pi-embedded-runner/run.ts:834

一个 attempt 不是“发一次 HTTP 请求”那么简单,它是一个完整的执行容器。

它在启动时要做这些事:

  1. 打开并修复 session。
  2. 创建 SessionManager
  3. 解析 workspace、sandbox、skills、bootstrap files。
  4. 构建 tools。
  5. 构建 system prompt。
  6. 清洗、校验、限制、修复历史 transcript。
  7. 让 contextEngine.assemble(…) 介入。
  8. 运行 hooks,改写 prompt / system prompt。
  9. 加载 prompt-local 图片。
  10. 订阅流式事件。
  11. 真正调用 activeSession.prompt(…)
  12. 等待 compaction 和后处理收束。
  13. 调用 afterTurn(…) / ingest(…)
  14. 返回结构化结果给 run.ts

因此你更应该把 attempt 理解成:

一次“从当前会话状态出发,让 agent 尽可能完整地执行任务”的容器。


第 4 层:agent 真正推进复杂任务的 loop,不在 run.ts 明面上,而在 activeSession.prompt(…) 背后

在 src/agents/pi-embedded-runner/run/attempt.ts:1775,真正触发 agent 执行的是:

await activeSession.prompt(effectivePrompt, ...)

这里很多人会误会,看源码时你可能会问:

“我在 attempt.ts 里没看到一个显式 while,那多轮llm调用到底在哪里?”

答案是:

真正的模型 -> 工具 -> tool result -> 模型 的内部执行循环,在底层 session / agent runtime 里,由 activeSession.prompt(…) 驱动。

所以:

  • attempt.ts 是容器和编排层
  • 底层 session runtime 才是内部 ReAct-like loop 的执行层

第 5 层:正常情况下,一个复杂任务几乎都“待在同一个 attempt 里”

例如一个代码修复任务:

用户提问-> 搜索文件-> 读代码-> 运行命令-> 修改文件-> 跑测试-> 根据测试结果再修-> 输出最终结论

如果整个过程没有发生需要外层恢复的错误,那么:

  • run.ts 只调用一次 runEmbeddedAttempt(…)
  • 这整个长链都会在这一 个 attempt 内完成
  • run.ts 直到 attempt 返回之前,基本不参与任务推进

所以可以非常明确地说:

run.ts 不是正常任务推进的主战场;正常推进主要发生在一次 attempt 背后的内部 agent loop 中。


第 6 层:如果 attempt 内部“第 3 轮”出错,外层不会从第 3 轮续跑

6.1 结论

如果一个任务在某次 attempt 内部已经走到了“第 3 轮工具调用之后”,然后异常冒泡到 run.ts,那么:

  • run.ts 不会恢复到“第 3 轮内部断点”
  • 它也不会把底层模型 loop 从中间接上
  • 它会根据 当前已经持久化/保留下来的最新 session 状态,重新发起一次新的 runEmbeddedAttempt(…)

也就是说:

attempt 级重试,而不是 internal round 级续跑。

6.2 为什么不是“从第 3 轮继续”

因为 OpenClaw 当前没有实现“agent 内部 loop 的断点恢复器”。

外层 run.ts 能掌握的是:

  • 本次 attempt 结束时的 messagesSnapshot
  • 最新 session 文件
  • 最后一次 assistant / tool / usage / error 信息
  • 当前 provider/model/profile/thinking 状态

它掌握不到的,是一个能安全恢复的“内部调用栈断点”。

因此外层能做的最稳妥动作只有:

  1. 修复上下文或运行条件。
  2. 从最新可用 transcript 出发。
  3. 发起一个新的 attempt。

本站文章均为手工撰写未经允许谢绝转载:夜雨聆风 » OpenClaw源码解析(七):Agent Runner 全执行链 | 上集

猜你喜欢

  • 暂无文章