openclaw源码解读(9)—— Agent执行链路2:agent-run-handler.ts 核心 Pipeline 每个 Agent 的执行生命周期

agent-run-handler.ts(531 行)— Agent 执行的 9 阶段总控流水线
agent-run-handler.ts 是所有 Agent 请求的统一入口。不管消息从哪个 channel 进来(网页、企微、Discord),最终都会流到这里。它的 9 个阶段像一条装配线。
逐阶段详解
① Preflight(预检)— agent-request-preflight.ts L35-62
prepareAgentRequestPreflight({ params, respond, context, client })
if (!preflight) return; // 请求不合法直接拦截。校验请求参数是否合法(runId、session、权限等),解析 20+ 个关键变量。
对 ClawForge (注:ClawForge是我基于AgentTeam+OpenClaw+阿里云token plan做的一个面向软件研发全流程的多Agent协同代码审查与自愈系统)的意义:这里是参数入口。如果 Task Decomposer 需要从请求里提取额外的元信息(比如 taskType: "pr" | "issue"),就在 Preflight 返回的变量(L39-62)里加字段。
做的事:
- 校验 params 的合法性(必填字段、格式)preflight.ts的L62-72
- 提取 request(用户消息体)、cfg(配置快照)preflight.ts的L147,校验request的合法性。preflight.ts的L73
- 生成 runId(每次执行的唯一 ID)preflight.ts的L148
- 判断是否允许模型覆盖(allowModelOverride),判断与request对象中的是否可覆盖状态是否一致 preflight.ts的L90
- 判断是否是 cron 延续运行(preflight.ts的L92)、是否是重启恢复、session返回的结果是否符合预期,如不符合抛出异常(preflight.ts的L93-105)
- 提取 dedupe keys(幂等键)(preflight.ts的L164-167)
- 提示词可持久化、获取缓存对象(preflight.ts的L168-200)
返回值是关键变量的集合,这些变量贯穿后续所有阶段。
② Dedupe(去重/幂等)— agent-dedupe-lifecycle.ts
createAgentDedupeLifecycle({ cfg, request, runId, lifecycleGeneration, agentDedupeKeys, ... })
相同 runId 的请求只执行一次。如果 runId 已经处理过,直接返回之前的结果。防止同一请求被重复执行。返回三个操作:
- reserve:预留一个 dedupe 槽位(占位)
- clearUnaccepted:清除未被接受的 dedupe 占用
- abortForLifecycleRotation:生命周期轮转时终止旧 run
对 ClawForge 的意义: 直接影响任务分发。在多 Agent 场景下,同一个 PR/Issue 被分解成 4 个子任务发给 4 个 Worker。每个子任务需要自己的 runId,但父任务需要一个全局唯一 ID。需要在这里考虑幂等链——父任务的幂等和子任务的幂等是什么关系?
③ Routing(路由)— agent-request-routing.ts
prepareAgentRequestRouting({ request, cfg, expectedSession, ... })
if (!routing) return;
决定消息发给哪个 Agent、哪个 Session。解析 to、agentId、sessionKey 等路由信息。
- 解析通过rpc协议提交到对话窗口的附件文件
- 从参数配置中获取所有已知的agent
- 解析 agentId(默认 agent 还是指定 agent),校验agentId是否存在agent列表中
- 解析 requestedSessionKey(会话键), sessionId, to(路由到哪个agent,收件人),校验sessionkey的格式
- 设置明确的接收人会话explicitRecipientSession,如果没有明确的接收人,报异常
- 校验sessionKey是否合法,如不合法报异常
- 解析附件、收件人、是否为 raw model run
- 以上校验都做完了,session key合法,respondUnavailableAgentSessionForKey返回true,则把重复的请求删掉
- 预留 dedupe 槽位(调用 reserveDedupe)
- 返回routing对象
这是多 Agent 拆分的关键切入点——如果需要根据消息内容路由到不同 Agent,就在这里做。
对 ClawForge 的意义:这是要动刀的地方之一。当前 Routing 是「消息 → 单个 Agent」。ClawForge 要做的是「消息 → Task Decomposer Agent」。需要在这里插入逻辑:
当前:Routing → 匹配单个 agentId
ClawForge:Routing → 匹配 Manager Agent(行为是"分析并分解任务")
④ Content(内容组装处理)— agent-content-phase.ts
prepareAgentContentPhase({ request, cfg, isRawModelRun, normalizedAttachments, ... })
if (!content) return;
解析消息内容——文本、附件、图片、引用等。把原始消息转成 Agent 能消费的格式。
构建发给 LLM 的消息体:
- 处理图片、及图片附件顺序
- 确定 replyTo(回复引用)
- 确定接收频道/账户/线程
- 如果 agentId 为空,自动选择一个
- 返回 message(最终的文本消息体)
对 ClawForge 的意义:和 Routing 紧密配合。Task Decomposer 拿到的输入就是从这里来的。如果需要在分解任务时附带源码上下文、PR diff、文件列表,这里就是注入额外上下文的地方。
⑤ Reset(重置处理)— agent-reset-phase.ts
runAgentResetPhase({ request, cfg, requestedSessionKey, message, agentId, ... })
if (resetPhase.stop) return;
处理 /reset、/clear 等重置命令。清空会话历史,重新开始。
处理 /reset 等特殊指令:
- 如果消息是 /reset,清除会话上下文
- resetPhase.accepted 标记是否接受了重置
- resetPhase.stop 标记是否应该停止后续流程
⑥ Session(会话准备)— agent-session-prepare.ts + agent-session-persist.ts
这是最长的阶段(约 170 行),大约占了文件 1/3 的代码,分两步:
prepareAgentSession() persistAgentSessionPhase()
│ │
├─ 查找已有 session ├─ 写入 session 到存储
├─ 或创建新 session ├─ 合并 session patch
├─ 确定 canonicalKey ├─ 处理 spawnedBy(子 Agent 来源)
├─ 确定 sessionAgentId ├─ 设置 cron continuation claim
├─ 读取 storePath └─ 设置 main restart recovery lease
└─ 构建 sessionPatch
关键变量:
- canonicalSessionKey:规范化的 session 键
- sessionAgentId:该 session 绑定的 agent
- isNewSession:是否为新建 session
- spawnedBy:谁 spawn 了这个 session(子 Agent 关系链)
- groupId / groupChannel / groupSpace:多 Agent 分组信息
const preparedSession = prepareAgentSession({ ... });
// ... 大量的 session 元数据处理
const persistedSession = await persistAgentSessionPhase({ ... });
- Prepare:加载或创建 Session(会话历史、配置、状态)
- Persist:持久化 Session 到磁盘(包括 session 的元数据、维护配置)
对 ClawForge 的意义:多 Agent 会话隔离的关键。每个 Worker 需要独立 Session,Manager 需要一个编排 Session。当前代码通过 normalizedSpawned.groupId、resolvedGroupId、resolvedGroupChannel、resolvedGroupSpace 这些变量已经预留了多 Agent 的会话基础设施。
groupId → 同一次运行的 4 个 Worker 共享同一个 groupId
groupSpace → 共享文件空间(clawforge的MinIO 路径就是拼这个)
这些字段已经存在于代码里,是要用起来的。
⑦ Admission(准入/并发控制)— agent-admission-controller.ts
createAgentAdmissionController({ cfg, runId, lifecycleGeneration, ... })
await acquireGatewayWorkAdmission(storePath ?? `agent:${sessionAgentId}`);
同一 Session 同时只能有一个 run 在执行。这是一个排他锁——后面的请求要排队等前面的完成。
- acquireGatewayWorkAdmission(storePath):申请入场许可
- assertGatewayWorkAdmissionAllowed:断言许可有效
- respondToGatewayAdmissionOutcome:如果有排队等待,通知客户端
设计意图类似"锁",防止两个并发请求同时修改同一个 session 的状态。
对 ClawForge 的意义:这是 ClawForge 调度器的战场。当前 Admission 是「按 Session 串行」,但 ClawForge 需要:
当前:1 Session = 1 Lock = 串行执行
ClawForge:1 Manager Session + N Worker Sessions = Manager 排队 + Workers 并行
因为每个 Worker 是独立 Session,Admission 锁天然隔离——不需要改 Admission 逻辑本身,只需要保证 Manager 把任务分发给不同 Session 的 Worker 就行。这就是为什么要在 Dispatch 阶段(下一步)动手,而不是在这里。
⑧ Delivery(投递设置)— agent-delivery-phase.ts
resolveAgentDeliveryPhase({ request, cfg, sessionEntry, resolvedSessionKey, ... })
if (!delivery) return;
决定最终结果投递到哪个 channel、哪个目标,确定回复发到哪里:
- 解析 channel、account、thread
- 确定 bestEffortDeliver 策略
- 返回 activeSessionAgentId
对 ClawForge 的意义:在 ClawForge 里,每个 Worker 的输出不是直接投递给用户的,而是投递回 Manager 做聚合。Delivery 阶段需要支持「投递到 Matrix Room」而不是直接投递给用户 channel。这可能需要微调,但初期可以用 Matrix Room 的消息机制替代。
⑨ Dispatch/Execute(分发+执行)— agent-run-admission-phase.ts + agent-run-execution-phase.ts
const preparedDispatch = await prepareAgentRunDispatch({...});
if (!preparedDispatch) return;
startAgentRunExecution({...}); // 注意:不 await!
// 注意:执行阶段是 fire-and-forget,不阻塞主流程
gatewayAdmissionTransferred = true; // �� Admission 锁在这转移
startAgentRunExecution({ // �� 进入 run-loop
prepared: preparedDispatch,
...
});
- Dispatch:最后一道准入检查 + 组装执行参数(ingressOpts)
- Execute:调用 commands/agent.ts → PI Embedded Runner → 进入 run-loop(LLM↔Tool 闭循环)
分两步:
- prepareAgentRunDispatch:做最后的准备(模型选择、系统提示词装配等)
- startAgentRunExecution:异步启动,不阻塞主流程
第 9 阶段是 fire-and-forget——主 handler 立即返回,Agent 的实际执行(LLM 调用、工具循环)在后台异步进行。执行结果通过 WebSocket 推送给客户端。
9 个阶段,每个阶段都有机会短路返回。现在看最后一步 startAgentRunExecution 连接到哪里——这是真正跑 LLM 调用的地方
对 ClawForge 的意义:这是核心插入点。
当前逻辑:
Dispatch → 组装 ingressOpts → Execute → run-loop → 回复
ClawForge 要变成:
Dispatch → Task Decomposer(新)→
├─ Agent A Execute → run-loop → MinIO
├─ Agent B Execute → run-loop → MinIO
├─ Agent C Execute → run-loop → MinIO
└─ Agent D Execute → run-loop → MinIO
→ Aggregator → 回复
具体改动思路:
- 在 prepareAgentRunDispatch 返回后,不直接 startAgentRunExecution
- 先让 Manager Agent 跑一次 run-loop,产出任务分解结果
- 对每个子任务调用 sessions_spawn 创建 Worker,Worker 各自跑完整 agent-run-handler(因为 Worker 也是标准 Agent)
- Aggregator 等待所有 Worker 完成,汇总结果
finally 块:清理与恢复
releaseGatewayAdmission() // 释放 session 锁
releaseCronContinuationClaimWithRecovery() // 释放 cron 占用
clearUnacceptedAgentDedupe() // 清理未完成的 dedupe
scheduleMainSessionRecoveryPendingTarget() // 如有需要,调度恢复
注意 gatewayAdmissionTransferred 这个开关:如果执行阶段(⑨)成功启动,admission 的控制权就转移给了执行阶段,这里不再释放——由执行阶段完成后释放。
ClawForge 切入点分析:
|
阶段 |
用途 |
怎么做 |
|
③ Routing |
主任务拆分 |
识别消息需要多 Agent 协作 → 修改 routing 逻辑,生成子任务 |
|
⑥ Session |
子 Agent spawn |
spawnedBy、groupId 字段建立父子关系链 |
|
⑨ Execution |
子 Agent 调度 |
在这里 sessions_spawn 出子 Agent,或修改 dispatch 逻辑 |
这三个阶段是核心三明治:Routing 拆任务 → Session 建关系 → Execution 调子 Agent。
|
位置 |
做什么 |
ClawForge 改什么 |
|
③ Routing |
消息→Agent |
路由到 Manager Agent(Task Decomposer) |
|
⑦ Admission |
串行锁 |
不改——Worker 各自独立 Session,天然并行 |
|
⑨ Dispatch→Execute |
单 Agent 执行 |
插入任务分解 + 多 Worker 分发 + 结果聚合 |
更多推荐



所有评论(0)