Agent 开发进阶(十二):从临时清单到持久工作图,任务系统的设计与实现
本文是「从零构建 Coding Agent」系列的第十二篇,适合想让 Agent 管理复杂、长期任务的开发者。
先问一个问题
当你的 Agent 需要处理复杂任务时,你是怎么管理的?
- 使用简单的 todo 列表?
- 手动跟踪任务之间的依赖关系?
- 每次会话都重新规划?
如果你的答案是肯定的,那么当任务变得复杂时,你会遇到越来越多的麻烦。
任务管理的「依赖困境」问题
到了这一阶段,你的 Agent 已经具备了多种能力:
- 核心循环运行
- 工具使用与分发
- 会话内规划(TodoWrite)
- 子智能体机制
- 技能加载
- 上下文压缩
- 权限系统
- Hook 系统
- Memory 系统
- 系统提示词组装
- 错误恢复
但当面对复杂任务时,简单的 todo 列表会遇到明显限制:
- 它更像当前会话里的临时清单
- 它不擅长表达「谁先谁后、谁依赖谁」
- 它无法跨会话持久化
- 它不适合多 Agent 协作
例如下面这组工作:
1. 先写解析器
2. 再写语义检查
3. 测试和文档可以并行
4. 最后整体验收
这已经不是单纯的列表,而是一张「依赖关系图」。
所以到了这个阶段,我们需要一个任务系统:
把「会话里的 todo」升级成「可持久化的任务图」。
任务系统的核心设计:依赖关系与持久化
用一个图来表示任务系统的工作流程:
用户提出复杂目标
->
模型决定先拆任务
->
调用 task_create 创建任务
->
调用 task_update 设置依赖
->
任务落到 .tasks/ 目录
->
完成任务时自动解锁后续任务
->
后续轮次继续读取并推进
关键点只有两个:
- 依赖关系:表达任务之间的先后顺序
- 持久化:跨会话保存任务状态
几个必须搞懂的概念
任务(Task)
这里的 task 指的是:
一个可以被跟踪、被分配、被完成、被阻塞的小工作单元。
它不是整段用户需求,而是用户需求拆出来的一小块工作。
依赖(Dependency)
依赖的意思是:
任务 B 必须等任务 A 完成,才能开始。
任务图(Task Graph)
任务图就是:
任务节点 + 依赖连线
你可以把它理解成:
- 点:每个任务
- 线:谁依赖谁
Ready 状态
ready 的意思很简单:
这条任务现在已经满足开工条件。
也就是:
- 自己还没开始
- 前置依赖已经全部完成
最小实现
1. Task Manager
import json
from pathlib import Path
class TaskManager:
"""任务管理器"""
def __init__(self, tasks_dir=".tasks"):
self.tasks_dir = Path(tasks_dir)
self.tasks_dir.mkdir(exist_ok=True)
self._tasks = {}
self._load_all()
def _load_all(self):
"""加载所有任务"""
for file_path in self.tasks_dir.glob("task_*.json"):
try:
task = json.loads(file_path.read_text(encoding="utf-8"))
self._tasks[task["id"]] = task
except Exception as e:
print(f"加载任务失败 {file_path}: {e}")
def _next_id(self):
"""生成下一个任务 ID"""
if not self._tasks:
return 1
return max(self._tasks.keys()) + 1
def _save(self, task):
"""保存任务"""
file_path = self.tasks_dir / f"task_{task['id']}.json"
file_path.write_text(json.dumps(task, indent=2, ensure_ascii=False), encoding="utf-8")
def create(self, subject, description=""):
"""创建任务"""
task = {
"id": self._next_id(),
"subject": subject,
"description": description,
"status": "pending",
"blockedBy": [],
"blocks": [],
"owner": "",
}
self._tasks[task["id"]] = task
self._save(task)
return task
def get(self, task_id):
"""获取任务"""
return self._tasks.get(task_id)
def list(self):
"""列出所有任务"""
return list(self._tasks.values())
def update(self, task_id, **kwargs):
"""更新任务"""
task = self._tasks.get(task_id)
if not task:
return None
task.update(kwargs)
self._save(task)
return task
def complete(self, task_id):
"""完成任务"""
task = self._tasks.get(task_id)
if not task:
return None
task["status"] = "completed"
self._save(task)
# 解锁后续任务
for other_id, other in self._tasks.items():
if task_id in other.get("blockedBy", []):
other["blockedBy"].remove(task_id)
self._save(other)
return task
def add_dependency(self, task_id, blocks_id):
"""添加依赖关系"""
task = self._tasks.get(task_id)
blocked = self._tasks.get(blocks_id)
if not task or not blocked:
return False
if blocks_id not in task.get("blocks", []):
task.setdefault("blocks", []).append(blocks_id)
if task_id not in blocked.get("blockedBy", []):
blocked.setdefault("blockedBy", []).append(task_id)
self._save(task)
self._save(blocked)
return True
def is_ready(self, task_id):
"""判断任务是否就绪"""
task = self._tasks.get(task_id)
if not task:
return False
return task.get("status") == "pending" and not task.get("blockedBy", [])
def get_ready_tasks(self):
"""获取所有就绪任务"""
return [task for task in self._tasks.values() if self.is_ready(task["id"])]
2. 任务工具
def create_task_tools(task_manager):
"""创建任务相关的工具"""
def task_create(subject, description=""):
"""创建新任务"""
task = task_manager.create(subject, description)
return f"任务创建成功: #{task['id']} {task['subject']}"
def task_update(task_id, status=None, description=None, owner=None):
"""更新任务"""
kwargs = {}
if status:
kwargs["status"] = status
if description:
kwargs["description"] = description
if owner:
kwargs["owner"] = owner
task = task_manager.update(task_id, **kwargs)
if task:
return f"任务更新成功: #{task['id']} {task['subject']}"
else:
return f"任务不存在: {task_id}"
def task_get(task_id):
"""获取任务详情"""
task = task_manager.get(task_id)
if task:
lines = [
f"## 任务 #{task['id']}",
f"**主题**: {task['subject']}",
f"**状态**: {task['status']}",
f"**描述**: {task['description']}",
f"**被阻塞**: {task.get('blockedBy', [])}",
f"**阻塞**: {task.get('blocks', [])}",
f"**负责人**: {task.get('owner', '')}",
]
return "\n".join(lines)
else:
return f"任务不存在: {task_id}"
def task_list():
"""列出所有任务"""
tasks = task_manager.list()
if not tasks:
return "暂无任务"
lines = ["# 任务列表\n"]
for task in tasks:
ready = task_manager.is_ready(task["id"])
ready_marker = "✅" if ready else ""
lines.append(f"- **#{task['id']}** {task['subject']} [{task['status']}] {ready_marker}")
if task.get('blockedBy'):
lines.append(f" 被阻塞: {task['blockedBy']}")
# 单独列出就绪任务
ready_tasks = task_manager.get_ready_tasks()
if ready_tasks:
lines.append("\n# 就绪任务\n")
for task in ready_tasks:
lines.append(f"- **#{task['id']}** {task['subject']}")
return "\n".join(lines)
def task_complete(task_id):
"""完成任务"""
task = task_manager.complete(task_id)
if task:
return f"任务完成成功: #{task['id']} {task['subject']}"
else:
return f"任务不存在: {task_id}"
def task_add_dependency(task_id, blocks_id):
"""添加依赖关系"""
success = task_manager.add_dependency(task_id, blocks_id)
if success:
return f"依赖关系添加成功: #{task_id} 阻塞 #{blocks_id}"
else:
return "依赖关系添加失败,任务不存在"
return {
"task_create": task_create,
"task_update": task_update,
"task_get": task_get,
"task_list": task_list,
"task_complete": task_complete,
"task_add_dependency": task_add_dependency,
}
3. 集成到 Agent Loop
def agent_loop_with_tasks(state):
"""带任务系统的 Agent Loop"""
# 初始化任务管理器
task_manager = TaskManager()
# 创建任务工具
task_tools = create_task_tools(task_manager)
state["tools"] = state.get("tools", []) + [
{
"name": "task_create",
"description": "创建新任务",
"parameters": {
"subject": {"type": "string", "description": "任务主题"},
"description": {"type": "string", "description": "任务描述", "optional": True}
}
},
{
"name": "task_update",
"description": "更新任务",
"parameters": {
"task_id": {"type": "integer", "description": "任务 ID"},
"status": {"type": "string", "description": "任务状态", "optional": True},
"description": {"type": "string", "description": "任务描述", "optional": True},
"owner": {"type": "string", "description": "负责人", "optional": True}
}
},
{
"name": "task_get",
"description": "获取任务详情",
"parameters": {
"task_id": {"type": "integer", "description": "任务 ID"}
}
},
{
"name": "task_list",
"description": "列出所有任务",
"parameters": {}
},
{
"name": "task_complete",
"description": "完成任务",
"parameters": {
"task_id": {"type": "integer", "description": "任务 ID"}
}
},
{
"name": "task_add_dependency",
"description": "添加依赖关系",
"parameters": {
"task_id": {"type": "integer", "description": "任务 ID"},
"blocks_id": {"type": "integer", "description": "被阻塞的任务 ID"}
}
}
]
# 主循环
while True:
# 调用模型
response = call_model(state["messages"])
if response.stop_reason != "tool_use":
return response.content
results = []
for block in response.content:
if hasattr(block, "type") and block.type == "tool_use":
tool_name = block.name
tool_input = block.input
# 执行任务工具
if tool_name in task_tools:
output = task_tools[tool_name](**tool_input)
else:
# 执行其他工具
output = run_tool(tool_name, tool_input)
results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": output
})
if results:
state["messages"].append({"role": "user", "content": results})
核心功能说明
1. 任务创建与管理
创建任务:
task = task_manager.create("写解析器", "实现 JSON 解析功能")
更新任务:
task_manager.update(1, status="in_progress", owner="Agent")
完成任务:
task_manager.complete(1) # 会自动解锁被它阻塞的任务
2. 依赖关系管理
添加依赖:
task_manager.add_dependency(1, 2) # 任务 1 完成后才能开始任务 2
检查就绪状态:
if task_manager.is_ready(2):
print("任务 2 可以开始了")
获取就绪任务:
ready_tasks = task_manager.get_ready_tasks()
3. 任务持久化
任务会被保存到 .tasks/ 目录,每个任务一个 JSON 文件:
.tasks/
task_1.json
task_2.json
task_3.json
这样即使系统重启,任务状态也不会丢失。
Todo vs Task 系统的边界
| 特性 | Todo | Task 系统 |
|---|---|---|
| 作用范围 | 当前会话 | 跨会话持久化 |
| 依赖管理 | 简单顺序 | 复杂依赖关系 |
| 状态管理 | 简单状态 | 完整状态流转 |
| 协作支持 | 单人 | 多人协作 |
| 持久化 | 内存 | 磁盘存储 |
使用建议:
- 如果是简单的短期任务:使用 Todo
- 如果是复杂的长期任务:使用 Task 系统
- 如果需要管理依赖关系:使用 Task 系统
- 如果需要跨会话继续:使用 Task 系统
新手最容易犯的 4 个错
1. 只会创建任务,不会维护依赖
# ❌ 错误
# 只是创建任务,没有设置依赖
task1 = task_manager.create("写解析器")
task2 = task_manager.create("写语义检查")
task3 = task_manager.create("测试")
# ✅ 正确
# 设置依赖关系
task1 = task_manager.create("写解析器")
task2 = task_manager.create("写语义检查")
task3 = task_manager.create("测试")
task_manager.add_dependency(task1["id"], task2["id"]) # 解析器完成后才能做语义检查
task_manager.add_dependency(task2["id"], task3["id"]) # 语义检查完成后才能测试
2. 任务只放内存,不落盘
# ❌ 错误
# 任务只存在内存中
class InMemoryTaskManager:
def __init__(self):
self.tasks = {}
# ✅ 正确
# 任务持久化到磁盘
class TaskManager:
def __init__(self, tasks_dir=".tasks"):
self.tasks_dir = Path(tasks_dir)
self.tasks_dir.mkdir(exist_ok=True)
self._load_all()
3. 完成任务后不自动解锁后续任务
# ❌ 错误
# 完成任务后没有解锁后续任务
def complete(self, task_id):
task = self._tasks.get(task_id)
task["status"] = "completed"
self._save(task)
# ✅ 正确
# 完成任务后自动解锁后续任务
def complete(self, task_id):
task = self._tasks.get(task_id)
task["status"] = "completed"
self._save(task)
# 解锁后续任务
for other_id, other in self._tasks.items():
if task_id in other.get("blockedBy", []):
other["blockedBy"].remove(task_id)
self._save(other)
4. 把工作目标和运行中的执行混成一层
# ❌ 错误
# 把任务和执行混在一起
def run_task(task_id):
# 执行任务
result = subprocess.run(["python", "test.py"])
# 更新任务状态
task_manager.complete(task_id)
# ✅ 正确
# 任务是工作目标,执行是运行时行为
def run_task(task_id):
# 标记任务开始
task_manager.update(task_id, status="in_progress")
# 执行任务
result = subprocess.run(["python", "test.py"])
# 标记任务完成
task_manager.complete(task_id)
为什么这很重要
因为一个真正强大的 Agent,不应该只能处理简单的线性任务。
任务系统让你能够:
- 管理复杂任务:处理有依赖关系的任务网络
- 跨会话持续:任务状态持久化,下次会话继续
- 多人协作:多个 Agent 可以共享任务板
- 可视化进度:清楚地看到任务状态和依赖关系
- 自动推进:完成任务后自动解锁后续任务
推荐的实现步骤
- 第一步:实现 TaskManager 类,支持任务的增删改查
- 第二步:实现依赖关系管理,支持添加依赖和自动解锁
- 第三步:实现任务持久化,保存到磁盘
- 第四步:创建任务相关的工具,暴露给模型
- 第五步:集成到 Agent Loop,支持任务驱动的工作流
任务系统与后续章节的关系
- s12 任务系统:解决工作目标如何被长期组织的问题
- s13 后台任务:解决慢命令在后台运行时主循环如何继续的问题
- s15 Agent 团队:会使用任务系统来协调多个 Agent 的工作
- s17 自主智能体:会利用任务系统来规划和执行复杂任务
所以任务系统是构建高级 Agent 系统的基础组件。
下一章预告
有了任务系统,你的 Agent 已经具备了管理复杂任务的能力。下一章我们将探讨后台任务系统,让 Agent 能够在执行慢命令时继续响应其他请求,提高系统的响应速度和并发能力。
一句话总结:任务系统的核心不是「保存清单」,而是「判断什么时候能开工」。
如果觉得有帮助,欢迎关注,我会持续更新「从零构建 Coding Agent」系列文章。