智能体执行框架:run / attempt / 订阅 / 回退
这一章关注“发动机层”:一次消息/请求是如何变成一次可追踪的 run,并在失败时有明确的回退策略。
适用范围
- 你要实现可复用的 agent runner:稳定、可观测、可降级
- 你要能解释为什么要把执行拆成 run/attempt/subscribe/fallback
关键分层(建议按这个边界实现)
run:调度层,负责“尝试候选模型/提供商”的循环与最终结果聚合attempt:执行层,负责“一次真实执行”(组 prompt、建 session、跑工具、产出输出)subscribe:事件翻译层,把模型/工具的流式事件整理成可投递输出fallback:回退层,负责“哪些错误可降级、按什么顺序重试、如何记录失败原因”
全局心智图(从一次消息到一次 run)
工程上更接近真实的链路是:
入站/请求 →(会话 lane 排队)→(全局 lane 限流)→ attempt → subscribe(流式事件聚合)→(可选:压缩重试)→ finalize →(失败时:认证轮换/会话恢复/模型回退)
如果你要先拿到“可观察”的最小闭环,建议先跑:
openclaw gatewayopenclaw logs --followopenclaw dashboard
并对照:Control UI / 日志。
1) 调度层(run):只做决策,不做细节
在 run 层建议聚焦这些职责:
- 双层排队:session lane 保序 + global lane 限流(见 会话与并发)。
- 窗口预检:窗口不足直接阻断(见 上下文工程)。
- 韧性编排:认证轮换、上下文恢复、模型回退的联动顺序(见 模型回退与鲁棒性)。
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)。
验收清单(建议你逐条对照)
- 同一
sessionId任意时刻只存在一个 active run(清理必须 handle 匹配)。 - compaction 重试期间不会提前返回“完成”(具备可等待语义)。
- abort 后不会残留 active run / 订阅监听器。
- fallback 输出 attempts 轨迹,且 abort 不参与回退。
- 工具事件与文本事件都能在订阅层被稳定收敛。
读源码入口(可选)
src/auto-reply/reply/agent-runner.tssrc/agents/pi-embedded-runner/run.tssrc/agents/pi-embedded-runner/run/attempt.tssrc/agents/pi-embedded-subscribe.tssrc/agents/model-fallback.ts