openclaw源码解读(10)——agent-run-execution-phase.ts— 从“准备“到“发射“
agent-run-execution-phase.ts Agent 循环调用 LLM、解析 Tool Call、执行工具、再把结果喂回 LLM 的整个闭循环。这个文件只有一个导出函数:startAgentRunExecution。它是 agent-run-handler 第⑨阶段的实现——不执行 LLM,而是做最后的上下文组装 + 发射。
整体结构
startAgentRunExecution(params) // 入口
│
├─ 1. 获取 admission 锁 // retainGatewayRootWorkAdmissionContinuation 防止 gateway 在执行期间关闭 │
│
├─ 2. prepared.activeGatewayWorkAdmission.run(async () => {
│ │ // 真正的 session 级准入锁
│ ├─ 3. 中止检查 // 已被 abort?直接返回 timeout
│ ├─ 4. 子 Agent session 恢复 // reactivateCompletedSubagentSession
│ ├─ 5. 发射 session 变更事件 // emitSessionsChanged (create/send)
│ ├─ 6. 标注消息来源 // annotateInterSessionPromptText
│ ├─ 7. 创建转录记录器 // userTurnTranscriptRecorder (条件创建)
│ ├─ 8. 处理 exec 审批延续 // consumeExecApprovalFollowupRuntimeHandoff
│ ├─ 9. 解析插件工具授权 // runtimePluginToolGrant
│ ├─ 10. 组装 runContext // channel/account/sender/thread
│ ├─ 11. 组装 ingressOpts // 所有 LLM 调用需要的参数
│ └─ 12. dispatchAgentRunFromGateway() // 发射!
│
└─ 13. catch/finally 清理 // 失败时恢复 + 释放所有锁
逐层解析
层 1-2:双重准入锁
let releaseGatewayRootContinuation = retainGatewayRootWorkAdmissionContinuation();
// ↑ 第一层:gateway 级别的锁,防止进程在 run 执行中关闭
void prepared.activeGatewayWorkAdmission.run(async () => {
// ↑ 第二层:session 级别的锁,同一 session 同时只能有一个 run
retainGatewayRootWorkAdmissionContinuation() 告诉 gateway "还有工作在跑,别关"。activeGatewayWorkAdmission.run() 确保同一 session 不会有两个并发 run。
层 3:中止检查 L118-142
if (prepared.activeRunAbort.controller.signal.aborted) {
// 已被 abort(比如用户取消了),直接返回 timeout 状态
setAbortedAgentDedupeEntries({...});
params.respond(true, { runId, status: "timeout", summary: "aborted", ... });
return;
}
在真正执行之前,检查是否已经收到取消信号。
层 4:子 Agent session 恢复 L144-150
if (!params.isOneShotModelRun && params.resolvedSessionKey) {
await reactivateCompletedSubagentSession({
sessionKey: params.resolvedSessionKey,
runId: params.runId,
task: params.message,
});
}
这是多 Agent 场景的关键! 如果当前 session 是一个已经完成的子 Agent session(比如上一个任务完成了,现在又来新消息),重新激活它。不是创建新 session,而是复用旧 session,保持上下文连续性。
层 5:Session 变更事件广播 L151-173
// 新建 session → emit "create"
if (isNewSession) emitSessionsChanged(..., "create");
// 每次 run → emit "send"
emitSessionsChanged(..., "send");
通知所有 WebSocket 客户端 session 列表有变化。
层 6-7:消息标注 + 转录记录 L175-236
// 在消息中标注跨 session 的来源信息,inputProvenance 记录了消息的来源链路(比如"来自子 Agent X 的结果")
message = annotateInterSessionPromptText(message, params.inputProvenance);
// 条件创建转录记录器(记录用户消息到 session transcript),userTurnTranscriptRecorder 把用户消息写入 session 持久化存储。
const userTurnTranscriptRecorder = params.resolvedSessionKey && ...
? createUserTurnTranscriptRecorder({...})
: undefined;
层 8:Exec 审批延续 L246-268
// 如果这个 run 是 exec approval(用户批准了一个高风险命令)的延续,
// 消费 handoff 状态,传递 bashElevated 权限
execApprovalFollowupRuntimeHandoff = consumeExecApprovalFollowupRuntimeHandoff({...});
层9:插件工具授权 L271-276
// 如果 run 来自插件 spawned 的子 agent,传递插件额外授权的工具
runtimePluginToolGrant = params.client?.internal?.agentRunTracking === "plugin_subagent"
? params.client.internal.runtimePluginToolGrant
: undefined;
层 10-11:组装 runContext + ingressOpts L285-298
这是最核心的数据组装:
const runContext = {
messageChannel, // 消息来源频道
accountId, // 发送者账户
senderId, // 发送者 ID
groupId, // 分组 ID(多 Agent 编组)
groupChannel, // 分组频道
groupSpace, // 分组空间
currentChannelId, // 当前频道
currentThreadTs, // 线程时间戳
};
// ingressOpts 包含了所有 LLM 调用需要的参数 L306-404
const ingressOpts = {
message, images, imageOrder, // 消息内容
agentId, // 用哪个 Agent
provider, model, // 用哪个模型
sessionId, sessionKey, // session 信息
thinking, // thinking 级别
deliver, deliveryTargetMode, // 投递方式
channel, threadId, // 频道/线程
spawnedBy, // 父 Agent 标识
groupId, groupChannel, groupSpace, // 多 Agent 编组
toolsAllow, // 允许的工具列表
runtimePluginToolGrant, // 插件工具授权
workspaceDir, cwd, // 工作目录
userTurnTranscriptRecorder, // 转录器
abortSignal, // 取消信号
lifecycleGeneration, // 生命周期版本
// ... 还有更多
};
注意三个多 Agent 关键字段:
- spawnedBy:谁 spawn 了这个 Agent(父子关系)
- groupId / groupChannel / groupSpace:Agent 编组信息
- skipInitialSessionTouch:子 Agent 可能不需要触发 initial touch
层 12:发射 L305-420
dispatchAgentRunFromGateway({
ingressOpts, // 所有上下文
runId, // 本次 run 的 ID
dedupeKeys, // 幂等键
abortController, // 取消控制器
cleanupAbortController, // 清理函数
onSettled: ..., // cron 延续完成回调
respond, // 响应函数
context, // gateway 上下文
taskTrackingMode, // 任务追踪模式
restoreAdmittedRecovery, // 重启恢复
});
dispatchAgentRunFromGateway(来自 agent-run-dispatch.ts)才是真正启动 Agent 循环的地方。这个函数创建 Agent 进程/线程、开始 LLM↔Tool 循环。
层 13:失败清理
catch (err) {
// 设置错误 dedupe 项
// 返回 error 响应给客户端
} finally {
if (!dispatched) {
// 恢复重启恢复状态
// 释放 cron continuation
// 释放 main restart recovery owner
// 清理 admitted run
}
}
如果发射失败,完整回滚所有状态,防止锁泄漏。
切入点细化
startAgentRunExecution()
│
┌───────────────────┼───────────────────┐
│ │ │
spawnedBy groupId/group* workspaceDir/cwd
(父子关系) (Agent编组) (工作目录隔离)
│ │ │
▼ ▼ ▼
dispatchAgentRunFromGateway() ──→ agent-run-loop ──→ LLM + Tools
在这里: 在这里:
• 如果主 Agent 决定拆分任务 • 子 Agent 执行自己的闭循环
• 调用 sessions_spawn • 完成后返回结果给主 Agent
• 传入 spawnedBy=主Agent ID • session 持久化结果
关键发现:spawnedBy、groupId、groupSpace 这些多 Agent 基础设施已经存在于代码中,比赛要做的是:
- 在 agent-run-handler 的 routing 阶段识别需要拆分
- 在 agent-run-execution-phase 的 ingressOpts 中正确设置 spawnedBy 和 groupId
- 让子 Agent 的执行结果能回流到主 Agent 的上下文
更多推荐


所有评论(0)