智能体执行框架:run / attempt / 订阅 / 回退

智能体执行框架:run / attempt / 订阅 / 回退

这一章关注“发动机层”:一次消息/请求是如何变成一次可追踪的 run,并在失败时有明确的回退策略。

适用范围

  • 你要实现可复用的 agent runner:稳定、可观测、可降级
  • 你要能解释为什么要把执行拆成 run/attempt/subscribe/fallback

关键分层(建议按这个边界实现)

  • run:调度层,负责“尝试候选模型/提供商”的循环与最终结果聚合
  • attempt:执行层,负责“一次真实执行”(组 prompt、建 session、跑工具、产出输出)
  • subscribe:事件翻译层,把模型/工具的流式事件整理成可投递输出
  • fallback:回退层,负责“哪些错误可降级、按什么顺序重试、如何记录失败原因”

全局心智图(从一次消息到一次 run)

工程上更接近真实的链路是:

入站/请求 →(会话 lane 排队)→(全局 lane 限流)→ attempt → subscribe(流式事件聚合)→(可选:压缩重试)→ finalize →(失败时:认证轮换/会话恢复/模型回退)

如果你要先拿到“可观察”的最小闭环,建议先跑:

  • openclaw gateway
  • openclaw logs --follow
  • openclaw dashboard

并对照:Control UI / 日志

1) 调度层(run):只做决策,不做细节

在 run 层建议聚焦这些职责:

2) 执行层(attempt):一次真实执行事务(必须 finally 可清理)

attempt 更像“一次可回滚的事务”,关键在于固定顺序与可清理:

  • 进入 prompt 前做历史卫生(尤其是 tool_call / tool_result 配对)
  • 先挂订阅再 prompt(避免丢事件)
  • Agent 启动前 hook 可注入 prepend context(见 Hook 与插件治理
  • prompt 完成后等待压缩重试稳定(不要提前判定完成)
  • 最后必须在 finally 做 unsubscribe / 清理 active run / 释放会话资源

3) 事件层(subscribe):把底层事件变成稳定信号

订阅层建议至少要能把三类流分开管理:

  • assistant 文本增量(用于流式输出)
  • 工具执行事件与摘要(用于审计与 UI 展示)
  • 压缩生命周期(用于 waitForCompactionRetry 语义)

4) 运行注册层(runs):支持 steer/abort 与竞态防护

运行注册表的价值是“运行时控制平面”:

  • 能把 run 注册成 active handle(steer/abort)
  • 清理时做 handle 匹配,避免旧 finally 误删新 run

5) 回退层(fallback):输出结构化 attempts

回退不应是“换个模型再试试”,而应做到:

  • 可回退错误的归一化与分类
  • abort 语义保留(用户中断不进入 fallback)
  • 失败轨迹结构化(attempts 带 reason/status/code)

详见:模型回退与鲁棒性

相关概念入口:

最小可验证行为(看得到/查得出)

  • 一次执行至少要能在日志/事件里被 runId 精确定位(参见 日志)。
  • 流式输出与工具调用事件应当“分流”呈现,而不是混成一段文本(参见 Control UI)。

验收清单(建议你逐条对照)

  1. 同一 sessionId 任意时刻只存在一个 active run(清理必须 handle 匹配)。
  2. compaction 重试期间不会提前返回“完成”(具备可等待语义)。
  3. abort 后不会残留 active run / 订阅监听器。
  4. fallback 输出 attempts 轨迹,且 abort 不参与回退。
  5. 工具事件与文本事件都能在订阅层被稳定收敛。

读源码入口(可选)

  • src/auto-reply/reply/agent-runner.ts
  • src/agents/pi-embedded-runner/run.ts
  • src/agents/pi-embedded-runner/run/attempt.ts
  • src/agents/pi-embedded-subscribe.ts
  • src/agents/model-fallback.ts