Agent 开发进阶(十二):从临时清单到持久工作图,任务系统的设计与实现

0 阅读9分钟

Agent 开发进阶(十二):从临时清单到持久工作图,任务系统的设计与实现

本文是「从零构建 Coding Agent」系列的第十二篇,适合想让 Agent 管理复杂、长期任务的开发者。

先问一个问题

当你的 Agent 需要处理复杂任务时,你是怎么管理的?

  • 使用简单的 todo 列表?
  • 手动跟踪任务之间的依赖关系?
  • 每次会话都重新规划?

如果你的答案是肯定的,那么当任务变得复杂时,你会遇到越来越多的麻烦。

任务管理的「依赖困境」问题

到了这一阶段,你的 Agent 已经具备了多种能力:

  • 核心循环运行
  • 工具使用与分发
  • 会话内规划(TodoWrite)
  • 子智能体机制
  • 技能加载
  • 上下文压缩
  • 权限系统
  • Hook 系统
  • Memory 系统
  • 系统提示词组装
  • 错误恢复

但当面对复杂任务时,简单的 todo 列表会遇到明显限制:

  • 它更像当前会话里的临时清单
  • 它不擅长表达「谁先谁后、谁依赖谁」
  • 它无法跨会话持久化
  • 它不适合多 Agent 协作

例如下面这组工作:

1. 先写解析器
2. 再写语义检查
3. 测试和文档可以并行
4. 最后整体验收

这已经不是单纯的列表,而是一张「依赖关系图」。

所以到了这个阶段,我们需要一个任务系统:

把「会话里的 todo」升级成「可持久化的任务图」。

任务系统的核心设计:依赖关系与持久化

用一个图来表示任务系统的工作流程:

用户提出复杂目标
  ->
模型决定先拆任务
  ->
调用 task_create 创建任务
  ->
调用 task_update 设置依赖
  ->
任务落到 .tasks/ 目录
  ->
完成任务时自动解锁后续任务
  ->
后续轮次继续读取并推进

关键点只有两个:

  1. 依赖关系:表达任务之间的先后顺序
  2. 持久化:跨会话保存任务状态

几个必须搞懂的概念

任务(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 系统的边界

特性TodoTask 系统
作用范围当前会话跨会话持久化
依赖管理简单顺序复杂依赖关系
状态管理简单状态完整状态流转
协作支持单人多人协作
持久化内存磁盘存储

使用建议

  • 如果是简单的短期任务:使用 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,不应该只能处理简单的线性任务。

任务系统让你能够:

  1. 管理复杂任务:处理有依赖关系的任务网络
  2. 跨会话持续:任务状态持久化,下次会话继续
  3. 多人协作:多个 Agent 可以共享任务板
  4. 可视化进度:清楚地看到任务状态和依赖关系
  5. 自动推进:完成任务后自动解锁后续任务

推荐的实现步骤

  1. 第一步:实现 TaskManager 类,支持任务的增删改查
  2. 第二步:实现依赖关系管理,支持添加依赖和自动解锁
  3. 第三步:实现任务持久化,保存到磁盘
  4. 第四步:创建任务相关的工具,暴露给模型
  5. 第五步:集成到 Agent Loop,支持任务驱动的工作流

任务系统与后续章节的关系

  • s12 任务系统:解决工作目标如何被长期组织的问题
  • s13 后台任务:解决慢命令在后台运行时主循环如何继续的问题
  • s15 Agent 团队:会使用任务系统来协调多个 Agent 的工作
  • s17 自主智能体:会利用任务系统来规划和执行复杂任务

所以任务系统是构建高级 Agent 系统的基础组件。

下一章预告

有了任务系统,你的 Agent 已经具备了管理复杂任务的能力。下一章我们将探讨后台任务系统,让 Agent 能够在执行慢命令时继续响应其他请求,提高系统的响应速度和并发能力。


一句话总结:任务系统的核心不是「保存清单」,而是「判断什么时候能开工」。


如果觉得有帮助,欢迎关注,我会持续更新「从零构建 Coding Agent」系列文章。