乐于分享
好东西不私藏

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

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

书接上回

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

第 7 层:为什么“attempt 级重试”是合理的

7.1 优点

  1. 恢复边界清晰

外层只需要信任 session transcript 和 attempt 结果,不需要理解底层 agent 内部状态机。

  1. 容错简单

一旦 provider、auth、上下文窗口、tool result 格式变化,直接新起 attempt 更安全。

  1. 便于跨 provider failover

底层内部 loop 的中间态可能带有强 provider 依赖;attempt 级重放更容易切模型。

  1. 和 compaction / truncation 兼容

如果外层刚做了上下文压缩,再回到旧内部断点反而会失真。

7.2 代价

  1. 某些中间推理会重复发生。
  2. 某些工具调用可能重新规划。
  3. 如果 transcript 保留不够好,重试质量会下降。

这也正是之后的非常重要的篇章:上下文工程和记忆系统。

因为一旦系统采用“attempt 级重放”,那它对“最新上下文是否足够好”就极为敏感。


第 8 层:attempt 启动前,真正重要的不是 prompt,而是“准备可执行会话”

attempt.ts 前半段做了大量准备工作,这些不是附属动作,而是 agent 能否稳定运行的前提。

包括:

  • 创建 agent session
  • 注入 built-in tools / custom tools / client tools
  • 设置 streamFn 兼容层
  • 安装 tool result context guard
  • 构建 system prompt
  • 将 system prompt 写入 session

第 9 层:进入模型前,历史 transcript 会经过一条严格预处理链

在 src/agents/pi-embedded-runner/run/attempt.ts:1384 history 先经过:

sanitizeSessionHistory-> validateGeminiTurns / validateAnthropicTurns-> limitHistoryTurns-> sanitizeToolUseResultPairing-> contextEngine.assemble
  • 进入模型前历史消息会先被“清洗、校验、结构裁剪、配对修复”。
  • contextEngine 在默认实现里不是唯一主角,它插在这条流水线的后段。

这对理解“两层 run loop”很重要,因为外层重试时,重新发起的新 attempt 并不是拿原始脏历史重新跑,而是再次执行这条预处理链路。


第 10 层:system prompt 并不是一次写死,而是 attempt 内部逐步改写

在一个 attempt 里,system prompt 大致经历:

  1. 初始构建。
  2. applySystemPromptOverrideToSession(…) 写入 session。
  3. contextEngine.assemble(…) 追加 systemPromptAddition
  4. hooks 可能 override / prepend / append。

这意味着:

一个新的 attempt 虽然基于旧 transcript 继续,但它的系统控制面会重新构建,不是沿用上一轮 attempt。

所以外层重试不是“冻结现场再接着跑”,而是“用最新上下文重建再次执行”。


第 11 层:subscribeEmbeddedPiSession(…) 说明 attempt 不是只等模型返回结果

attempt.ts 在 src/agents/pi-embedded-runner/run/attempt.ts:1505 会创建订阅器:

const subscription = subscribeEmbeddedPiSession(...)

它跟踪的不只是文本流,还有:

  • assistant 文本块
  • tool meta
  • usage totals
  • compaction count
  • tool error
  • messaging tool 输出

这说明 attempt 的后半段实际上在做:

“把底层 runtime 的流式事件重构成一个可供外层判断的结构化状态聚焦。”

所以 attempt 返回的不是一个“字符串”,而是一个诊断对象。


第 12 层:为什么 activeSession.prompt(…) 返回后,attempt 还不能立刻结束

在 src/agents/pi-embedded-runner/run/attempt.ts:1807,attempt 还会:

await waitForCompactionRetry();

这意味着:

  • 模型主调用结束,不代表这轮上下文已经稳定。
  • 可能还有 auto-compaction 或 compaction retry 在进行。
  • 只有等 compaction 相关流程结束,当前 attempt 才算真正结束。

这对两层 loop 的边界非常关键:

attempt 内部不仅包含“模型-工具循环”,还包含“本轮执行后的上下文稳定化阶段”。


第 13 层:attempt 结束后,外层 run.ts 怎么决定是成功还是继续

run.ts 拿到 attempt 结果后,做的不是简单 if error then retry

它会区分很多分支:

  • 上下文溢出
  • compaction failure
  • oversized tool result
  • prompt submission error
  • role ordering error
  • image size error
  • auth failure
  • rate limit / billing / overload
  • thinking level 不兼容
  • timeout

其中最重要的几类恢复分支都在 src/agents/pi-embedded-runner/run.ts

  • context overflow 后 continuerun.ts:994run.ts:1048run.ts:1091
  • auth/profile/thinking/failover 后 continuerun.ts:1147run.ts:1222run.ts:1233run.ts:1260run.ts:1281run.ts:1330

这些 continue 的意义不是“继续 attempt 内的某一轮”,而是:

回到外层 run loop 顶部,重新发起一个新的 attempt。


第 14 层:把整个运行过程压成一条最终流程图

用户发消息-> run.ts 解析 provider/model/profile/context window/context engine-> run.ts 外层恢复 loop 开始-> attempt.ts 创建 session + tools + prompt + context-> activeSession.prompt(...) 进入底层 agent 内部循环   -> 模型判断   -> 调工具   -> tool result 写回 transcript   -> 模型继续判断   -> 重复直到停止-> attempt.ts 等待 compaction / streaming / afterTurn 收束-> attempt.ts 返回结构化结果-> run.ts 判断   -> 成功:返回最终 payload   -> 可恢复失败:更新条件并重新发起一个新 attempt   -> 不可恢复失败:返回最终错误

这一篇的最终结论

  1. OpenClaw 至少有两层 loop,但它们语义完全不同。

run.ts 是恢复 loop,activeSession.prompt(…) 背后才是 agent 真正的任务推进 loop。

  1. 正常复杂任务通常只需要一次 attempt。

只要没有逃逸到外层恢复条件,任务的大部分执行都待在 attempt.ts 这一个容器里。

  1. 外层 retry 不是从内部 round 断点继续。

它是基于最新稳定 transcript 发起一个全新的 attempt。

  1. 因为重试是 attempt 级,所以上下文工程和记忆系统变得极其重要。

它们决定“新的 attempt 能不能快速接上之前的任务状态”。

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

猜你喜欢

  • 暂无文章