前面的文章已经解决了 Multi-Agent 中的控制问题:任务怎样拆分、Agent 之间传什么、下一步由谁执行,以及运行过程中如何管理 Context、State 和 Memory。

然而,流程设计正确,不代表任务一定能够执行完成。

一个任务可能运行几十分钟,调用几十次模型和外部工具。期间任何一次网络超时、API 限流、进程重启等,都可能让执行中断。

本文我们主要讨论:怎样让已经确定的流程可靠地执行下去,并在发生故障后继续完成。

这也是 Multi-Agent 从原型进入生产环境后须补上的一层防护机制:Execution Runtime,执行运行时。

一、流程控制与可靠执行

我们首先区分两个容易混在一起的问题:流程控制与可靠执行。

假设一个研究任务需要三个 Agent:

代码片段Text
Planner
   |
   ├── Agent A:研究市场
   ├── Agent B:研究竞品
   └── Agent C:研究技术
          |
          ▼
      Synthesizer

Planner 决定创建哪几个 Agent,以及完成之后进入哪个步骤,这属于控制流。

但真正运行时,还会遇到另一类问题:

代码片段Text
Agent A ✓
Agent B ✓
Agent C ───── X timeout

此时系统已经知道下一步应该做什么,问题在于 C 没有执行完成。

如果程序直接退出,再重新启动整个任务,那么 A、B 已经完成的搜索、模型调用和计算都会被重新执行。

对于几秒钟的流程,这可能还能接受。

对于运行十几分钟甚至更久的 Agent,这种方式则不能接受。

Anthropic 在其 Multi-Agent Research 系统的生产经验中提到,Agent 会长期运行并持续维护状态,错误也会随着执行步骤累积。因此,他们会结合 checkpoint、retry 等机制,让失败后的任务从现有进度继续,而不是每次从头开始。

所以流程控制与可靠执行的职责分别是:

代码片段Text
Control Plane
决定:接下来做什么

Execution Runtime
保证:这件事最终能够执行完成

接下来要介绍的 Checkpoint、Retry、Idempotency,都围绕执行运行时(Execution Runtime),确保 Agent 可靠地执行完成。

二、Checkpoint 保存执行进度

实现故障恢复的第一步,是保存执行进度。

这种保存点通常称为 Checkpoint。

可以把一次长任务理解为下面这条时间线:

代码片段Text
t0        t1          t2          t3
start   checkpoint  checkpoint   crash
                        |
                        └──────────→ resume

如果系统只在 t0 保存输入,那么 t3 崩溃以后,只能重新开始。

如果 t1、t2 都保存了执行状态,那么系统可以找到最近一次有效状态,从那里继续。

例如:

代码片段Text
Checkpoint #12

task_id: research-001

A:
  status: completed
  artifact: artifact-a.json

B:
  status: completed
  artifact: artifact-b.json

C:
  status: running

恢复以后就不需要重新执行 A 和 B。

运行时只需要知道:

代码片段Text
A completed → 跳过
B completed → 跳过
C unfinished → 继续执行

这就是持久化执行(Durable Execution)的核心思想之一:执行过程中的关键状态不能只存在于当前进程内存中。

LangGraph 的 persistence 机制采用 checkpointer 保存 Graph State。执行过程中可以通过 thread_id 关联到同一条运行线程,之后继续读取已经保存的状态。它的 checkpoint 通常发生在 Graph 的节点边界,因此节点划分也会直接影响恢复粒度。

例如把任务设计成:

代码片段Text
search
  ↓
analyze
  ↓
write

如果 analyze 失败,通常只需要重新执行当前节点。

但如果把三项操作全部塞进一个节点:

代码片段Text
search + analyze + write

即使最后一步失败,也可能需要重新执行整个节点。

因此,节点粒度除了影响代码结构,也会影响故障恢复成本。

不过 checkpoint 也不能无限细。

每一个微小步骤都写一次数据库,会增加存储和协调开销。更实用的做法是优先保存那些:

  • 成本较高的模型调用结果;
  • 已经完成的子任务;
  • 外部系统返回的重要 ID;
  • 后续步骤无法轻易重新计算的数据;
  • 人工确认后的决策。

Checkpoint 的目标不是记录所有变量,而是确保发生故障以后,已有成果不会轻易丢失。

三、Retry 处理临时故障

有了 checkpoint,还需要处理另一类更常见的问题:临时性的失败。

例如:

代码片段Text
HTTP 429
HTTP 502
connection reset
request timeout
database connection unavailable

这些错误有一个共同特点:

稍后再执行一次,有可能成功。

因此系统通常会进行 Retry,也就是重试。

最简单的策略是:

代码片段Text
失败
 ↓
等待
 ↓
重试

但生产环境里的 Retry 通常还需要三个限制。

1. 最大重试次数

例如:

代码片段Text
max_attempts = 3

如果已经连续失败三次,就不要无限尝试。

否则一个下游服务故障,可能让几百个 Agent 不断发送请求,进一步放大故障。

2. Backoff

Backoff 指每次失败后逐渐增加等待时间。

例如:

代码片段Text
第 1 次失败 → 等 1 秒
第 2 次失败 → 等 2 秒
第 3 次失败 → 等 4 秒

这就是常见的 Exponential Backoff,指数退避。

它可以避免大量失败任务同时立即重试。

3. Retry Budget

系统还可以给整个任务设置重试预算。

例如:

代码片段Text
整个任务最多允许:

20 次 Tool Retry
3 次 Agent Retry
10 分钟恢复时间

这样能够防止一个异常任务无限消耗 token、API 配额和计算资源。

LangGraph 的官方示例也把错误分成不同类型:网络错误、限流等临时问题适合自动 Retry;需要用户补充信息的问题可以暂停;未知错误则应该暴露出来供调试。

因此,一个重要原则是:Retry 只适合有较大概率自行恢复的错误。

相反,类似参数本身错误:

代码片段Text
HTTP 400
invalid schema
unsupported tool
permission denied

连续执行十次通常也不会改变结果,这一类错误不应使用 Retry 策略。

因此,可靠系统需要先判断失败类型,再决定是否重试。

四、幂等避免重复执行

Retry 看起来很简单,但执行时可能会隐含着问题。

假设 Agent C 的任务是:

代码片段Text
创建一个客服 Ticket

第一次调用外部系统:

代码片段Text
Agent C
   |
   ├── create_ticket()
   |
Ticket System
   |
Ticket #1001 创建成功
   |
   X response timeout

这里出现了一个不正常的状态。

Agent 看到的是:

代码片段Text
timeout

于是 Runtime 判断任务失败,并再次执行:

代码片段Text
create_ticket()

结果可能变成:

代码片段Text
Ticket #1001
Ticket #1002

事实上第一次已经成功,只是响应没有成功返回。

因此:

能够 Retry,并不代表能够安全 Retry。

这里就要引入 Idempotency,幂等性。

幂等要求同一个逻辑操作执行多次,最终结果仍然与执行一次相同。

例如查询:

代码片段Text
GET /customer/123

重复执行多次结果都完全一样。

但下面这些操作多次执行可能存在幂等问题:

代码片段Text
创建订单
发送邮件
写数据库
扣款
提交工单
发布内容

一旦自动 Retry,就必须考虑重复执行。

常见做法是给一次逻辑操作生成唯一的 operation_id:

代码片段Text
operation_id = "research-001-create-ticket-C"

第一次执行:

代码片段Text
create_ticket(
    operation_id="research-001-create-ticket-C"
)

第二次 Retry 仍然使用相同 ID。

外部系统如果发现这个 operation 已经成功处理,就直接返回第一次的结果:

代码片段Text
operation already completed
ticket_id = 1001

这样可以避免重复创建。

如果目标系统本身不支持幂等键,还可以在自己的数据库中保存:

代码片段Text
operation_id
status
external_resource_id

然后在执行前进行去重检查。

对于无法做到严格幂等的操作,还可以使用 Compensation,补偿操作。

例如:

代码片段Text
创建资源
   ↓
后续失败
   ↓
删除刚才创建的资源

这种思路在分布式系统中非常常见。

因此真正可靠的 Retry 通常要和三件事一起设计:

代码片段Text
Retry
+
Idempotency
+
Compensation

五、同步执行与异步执行

Multi-Agent 系统还会遇到一个运行方式上的选择:同步还是异步。

假设三个 Agent 并行执行:

代码片段Text
A ───────── 15s
B ───────────────── 30s
C ───────────────────────── 60s

同步模式下,Coordinator 通常会等待:

代码片段Text
await A
await B
await C

只有全部完成之后才进入下一步。

这种方式的优势非常明显:

代码片段Text
状态简单
结果容易聚合
错误传播容易理解

因此很多系统最开始都会采用同步执行。

Anthropic 当前公开的 Multi-Agent Research 架构也提到,Lead Agent 会等待一批 Subagent 完成后再继续。这降低了协调复杂度,但一个执行很慢的 Subagent 也可能阻塞整个过程。

异步模式则允许结果独立到达:

代码片段Text
A finished
   ↓
立即处理 A

B finished
   ↓
立即处理 B

C 继续运行

甚至 Coordinator 可以在 A 返回以后,又创建新的 Agent D:

代码片段Text
A result
   |
   └── spawn D

B result
C running
D running

吞吐量和并行能力会明显提高。

但 Runtime 也必须处理更多问题:

代码片段Text
哪个结果已经到达?
哪个任务还在执行?
某个 Agent 失败是否影响其他 Agent?
结果到达顺序改变怎么办?
Coordinator 什么时候可以继续?

这些都属于状态协调问题。

在实践中,原则上:任务规模较小时优先同步;当等待时间和并行度已经成为明显瓶颈,再引入异步执行。

异步可以提高性能,但它同时会增加恢复、状态一致性和错误传播的设计成本。

六、一次完整的故障恢复

现在把前面的机制放进同一个例子。

一个任务同时启动三个 Agent:

代码片段Text
A ───────────── completed
B ─────────────────── completed
C ───────────── X timeout

A 和 B 已经产生 Artifact。

系统此时存在一个 checkpoint:

代码片段Text
Checkpoint #18

A:
  status: completed
  artifact: artifact-a

B:
  status: completed
  artifact: artifact-b

C:
  status: running

C 调用了外部 Ticket API:

代码片段Text
operation_id:
job-123-agent-c-ticket

外部系统实际上已经创建:

代码片段Text
Ticket #8421

但响应返回过程中发生网络超时。

Runtime 捕获异常以后,首先判断:

代码片段Text
Timeout
→ transient error
→ 可以 Retry

随后读取 checkpoint:

代码片段Text
A completed → 不执行
B completed → 不执行
C failed    → retry

C 再次调用:

代码片段Text
create_ticket(
    operation_id="job-123-agent-c-ticket"
)

外部系统发现相同 operation 已经处理:

代码片段Text
return Ticket #8421

C 得到结果:

代码片段Text
C completed
artifact-c saved

随后写入新的 checkpoint:

代码片段Text
A ✓
B ✓
C ✓

Coordinator 再进入后续汇总阶段。

整个过程可以表示为:

代码片段Text
Checkpoint
   |
   ├── A artifact ✓
   ├── B artifact ✓
   └── C pending
          |
        retry
          |
   idempotency check
          |
        resume
          |
      C artifact ✓
          |
     next stage

从这个例子可以看到,真正的故障恢复并不是简单地“失败以后再调用一次”。

它至少涉及:

代码片段Text
Checkpoint
知道已经完成了什么

Retry
重新执行可恢复的失败

Idempotency
避免重复执行而产生错误结果。

Resume
从已有状态继续流程

缺少其中任何一个环节,都可能让恢复过程重新产生新的问题。

总结

很多 Multi-Agent Demo 的重点放在 Agent 怎么调用、Prompt 怎么写、Supervisor 怎样决定下一步。

这些内容解决的是“系统如何思考和调度”。

进入真实业务以后,必须要解决另一类更基础的问题:

代码片段Text
任务跑到一半会不会丢?
API 超时以后怎么办?
已经完成的步骤会不会重新执行?
重试会不会创建两张订单?
系统升级会不会破坏正在运行的任务?

这些问题已属于典型的分布式系统工程范围。

Temporal 这类 durable workflow 系统的核心价值,也正是在进程崩溃、网络故障或基础设施中断以后,仍然能够利用持久化的执行历史恢复工作流。

而在 Agent Runtime 中,同样可以借鉴这些成熟思想。

最终,一个可靠的 Multi-Agent 执行层可以概括为:

代码片段Text
State 持久化
      +
Checkpoint
      +
Retry Policy
      +
Idempotency
      +
Timeout
      +
Observability

流程设计告诉系统下一步应该运行谁。

可靠执行则保证:在模型失败、工具超时、进程重启和系统升级都可能发生的环境里,已经确定的工作仍然能够继续向前推进,并最终得到一个可确认的结果。

这也是 Multi-Agent 从“能够运行”走向“能够长期运行”必须补上的工程基础。