Home Projects Blog Resume Contact 中文
Back to list
2026年7月5日 1,800 words 5 min read

05:让 Agent 可靠停下:状态机、终止和失败恢复

围绕状态机、错误分类、恢复预算与终止策略,说明 Agent Runtime 如何修正有价值的失败,同时识别重复调用、超时和无效循环。

#RunLens#状态机#失败恢复#终止策略#Agent Runtime

05:让 Agent 可靠停下:状态机、终止和失败恢复

上一篇跟踪了一次 ToolCall。模型提出动作后,Runtime 会完成 Schema、权限、Hook、Handler 和结果回流。

当这条链路进入循环,新的问题随之出现:模型可能连续调用工具,Provider 可能临时失败,工具参数可能反复出错,任务也可能长时间得不到新信息。

Agent Runtime 需要同时处理两个方向:有价值的失败要进入下一轮,无效循环要及时收束。

Recovery 决定失败后怎样继续,Termination 决定什么时候停止继续。

Agent 会失败,也会重复自己

一次真实 Run 中,失败来源很多:

  • Provider 请求超时。
  • API 返回 429 或 5xx。
  • Tool arguments 缺少字段。
  • 工具选择和当前 Plan Step 不匹配。
  • Handler 读取文件失败。
  • 模型连续发出相同 ToolCall。
  • 多轮调用后 final answer 仍在等待中。

这些失败的处理方式不同。

临时网络错误适合重试。参数错误可以交给模型修正。权限拒绝通常要换一条行动路径。重复调用和超时则说明继续运行的收益已经很低。

RunLens 使用状态机记录当前阶段,用 RecoveryPolicy 处理可恢复错误,再用 TerminationPolicy 控制循环出口。

Run 状态机记录任务处在哪一阶段

RunState 包含:

created
initializing
planning
model_thinking
tool_executing
recovering
validating
completed
failed

一次常见 Happy Path 是:

created
→ initializing
→ planning
→ model_thinking
→ tool_executing
→ model_thinking
→ validating
→ completed

Provider 或参数错误需要恢复时,路径会经过:

model_thinking / tool_executing
→ recovering
→ model_thinking / tool_executing

所有活动状态都可以进入 failed。

状态机带来的价值

状态机让几个模块共享同一种生命周期语言:

  • Event Timeline 可以记录每次状态变化。
  • 前端可以展示 Run 当前阶段。
  • Recovery 可以明确回到哪个执行位置。
  • 最终状态只能通过统一入口修改。
  • 非法状态跳转会直接抛出错误。

例如 planning 结束后进入 model_thinking,模型请求工具后进入 tool_executing。工具批次完成,再回到 model_thinking

这比单独维护几个布尔值更容易解释。

AgentLoop 维护哪些运行状态

状态机描述宏观阶段,TerminationState 记录循环内部计数:

状态用途
iterations已完成模型响应次数
tool_calls已请求工具调用数
elapsed_seconds已运行时间
final_answer_received模型是否返回最终回答
repeated_failure_count相同失败最大重复次数
repeated_tool_call_count相同工具请求最大重复次数
token_usageProvider token 统计
recovery_attempts已执行恢复次数
tool_correction_attempts工具参数纠正次数
recovery_exhausted恢复预算是否耗尽

AgentLoop 每轮更新这些值,再交给 TerminationManager 评估。

Termination Policy 决定循环出口

RunLens 把终止规则拆成多个独立 Policy。

Final Answer

模型返回纯内容响应时,Runtime 记录 final answer,并把循环状态标记为完成候选。

这里结束的是模型循环。后续 Plan 和 Stop Hook 还会检查业务结果。

Timeout

elapsed time 达到 timeout_seconds 时,Run 进入 failed,termination reason 为 run_timeout

Max Tool Calls

模型一次可以返回多个工具调用。RunLens 会先把整个批次数量加入计数,再评估上限。

批次超过上限时,Runtime 会在 Handler 执行前拦截整批调用。

Max Iterations

模型响应次数达到上限时,Run 结束为 max_iterations_exceeded

Repeated Tool Call

Runtime 根据工具名和排序后的 JSON 参数生成调用签名。

同一个签名重复达到三次时,Run 以 repeated_tool_call 结束。

这个规则用于识别模型在相同动作上原地循环。

Repeated Failure

失败签名由工具名、error_code 和 error_message 组成。

同类失败超过 max_retries 后,Run 以 repeated_tool_failure 结束。

Recovery Exhausted

恢复策略已经用完预算时,TerminationManager 返回 recovery_exhausted

这些 Policy 按固定顺序执行,任意一个给出停止结论,AgentLoop 就会返回结构化结果。

Provider 临时错误怎样重试

OpenAI 和 DeepSeek 都通过 OpenAI-compatible Client 调用。

Provider 异常会被统一分类:

场景retryable错误类型
HTTP 408、409、429、5xxtransient HTTP error
Timeoutprovider timeout
Connection Errorconnection error
其他 4xxpermanent HTTP error
其他异常request failed

RecoveryPolicy 只重试 retryable 错误。

退避时间为:

0.05 × 2^completed_retries

默认两次重试大致等待:

0.05s
0.10s

每次重试都会:

  • 状态进入 recovering。
  • 发布 recovery.started。
  • 记录 attempt 和 delay。
  • 等待后重新进入 model_thinking。
  • 成功时发布 recovery.completed。
  • 耗尽时发布 recovery.failed。

Provider 收到的 timeout 参数来自本次 Run 剩余时间,重试继续受整体 deadline 约束。

工具参数错误怎样交给模型纠正

工具参数来自模型,输入 Schema 能够提供精确错误路径。

例如模型调用:

{
  "name": "write_markdown_file",
  "arguments": {
    "artifact_name": "insight_report.md"
  }
}

Schema 会指出:

arguments.content: field is required

ToolResult 标记为 retryable,AgentLoop 进入工具参数纠正流程:

tool.failed
→ recovering
→ recovery.started: tool_argument_correction
→ 错误 observation 回到模型
→ 模型重新生成参数
→ ToolExecutor 再次校验

Runtime 保留模型修正参数的空间,同时通过 max_retries 控制纠正次数。

Runtime 保留原始错误信息并交给下一轮,模型再根据业务上下文生成完整参数。

Permission Denied 属于另一类反馈

权限拒绝通常来自:

  • 工具不在白名单。
  • 路径位于允许根目录外。
  • 目标包含 .env
  • 写入内容超过限制。

ToolExecutor 返回 blocked ToolResult,error_code 为 permission_denied

它会进入模型 observation。工具参数纠正预算专门处理 Schema 错误;权限拒绝后,模型根据原因选择其他工具、调整目标路径,或者在可行路径耗尽时结束任务。

权限结果本身就是任务状态的一部分。

一个例子:错误参数后恢复成功

测试场景可以概括为:

第 1 轮模型响应
→ 请求 write_json_file,参数缺少 content
→ ToolExecutor 返回 input_validation_failed
→ Runtime 记录 tool_argument_correction

第 2 轮模型响应
→ 根据错误信息补齐 content
→ 工具执行成功
→ Plan Step 完成

后续模型继续生成剩余产物
→ final answer
→ Stop Validation
→ completed

这条路径说明失败可以为下一轮提供高价值信息。

completed 还只是候选状态

AgentLoop 的 FinalAnswerTermination 会返回 completed 候选结果。

runtime/runner.py 接到结果后继续执行:

  1. 状态进入 validating。
  2. PlanProgress 根据成功条件完成收尾。
  3. 检查 Plan 是否全部完成。
  4. 触发 Stop Hooks。
  5. 检查 Artifact Contracts。

Plan 或 Stop Hook 失败时,最终 Run Status 会改为 failed。

因此 RunLens 有两层完成判断:

  • 循环完成:模型已经结束行动。
  • 业务完成:任务步骤和交付产物全部通过检查。

这两层分开后,模型提前结束只会得到完成候选,最终状态继续由验证阶段决定。

Timeout 的真实边界

Run timeout 主要覆盖:

  • AgentLoop elapsed time。
  • Provider 请求的 timeout 参数。
  • 多轮重试和模型循环。

Tool Handler 在当前 Python 进程中同步执行。Handler 已经进入阻塞状态时,Runtime 缺少强制中断任意 Python 函数的安全手段。

当前设计通过短小 Handler、文件大小限制和工具拆分降低风险。

后续如果加入耗时外部任务,可以考虑:

  • 子进程执行器。
  • 后台队列。
  • 可取消任务协议。
  • Handler 自身 deadline。

小结

RunLens 使用 RunStateMachine 描述生命周期,TerminationState 保存循环计数,RecoveryPolicy 处理可恢复错误,TerminationManager 控制明确出口。

Provider 临时错误进入重试,工具参数错误交给模型修正,权限拒绝形成行动反馈,重复调用、重复失败和超时负责及时收束。

模型循环停下以后,Runtime 还会进入 validating。下一篇继续拆这一阶段:ArtifactContract、Stop Hook 和 Evidence Index 怎样判断三个业务产物是否真的可以交付。