Durable Execution:如何让长任务 Agent 崩溃后接着工作

长任务 Agent 会遇到进程崩溃、网络故障、工具超时和人工等待。本文用 Workflow、Activity 与 Event History 拆解 Durable Execution,解释它与普通重试的区别、幂等副作用的边界,以及如何设计可暂停、恢复和审计的 Agent 工作流。

Durable Execution:如何让长任务 Agent 崩溃后接着工作

Agent 最难的不是“会思考”,而是“不会丢进度”

一个 Agent 要完成“调研供应商、比较报价、提交审批”这样的任务,往往需要十几个步骤。模型调用可能超时,工具服务可能暂时不可用,执行进程可能在第八步重启,用户也可能隔几个小时才回来确认。

如果系统只把整个过程写成一段普通函数,失败后通常只能从头再来。这不仅浪费时间和模型调用,还可能重复扣款、重复发邮件或重复创建订单。

Durable Execution(持久化执行)提供了另一种思路:把长任务写成可恢复的工作流,持续保存每一步的状态和结果。进程挂掉后,系统从最近一次完成的位置继续,而不是把 Agent 当成一次性请求重新启动。

Temporal 工作流项目概念图

Google 的 Gemini 官方示例使用 Temporal 构建可恢复的 Agent 循环:模型调用和工具调用作为可重试的活动执行,工作流负责组织顺序和状态。Temporal 官方文档把这种能力概括为让应用在崩溃、网络故障或基础设施中断后从原位置恢复。

重试不等于持久化执行

最简单的重试是:请求失败后,再调用一次同一个函数。

但长任务通常有多个步骤:

读取客户资料 → 查询库存 → 生成报价 → 请求审批 → 创建订单 → 发送通知

如果“创建订单”之后通知服务超时,系统无法判断订单到底创建成功没有。此时直接重试,可能得到两个订单;不重试,用户又可能永远收不到通知。

持久化执行关注的不是“把异常捕获住”,而是把工作流历史、每一步的输入输出和重试状态保存下来。系统可以知道哪些步骤已经完成,哪些步骤还没有拿到确定结果。

不过,持久化执行也不会自动把外部世界变成 exactly-once。数据库写入、付款、发邮件等外部副作用仍然需要幂等键、去重表或业务状态机配合。它解决的是“执行进度可恢复”,不是“所有外部系统天然只执行一次”。

三个核心概念

Workflow:稳定的流程骨架

Workflow 描述任务的顺序、分支、等待和超时。例如:先并行查询三个供应商,等用户选择后再发起审批。它应该尽量保持确定性,因为系统可能会根据历史事件重放 Workflow 代码。

Activity:可以失败的具体动作

Activity 承担模型调用、HTTP 请求、数据库读写、文件处理等不稳定工作。它们可以单独设置超时、重试策略和并发限制,也可以记录调用结果。

Event History:可重放的执行历史

每完成一步,系统都会留下事件。恢复时,Workflow 根据历史跳过已完成的 Activity,重新计算下一步应该做什么。对 Agent 来说,这相当于把“上下文”从一段容易丢失的内存,变成可审计的执行记录。

Agent 为什么特别需要这个能力

传统 CRUD 请求通常几百毫秒到几秒就结束,失败后重新请求的代价有限。Agent 任务则经常包含:

  • 多轮模型调用,且每轮可能选择不同工具。
  • 长时间等待人工审批、第三方回调或定时条件。
  • 需要跨越多个服务,任何一个依赖都可能短暂失败。
  • 不能重复执行的外部副作用。

例如“整理一批合同并生成风险清单”可以分成:上传文件、解析文本、并行抽取条款、合并结果、人工复核、生成报告。解析到第六份文件时进程崩溃,如果前五份结果已经被保存,系统就不该从第一份重新开始。

Agent 工作流应该怎样拆

一个实用原则是:模型负责做判断,Workflow 负责保存进度,Activity 负责接触外部世界。

可以把 Agent 循环写成下面的逻辑:

while not task_done:
    decision = await call_model(state)
    if decision.kind == "tool_call":
        result = await run_tool_activity(decision.tool, decision.args)
        state = update_state(state, result)
    elif decision.kind == "needs_human":
        await wait_for_signal()
    else:
        return decision.answer

这里的 call_modelrun_tool_activity 不应被当成普通内存函数。模型请求应有超时、重试和版本记录;工具调用应有幂等键、权限检查和结果快照;state 应能在任务恢复后重新获得。

对于高风险动作,最好把“决定要做”和“真正执行”拆成两步:先生成待确认计划,再由用户或策略引擎发出批准信号。这样 Agent 可以长时间等待,而不需要占用一个一直在线的 HTTP 请求。

重放为什么要求 Workflow 保持确定

持久化引擎恢复任务时,可能会重放 Workflow 代码,让它重新读取历史事件并走到当前节点。因此 Workflow 里不应该直接调用随机数、当前时间、网络请求或 LLM。否则同一份历史在第二次计算时得到不同分支,系统就无法判断哪些 Activity 已经执行过。

正确的拆法是:Workflow 只负责调度,外部世界交给 Activity。当前时间可以由引擎提供一个可重放的时间值;随机 ID 可以在 Workflow 外生成后作为输入;模型调用必须作为 Activity 保存请求和结果。伪代码看起来相似,但责任边界不同:

@workflow
async def order_workflow(request):
    plan = await execute_activity(make_plan, request)
    await workflow.wait_condition(lambda: workflow_state.approved)
    result = await execute_activity(create_order, plan)
    await execute_activity(send_notification, result)
    return result

这里的 make_plancreate_ordersend_notification 都可能失败,但 Workflow 本身只在事件历史上推进。尤其是 send_notification,不能因为它超时就假设“肯定没发出去”;它需要一个业务侧的幂等键,例如 workflow_id + step_name,让重复尝试最终只产生一条通知。

幂等、副作用与“结果未知”

工程上最危险的不是明确失败,而是结果未知:客户端发出创建订单请求,连接在服务端返回之前断开。此时 Agent 无法仅靠异常判断订单是否存在。

常见处理方式是把副作用设计成三段:

  1. 生成全局幂等键,并把它写入请求。
  2. 服务端在事务中记录“幂等键 → 业务结果”。
  3. 重试前先用幂等键查询;如果已有结果,直接复用,不再创建新副作用。

邮件、支付、工单、仓储扣减都可以采用类似模式。若第三方 API 不支持幂等键,就要在自己的系统里增加状态表或中间层,至少能够区分“尚未执行”“执行中”“已确认成功”和“需要人工核查”。

这也是为什么 Durable Execution 不能单独解决一致性问题:它能可靠地恢复你的流程,却无法替你修改银行、邮件服务或供应商系统的语义。

人工确认其实是工作流的一部分

很多 Agent Demo 把人工确认做成一个同步接口:模型问“要不要继续”,用户必须立刻回答。生产系统更常见的情况是用户关掉页面,第二天才点批准。

持久化 Workflow 可以把等待设计成显式状态:

准备计划 → 等待审批 → 已批准 / 已拒绝 / 已过期

用户批准时发送一个带有 workflow_id、审批人、审批时间和审批版本的信号。执行前再次检查计划是否被修改、权限是否仍然有效、价格或库存是否过期。这样“用户点过同意”不会被误当成对任何未来状态都永久授权。

还可以设置补偿动作。例如订单已创建但通知失败,补偿动作不是删除订单,而是把通知标记为待补发;如果支付已扣款但库存预留失败,则进入人工处理队列,而不是让 Agent 自己随意退款。

重试策略不能一刀切

不同错误应该使用不同策略:

  • 网络暂时不可用:指数退避后重试。
  • 限流:尊重服务端的 Retry-After,并降低并发。
  • 参数错误:先让 Agent 修正参数,不要盲目重复。
  • 权限错误:暂停并请求用户授权。
  • 副作用结果未知:先查询业务状态,再决定是否重试。

模型调用通常可以重试,但重试也可能产生不同答案。若下游依赖结构化输出,恢复时应保存原始响应、解析结果和模型版本,避免同一任务在重放中悄悄换成另一种决策。

建议把每次重试都记录成一个可查询的事件,而不是只在应用日志里写一行“retry”。至少保留:步骤名、尝试次数、错误类别、等待时长、依赖版本和最终结果。这样可以回答两个很实际的问题:一次任务失败是依赖偶发抖动,还是某个工具从根本上不稳定;以及重试成功到底为平均延迟和成本增加了多少。

版本升级也要谨慎。Workflow 可能持续运行数天,旧任务的历史需要由旧代码解释,新任务才使用新逻辑。实际落地时要为流程定义做版本兼容或迁移策略,不要直接修改一个正在执行的分支含义。

什么时候不值得上 Durable Execution

它不是所有 Agent 的默认基础设施。一次性问答、无副作用的摘要、几秒内完成的简单分类,普通请求加超时和日志就够了。

引入工作流平台会增加服务部署、事件存储、版本迁移和运维成本。只有当任务真的跨步骤、跨时间、跨服务,且失败恢复的价值高于基础设施成本时,才值得采用。

落地清单

  • 把每一步标成“可重试”“不可重试”或“需要人工确认”。
  • 为所有外部副作用设计幂等键和状态查询接口。
  • 把模型调用、工具调用、解析和业务写入拆成可观测的 Activity。
  • 记录模型版本、提示模板版本、工具参数和返回摘要。
  • 为 Workflow 设置总时限、单步时限和最大重试次数。
  • 设计暂停、恢复、取消和人工接管,而不只是成功路径。

持久化执行的核心价值,可以用一句话概括:Agent 不再是一段“运行时可能忘记一切”的循环,而是一条可以暂停、恢复、审计和接管的业务流程。

一手资料

KEEP READING