Durable Agent:长任务执行架构

比较内存循环、Checkpointed Graph、Queue Workers 与 Durable Workflow 四种长任务执行拓扑:进度存在哪个节点、故障后从哪个单位恢复、副作用怎样避免做两次、等人批准时进程要不要活着。

一个运行数分钟或数小时的 Agent 不能只靠 process memory。部署、timeout、人工等待和 provider failure 都会中断 loop;系统必须知道已经完成什么、哪些 side effect 可以重试,以及从哪里恢复。四个问题决定了架构:进度存在哪个节点、故障后恢复的单位是什么、副作用怎样避免做两次、等人批准时进程要不要一直活着。

想亲手走一遍四种拓扑、注入故障再恢复,打开互动 Lab:/system-design-lab/durable-agent-execution-architectures

四种 Durable Agent 执行架构

有约束的设计问题

一个运维助手 Agent 要处理四类任务:几秒钟查完资料就总结的问答;走一张推理图、中间某一步必须等人批准、人可能几小时后才回复的变更方案;几百个互相独立、能并行、量忽大忽小的批量检查;以及跨几天、要等定时器和审批、有多个不能重复的副作用、事后必须能审计的故障处置。每一类该用哪种执行架构?

四种 topology signature

架构State owner恢复单位副作用怎样避免做两次怎样等人适合主要代价
In-memory Loopprocess memory整个 request 重跑做不到,除非工具本身幂等做不到,进程必须活着秒级、无副作用崩溃、部署、超时丢全部进度
Checkpointed Graphcheckpoint store,按 thread idgraph nodeinterrupt 前的代码恢复时会重跑,副作用放在 interrupt 之后或幂等interrupt() 存档后无限等待,同一 thread id 恢复可暂停 / 恢复的推理图恢复从节点开头重跑;checkpoint 与外部副作用可能对不上
Queue Workersjob store + queue一条 job 消息执行前先读执行记录;每步幂等一步写「等待中」,之后的消息再唤醒大量独立异步步骤至少一次投递会重复;不保证顺序;毒消息
Durable Workflow引擎里的 event history一个 activityactivity 只跑一次,结果记入历史,回放时复用;仍要幂等以防重试定时器和 signal 由引擎保管,等待不占进程跨小时 / 天、timer、approval、审计工作流代码必须确定性;历史膨胀;多一个引擎要运维

1. In-memory Loop:进度只在进程里

循环在一个进程里跑,每一轮调模型、调工具,中间结果放在变量里。进程一挂、一次部署、一次请求超时,进度全没,只能整个请求从头再来;已经做过的工具调用没有任何记录,重跑就再做一次。只适合几秒跑完、没有副作用、重跑无所谓的任务。一旦任务变长或要改外界,就该换架构。

2. Checkpointed Graph:每步存档,interrupt 等人

Agent 是一张图,运行器每执行完一个节点就把图状态按 thread id 存进 checkpoint store(LangGraph 的 checkpointer 就是这个角色)。遇到需要人批准的地方调用 interrupt():图状态被保存,运行无限期停下,不占任何进程;人带着同一个 thread id 发回 Command(resume=值),值会被送回 interrupt 调用点。

关键细节:恢复不是从 interrupt 那一行继续,而是从那个节点的开头重跑,节点里 interrupt 之前的代码会再执行一次。所以有副作用的调用要挪到 interrupt 之后,挪不了就给它幂等键;恢复前比对提案哈希,参数变了不能直接续跑。checkpoint store 写不进去时这一步不能算完成,不能带着没存档的状态进入下一个节点。

3. Queue Workers:每一步是一条消息

API 把任务的每一步变成一条消息放进队列,一群无状态的 Worker 竞争消费(Microsoft 的 Competing Consumers 模式)。队列保证每条消息至少送达一次,不保证只送一次,也不保证顺序,所以每一步必须幂等:Worker 执行前先按 job id 读作业仓库的执行记录,已经完成就只确认消息不再执行;每个副作用带稳定的幂等键。

取消息时加租约(peek-lock):锁着的时候别的 Worker 看不见它,成功就 complete,失败就 abandon 让它重新可见;长步骤要在到期前续锁,否则做到一半消息被别的 Worker 拿走。反复失败的消息在投递次数超过阈值后进死信队列,连同失败信息一起保存,供人分析后重放。Worker 和 API 不直接通信,结果写进作业仓库或回复队列,按原消息关联。

4. Durable Workflow:事件历史驱动回放

引擎(Temporal 是典型实现)为每次执行保存一份完整、有序的 event history,它是唯一真相。Worker 崩溃后引擎不恢复内存快照,而是让工作流代码从头重跑并回放历史,代码走到历史末尾就是崩溃前的位置。这要求工作流代码确定性:给同样的历史必须做同样的决定,读时钟、随机数、直接网络调用都会让回放走岔。

一切接触外界的事放进 activity:API 调用、查库、调模型。activity 只执行一次,结果作为事件记入历史,回放时直接复用不再执行;activity 可能被重试,所以仍要幂等,长 activity 用心跳上报进度,大任务拆成多个小 activity。定时器和 signal(例如人工批准)由引擎保管,等几天也不占 Worker。

托管引擎给出同样的取舍:AWS Step Functions 的 Standard 工作流最长跑一年、exactly-once、状态在转换之间持久化、执行历史保留 90 天,适合不幂等的动作;Express 工作流最长五分钟、at-least-once,适合幂等动作。

Exactly-once 是系统结论,不是 broker 配置

四种架构里没有一种能靠配置得到「恰好一次」。每个有副作用的步骤使用稳定的幂等键,把意图、对方响应和提交状态写进持久状态后再确认;Worker 收到可能重复的消息先读执行记录;等待人工批准时保存提案哈希,恢复后只有相同参数才能继续。

故障与恢复

架构故障用户看到什么恢复
In-memory进程在任务中途重启进度全丢,重跑时副作用重复短任务整个重跑并要求工具幂等;任务变长就换架构
In-memory模型调用卡住,请求超时把进程杀了超时错误,没有中间结果每次调用单独超时重试;长任务改异步并持久化进度
Checkpointedinterrupt 前的副作用恢复时又跑了一次两个工单副作用挪到 interrupt 之后或加幂等键;恢复前比对提案哈希
Checkpointedcheckpoint store 不可用所有线程无法推进或恢复写失败就让这一步失败并重试;把仓库当核心依赖运维
Queue同一条消息投递两次副作用重复,例如退款两次执行前读执行记录;副作用带幂等键
Queue毒消息反复失败其他任务被拖慢投递次数上限后进死信队列;人工分析后重放
Queue长步骤跑过了租约两个 Worker 同时做同一步续锁;把长步骤拆小
Workflow工作流代码读时钟,回放走岔工作流卡住报错时钟、随机数、网络调用放进 activity;代码改动版本化
Workflowactivity 重试时副作用做了两次外部多一条重复记录activity 带幂等键;拆小并加心跳

怎么选

  • 几秒跑完、没有副作用、重跑无所谓:In-memory Loop。
  • 推理图要暂停等人、跨对话继续、副作用不多:Checkpointed Graph,副作用放在 interrupt 之后或幂等。
  • 大量互相独立、能并行、顺序无所谓、能重复执行的步骤:Queue Workers,执行记录 + 幂等键 + 死信队列。
  • 跨小时到几天、要定时器、重试、审批和审计,副作用不能重复:Durable Workflow,接受确定性约束和引擎运维。
  • 一个 deterministic workflow 已能完成的任务,不要自动加 Agent。

回到开头的运维助手:秒级问答走内存循环;等人批准的变更方案走 Checkpointed Graph;批量检查走 Queue Workers;跨几天的故障处置走 Durable Workflow。

面试时这样回答

  1. 先复述约束:任务时长、有没有副作用、要不要等人、要不要审计。
  2. 说执行路径:点名图上的边,例如「每个节点后存 checkpoint,interrupt 后用同一 thread id 恢复」「队列租约投递,Worker 先读执行记录」「引擎把任务和历史交给 Worker 回放,activity 结果记入历史」。
  3. 说进度存在哪:进程内存、checkpoint store、job store,还是事件历史。
  4. 说代价与一个故障:例如恢复从节点开头重跑会重复 interrupt 前的副作用,修法是把副作用挪到 interrupt 之后或加幂等键。

一手证据