长任务最容易出问题的地方不是规划,而是恢复
补档说明:本文属于「AI 工程落地周记」系列,计划发布时间为 2025-08-06 20:15。当前先保留为草稿,后续补充真实案例、代码片段和复盘细节后再发布。
现在大家做长任务系统,最容易把注意力放在“怎么规划得更聪明”上:
- 怎么拆步骤
- 怎么给模型更好的计划模板
- 怎么让 Agent 看起来更会思考
这些当然重要,但我后来在线上遇到更多真实事故后,结论越来越清楚:长任务真正先出问题的,通常不是规划,而是恢复。
因为只要任务跨分钟、跨系统、跨多个工具执行,失败就不是偶发事件,而是常态。你不可能要求:
- 外部接口永远不超时
- 模型永远不空输出
- 机器永远不断连
- 队列永远不抖动
这时候系统能不能继续跑下去,关键就不在“第一次计划得多漂亮”,而在“中途挂掉之后能不能从正确的位置接着干”。
一个长任务最怕的不是失败,是“不知道失败到了哪”
我印象很深的一次,是一条内容生产链路,单次执行可能会持续十几分钟:
- 收集输入资料
- 生成草稿
- 跑规则检查
- 进入人工待审
- 发布到站点
- 触发索引更新和通知
有一次任务在“发布成功、索引更新未完成”这个状态挂掉了。
这时最麻烦的不是重新跑,而是团队根本说不清:
- 页面到底有没有发出去
- 索引是不是已经开始写了
- 重跑会不会重复发通知
- 审核状态要不要重新走
也就是说,系统失败了并不可怕,可怕的是恢复点不明确。
为什么规划常被高估
规划之所以容易被高估,是因为它最容易展示:
- 屏幕上能看到“思考过程”
- 很容易给出分步计划
- 人会天然相信“计划越完整,执行越稳”
但长任务真正让系统变脆的,往往是下面这些更底层的问题:
- 状态是不是可持久化
- 每一步是不是可重入
- 副作用节点是不是幂等
- 中途重启后是不是知道从哪恢复
这几件事不补,计划再漂亮也只是第一次跑看着顺。
我现在把长任务设计成 4 个恢复问题
只要任务执行时间一长,我就会逼自己先回答下面 4 个问题:
1. 当前进度怎么被机器读懂
不能只靠模型上下文记住“已经做到哪里了”。
必须有外部状态,例如:
{
"jobId": "publish_20250806_01",
"status": "running",
"currentStep": "update_search_index",
"completedSteps": [
"collect_context",
"generate_draft",
"review_passed",
"publish_site"
],
"attempt": 2
}
2. 哪些步骤可以安全重跑
不是所有步骤都该统一重试。
我现在会把节点分成三类:
- 纯计算节点:可以直接重跑
- 幂等副作用节点:可以带幂等键重跑
- 非幂等副作用节点:必须人工确认或先查状态再继续
3. 恢复时需要的上下文是什么
很多系统喜欢把“恢复”理解成重新把整段历史上下文丢回模型。
这通常既贵又不稳。
我更倾向于恢复时只带:
- 当前任务目标
- 已完成步骤
- 上一步关键结果
- 需要继续执行的下一个节点
也就是把恢复上下文压成“机器可执行摘要”,而不是“聊天记录回放”。
4. 谁来判断任务真的恢复成功了
恢复不是把进程重新拉起来,而是确认系统重新回到一致状态。
例如:
- 页面已发布
- 索引已更新
- 通知未重复发送
- 审计日志补齐
没有这些验证,所谓恢复只是“程序又动了”。
一个更靠谱的恢复执行方式
后来我们把长任务执行器改成按 step checkpoint 驱动,而不是一口气跑到底:
async function resumeJob(jobId: string) {
const job = await jobStore.get(jobId)
switch (job.currentStep) {
case 'generate_draft':
return runGenerateDraft(job)
case 'update_search_index':
return runSearchReindex(job)
case 'send_notifications':
return runNotification(job)
default:
return markJobFailed(jobId, 'unknown_step')
}
}
这段代码的关键不在语法,而在思路:恢复从来不是重新开始,而是从明确状态继续。
长任务最容易漏掉的两个基础设施
心跳和租约
任务跑得久,就必须知道“它还活着吗”。
没有心跳,你根本分不清任务是慢、卡住了,还是执行器已经死了。
审计日志
恢复时最怕没有证据。
至少要能回答:
- 哪一步开始了
- 哪一步完成了
- 用了哪个输入版本
- 失败时错误是什么
这些信息比“完整思维链”更有用。
一个我现在很少再做的错误
以前我总想让模型自己重新规划,然后接着往下跑。
后来发现这会把恢复变成第二次不确定执行。
同一个任务第一次和第二次规划结果不一样时,你根本无法保证恢复后的一致性。
所以现在我更倾向于:
- 任务图由系统定义
- 模型只负责节点内推理
- 恢复由状态机驱动
这样系统才知道自己在恢复什么。
总结
长任务最容易出问题的地方不是规划,而是恢复。因为规划解决的是“第一次怎么跑”,恢复解决的是“出事以后系统还能不能接住自己”。
只要任务足够长、工具链足够多、外部依赖足够复杂,失败就一定会发生。真正把系统从 demo 带进生产的,不是更会计划,而是更会从错误状态里回来。
