Run 生命周期与 Phases
本页描述 run 和 tool call 的状态机、9 个 phase、终止条件、checkpoint 触发点,以及挂起 / 恢复如何桥接 run 层与 tool-call 层。
RunStatus
Section titled “RunStatus”Created --+--> Running --+--> Waiting --+--> Running (resume) | | | +--> Done +--> Done +--> Donepub enum RunStatus { Created, // 已持久化但还未执行 Running, // 正在执行 Waiting, // 暂停,等待外部决策 Done, // 终态 —— 不可再迁移}Created -> Running:runtime 接管该 run 并开始执行 phase。Created -> Done:run 在任何 phase 执行前被取消或拒绝。Running -> Waiting:工具调用挂起,run 等待外部输入。Waiting -> Running:决策到达,run 恢复。Running -> Done或Waiting -> Done:完成、取消或出错的终态迁移。Done -> *:不允许。终态。
ToolCallStatus
Section titled “ToolCallStatus”New --> Running --+--> Succeeded +--> Failed +--> Cancelled +--> Suspended --> Resuming --+--> Running +--> Suspended +--> Succeeded/Failed/Cancelledpub enum ToolCallStatus { New, Running, Suspended, Resuming, Succeeded, Failed, Cancelled,}Phase Enum
Section titled “Phase Enum”Awaken 的执行顺序由 9 个 phase 固定下来:
pub enum Phase { RunStart, StepStart, BeforeInference, AfterInference, ToolGate, BeforeToolExecute, AfterToolExecute, StepEnd, RunEnd,}RunStart:run 级初始化StepStart:每轮推理开始BeforeInference:最后修改推理请求AfterInference:观察 LLM 返回,修改工具列表或请求终止ToolGate:纯判定阶段,用于 allow / block / suspend / set-result,可在前序 tool 提交状态后重判BeforeToolExecute:只对真正要执行的 tool 运行一次,用于执行前副作用AfterToolExecute:消费工具结果并触发副作用StepEnd:checkpoint 和 stop policyRunEnd:清理与最终持久化
TerminationReason
Section titled “TerminationReason”pub enum TerminationReason { NaturalEnd, BehaviorRequested, Stopped(StoppedReason), Cancelled, Blocked(String), Suspended, Error(String),}只有 Suspended 会映射到 RunStatus::Waiting;其他都映射为 Done。
Stop Conditions
Section titled “Stop Conditions”可通过配置声明 stop 条件,例如:
MaxRoundsTimeoutTokenBudgetConsecutiveErrorsStopOnToolContentMatchLoopDetection
这些条件在 StepEnd 评估。
Checkpoint Triggers
Section titled “Checkpoint Triggers”StepEnd 会把以下内容写入 checkpoint:
- thread messages
- run 生命周期状态
- 持久化状态键
- 挂起的 tool call 状态
从 ToolCall 状态推导 RunStatus
Section titled “从 ToolCall 状态推导 RunStatus”run 的状态本质上是所有 tool call 状态的聚合投影:
fn derive_run_status(calls: &HashMap<String, ToolCallState>) -> RunStatus { let mut has_suspended = false; for state in calls.values() { match state.status { ToolCallStatus::Running | ToolCallStatus::Resuming => { return RunStatus::Running; } ToolCallStatus::Suspended => { has_suspended = true; } _ => {} } } if has_suspended { RunStatus::Waiting } else { RunStatus::Done }}Tool call 状态时间线
Section titled “Tool call 状态时间线”当 LLM 一次返回多个 tool call(例如 [tool_A, tool_B, tool_C])时,每个 call 都有自己的状态。挂起调用可以等待外部 decision,允许执行的调用则继续通过当前配置的 executor 运行。
Time tool_A(需审批) tool_B(需审批) tool_C(正常) → Run Status────────────────────────────────────────────────────────────────t0 Created Created Created Runningt1 Suspended Created Running Runningt2 Suspended Suspended Running Runningt3 Suspended Suspended Succeeded Waitingt4 Resuming Suspended Succeeded Runningt5 Succeeded Suspended Succeeded Waitingt6 Succeeded Resuming Succeeded Runningt7 Succeeded Succeeded Succeeded Done挂起如何桥接 run 层与 tool-call 层
Section titled “挂起如何桥接 run 层与 tool-call 层”当前执行模型
Section titled “当前执行模型”step runner 中的工具执行分三段:
Stage 1 - ToolGate(串行,逐 call): 对每个 call: ToolGate hooks -> 允许 / 阻断 / 挂起 / 设置结果 Suspend? -> 标记 Suspended,继续检查后续 call Block? -> 标记 Failed,立即返回 SetResult -> 写入提供的结果,继续 None -> 加入 allowed_calls
Stage 2 - BeforeToolExecute(仅 allowed_calls): 对将要执行的调用运行执行前 hook
Stage 3 - Execute(仅 allowed_calls): Sequential mode: 逐个执行,遇到首次挂起就停止 Parallel mode: 批量执行,收集所有结果如果任一调用挂起,step 会返回 StepOutcome::Suspended。orchestrator 随后:
- checkpoint 持久化
- 发出
RunFinish(Suspended) - 进入
wait_for_resume_or_cancel
wait_for_resume_or_cancel 循环
Section titled “wait_for_resume_or_cancel 循环”loop { let decisions = decision_rx.next().await; emit_decision_events_and_messages(decisions); prepare_resume(decisions); detect_and_replay_resume(); if !has_suspended_calls() { return WaitOutcome::Resumed; }}关键属性:
- 循环支持部分恢复:如果只收到 tool_A 的 decision,而 tool_B 仍挂起,tool_A 会先回放,循环继续等待 tool_B。
- decision 可以批量到达,也可以逐个到达。
- 返回
WaitOutcome::Resumed后,orchestrator 会回到 step loop,进入下一轮 LLM 推理。
Resume replay
Section titled “Resume replay”恢复时会扫描 status == Resuming 的 tool call,并按 ToolCallResumeMode 回放:
| Resume Mode | Replay 参数 | 行为 |
|---|---|---|
ReplayToolCall | 原始参数 | 完整重跑 |
UseDecisionAsToolResult | decision 结果 | ToolGateHook 在回放时返回 ToolInterceptPayload::SetResult |
PassDecisionToTool | decision 结果 | 作为新参数传给 tool |
已完成调用(Succeeded、Failed、Cancelled)会被跳过。
工具执行期间到达的 decision
Section titled “工具执行期间到达的 decision”内置 resolver 默认安装 SequentialToolExecutor,因此允许执行的 tool call 会逐个运行。若 decision 在某个工具仍在执行时到达,它会留在 decision channel 中,直到 step 进入 wait_for_resume_or_cancel 后才被消费。
如果通过自定义 resolver 或 ResolvedAgent::with_tool_executor(...) 安装 ParallelToolExecutor,allowed batch 可以并发执行。即便如此,内置 resume loop 仍会在 step 挂起后消费 decision,准备 resume state,并通过标准的 ToolGate -> BeforeToolExecute -> tool -> AfterToolExecute 流水线回放挂起调用。契约层的 DecisionReplayPolicy 用于描述自定义并行 HITL 集成里的协同行为;它不是 AgentSpec 字段。
协议适配器:SSE 重连
Section titled “协议适配器:SSE 重连”长生命周期 run 可能跨多个前端 SSE 连接,尤其是 AI SDK v6 这类“一次 HTTP 请求对应一次 SSE 流”的协议。
Turn 1: HTTP POST -> SSE 1 -> tool suspend -> stream 关闭 但 run 还活着,正在 wait_for_resume_or_cancel
Turn 2: 新 HTTP POST 带 decision 到来 如果事件仍然发往旧 channel,就会丢失解决方案:ReconnectableEventSink
Section titled “解决方案:ReconnectableEventSink”用一个可替换底层 sender 的 event sink 包装原始 channel,新连接到来时先 reconnect() 再投递 decision。
Turn 1: submit() -> 创建 event_tx1 / event_rx1 run suspend -> SSE 1 结束
Turn 2: 新请求创建 event_tx2 / event_rx2 sink.reconnect(event_tx2) send_decision 后续事件都发往 SSE 2| 协议 | 挂起信号 | 恢复机制 |
|---|---|---|
| AI SDK v6 | finish(finishReason: "tool-calls") | 新 HTTP POST -> reconnect -> send_decision |
| AG-UI | RUN_FINISHED(outcome: "interrupt") | 新 HTTP POST -> reconnect -> send_decision |
| CopilotKit | renderAndWaitForResponse UI | 同一 SSE 或新请求恢复 |