Appearance
长任务恢复策略专题
版本:
v1.1最后更新:
2026-07-07
只要 Agent 开始处理长文档、多步骤审批、批量任务、跨系统编排或后台异步执行,就一定会遇到一个问题:
- 任务做到一半断了,接下来怎么办
如果系统只能“失败后全量重跑”,那么一旦任务链路变长、依赖变多、外部副作用变重,成本、时延、稳定性和人工运维压力都会一起上来。长任务恢复策略的目标,不是把任务永远跑成功,而是让系统在中断、重试、重启、审批暂停、工具失败和人为接管时,仍然知道自己现在在哪里、下一步该做什么、哪些动作绝不能重复执行。
1. 什么叫长任务恢复
更实用的定义不是“任务失败后再试一次”,而是:
- 任务有可识别的生命周期
- 中间状态可持久化
- 失败后能区分重试、续跑、补偿、取消和人工接管
- 恢复后能证明状态没有被破坏
换句话说,恢复策略本质上是一份执行契约,而不是一段兜底代码。
2. 为什么 Agent 长任务比普通异步任务更难恢复
传统异步任务常见难点主要是:
- 执行时间长
- 依赖远端服务
- 工作者重启后任务丢失
Agent 长任务还会叠加更多复杂性:
- 模型推理和工具调用交替出现
- 中间结论并不总是最终输出,但会影响后续决策
- 人工审批可能把流程暂停数分钟到数天
- 某些步骤有外部副作用,例如发消息、建工单、扣减额度、写 CRM
- 任务可能按批次、游标、文件分片或对话回合渐进推进
所以恢复策略必须同时回答三件事:
- 当前进度在哪里。
- 这一步能不能安全重做。
- 如果不能重做,系统如何跳过、对账或补偿。
3. 哪些任务最应该优先做恢复设计
建议优先覆盖这些场景:
background mode后台长任务- 批量文件理解、切片、结构化抽取
- 多工具 Agent workflow
- 跨审批节点的业务流程
- 大规模评测、回放和重算任务
- Realtime 会话里派生出来的后台补全任务
只要一条链路同时满足“耗时长、步骤多、有副作用、无法接受全量重跑”中的两个条件,就值得单独设计恢复策略。
4. 先建立统一的任务状态模型
恢复能否成立,首先取决于系统是不是知道自己当前在哪个状态。建议至少维护这些字段:
| 字段 | 作用 |
|---|---|
task_id | 业务任务标识,跨重试保持稳定 |
run_id | 本次执行实例标识,用来区分不同尝试 |
workflow_version | 当前工作流定义版本 |
state | 任务当前状态,如 queued、running、awaiting_approval、failed、completed |
step_id | 当前或最近完成的步骤 |
cursor | 批量处理进度,如文件分片、页码、记录偏移量 |
checkpoint_ref | 最近一次持久化快照或状态引用 |
side_effect_log | 已执行外部动作的记录 |
retry_count | 当前步骤或任务的重试次数 |
owner | 当前责任人或责任服务 |
last_error | 最近一次错误摘要 |
如果没有这些最小字段,所谓恢复通常只能靠日志猜。
5. 长任务不要只分“成功”和“失败”
更可执行的状态模型一般至少包含:
queuedrunningawaiting_tool_resultawaiting_approvalpausedretryable_failednon_retryable_failedcompensatingcancelledcompleted
这样设计的价值在于,系统可以明确表达:
- 现在是在等人,还是在等工具
- 是允许自动续跑,还是必须人工判断
- 是已经开始补偿,还是仍可继续前进
6. 恢复点应该放在哪些位置
恢复点不是越多越好,而是应该放在“边界清晰、价值高、能稳定对账”的位置。
更适合设置 checkpoint 的位置通常是:
- 工具调用前后
- 模型输出已经结构化并完成校验后
- 批处理某一分片完成后
- 检索与 rerank 结果确定后
- 审批发起后与审批完成后
- 外部副作用执行前后
一个简单原则是:
- 计算密集但无副作用的步骤,可以少量粗粒度 checkpoint
- 有副作用的步骤,必须有细粒度且可对账的 checkpoint
这里还要额外注意一个常见误区:
- “框架支持持久化”不等于“中途崩溃一定能从最近一步恢复”
以 LangGraph 的 checkpointer 文档为例,exit 持久化模式只会在图执行退出时统一落盘,包括正常结束、报错退出或 human-in-the-loop interrupt;如果进程在中途崩溃,尚未落盘的中间状态并不会自动可恢复。所以恢复设计不仅要问“有没有 checkpoint”,还要问:
- checkpoint 什么时候刷盘
- 是步骤级刷盘,还是只在整段执行退出时刷盘
- worker 宕机时最多会丢掉哪一段进度
7. 四种最常见的恢复方式
7.1 全量重试
适合:
- 任务短
- 幂等性强
- 成本低
- 中间状态价值不高
不适合:
- 模型推理成本高
- 任务已产生外部副作用
- 某些步骤依赖人工审批或外部时窗
7.2 断点续跑
适合:
- 任务可拆步骤
- 每步输入输出可持久化
- 上一步结果已稳定
这是 Agent 长任务中最常见、也最值得优先建设的方式。
7.3 补偿后继续
适合:
- 某个副作用已执行,但后续失败
- 不能简单重做
- 有明确反向动作或对账动作
例如:
- 已创建外部工单,需要先取消或标记失效
- 已写入 CRM,需要回滚或生成对账任务
7.4 转人工接管
适合:
- 自动恢复无法判断当前真实状态
- 重复执行有明显业务风险
- 安全、财务、合规动作需要人拍板
人工接管不是失败,它是恢复策略的一部分。
8. 先给步骤分类型,再决定恢复方式
不是所有步骤都该同一种恢复策略。建议至少区分四类:
8.1 纯计算步骤
例如:
- 文本清洗
- 嵌入计算
- 内容摘要
特点:
- 无外部副作用
- 可重算
- 更适合自动重试或续跑
8.2 外部读取步骤
例如:
- 调第三方检索 API
- 拉取用户资料
- 查询审批状态
特点:
- 没有写副作用,但结果可能随时间变化
- 恢复时要注意时效性和缓存失效
8.3 外部写入步骤
例如:
- 发消息
- 建工单
- 扣积分
- 改状态
特点:
- 幂等要求最高
- 必须记录
request_id、外部对象 ID 和写入结果
8.4 人机协同步骤
例如:
- 等待人工审批
- 等待业务确认
- 进入人工纠偏工作台
特点:
- 恢复不是继续推理,而是恢复上下文和决策现场
9. 幂等性是恢复策略的底层前提
很多系统以为自己做了恢复,实际上只是反复重放副作用。
只要任务里存在外部写动作,就要尽量满足:
- 每个动作有稳定的幂等键
- 能记录动作是否已成功落地
- 能根据外部返回结果判断是“已执行”还是“未执行”
建议至少对这些对象做幂等设计:
- 外部消息发送
- 审批单创建
- 工单创建
- 账务或额度相关动作
- 知识库发布与索引切换
否则系统一旦在写成功但本地状态未落盘时崩溃,就会出现经典问题:
- 不知道该不该重发
- 重发又可能造成真实重复操作
10. 批处理任务要有游标和分片语义
长任务很常见的一类是:
- 扫描大量记录
- 处理大文件
- 对数据集逐项评测
这类任务如果没有游标,恢复就只能从头开始。建议显式记录:
- 当前批次号
- 当前分片号
- 已完成对象列表或范围
- 失败对象列表
- 最后一次成功提交点
更稳妥的做法是:
- 对每个分片单独产出结果
- 主任务只负责汇总
- 单分片失败不拖垮整批
11. 人工审批会把恢复策略变成“暂停与恢复”
OpenAI 的长任务和 Agent 官方资料都强调后台执行、异步完成和人工审批节点的重要性。放到工程实现里,这意味着:
- 任务不只是执行和失败两种命运
- 很多时候它会停在
awaiting_approval - 真正需要恢复的是上下文、证据和下一步决策点
所以审批恢复应至少保留:
- 当前候选动作
- 模型给出的理由与证据
- 审批对象、时效和超时策略
- 审批前已经完成的步骤
- 审批被拒绝后的分支路径
这样审批人回来时看到的是“可继续执行的现场”,而不是一段已经失去上下文的日志。
12. 后台执行不等于天然可恢复
OpenAI 官方 Background mode 的核心价值是把长耗时工作从同步请求里移出去,但这并不自动等于恢复能力已经具备。
你仍然需要自己定义:
- 后台任务状态怎么持久化
- 成功、失败、取消和超时如何回写业务层
- 任务结果由轮询、Webhook 还是事件总线通知
- 中途失败后是重试整任务还是续跑某一步
如果只把同步请求改成异步任务,却没有状态持久化和恢复契约,那只是把问题从前台移到了后台。
13. 建议把“恢复决策树”写进 runbook
每个长任务都建议有一套统一判断顺序:
- 当前失败发生在哪个步骤。
- 这个步骤是否有外部副作用。
- 该副作用是否确认已落地。
- 是否具备幂等键或外部对象 ID。
- 是否可以直接从最近 checkpoint 续跑。
- 是否必须先补偿。
- 是否应转人工接管。
当团队把这套判断写成 runbook 后,值班处理速度会明显快于“边看代码边猜”。
14. 任务所有权、租约和心跳要单独设计
很多恢复失败,并不是因为没有 checkpoint,而是因为系统根本说不清“现在到底谁在执行这个任务”。
只要存在这些场景,就要考虑任务所有权:
- 多个 worker 可能抢同一任务
- worker 重启后旧实例可能还在误判自己活着
- 审批通过后恢复执行的人和最初执行的人不是同一个进程
- 后台任务跨班次、跨实例迁移
建议至少单独维护:
lease_ownerlease_expires_atheartbeat_atresume_requested_atresume_token或恢复授权标识
这样才能解决几个关键问题:
- 某个实例失联多久后可以被接管。
- 恢复时谁有资格继续跑。
- 同一个恢复动作如何避免被重复领取。
恢复策略如果没有所有权设计,最终很容易变成:
- 旧实例继续写
- 新实例也开始写
- 两边都觉得自己在做正确的事
15. 会话状态不要只靠日志回放,最好保留可续接上下文
长任务里的模型状态,不只是最终文本输出,还包括:
- 之前的 tool calls
- tool results
- 当前决策点之前的关键上下文
- 继续执行所需的响应链
根据 OpenAI Conversation state 文档在 2026-07-07 可访问的说明,可以通过 previous_response_id 在 Responses API 里延续上下文链;如果无法解析该 ID,则需要显式回传完整上下文。
这对恢复策略有很直接的影响:
- 不能默认平台一定替你永久保存可恢复上下文。
- 应用层要知道哪些内容必须自持久化。
- 恢复时要区分“继续原链路”和“基于摘要重建现场”。
根据 OpenAI Background mode 文档在 2026-07-07 可访问的说明,后台模式会为了轮询保存响应数据大约 10 分钟,因此它适合承接长耗时任务,但不能把“短期可轮询”误解成“长期可恢复历史”。
更稳妥的做法通常是:
- 短时恢复依赖平台返回对象
- 中长期恢复依赖自己保存的 checkpoint、摘要和外部对象引用
- 如果上下文链已经不可续接,就显式走“重建上下文后续跑”的路径
16. 超长工作流要考虑历史裁剪与执行链续接
很多团队会把“长任务恢复”理解成失败后的重试,却忽略另一类问题:
- 任务没失败,但执行历史越来越长,最终把运行时拖垮
Temporal 官方文档明确给出了这类风险:单个 Workflow Execution 的 Event History 有大小与事件数量上限,官方推荐在历史接近阈值时使用 Continue-As-New,把当前状态显式传给新的执行链,让同一个 workflow_id 在新的 run_id 上继续跑。
对 Agent 长任务来说,这个思路非常实用,因为以下对象都会持续膨胀:
- tool call 历史
- 审批往返记录
- 批量分片完成事件
- 重试、补偿和人工接管事件
- 恢复校验与观测事件
更稳妥的做法不是把所有回合都塞进一个永不结束的 run,而是:
- 保持稳定的
task_id或workflow_id - 允许
run_id分段轮换 - 在切换前产出明确的 carry-over state
- 让新 run 只接住“继续执行所需的最小状态”
建议额外持久化这些字段:
| 字段 | 作用 |
|---|---|
continued_from_run_id | 标记本次 run 由哪个 run 续接而来 |
continue_reason | 为什么触发执行链切换,例如历史过长、审批跨天、上下文重建 |
carry_state_ref | 续接所需的最小状态快照 |
history_compacted_at | 何时完成历史裁剪 |
如果系统天然支持长历史续接,可以直接复用框架能力;如果不支持,也建议在应用层自己做“逻辑上的 Continue-As-New”,否则超长任务后期的恢复复杂度只会越来越高。
17. 部分结果要能提前暴露,但要和最终结果分层
长任务不是只有“开始”和“结束”两个时刻。对用户和运维同学来说,更有价值的是:
- 现在任务卡在哪
- 已经产出了哪些阶段性结果
- 这些结果是预览、草稿,还是最终可提交版本
OpenAI 的 Background mode、Deep research 等官方资料都强调了长任务的异步执行、轮询和 webhook 回调能力。这意味着工程上通常应该把“任务状态”和“阶段性产物”拆开管理,而不是等全部结束后一次性吐出最终结果。
建议把部分结果设计成正式对象,而不是临时日志:
| 字段 | 作用 |
|---|---|
artifact_id | 阶段性产物标识 |
artifact_version | 产物版本,便于替换和回放 |
provisional | 是否仍属暂存结果 |
visible_to_user | 是否允许前台可见 |
derived_from_checkpoint | 该产物对应哪个恢复点 |
finalized_at | 何时转为最终版本 |
这样做有三个直接收益:
- 恢复时能先展示已有产物,不必假装“一切从零开始”。
- 人工接管时能看到已完成部分,而不是只看到失败提示。
- 系统可以把“部分完成”与“最终完成”区分清楚,避免误把草稿当成正式结果。
18. 恢复演练要常态化,不要等事故时第一次走通
恢复策略最怕的不是文档没写,而是:
- 文档写了,但真实故障来临时没人确认它真的可用
所以长任务系统应该像灾备演练一样,定期做恢复演练。建议覆盖这些最容易暴露问题的场景:
- worker 在工具调用后、落盘前崩溃
- 外部接口超时但实际已经落地
- 审批节点跨天恢复
- 相同恢复请求被重复投递
- 历史 checkpoint 可读,但关联产物缺失
- 续跑时发现权限、租约或上下文版本已经变化
演练的目标不是证明“系统永远没问题”,而是验证这些关键问题:
- runbook 是否真的可执行
- 值班同学是否知道该查哪些对象
- 恢复指标是否能被正确观测
- 人工接管入口是否真的能接住异常链路
建议把演练本身也记成结构化记录,例如 drill_id、scenario、expected_recovery_path、actual_outcome、followup_action,否则每次演练都会变成一次性经验。
19. 毒丸任务要隔离,不要无限重试
有些任务不是“再试一次也许能过”,而是:
- 输入本身损坏
- 外部契约已经漂移
- 权限长期不满足
- 业务前置条件根本没成立
这类任务如果继续自动重试,只会造成三种后果:
- 不断占用 worker 和配额
- 放大重复副作用风险
- 把真正需要人工处理的问题埋在重试噪音里
更成熟的做法是引入 quarantine 语义。达到阈值后,不再进入普通重试队列,而是转入隔离区并等待明确处置。
建议至少维护:
| 字段 | 作用 |
|---|---|
quarantine_reason | 进入隔离区的主因 |
quarantined_at | 隔离时间 |
last_safe_checkpoint | 最近确认安全的恢复点 |
review_owner | 下一位负责判断的人或队列 |
release_condition | 什么条件满足后允许重新放回执行队列 |
这样系统才能把“暂时失败、可以自动恢复”和“已经变成毒丸、必须人工决策”分开治理。
20. 恢复后的第一件事不是继续跑,而是先验真
很多系统恢复失败,不是因为没恢复起来,而是因为恢复后没校验状态一致性。
建议至少做三类校验:
20.1 进度一致性
- 当前
step_id是否和持久化记录一致 - 批处理游标是否跳步或回退
20.2 副作用一致性
- 外部系统对象是否已创建
- 本地记录是否和外部状态对得上
20.3 质量一致性
- 模型输出是否仍满足结构化校验
- 回滚或续跑后,关键基线样例是否恢复
21. 可观测性必须覆盖恢复过程,而不只是最终状态
对长任务而言,只有“任务成功/失败”远远不够。建议最少记录这些事件:
- 任务创建
- 任务开始
- checkpoint 写入
- 工具调用开始/完成/失败
- 审批发起/通过/拒绝/超时
- 自动重试
- 人工接管
- 补偿开始/完成/失败
- 恢复成功
- 恢复放弃
如果没有这些事件,后续做事故复盘、补偿对账和恢复优化都会非常痛苦。
22. 关键指标建议单独监控
建议至少补齐这些指标:
resume_success_ratecheckpoint_coverageavg_recovery_timehuman_handoff_ratereplay_duplicate_side_effect_ratecompensation_success_rateapproval_timeout_rateper_step_retry_ratebatch_partial_failure_ratequarantine_ratecontinue_as_new_rate
这些指标能帮助团队分清楚:到底是系统太容易中断,还是恢复机制本身不可靠。
23. 常见反模式
23.1 只靠超时后全量重试
任务越长,这种策略越贵,也越危险。
23.2 没有 side effect log
一旦进程在外部写成功后本地崩溃,就无法判断该不该重放。
23.3 把审批当成普通等待
审批暂停往往会跨人、跨班次、跨天,不保留上下文就谈不上恢复。
23.4 checkpoint 粒度过粗
失败后虽然能恢复,但恢复点离当前太远,成本依然高。
23.5 恢复后不校验
任务“继续跑了”不代表状态就真的正确了。
23.6 把隔离任务继续塞回普通重试队列
这会让毒丸任务长期污染吞吐、告警和人工排障视野。
24. 推荐搭配阅读
25. 重点官方资源
以下资源是本次补写时重点参考的官方资料,适合继续补强后台执行、状态持久化、人工中断恢复和长任务编排设计:
- OpenAI Background mode:https://developers.openai.com/api/docs/guides/background
- OpenAI Deep research guide:https://developers.openai.com/api/docs/guides/deep-research
- OpenAI Running agents:https://developers.openai.com/api/docs/guides/agents/running-agents
- OpenAI Agents SDK overview:https://developers.openai.com/api/docs/guides/agents
- OpenAI Conversation state guide:https://developers.openai.com/api/docs/guides/conversation-state
- OpenAI Batch guide:https://developers.openai.com/api/docs/guides/batch
- OpenAI Text generation guide(
previous_response_id链接上下文):https://developers.openai.com/api/docs/guides/text - OpenAI Data controls(后台结果保留与存储边界):https://developers.openai.com/api/docs/guides/your-data
- Anthropic Context windows:https://docs.anthropic.com/en/docs/build-with-claude/context-windows
- Anthropic Prompt caching:https://docs.anthropic.com/en/docs/build-with-claude/prompt-caching
- LangGraph Persistence:https://docs.langchain.com/oss/python/langgraph/persistence
- LangGraph Interrupts:https://docs.langchain.com/oss/python/langgraph/interrupts
- LangGraph Checkpointers:https://docs.langchain.com/oss/javascript/langgraph/checkpointers
- Temporal Workflow Execution limits:https://docs.temporal.io/workflow-execution/limits
- Temporal Continue-As-New:https://docs.temporal.io/workflow-execution/continue-as-new
- Temporal Activity idempotency / retry guidance:https://docs.temporal.io/develop/python/best-practices/error-handling
26. 落地检查清单
- 是否为每类长任务定义了稳定的
task_id、run_id和状态模型 - 是否在关键边界设置了 checkpoint,而不是只在任务开始和结束记状态
- 是否确认 checkpoint 真的是“中途崩溃可恢复”的刷盘语义,而不是只在整段执行退出时持久化
- 是否对所有外部写动作设计了幂等键和 side effect log
- 是否设计了任务所有权、租约和心跳,而不是默认单实例永远在线
- 是否显式区分可重试失败、不可重试失败、审批暂停和人工接管
- 是否为批处理任务记录了游标、分片和部分失败结果
- 是否为超长执行链设计了历史裁剪、分段续接或 Continue-As-New 机制
- 是否为审批与人工纠偏保留了完整上下文,而不是只留一条失败日志
- 是否能把阶段性产物作为正式对象对外暴露,并明确区分暂存结果与最终结果
- 是否对重复失败任务做隔离治理,而不是无限重试
- 是否定期做恢复演练,而不是只在事故发生后第一次验证 runbook
- 是否记录了恢复相关事件和指标,能支持复盘与持续优化