OpenClaw 的 Agent Runner 是驱动一次智能体运行的核心引擎,它把一条用户消息转化为操作与回复,经历参数校验、会话解析、上下文组装、排队调度、模型推理、工具执行、流式传输、持久化共五个阶段。整个流程按会话串行执行,通过会话队列与全局队列防止竞态,借助插件 Hook 实现各阶段可插拔,并在超时、溢出时触发降级重试。
一、Agent Runner 在架构中的定位
Agent Runner(又称 Agent Runtime)是 OpenClaw 架构中真正”干活”的组件。Gateway 负责”接消息、做路由、控权限”,Runner 负责”把用户指令变成实际操作,再把结果送回来”。
| 组件 | 职责 | 类比 |
|---|---|---|
| Gateway | 接收消息、路由、权限控制 | 前台接待 |
| Agent Runner | 驱动模型循环、执行工具、返回回复 | 生产车间 |
| Provider | 封装具体 LLM 调用 | 原材料供应商 |
| Channel | 管理消息通道与队列模式 | 传送带 |
Runner 与 Provider 的边界容易混淆:Provider 只管”把 prompt 发给模型、把回复拿回来”,Runner 在此之上编排会话、工具、流式传输和持久化,是一个完整的运行时。
二、一次 Agent 运行的完整阶段
从用户发送消息到最终输出回复,Agent Runner 大致经历以下五个阶段。
2.1 阶段一:参数校验与会话解析
入口点是 Gateway RPC 的 agent 和 agent.wait,命令行则通过 openclaw agent 触发。agent RPC 做三件事:
- 验证请求参数合法性(消息体、会话标识等);
- 解析会话标识(
sessionKey/sessionId),持久化会话元数据; - 立即返回
{ runId, acceptedAt },不等执行完成。
这一步是”先受理、后处理”的异步模式,调用方拿到 runId 后可通过 agent.wait 等待结果。
2.2 阶段二:上下文组装
agentCommand 接手本轮执行,进入准备工作:
- 解析模型及思考等级(thinking level)、详细模式、追踪模式等默认值;
- 加载 Skills 快照(或复用已有快照),将 Skills 提示词注入环境;
- 解析工作区引导文件(如
SOUL.md、USER.md),注入系统提示词; - 解析模型定义和上下文窗口大小,按优先级选择可用的认证配置文件(API Key)。
2.3 阶段三:执行循环(Agent Loop)
这是核心环节,由 runEmbeddedAgent 驱动。它会创建或恢复 OpenClaw 会话,注册可用工具集,设置流式引擎,然后进入 ReAct 循环:LLM 调用 → 工具执行 → 结果回传 → 再次调用 LLM,直到模型认为任务完成。
# Agent Loop 核心伪代码
def run_embedded_agent(session, messages, tools):
while True:
# 1. 调用 LLM
response = model.chat(messages, tools=tools)
if response.has_tool_calls:
for call in response.tool_calls:
# 2. 执行工具
result = execute_tool(call.name, call.args)
# 3. 结果回传为新的 observation
messages.append(tool_result(call.id, result))
# 4. 继续循环,让模型基于新信息思考
continue
else:
# 模型输出最终回复,循环结束
return response.text
排队与并发控制在这一阶段尤为关键。运行按会话键(会话通道)串行执行,可选再经过一个全局通道,防止工具和会话竞态。会话文件上还有一把可感知进程的写入锁,保护对话记录写入,默认最多等待 60 秒。
2.4 阶段四:流式回复与事件投递
subscribeEmbeddedAgentSession 将运行时事件桥接到 agent 流,分三条通道投递:
| 事件流 | 内容 | 投递方向 |
|---|---|---|
lifecycle |
生命周期事件(start / end / error) | stream: "lifecycle" |
assistant |
助手回复的流式增量 | stream: "assistant" |
tool |
工具开始 / 更新 / 结束事件 | stream: "tool" |
助手增量会缓冲到聊天 delta 消息中,发生生命周期结束或错误时发出聊天 final。如果本轮没有任何可渲染的有效载荷且工具出错,系统会发出后备工具错误回复。
2.5 阶段五:持久化与生命周期结束
agent.wait(waitForAgentRun)在 runId 上等待生命周期结束或错误事件,返回 { status: ok|error|timeout, startedAt, endedAt, error? }。对话记录在执行过程中已写入 SQLite 会话存储,完整历史保留在磁盘上。
三、插件 Hook 与可插拔架构
OpenClaw 有两套 Hook 系统。内部钩子(Gateway 钩子)处理命令和生命周期事件,插件钩子则在 Agent 循环管线中提供扩展点。
| 插件 Hook | 运行时机 | 典型用途 |
|---|---|---|
before_model_resolve |
会话前(无 messages) | 确定性覆盖提供商 / 模型 |
before_prompt_build |
加载会话后(含 messages) | 注入上下文、系统提示词 |
before_agent_reply |
调用 LLM 前 | 接管本轮或静默 |
before_tool_call / after_tool_call |
工具调用前后 | 拦截 / 转换参数与结果 |
before_compaction / after_compaction |
压缩周期前后 | 观察或注解压缩 |
before_model_resolve 允许插件在模型解析前动态覆盖 provider 和 model,实现按场景切换模型。before_tool_call 的 { block: true } 是终止决策,会阻止优先级较低的处理程序。
四、超时机制与容错降级
4.1 多层超时体系
| 超时类型 | 默认值 | 说明 |
|---|---|---|
agent.wait |
30s | 仅等待,不停止底层运行 |
| Agent 运行时 | 172800s(48h) | 由 runEmbeddedAgent 中止计时器强制 |
| 模型空闲超时 | 云端 120s / 自托管 300s | 无响应分块时中止模型请求 |
| CLI 无输出看门狗 | 动态计算 | 由后端插件负责 |
设置 agents.defaults.timeoutSeconds: 0 可获得无限运行时限,但模型流活跃性看门狗仍然生效。
4.2 容错:Auth 轮转与模型级 Fallback
Agent Runner 的容错分两层。第一层是认证配置文件轮转:如果一次尝试因认证失败、限流或服务过载而中断,Runner 自动切换到同一 Provider 的下一个 API Key 重试。第二层是模型级 Fallback:所有 Key 轮完仍失败时,Runner 向外层抛出 FailoverError,外层的 model-fallback 层切换到配置的备用模型。
4.3 溢出降级链路
当上下文超限时,Runner 按顺序尝试降级:
- 先执行 Compaction 压缩历史对话;
- 再截断超大的 tool result;
- 都不够用时报错,引导用户开新会话。
五、实际运行中的关键设计考量
会话写入锁是容易被忽视但至关重要的机制。它可感知进程且基于文件,能捕获绕过进程内队列或来自其他进程的写入方。如果辅助函数在维持单一逻辑写入方的同时有意嵌套获取同一把锁,必须通过 allowReentrant: true 明确启用。
卡住的会话诊断也有分层设计。内置两分钟阈值对长时间没有进度的 processing 会话分类:活跃的嵌入式运行报告为 session.long_running,没有近期进度的报告为 session.stalled,可恢复的陈旧记录标记为 session.stuck。中止阈值至少 5 分钟,为警告阈值的 3 倍,避免过早切断仅仅运行缓慢的任务。
常见问题(FAQ)
Q1:Agent Runner 和 Gateway 是什么关系?
Gateway 接消息做路由,Runner 负责驱动模型循环和工具执行。
Q2:同一会话的两条消息能并行处理吗?
不能。按会话键串行执行,保证上下文一致性,防止竞态。
Q3:Agent 运行超时了怎么办?
agent.wait 超时仅停止等待,底层运行继续;运行时超时则中止执行。