第 12 章 生产级 Agent 任务引擎 · Agent架构实操十二 开始测验

5 个工具、3 个状态,撸出一个生产级任务引擎

真正麻烦的不是列「建库表、写接口、加测试」,而是:哪些任务现在可以开始?谁认领了?进程退出后任务图能否原样恢复?本章:workspace 级 JSON Task DAG。
⏱ 约 15 分钟 🔧 5 工具 📊 3 状态 +task_dag_json

🎯 学习目标

学完能答
  • todo_write 和 JSON Task DAG 有什么不同?为什么不合并?
  • 三个状态是什么?为什么没有第四个 blocked 状态?
  • 状态机允许哪些单向推进?有没有 update/delete/unclaim/回退?
  • Task 文件存哪?为什么锁的是整张图不是单个文件?
  • claim 的并发唯一认领是怎么保证的?
  • 进程重建后 in_progress 任务怎么处理?

🧠 核心概念

点击展开。
1 · TODO 与 Task 不是一回事
维度todo_writeJSON Task DAG
粒度当前工作步骤快照可独立认领/完成的项目任务
所有者单个 Agent session当前 workspace
存储进程内存.agent_tutorial/.tasks/{uuid}.json
依赖blocked_by 有向边
重建新会话为空从磁盘严格恢复

P12 仍保留 todo_write。模型用 TODO 拆「怎样完成当前任务」,用 Task DAG 管「整个项目哪些先做、哪些被阻塞」。P06 的 task(一次性 subagent)也不等于项目 Task,三种概念不同。

2 · 严格模型:3 状态,无 blocked

状态只有三个:pendingin_progresscompleted。Task 是构造时校验并冻结的领域对象。

没有第四个 blocked 状态 「阻塞」是派生事实:任务仍是 pending,且至少一个依赖未完成。这样完成上游时不需要批量改写所有下游文件。

不变量:pending 必须 owner===null;in_progress 和 completed 必须有非空 owner。id 必须是 canonical UUID 且文件名与 payload ID 完全相同;blocked_by 每个值都是 canonical UUID 且不重复;额外字段/未知状态/坏 UTF-8/坏 JSON 都明确失败。

3 · 状态机:单向推进,无回退
pending --claim--> in_progress --complete--> completed

当前章节没有 update、delete、unclaim 或自动回退。进程重建后,in_progress 仍保持原状态和 owner,不猜测未知副作用是否可以重放。complete_task 返回本次直接解锁的 unblocked 列表——只往下一层,且不改写下游文件:「可以开始了」是算出来的,不是写进文件的。

4 · 路径与文件
workspace/
  .agent_tutorial/
    .tasks.lock
    .tasks/
      1b73c624-....json
      9d59f5ec-....json

一个文件一个 Task,UTF-8、稳定字段顺序、结尾换行。外部传入的 task ID 先解析成 canonical UUID 再映射文件名——../outside、绝对路径、UUID 替代拼写都无法到文件系统。JsonTaskStore 还验证 .agent_tutorial 和 .tasks 解析结果仍在 workspace 内,目录 symlink/junction 指向外时创建前就失败。

5 · 为什么锁的是整张图

claim/complete 需要读依赖状态:一个任务能否 claim 取决于它所有 blocked_by 是否 completed。如果只锁单个文件,两个 Agent 同时认领同一任务、或同时改依赖和被依赖任务时会产生不一致。

整图锁:锁内重载全图 → 全图校验(环、缺边、ID 碰撞)→ 条件迁移 → 原子替换。这保证 claim 的并发唯一认领:两个竞争者都依次进入临界区,第二个看到状态已是 in_progress,输家拿到 TaskStateError(task_invalid_state)而非锁冲突错误——锁的作用是让「读→判断→写」决策不失效。原子写用临时文件+sync+rename,读方看不到半写文件;但原子写只保证「不写坏」不保证「不覆盖」,互斥必须靠锁。

6 · 5 个工具

五个任务工具使用带动词的完整名称(避免与 P06 的 task 混淆):

工具作用
create_task创建任务(可带 blocked_by)
list_tasks列出任务(可按状态过滤)
claim_task认领一个 ready(pending 且依赖全 completed)任务
complete_task完成自己认领的任务
get_task查询单个任务详情

主/子 Agent 共享同一任务图(同一 TaskStore),所以子 Agent 也能认领任务。但子 Agent 工具集仍不含 task(递归委派禁止)。owner 不能由模型参数伪造:claim_task 的 schema 只有 task_id,owner 一律取 ToolContext.identity(运行时缺省 "user");额外字段在副作用前就被 strict schema 拒绝。

▶️ 交互演示:Task DAG 状态机

点按钮模拟 claim/complete,看任务如何在三状态间单向推进,blocked 任务如何随上游完成而变 ready。注意没有第四个 blocked 状态——它是派生的。

🔑 一句话总结

5 工具 + 3 状态 + 整图锁 + 无 blocked 第四态 + 从磁盘严格恢复 = 生产级任务引擎

QA 测验