Skip to content

子任务生命周期与失败恢复 ​

多 Agent 的任务管理负责记录每个子任务从分派到结束的状态,并决定失败后重试哪一项、哪些结果可以复用、整个任务何时结束。它由运行程序执行:Agent 可以提出重新分工或补查资料,程序负责检查任务状态、权限和剩余预算,再启动下一次执行。

假设用户要求比较两个搜索服务的部署条件。主管安排三个子任务:A 查服务甲,B 查服务乙,C 补充迁移案例,之后汇总成对照表。A 已交付,B 超时,C 还在运行。此时把整个流程重新跑一遍会重复 A 的工作;直接汇总,又可能把没有查到的服务乙写成“不支持”。

要继续这项任务,系统必须分清:哪项工作已经验收,哪次执行失败,以及缺失结果会不会阻止交付。

一个子任务可以有多次执行 ​

task 表示一项稳定的工作目标,attempt 表示为完成这项工作发起的一次执行。例如“查明服务乙的私有部署条件”可以保持同一个 task_id,第一次请求超时后,第二次执行使用新的 attempt_id。

text
run-42:比较搜索服务
├─ task-A:查服务甲
│  └─ attempt-A1:已完成,结果已验收
├─ task-B:查服务乙
│  ├─ attempt-B1:超时
│  └─ attempt-B2:执行中
├─ task-C:补充迁移案例
│  └─ attempt-C1:执行中
└─ task-S:汇总对照表,等待 A、B 的有效结果

同一个 Agent 可以执行多个 task,一个 task 也可以在重试时改派给另一个 Agent。任务身份不应依赖执行者名字,否则换人后就难以判断新旧结果是否属于同一项工作。

下面给应用任务表约定一组状态。具体名称可以变化,状态的含义和允许的转移需要固定。

task 状态含义何时离开这个状态
pending已创建,等待依赖或调度依赖满足且取得执行预算
running当前 attempt 正在执行返回结果、失败、需要补充输入或收到取消
waiting_input缺少必须由用户或上游提供的信息输入补齐后重新调度,或超时结束
retry_wait当前 attempt 已失败,允许稍后重试到达重试时间且预算允许
reviewing收到交付物,等待验收验收通过,或带着具体反馈重新执行
succeeded交付物满足子任务契约并已保存终态
failed无法按当前契约完成,且停止尝试终态
cancelled已结束该子任务的调度,不再接收其结果用于交付终态

一次 attempt 退出,并不直接把 task 标成 succeeded。例如 B 返回“未找到资料”时,模型调用可以正常结束,但还没有回答部署条件。主管需要判断是否查错来源、确实缺少信息,或应向用户澄清。验收依据来自任务契约,不能只看进程退出码或模型是否生成了最终回复。

任务记录保留当前状态,attempt 记录保留每一次执行的结果。B 的第二次执行开始后,记录可以是:

json
{
  "run_id": "run-42",
  "task_id": "task-B",
  "parent_task_id": "task-root",
  "plan_revision": 7,
  "input_revision": "sources-v3",
  "status": "running",
  "current_attempt_id": "attempt-B2",
  "accepted_result_ref": null,
  "required": true,
  "attempts": [
    {"attempt_id": "attempt-B1", "status": "timed_out"},
    {"attempt_id": "attempt-B2", "status": "running"}
  ]
}

plan_revision 表示主管当前采用哪版分工,input_revision 表示执行依据的资料或输入版本。两者分别回答“这项工作还需要做吗”和“它是否基于当前资料”。

跟着一次部分失败继续执行 ​

回到搜索服务调研。任务约定 A、B 是必需结果,C 是可选补充;没有迁移案例也可以交付部署条件对照表。执行过程如下:

顺序发生的事运行程序怎样处理
1主管建立第 7 版计划,分派 A、B、C保存依赖和验收要求,分别登记 attempt
2A 返回部署文档与结论验证来源和范围,保存 result-A1,A 进入 succeeded
3B1 等待文档服务超时B1 记为 timed_out,B 进入 retry_wait;A 的结果保留
4文档读取允许重试,剩余预算足够只启动 B2,更新 B 的 current_attempt_id
5B1 的响应迟到保存迟到事件和实际费用,但拒绝它覆盖 B2 的任务状态
6B2 返回有效来源与结论,验收通过保存 result-B2,B 进入 succeeded
7C 尚未结束,主管决定省去可选案例,计划更新为第 8 版确认 A、B 的目标和输入未变,将两份已验收结果纳入新计划;取消 C,启动汇总
8C1 返回案例记录为已取消任务的迟到结果,不加入正在生成的对照表
9汇总完成并通过检查父任务结束;记录已使用的结果和未提供的可选案例

这段流程保留了 A 的已完成工作,也保留了 B1 失败的历史。重试范围是 B 的执行,汇总依赖仍然指向逻辑任务 B,只有 B2 的结果通过验收后才满足依赖。

如果 B 的目标改成“验证服务乙在指定网络环境中的部署能力”,输入和验收要求都变了,就应创建新的任务版本或新 task。继续沿用原任务的成功状态,会让旧答案被误当成新问题的结果。

重试前先确认失败发生在哪里 ​

在上面的例子中,B 只是读取文档。临时网络错误可以在退避等待后重试,费用和调用次数仍计入同一父任务。已经成功的 A 不需要重新执行,只有依赖 B 的汇总节点继续等待。

其他失败需要不同处理。无访问权限要返回权限问题;缺少部署环境要补充输入;结果格式错误可以要求按契约重新交付。若主管分错了任务,重复发送同一任务描述通常不会解决问题,应先修改分工或验收要求。

涉及外部写入时,超时只能说明当前执行没有取得确认。比如子 Agent 已发送创建工单的请求,服务端可能已经创建成功。恢复程序应使用之前保存的业务操作标识查询状态,或按接口约定使用同一幂等键重试。换一个 attempt_id 不应顺便换掉同一业务动作的幂等键。

无法查明的写入需要保持“结果未知”,暂停依赖这一结果的后续动作。任务记录、调用记录和实际业务状态要分别核对。通用的超时、退避和幂等实现见 Agent 可靠性与故障防护。

迟到结果怎样失去写入资格 ​

B1 超时后,远程请求可能仍在处理。仅向旧执行者发出停止信号,无法保证它不再返回数据。因此,接收结果时需要重新核对任务的当前记录。

对 B2 的结果进行验收时,运行程序至少检查:

text
结果属于当前 run 和 task
结果的 attempt_id 等于 task.current_attempt_id
task 处于可接收结果的状态,当前 attempt 未超时或被撤销
结果所依据的计划和输入仍被当前任务接受
任务没有被取消,也没有被新任务替代
同一个结果尚未被接收

这些检查和“保存结果、推进状态”应在同一次原子操作中完成。先读取状态,隔一段时间再无条件写入,会给计划更新和取消留下竞争窗口。Response Job 中的条件写入与 fencing 思路可以复用到这里,只是判断对象从一次回答扩展成了父子任务。

失去写入资格的结果仍有排障价值,可以作为历史产物保存。它不能自动改变当前答案、任务成功状态或共享计划。若主管认为旧结果仍然可用,应检查它是否符合当前契约,再显式纳入当前计划;不能仅因为内容看起来相关,就绕过版本检查。

取消要传给整个子任务树 ​

用户取消父任务后,调度器先停止创建新子任务和新 attempt,再将取消意图传给所有未结束的后代。正在等待依赖或退避的任务可以直接停止;运行中的 Agent 则需要在模型调用、工具调用和状态写入的边界检查取消状态。能够取消的远程请求也应尝试取消。

取消过程需要保留“已请求取消”和“执行已停止”的区别。对不支持远程取消的调用,系统可以停止接收它的结果,但要继续记录调用的最终状态和费用。在未确认停止前,不应报告所有后台活动都已结束。

已经发生的副作用不会随任务取消自动回滚。假如子任务已经创建工单,取消父任务不等于删除工单;后续撤销需要业务接口支持,也需要相应授权。对照表生成这样的只读任务可以丢弃迟到输出,写入任务还必须确认最终业务状态。

当某个必需子任务失败时,也不一定立即取消所有兄弟任务。父任务可能仍要收集已完成结果,交付部分答案;也可能因为缺失项使整体任务失去用途,而终止剩余工作。这个选择由任务的完成要求决定。

父任务按交付要求结束 ​

子任务状态不能简单按“多数成功”汇总。A 和 C 成功、B 失败时,仍然缺少服务乙的部署条件,无法完成双方案比较。

当前结果父任务可以怎样结束
A、B 均已验收,C 缺失,汇总通过按原契约成功交付,说明没有提供可选案例
A 已验收,B 无法完成,允许交付部分结果返回服务甲的已知条件和服务乙的缺失项,标记部分完成
A 已验收,B 无法完成,要求必须完成双方案比较记录任务失败,保留 A 的产物供后续复用
A、B 都有结果,但版本不一致或结论冲突尚未解决继续核对或返回未解决项,暂不标记完整成功
用户已取消停止交付流程,返回取消状态及必要的副作用处理结果

“部分完成”需要对应具体缺失项。把 B 的空结果补成“服务乙没有该能力”,会把执行失败转成错误业务结论。汇总应从已验收的产物生成,并保留未完成事项。

子任务共享一份总预算 ​

主管、工人、审查和重试都消耗父任务的资源。若每个 Agent 都独立获得 12 次调用额度,启动三个 Agent 就会使总上限扩大,后续再派生子任务还会继续放大。

例如父任务最多允许 12 次模型调用,可以给 A、B 各分配 3 次,C 分配 1 次,为主管判断与最终汇总预留 3 次,另留 2 次处理重试。子任务可以申请追加额度,但新增额度必须来自未分配或已确认可收回的份额。B2 属于 B 的下一次执行,不会重置 B 或父任务的累计用量。

并行场景中,额度申请要通过同一预算记录原子扣减或预留,否则 A、B 可能同时读到“还剩 2 次”,各自启动 2 次调用。费用预算还需为在途调用预留预计上限,调用结束后按实际 usage 结算;尚未确认结束的取消请求不能立即按零费用释放预算。

时间预算也要给汇总留出空间。子任务的截止时间应早于父任务的最终截止时间。重试前若已不足以完成一次调用和汇总,就应返回已有产物及缺失项,不再开始下一轮。

进程重启后从已确认的状态恢复 ​

恢复父任务时,先读取任务树、当前计划、已验收产物、各 attempt 的执行记录,以及已经消耗和预留的预算。随后核对仍在运行的远程任务和外部操作,确认哪些只是本地断线,哪些确实需要再次执行。

以 B2 执行期间主管重启为例:如果 B2 已将产物保存并完成验收,新主管可以直接满足汇总依赖;如果 B2 还在远程执行,新主管应继续查询或订阅它;如果确认 B2 已失败且没有未知副作用,才创建 B3。租约过期可以触发接管,但不能证明旧 Worker 已经停止,旧 attempt 的写入资格仍需撤销。

恢复后的模型输入只需要当前目标、仍有效的分工、已确认结果和未完成项。聊天摘要中一句“调研差不多完成了”不能代替这些记录。Agent Harness 讨论了状态、上下文和产物的保存边界;多 Agent 在此基础上还需要逐个确认父子依赖,避免跳过未完成子项或重复执行已经完成的工作。

持续学习,持续实践。