[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"$fwGl7p4ehDuS-YyVZXTGzXMZaFvTL49Es5wFEGZstBWo":3},{"item":4,"related":44},{"id":5,"type":6,"title":7,"slug":8,"summary":9,"body":10,"coverUrl":11,"productScreenshots":12,"productLinks":13,"authorName":14,"authorUrl":15,"authorSubject":16,"category":17,"tags":22,"sourceLabel":35,"sourceName":36,"sourceUrl":36,"status":37,"seoTitle":36,"seoDescription":36,"canonicalUrl":36,"isFeatured":38,"sno":39,"sortOrder":40,"publishedAt":41,"updatedAt":42,"createdAt":43},"03ca8001-f2c1-4b68-9a9a-1d2225483c68","article","Durable Execution：如何让长任务 Agent 崩溃后接着工作","durable-execution-for-long-running-agents","长任务 Agent 会遇到进程崩溃、网络故障、工具超时和人工等待。本文用 Workflow、Activity 与 Event History 拆解 Durable Execution，解释它与普通重试的区别、幂等副作用的边界，以及如何设计可暂停、恢复和审计的 Agent 工作流。","## Agent 最难的不是“会思考”，而是“不会丢进度”\n\n一个 Agent 要完成“调研供应商、比较报价、提交审批”这样的任务，往往需要十几个步骤。模型调用可能超时，工具服务可能暂时不可用，执行进程可能在第八步重启，用户也可能隔几个小时才回来确认。\n\n如果系统只把整个过程写成一段普通函数，失败后通常只能从头再来。这不仅浪费时间和模型调用，还可能重复扣款、重复发邮件或重复创建订单。\n\nDurable Execution（持久化执行）提供了另一种思路：把长任务写成可恢复的工作流，持续保存每一步的状态和结果。进程挂掉后，系统从最近一次完成的位置继续，而不是把 Agent 当成一次性请求重新启动。\n\n![Temporal 工作流项目概念图](https:\u002F\u002Fopengraph.githubassets.com\u002F1\u002Ftemporalio\u002Ftemporal)\n\nGoogle 的 Gemini 官方示例使用 Temporal 构建可恢复的 Agent 循环：模型调用和工具调用作为可重试的活动执行，工作流负责组织顺序和状态。[Temporal 官方文档](https:\u002F\u002Fdocs.temporal.io\u002F)把这种能力概括为让应用在崩溃、网络故障或基础设施中断后从原位置恢复。\n\n## 重试不等于持久化执行\n\n最简单的重试是：请求失败后，再调用一次同一个函数。\n\n但长任务通常有多个步骤：\n\n```text\n读取客户资料 → 查询库存 → 生成报价 → 请求审批 → 创建订单 → 发送通知\n```\n\n如果“创建订单”之后通知服务超时，系统无法判断订单到底创建成功没有。此时直接重试，可能得到两个订单；不重试，用户又可能永远收不到通知。\n\n持久化执行关注的不是“把异常捕获住”，而是把工作流历史、每一步的输入输出和重试状态保存下来。系统可以知道哪些步骤已经完成，哪些步骤还没有拿到确定结果。\n\n不过，持久化执行也不会自动把外部世界变成 exactly-once。数据库写入、付款、发邮件等外部副作用仍然需要幂等键、去重表或业务状态机配合。它解决的是“执行进度可恢复”，不是“所有外部系统天然只执行一次”。\n\n## 三个核心概念\n\n### Workflow：稳定的流程骨架\n\nWorkflow 描述任务的顺序、分支、等待和超时。例如：先并行查询三个供应商，等用户选择后再发起审批。它应该尽量保持确定性，因为系统可能会根据历史事件重放 Workflow 代码。\n\n### Activity：可以失败的具体动作\n\nActivity 承担模型调用、HTTP 请求、数据库读写、文件处理等不稳定工作。它们可以单独设置超时、重试策略和并发限制，也可以记录调用结果。\n\n### Event History：可重放的执行历史\n\n每完成一步，系统都会留下事件。恢复时，Workflow 根据历史跳过已完成的 Activity，重新计算下一步应该做什么。对 Agent 来说，这相当于把“上下文”从一段容易丢失的内存，变成可审计的执行记录。\n\n```mermaid\nflowchart TD\n    Q[\"用户提交长任务\"] --> W[\"启动持久化 Workflow\"]\n    W --> A1[\"Activity：读取资料\"]\n    A1 --> A2[\"Activity：调用模型与工具\"]\n    A2 --> H{\"需要人工确认?\"}\n    H -->|是| P[\"等待外部信号\"]\n    P --> A3[\"Activity：执行副作用\"]\n    H -->|否| A3\n    A3 --> C[\"记录完成事件\"]\n    C --> D[\"返回结果\"]\n    A2 -. \"进程崩溃或网络失败\" .-> R[\"从最近事件恢复并重试\"]\n    R --> A2\n```\n\n## Agent 为什么特别需要这个能力\n\n传统 CRUD 请求通常几百毫秒到几秒就结束，失败后重新请求的代价有限。Agent 任务则经常包含：\n\n- 多轮模型调用，且每轮可能选择不同工具。\n- 长时间等待人工审批、第三方回调或定时条件。\n- 需要跨越多个服务，任何一个依赖都可能短暂失败。\n- 不能重复执行的外部副作用。\n\n例如“整理一批合同并生成风险清单”可以分成：上传文件、解析文本、并行抽取条款、合并结果、人工复核、生成报告。解析到第六份文件时进程崩溃，如果前五份结果已经被保存，系统就不该从第一份重新开始。\n\n## Agent 工作流应该怎样拆\n\n一个实用原则是：**模型负责做判断，Workflow 负责保存进度，Activity 负责接触外部世界。**\n\n可以把 Agent 循环写成下面的逻辑：\n\n```python\nwhile not task_done:\n    decision = await call_model(state)\n    if decision.kind == \"tool_call\":\n        result = await run_tool_activity(decision.tool, decision.args)\n        state = update_state(state, result)\n    elif decision.kind == \"needs_human\":\n        await wait_for_signal()\n    else:\n        return decision.answer\n```\n\n这里的 `call_model` 和 `run_tool_activity` 不应被当成普通内存函数。模型请求应有超时、重试和版本记录；工具调用应有幂等键、权限检查和结果快照；`state` 应能在任务恢复后重新获得。\n\n对于高风险动作，最好把“决定要做”和“真正执行”拆成两步：先生成待确认计划，再由用户或策略引擎发出批准信号。这样 Agent 可以长时间等待，而不需要占用一个一直在线的 HTTP 请求。\n\n## 重放为什么要求 Workflow 保持确定\n\n持久化引擎恢复任务时，可能会重放 Workflow 代码，让它重新读取历史事件并走到当前节点。因此 Workflow 里不应该直接调用随机数、当前时间、网络请求或 LLM。否则同一份历史在第二次计算时得到不同分支，系统就无法判断哪些 Activity 已经执行过。\n\n正确的拆法是：Workflow 只负责调度，外部世界交给 Activity。当前时间可以由引擎提供一个可重放的时间值；随机 ID 可以在 Workflow 外生成后作为输入；模型调用必须作为 Activity 保存请求和结果。伪代码看起来相似，但责任边界不同：\n\n```python\n@workflow\nasync def order_workflow(request):\n    plan = await execute_activity(make_plan, request)\n    await workflow.wait_condition(lambda: workflow_state.approved)\n    result = await execute_activity(create_order, plan)\n    await execute_activity(send_notification, result)\n    return result\n```\n\n这里的 `make_plan`、`create_order` 和 `send_notification` 都可能失败，但 Workflow 本身只在事件历史上推进。尤其是 `send_notification`，不能因为它超时就假设“肯定没发出去”；它需要一个业务侧的幂等键，例如 `workflow_id + step_name`，让重复尝试最终只产生一条通知。\n\n## 幂等、副作用与“结果未知”\n\n工程上最危险的不是明确失败，而是**结果未知**：客户端发出创建订单请求，连接在服务端返回之前断开。此时 Agent 无法仅靠异常判断订单是否存在。\n\n常见处理方式是把副作用设计成三段：\n\n1. 生成全局幂等键，并把它写入请求。\n2. 服务端在事务中记录“幂等键 → 业务结果”。\n3. 重试前先用幂等键查询；如果已有结果，直接复用，不再创建新副作用。\n\n邮件、支付、工单、仓储扣减都可以采用类似模式。若第三方 API 不支持幂等键，就要在自己的系统里增加状态表或中间层，至少能够区分“尚未执行”“执行中”“已确认成功”和“需要人工核查”。\n\n这也是为什么 Durable Execution 不能单独解决一致性问题：它能可靠地恢复你的流程，却无法替你修改银行、邮件服务或供应商系统的语义。\n\n## 人工确认其实是工作流的一部分\n\n很多 Agent Demo 把人工确认做成一个同步接口：模型问“要不要继续”，用户必须立刻回答。生产系统更常见的情况是用户关掉页面，第二天才点批准。\n\n持久化 Workflow 可以把等待设计成显式状态：\n\n```text\n准备计划 → 等待审批 → 已批准 \u002F 已拒绝 \u002F 已过期\n```\n\n用户批准时发送一个带有 `workflow_id`、审批人、审批时间和审批版本的信号。执行前再次检查计划是否被修改、权限是否仍然有效、价格或库存是否过期。这样“用户点过同意”不会被误当成对任何未来状态都永久授权。\n\n还可以设置补偿动作。例如订单已创建但通知失败，补偿动作不是删除订单，而是把通知标记为待补发；如果支付已扣款但库存预留失败，则进入人工处理队列，而不是让 Agent 自己随意退款。\n\n## 重试策略不能一刀切\n\n不同错误应该使用不同策略：\n\n- **网络暂时不可用**：指数退避后重试。\n- **限流**：尊重服务端的 Retry-After，并降低并发。\n- **参数错误**：先让 Agent 修正参数，不要盲目重复。\n- **权限错误**：暂停并请求用户授权。\n- **副作用结果未知**：先查询业务状态，再决定是否重试。\n\n模型调用通常可以重试，但重试也可能产生不同答案。若下游依赖结构化输出，恢复时应保存原始响应、解析结果和模型版本，避免同一任务在重放中悄悄换成另一种决策。\n\n建议把每次重试都记录成一个可查询的事件，而不是只在应用日志里写一行“retry”。至少保留：步骤名、尝试次数、错误类别、等待时长、依赖版本和最终结果。这样可以回答两个很实际的问题：一次任务失败是依赖偶发抖动，还是某个工具从根本上不稳定；以及重试成功到底为平均延迟和成本增加了多少。\n\n版本升级也要谨慎。Workflow 可能持续运行数天，旧任务的历史需要由旧代码解释，新任务才使用新逻辑。实际落地时要为流程定义做版本兼容或迁移策略，不要直接修改一个正在执行的分支含义。\n\n## 什么时候不值得上 Durable Execution\n\n它不是所有 Agent 的默认基础设施。一次性问答、无副作用的摘要、几秒内完成的简单分类，普通请求加超时和日志就够了。\n\n引入工作流平台会增加服务部署、事件存储、版本迁移和运维成本。只有当任务真的跨步骤、跨时间、跨服务，且失败恢复的价值高于基础设施成本时，才值得采用。\n\n## 落地清单\n\n- 把每一步标成“可重试”“不可重试”或“需要人工确认”。\n- 为所有外部副作用设计幂等键和状态查询接口。\n- 把模型调用、工具调用、解析和业务写入拆成可观测的 Activity。\n- 记录模型版本、提示模板版本、工具参数和返回摘要。\n- 为 Workflow 设置总时限、单步时限和最大重试次数。\n- 设计暂停、恢复、取消和人工接管，而不只是成功路径。\n\n持久化执行的核心价值，可以用一句话概括：Agent 不再是一段“运行时可能忘记一切”的循环，而是一条可以暂停、恢复、审计和接管的业务流程。\n\n## 一手资料\n\n- [Temporal 官方文档](https:\u002F\u002Fdocs.temporal.io\u002F)\n- [Gemini + Temporal 的 Durable AI Agent 示例](https:\u002F\u002Fai.google.dev\u002Fgemini-api\u002Fdocs\u002Ftemporal-example?hl=en)\n- [Temporal Durable Execution 介绍](https:\u002F\u002Ftemporal.io\u002Fhow-it-works)","\u002Fuploads\u002F2026-08-05\u002F6ea439e7-4d7e-46cc-ace8-efc556719f28.jpg",[],[],"Foundit AI","https:\u002F\u002Ffoundit.cn","f39339b1-aaa6-4e86-b0c2-a6e6a21113b5",{"id":18,"name":19,"slug":20,"description":21},"6179d3b6-dc34-4483-9ded-3cd9f1b37a47","科普","abbreviation","介绍各领域新兴概念",[23,27,31],{"id":24,"name":25,"slug":26},"88d2bc27-0e0f-468a-b907-2991cb97b87b","人工智能","ai",{"id":28,"name":29,"slug":30},"4c2bbea6-eab7-40a8-8447-1de478ff7749","分析","analyse",{"id":32,"name":33,"slug":34},"7c76bfc2-f80f-4ee0-a95d-27bd8708b434","技术","slug","资料来源",null,"published",false,65,0,"2026-08-04T00:00:00.000Z","2026-08-05T03:15:43.428Z","2026-08-05T02:13:44.156Z",[45,55,64],{"id":46,"type":6,"title":47,"slug":48,"summary":49,"coverUrl":50,"authorName":51,"sno":52,"publishedAt":53,"createdAt":54},"f00e5274-8e0b-40fe-af3b-b6a8d0183b9c","一张贴纸就能骗过视觉 AI？对抗样本不是魔法","adversarial-examples-fool-visual-ai","人眼仍能认出的物体，经过精心设计的微小扰动或标记后，机器却可能改变判断。这类输入称为对抗样本。本文解释攻击者如何利用模型的决策边界，现实攻击为何比实验更难，以及为什么目前不存在一劳永逸的防御。","\u002Fuploads\u002F2026-09-08\u002F151f887b-c5f1-4647-b369-cdda8a1e1b4f.jpg","Foundit",50,"2026-09-03T00:00:00.000Z","2026-08-14T03:06:09.765Z",{"id":56,"type":6,"title":57,"slug":58,"summary":59,"coverUrl":60,"authorName":51,"sno":61,"publishedAt":62,"createdAt":63},"305492d4-c146-456a-b341-31140ab9cafd","天气预报不再一格一格算空气，AI 是怎么预测风暴的？","how-ai-weather-forecasting-works","传统数值预报依据物理方程推进大气状态，AI 天气模型则从历史观测与再分析数据中学习状态如何演变。本文以 GraphCast 为例，解释图神经网络如何快速预测全球天气、它与传统方法如何协作，以及极端天气仍有哪些难点。","\u002Fuploads\u002F2026-08-16\u002Fa8b5ddad-d6db-4d12-a159-87a6c5309082.jpg",60,"2026-08-16T00:00:00.000Z","2026-08-14T03:06:12.102Z",{"id":65,"type":6,"title":66,"slug":67,"summary":68,"coverUrl":69,"authorName":51,"sno":61,"publishedAt":70,"createdAt":71},"7f962194-e0c6-4072-9b50-c70054beb0e5","同一个问题问三遍，AI 为什么会给出三个答案？","why-ai-gives-different-answers-sampling","大模型每次回答都在从候选词中继续选择，而不是从数据库里取出一段固定文字。温度、Top-p 和随机采样共同决定回答更稳定还是更有变化。本文用抽签与岔路的比喻，解释 AI 的随机性从哪里来，以及什么时候应该追求一致。","\u002Fuploads\u002F2026-08-14\u002Fb322d338-b5c0-48c7-addf-fbd91421e316.jpg","2026-08-14T00:00:00.000Z","2026-08-14T03:06:07.351Z"]