清单还在,为什么仍不知道从哪里继续?
“先检查 main.py,再运行核对,最后给我报告。”这件事可以写成三项清单。可调查做到一半,会话结束了;重新打开后,模型既不知道谁在处理,也不知道“运行”和“报告”为什么还不能开始。
第五课的 TodoWrite 保存当前会话的步骤,第十一课让同一轮模型请求有机会从暂时故障中恢复。现在换了一个问题:把工作之间的先后关系和当前进度留在文件里,让新会话能判断下一件可开始的事。 Task System(任务系统)在本课承担这个职责。
仍用同一个小例子:check 读取源码和预期,run 在取得许可后真实运行并核对,report 引用已保存的核对结果。三个任务各有完整描述,不能只留一个“处理一下”的标题,让下一次会话重新猜。
三个 pending,为什么只有一个能开始?
pending 只表示尚未认领。run 的 blocked_by=["check"] 说明它在等待调查完成;report 又依赖 run。若把三个 pending 都当作可执行,任务图就退化成了一张没有约束的列表。
本例的“可开始”由两个条件一起决定:任务仍为 pending,而且所有前置任务都为 completed。依赖字段指向前置任务,图中箭头则从前置指向后续,沿箭头读就是“先完成这个,后项才可开始”。
下面的独立短代码只检查内存中的依赖,没有持久文件或模型调用:
tasks = {
"check": {"status": "pending", "blocked_by": []},
"run": {"status": "pending", "blocked_by": ["check"]},
"report": {"status": "pending", "blocked_by": ["run"]},
}
def ready_ids():
return [key for key, task in tasks.items()
if task["status"] == "pending"
and all(tasks[dep]["status"] == "completed" for dep in task["blocked_by"])]
print("开始时:", ready_ids())
tasks["check"]["status"] = "completed"
print("检查完成后:", ready_ids())
print("运行任务状态:", tasks["run"]["status"])点击行号设置断点,再点“调试”;也可以直接运行。
输出从 ["check"] 变为 ["run"],但 run 的状态仍然是 pending。这里最值得留意的就是最后一行:解除阻塞只表示可以开始,没有替它认领,更没有启动程序。 完成以后重新计算可开始项即可,不必再保存一套容易过时的“已解锁”标志。
认领、完成和跨会话读取,分别改变什么?
任务在本例中沿 pending → in_progress → completed 推进。认领会绑定宿主给出的负责人;完成必须由同一负责人提交,并留下结果说明。它们都是任务记录上的动作,真正的文件读取或程序执行发生在两个动作之间。
用户目标拆成check、run、report。这里的箭头表示后项依赖前项完成,不表示自动调用或已经执行。
- 把先后关系保存下来:用户目标拆成check、run、report。这里的箭头表示后项依赖前项完成,不表示自动调用或已经执行。
- 只有依赖满足的pending任务可以认领:check没有前置任务,可以先认领。run与report仍被前项阻塞;可开始状态由任务图计算,不另存一份开关。
- 认领保存了负责人,工作才开始:check从pending变为in_progress,绑定worker-a。它不是完成标记,程序也尚未运行。
- 调查结束,提交完成与结果说明:宿主完成真实读取后提交check,说明明确写“尚未运行”。先持久保存,再重新计算哪些下游可以开始。
- 新进程读盘,看到run现在可开始:旧会话已经结束,任务图仍在。新进程读取描述、依赖和结果;它不会恢复旧对话,也不会自动接着某行代码执行。
- 运行核对完成后,报告才解除阻塞:run另行认领,取得本次许可、真实执行并核对输出后提交。report现在可开始,仍须另行认领;报告引用已存结果,不能称又执行了一次。
切到“越过前置任务”,run 的认领会被拒绝,原文件保持。换一个负责人也不能改变这个结论,因为负责人与依赖是两项不同检查。
新进程读取的是任务描述、负责人、依赖、状态和已存结果。它没有拿回旧模型对话,也不知道上次程序具体停在哪个执行位置。因此,跨会话恢复进度和恢复程序现场是两件事;任务文件只能提供它实际保存的那部分事实。
任务写着完成,能证明程序已经通过吗?
不能单凭这个字段下结论。completed 以及 result 都由调用者提交:有人写“已通过”,并不等于宿主已经看到了退出码和完整输出。任务系统能检查状态转换,不能替一段文字担保现实结果。
本课的固定流程先读取当前预期,再取得运行许可,真实启动 main.py,核对退出码与完整输出后才提交 run 完成。拒绝执行或输出不一致,都保留进行中状态,report 继续被阻塞。报告阶段引用先前 run 记录,明确这一次没有重新运行程序。
这也说明了任务图与另外两种机制的边界。TodoWrite 是会话内的执行清单;第六课的 task 工具是一次独立上下文调查调用;这里的 task_claim 等工具则操作可跨进程读取的工作记录。一个持久任务可以需要一次子调查,但“委派调用结束”不会自动把对应任务标成完成。
文件里多存一个 owner,就能安全分工了吗?
串行使用时,检查 pending 后写入 owner,能防止下一次会话直接重复认领。但两个进程若同时读取 pending,都可能通过检查,再互相覆盖文件。原子替换保证读者看到完整文件,不能把整个“读取、检查、写入”自动变成并发事务。
所以本课明确采用单写者:同一任务目录一次只运行一个 work。负责人由宿主绑定,模型不能在工具参数里随意换成另一个名字;这仍是教学约定,不是多租户身份认证。需要并发认领时,还应把检查与提交放在同一个锁或事务边界里。
依赖也需要约束。本例创建时只能引用已经存在的任务,不能覆盖旧 ID 或事后改边,新任务不会反过来成为既有前置项的前置;加载文件仍校验缺失依赖、自依赖和环。若文件损坏,先报错,不能悄悄清空任务图来“恢复运行”。
进行中的任务,重启以后要不要自动再跑?
假设 main.py 已经执行,任务完成记录还没写入,进程就退出了。新会话只看见 in_progress,无法区分“完全没执行”和“已经执行,只差提交”。直接重跑可能重复副作用,直接写完成又可能漏掉实际工作。
本例选择停下核对:进行中任务不能再次认领,也不自动退回 pending。宿主先查实际产物或执行记录,再明确提交结果说明。任务图帮助暴露这个不确定状态,没有提供恰好一次执行保证。
回到开头的问题,新会话现在能读出“谁在做、前置是否完成、下一项是否可认领”。它仍须为即将发生的执行取得许可,并区分已存记录和本次证据。实践篇逐个展开创建、认领和完成函数,沿断点观察候选何时才真正提交。
资料核对于2026-10-19:课程 Task System提供持久任务图的学习路线;Claude Code 交互文档的 Task list和团队任务协调文档用于核对产品中任务列表、依赖与认领的概念。本课采用独立的单写者 Python 实现,不把社区不可核验的内部源码描述当作官方实现事实。