1
你买了一个宜家书柜。打开包装,里面有 5 个步骤 的说明书:
📦 第 1 步:组装框架
🔩 第 2 步:装上隔板(需要第 1 步完成)
🚪 第 3 步:装柜门(需要第 1 步完成)
🔒 第 4 步:装门把手(需要第 3 步完成——门没装好,把手没地方拧)
✅ 第 5 步:检查所有螺丝(需要第 2 步和第 4 步都完成)
你一看就明白了:第 2 步和第 3 步可以同时做(都只依赖第 1 步,互不依赖)。但第 4 步必须等第 3 步完成。第 5 步必须等第 2 和第 4 步都完成。
2
现在想象:这个说明书不是画在纸上的,而是写在 5 张便签纸上——每张写一个步骤,没有编号,没有箭头。你把它们贴在墙上。
问题来了:你怎么知道哪步先做?哪步后做?哪两步可以同时做?
你只能靠脑子猜。如果猜错了——比如先做第 4 步(装门把手)——你会发现门还没装,白费功夫。
3
这就是 s03(TodoWrite)和 s07(Task System)的区别。
s03 = 便签纸:每个任务独立写在一张纸上,贴在墙上。顺序和依赖关系全靠你(Agent)的脑子。
s07 = 说明书:每个任务有编号、有依赖箭头、有完成状态。系统帮你记住了"哪步要等哪步"——你不需要记,看说明书就行。
先理解 s03 的 Todo 长什么样,然后看它在哪些场景下会出问题。
s03 的 Todo 就是一张"待办清单"
你见过手机上的备忘录吧?s03 就是那个:
[ ] 搭建项目骨架
[>] 实现登录功能 ← 正在做
[ ] 写单元测试
这张清单有三个特点:
① 存在脑子里(内存里):关掉程序,清单就没了。就像你把待办事项记在心里——忘了就是忘了。
② 每一项都是平等的:看不出来"写测试"依赖"实现登录"——它们是并列的。就像三张便签纸贴在墙上,没有箭头连接。
③ 只有做没做两种状态:pending(还没做)、in_progress(正在做)、completed(做完了)。没有"我在等别人先做完"这种状态。
三个具体的问题场景
场景一 —— 关掉程序再打开
你今天用 Agent 建了一个项目,分解成 5 个任务,完成了 2 个。明天打开 Agent 继续——它完全不记得昨天做过什么了。Todo 列表是从零开始的。
场景二 —— 任务 B 必须等 A 做完
"写单元测试"必须等"实现登录功能"完成。但 Todo 列表看不出来这种关系。Agent 可能同时做这两件事——然后发现测试写好了但登录代码还没写,测试当然全失败。
场景三 —— Agent 对话太长,被压缩了
s06 的压缩机制一跑,Todo 列表就没了——它在内存里,不在磁盘上。Agent 的"记忆"被重置了。
🗂️ 生活类比
s03 的 Todo = 你脑子里记的待办事项。洗个澡可能就忘了。
s07 的 Task System = 你写在笔记本上的待办事项。合上本子、过一周再翻开——还在。
而且 s07 的笔记本不光记"要做什么",还记"这件事要等那件事做完才能开始"——像宜家说明书的箭头。
s07 做了两件事,每件都很简单,但合在一起就完全不一样了。
s03 的做法
存储位置:存在 Python 程序的内存里。
程序关了 → 没了。
任务关系:没有关系。
每个任务是孤立的。
任务格式:一条文本 + 一个状态标记。
"实现登录" [pending]
s07 的做法
存储位置:存在硬盘上,一个任务一个文件。
程序关了 → 文件还在。
任务关系:每个任务可以写"我要等谁做完"。
blockedBy: [1, 3]
任务格式:更丰富的信息。
标题 + 描述 + 状态 + 依赖列表 + 谁在做
"改变一":把任务写到硬盘上,而不是记在脑子里
s07 里每个任务是一个独立的 JSON 文件,存在 .tasks/ 文件夹下。JSON 是一种人类也能读懂的文本格式——你可以双击打开看。
下面是一个真实的任务文件内容:
📝 中文注释版
{ ← 一个花括号 = 一个任务
"id": 3, ← 编号:第 3 号任务
"subject": "实现密码哈希", ← 要做什么(标题)
"description": "用bcrypt加密",← 详细说明(可选)
"status": "pending", ← 还没开始做
"blockedBy": [1], ← ⭐ 依赖任务1,它完成了我才能开始
"owner": "" ← 谁在做(s09多Agent时用到)
}
💻 英文代码原版
{
"id": 3,
"subject": "Implement password hashing",
"description": "Use bcrypt to hash user passwords",
"status": "pending",
"blockedBy": [1],
"owner": ""
}
保存到磁盘的关键好处:s06 的压缩不会影响它。Agent 重启后,扫一眼 .tasks/ 文件夹就知道所有任务的状态。就像你合上笔记本,第二天翻开——内容还在。
"改变二":任务之间可以写"我等谁"
s03 的每个任务彼此独立。s07 加了一个关键信息:"我等谁"(blockedBy)。
↓ 第1步完成,解锁了两个任务
#2 装隔板
正在做 →
等谁:[1]
#3 装柜门
正在做 →
等谁:[1]
↑ 这两个互不等对方——它们可以同时做
↓ 两个都完成才解锁下一步
关键理解:任务 2 和任务 3 为什么能同时做?不是因为有人告诉它们"你们可以并行"——而是因为它们都不在对方的"等谁"列表里。没有依赖 = 可以并行。这是一个非常自然的表达方式。
不需要数据库,不需要安装任何东西。就是一个文件夹,里面放一堆 JSON 文件。
文件夹长这样
.tasks/ ← 一个叫 .tasks 的文件夹
├── task_1.json ← 第 1 号任务
├── task_2.json ← 第 2 号任务
├── task_3.json ← 第 3 号任务
└── task_4.json ← 以此类推...
文件命名规则很简单:task_编号.json。编号是自动递增的——你创建第 1 个任务它是 1,第 2 个是 2,第 3 个是 3……
为什么每个任务是一个文件,而不是所有任务塞一个文件? 因为改一个任务时只需要写那一个文件。如果 50 个任务塞一个文件,你改第 23 号任务的状态,要重新写整个文件——慢而且容易出错。
程序怎么操作这些文件?
代码做四件事(专业术语叫 CRUD——创建、读取、更新、删除):
① 创建任务:程序给任务分配一个编号(比已有最大编号 +1),写好标题、状态、依赖等信息,写入一个新文件。
② 读取单个任务:程序根据编号找到对应的文件,读出来。
③ 更新任务:程序找到文件,读出来,修改里面的字段(比如把状态从 pending 改成 completed),再写回去。
④ 列出所有任务:程序扫描 .tasks/ 文件夹下的所有 task_*.json 文件,读取每个,整理成列表。
不需要担心并发冲突——目前只有一个 Agent 在操作这些文件。后面 s09 多个 Agent 同时操作时,才会涉及"谁先改谁后改"的问题。
核心代码:怎么创建和更新任务
📝 中文注释版
# 创建任务:填好信息 → 写入文件 → ID 自增
def create(self, subject, description=""):
task = {
"id": self._next_id, # 自动分配编号
"subject": subject, # 任务标题
"description": description, # 详细描述(可选)
"status": "pending", # 新任务默认"还没开始"
"blockedBy": [], # 初始不依赖任何人
"owner": "",
}
self._save(task) # 写入 task_{id}.json
self._next_id += 1 # 编号+1
return json.dumps(task) # 返回给 LLM 看
# 更新任务:读文件 → 改字段 → 写回去
# ⭐ 标为 completed 时自动触发 _clear_dependency
def update(self, task_id, status=None, add_blocked_by=None, remove_blocked_by=None):
task = self._load(task_id) # 读文件
if status:
task["status"] = status # 改状态
if status == "completed": # ⭐ 完成了 → 自动解锁下游
self._clear_dependency(task_id)
if add_blocked_by: # 添加依赖
task["blockedBy"] = list(set(task["blockedBy"] + add_blocked_by))
if remove_blocked_by: # 移除依赖
task["blockedBy"] = [x for x in task["blockedBy"] if x not in remove_blocked_by]
self._save(task) # 写回文件
💻 英文代码原版
def create(self, subject, description=""):
task = {
"id": self._next_id,
"subject": subject,
"description": description,
"status": "pending",
"blockedBy": [],
"owner": "",
}
self._save(task)
self._next_id += 1
return json.dumps(task, indent=2)
def update(self, task_id, status=None,
add_blocked_by=None, remove_blocked_by=None):
task = self._load(task_id)
if status:
task["status"] = status
if status == "completed":
self._clear_dependency(task_id)
if add_blocked_by:
task["blockedBy"] = list(set(task["blockedBy"] + add_blocked_by))
if remove_blocked_by:
task["blockedBy"] = [x for x in task["blockedBy"] if x not in remove_blocked_by]
self._save(task)
注意 update 里 if status == "completed" 那行——标为完成时自动调用 _clear_dependency。Agent 只需说"我做完了",系统自动通知所有下游。
这是 s07 最聪明的地方。不是让 Agent 手动去通知"我做完了,你们可以开始了"——而是系统自动帮你做这件事。
1
没有自动解锁的话,Agent 要这样做:
① 把任务 #1 标记为"完成"
② 去检查任务 #2:它等 #1 吗?是 → 把 #1 从 #2 的"等谁"列表里删掉
③ 去检查任务 #3:它等 #1 吗?是 → 把 #1 从 #3 的"等谁"列表里删掉
④ 去检查任务 #4:它等 #1 吗?...
⑤ 去检查任务 #5:...
做完一个任务,还要挨个通知所有"等它的人"。又麻烦又容易漏。
2
s07 的做法:Agent 只需要做一件事——把 #1 标为"完成"。
系统自动做剩下的:扫描所有任务文件,找到所有"等谁"列表里包含 #1 的任务,把 #1 从它们的列表里删掉。
Agent 完全不用操心"通知下游"这件事。它只需要说"我做完了",系统自动通知所有等它的人。这就像一个连锁反应——你推倒了第一块多米诺骨牌,后面的事情自动发生。
一个具体例子,走一遍
假设有三个任务:#1 搭建框架(已完成)→ #2 实现登录(等 #1)→ #3 写测试(等 #2)。
#1
已完成 ✓
→
#2
正在做
等谁:[1]
→
#3
等待中
等谁:[2]
Agent 把 #2 标为"完成"时,代码层面发生了什么:
📝 中文注释版
# Agent 调用 task_update(task_id=2, status="completed")
# ↓ update() 内部检测到 status=="completed",自动触发:
def _clear_dependency(self, completed_id):
# 扫描 .tasks/ 下所有 task_*.json 文件
for f in self.dir.glob("task_*.json"):
task = json.loads(f.read_text()) # 读取每个任务
# 如果这个任务的 blockedBy 里包含已完成的 ID
if completed_id in task.get("blockedBy", []):
task["blockedBy"].remove(completed_id) # 从依赖列表移除
self._save(task) # 写回磁盘
# 执行结果:#3 的 blockedBy 从 [2] 变成 [] → 自动解锁!
💻 英文代码原版
def _clear_dependency(self, completed_id):
for f in self.dir.glob("task_*.json"):
task = json.loads(f.read_text())
if completed_id in task.get("blockedBy", []):
task["blockedBy"].remove(completed_id)
self._save(task)
执行结果:#3 的"等谁"列表从 [2] 变成 [](空列表)。Agent 下次看任务列表时,发现 #3 不再被阻塞——可以开始做了。
Agent 从头到尾只做了一件事:把 #2 标为完成。它不需要知道谁在等它——系统替它通知了所有人。
💡 为什么用"全量扫描"而不是"反向索引"?
全量扫描 = 每次遍历所有文件找谁依赖了我。简单、容错、不需要维护额外数据。对于几十到几百个任务的规模,毫秒级完成。反向索引更快(O(1)查找),但需要额外维护一个"谁依赖谁"的映射表——复杂、容易出错。在 s07 的规模下,简单比高效更重要。
s03 只有一个 todo 工具(创建和更新混在一起)。s07 拆成了四个,每个只做一件事——就像厨房里切菜刀、水果刀、面包刀各有各的用途。
四个工具,各司其职
🔨 task_create —— 创建一个新任务。告诉它"标题"(必填)和"详细描述"(可选)。系统自动分配编号,初始状态为"还没开始",初始依赖为空。
✏️ task_update —— 修改一个已有任务。可以改三样东西:① 状态(还没做 → 正在做 → 已完成);② 添加依赖(让它等人);③ 移除依赖(不再等人)。三样可以一次全改,也可以只改其中一样。
📋 task_list —— 看全局。列出所有任务,每个一行,显示编号、标题、状态、在等谁。就像看一个项目看板。
🔍 task_get —— 看详情。查看某一个任务的完整信息,包括详细描述。大部分时候用 task_list(省 token),需要看详情时才用这个。
这些工具怎么加到 Agent 里的?
和 s02 学的方式一模一样——在 dispatch map 里加四行:
# 工具名 → 执行哪个函数
"task_create" → TaskManager.创建任务()
"task_update" → TaskManager.更新任务()
"task_list" → TaskManager.列出所有任务()
"task_get" → TaskManager.查看单个任务()
加新工具不需要改 Agent Loop——这就是 s02 学的 dispatch map 模式。从 s02 到 s07,这个模式一直没变过。
这三个问题是任何项目管理的核心。s03 的扁平清单一个都回答不了,s07 的任务图三个都能回答。
问题一:什么现在可以做?
看 task_list——状态是"还没开始"且"等谁"列表为空的任务,就是可以做的。Agent 不需要推理——直接看列表就知道。
就像你看宜家说明书:哪一步的箭头前面全是勾(已完成)?那就是下一步该做的。
问题二:什么被卡住了?
看 task_list——"等谁"列表不为空的任务,就是被卡住的。Agent 知道它们现在不能做,需要等。
有了这个信息,Agent 不会浪费时间尝试做被卡住的任务。
问题三:什么可以同时做?
如果有多个任务都满足"还没开始 + 等谁列表为空",它们可以并行执行——因为它们互不依赖。Agent 可以同时派多个子 Agent(s04)去处理它们。
s03 的 Agent 看不出并行机会——所有任务看起来都一样。s07 的 Agent 一眼就能识别。
这三种模式覆盖了几乎所有项目场景:
顺序执行——一个接一个
↙ 解锁 ↘
并行执行——B和C可以同时开工
↓ 两个都完成才解锁
汇聚等待——等所有前置都完成
🏗️ 这个模式无处不在
盖楼:打地基 → 砌墙 & 铺管道(并行)→ 两个都完成 → 封顶。
做菜:备菜 → 炒菜 & 煮饭(并行)→ 两个都完成 → 装盘上桌。
写软件:设计数据库 → 写后端 & 写前端(并行)→ 都完成 → 联调测试。
任何"分步骤、有依赖"的事情,都是一个任务图。
s07 一句话:把任务从"脑子里的便签"变成"硬盘上的说明书"——
有关键的依赖箭头,有自动的连锁解锁,压缩不掉,重启还在。
01
硬盘 > 脑子
记在磁盘上的信息比记在内存里的可靠得多。程序崩溃、压缩、重启——只要文件在,状态就在。
02
有箭头 > 没箭头
任务之间写清楚"我等谁",Agent 就知道什么能做、什么被卡、什么可并行。不需要推理,看箭头就行。
03
自动 > 手动
完成一个任务后,系统自动解锁所有等它的人。Agent 只管做事,不需要操心通知下游。
📋 Claude Code 的 TodoWrite
你在 Claude Code 里看到它列任务时,显示 [ ] #3: 修复登录Bug (blocked by: [1, 2])——这就是 s07 的任务图渲染格式。"blocked by" = "等谁"。
📦 GitHub Projects / Jira
GitHub 的 "Blocked by" 功能、Jira 的 "Depends on" 链接——都是任务图。每个 issue 有依赖关系,完成一个自动通知下游。
🔄 CI/CD Pipeline(自动化部署流程)
Build → Test & Lint(并行)→ Deploy。每个阶段等前面的完成。这就是一个被自动化执行的任务图。s07 的 Agent 就是"人形 CI/CD 引擎"——只不过每个阶段的"怎么执行"由 LLM 决定,而不是写死的脚本。
从 s08 开始,任务图将成为多 Agent 协作的核心。
一个 Agent 完成了一项任务 → 自动解锁了另一个 Agent 等待的任务。
多个 Agent 在同一个 .tasks/ 文件夹上有序工作——就像一个团队在看同一块白板。