任务系统
任务系统把大目标拆成带依赖的小任务,保存任务图,并在每次完成后保存进度。调度器只选择依赖全部完成的任务;进程重启后先读取记录,再继续工作。本课的检查点模型让崩溃只中断当前任务,已保存的完成项不重做。
如果计划只在 messages[] 等进程内存里,进程退出就可能丢掉完成记录。文件或外部效果可能仍在,但新进程不知道哪些任务已完成。本课相同的崩溃条件下,磁盘模式有 5 次完成执行,内存模式有 7 次,多出的两次是重做 t1、t2。
tasks.json 保存五个任务、依赖边和状态。初次分解后保存计划,每次 task.status = "done" 后再保存进度。重启时先 load,找不到记录才 decompose。
本课建立在智能体循环上,给执行循环增加调度器。子智能体在不同上下文间划分工作,本课则让工作跨进程生命周期继续。
依赖控制顺序,检查点保存完成状态
磁盘模式代码的两个关键位置是 next_ready(graph) 和完成后的 save。
next_ready 只返回依赖全为 done 的任务。本例 t1“搭建模块”解锁 t2“实现解析器”和 t3“编写测试”;t4“运行测试套件”同时依赖 t2、t3;t5“发布说明”依赖 t4。t2 完成而 t3 未完成时,t4 不在就绪集合中,循环不能选它。t2、t3 同时就绪是合法的;单工作者按较小 ID 选择,多工作者系统可以并行处理。
load("tasks.json") 放在 decompose 前,每次完成后 save。示例只在分解与完成时保存,因此检查点不记录 running,只记录“完成已登记”和“完成未登记”。崩溃时进行中的任务在文件里仍是 pending,重启后重试。真实操作可能已有部分副作用,重复执行必须靠幂等或恢复协议处理,不能把 pending 理解为外界什么都没发生。
内存模式使用同一任务图,只去掉持久读取与保存边界,其余调度一致。
任务系统逐步演示
默认使用 disk,在运行中模拟进程终止。右侧显示最近的文件检查点,与当前运行状态分开。
- 初始化。 目标是“交付解析器功能”,尚未分解。
- 分解。 生成 t1 → t2/t3 → t4 → t5,五任务、五条边,全部 pending。
- 保存。
save("tasks.json")让右侧出现五行 pending。计划进入检查点。 - 选 t1。 唯一没有依赖的任务,变为紫色 running。
- 完成 t1。 节点变绿,同一步文件行变为 done,表示先标记再保存的两行相邻代码。
- 解锁。 t1 的两条出边变绿,t2、t3 一起进入就绪集合。
- 选 t2。 两者都合法,较小 ID 胜出。
- 完成 t2。 文件已有 t1、t2 完成。t4 仍被阻塞,因为汇合节点只满足一条输入依赖。
- 选 t3。 开始编写测试。
- 崩溃。 t3 运行中进程被终止,运行图与顶部读数清空。文件仍保留 t1、t2 done、t3 pending。屏幕曾显示 running,但该状态未保存。
- 重启。 先
load("tasks.json"),从记录重建图,t1、t2 直接恢复为绿色,不重新分解或执行它们。 - 恢复。
next_ready按加载状态重算就绪集合,返回 t3。已保存完成项重做 0 次,未记录完成的 t3 重试。 - 完成 t3,解锁 t4。 两条依赖终于都满足。
- 完成后续。 选 t4、完成、保存、解锁 t5,再选 t5、完成、保存。
- 结束。 五个任务、五次完成执行,另有一次中断尝试。本例只重启被打断的任务。
改成 in-memory 并保留崩溃点,图和调度顺序不变,但重启没有记录可读。重新分解后五项都 pending,t1、t2 被再次执行,显示 again。最终五任务有七次完成执行。
把 scenario 设为 smooth,两种存储都无崩溃,执行结果相同。再把 crash after 从 0 调到 4:disk 的完成执行总数始终为 5,另重试一次中断尝试;内存模式从 5 增到 9,每丢失一项完成记录就多重做一次。0 表示尚无完成项可丢,4 表示四项完成工作被重复。
为什么必须在循环中保存
只在 decompose 后保存一次,只能保住五项 pending 的计划。崩溃后仍会重做所有工作。完成后的 save 才保存进度。在本课的理想化检查点中,文件最多落后一个正在处理的任务。
本例不需要持久 running。若只保存 running 却没有所有权或恢复规则,重启后无法判断该工作是否还由其他执行者处理。生产系统可以保存 running,但必须同时设计租约、所有者及失联恢复。这里选择只登记完成,以重试中断任务换取简单恢复。
定时工作也可把时间戳加入就绪条件,并继续检查依赖。不过周期重复、重叠和错过执行时间还需要其他策略,不能只换一个条件就得到完整定时系统。
如果所有任务都短小、重做成本低,持久化的维护成本可能超过收益。是否保存,取决于崩溃会抹掉多少有价值的进度。
边界情况
- 完成与保存之间崩溃。 两行代码不是原子操作,此时会重做已经产生效果的任务。临时文件加 rename 可保护检查点文件完整性,却不能把外部副作用与完成记录一起原子提交。需要幂等、事务协调或明确恢复协议;日志也只能提供其设计范围内的保证。
- 两项就绪,一个工作者。 t2、t3 没有先后边,任何选择顺序都合法。图保证的是 t4 必须等两者都完成。
- 依赖成环。 t2 等 t3、t3 等 t2 时,两者一直 pending,
next_ready无项可选。真实分解器应先验证无环。 - 运行中目标改变。 记录仍描述旧计划,重启会照旧恢复。重新规划应显式替换或版本化旧记录,而不是以为持久化自动理解新目标。
常见错误
- 只保存计划,不保存进度。 初始保存没有完成状态,重启仍重做。save 应在完成循环里。
- 用崩溃前的画面代替检查点。 t3 曾是 running,但文件只确认 pending。恢复应从持久记录与既定恢复规则出发。
- 把顺序藏在执行器判断里。 将
t4.deps = [t2, t3]写入数据,才能在保存、加载及共享同一图时保留约束。 - 刚开始就标 done。 恢复会跳过 done,提前标记可能永久跳过半成品。
- 每次启动都重建图。 有有效记录时应先 load;无条件重规划会丢失这个系统的恢复依据。
模型的简化
DAG、依赖检查、每次完成保存、崩溃和恢复均由确定性代码执行。所谓文件也由独立内存检查点模拟,不写入你的磁盘。任务名称与执行是预设,没有真的搭建模块。真实智能体可能用模型分解任务,工具调用与 ReAct 循环展示那部分记录。
本课重规划总得到同一图,因为 decompose 固定;真实模型可能分解成另一张图。因此这里的重做次数不能作为真实恢复成本的上下界。
生产任务系统还要处理原子文件写入、日志、任务副作用与记录之间的间隙、重试策略、超时、优先级、并行认领及环检测。这些机制也是工作流引擎(如 Airflow、Temporal)、CI 恢复和智能体任务文件所处理的内容。本例保留共同结构:保存计划与进度,按依赖选择工作,再依据记录恢复。