# 任务系统 (Task System) 任务系统为 Agent 提供 **规划 → 执行跟踪 → 状态持久化 → 跨 turn 恢复** 的完整闭环,参考 Claude Code s12 Task System 和 s17 Autonomous Agents 设计。 --- ## 概览 ```mermaid flowchart TD subgraph RT["AgentRuntime (run_react_loop)"] direction TB S1["1. LLM 调用"] S2["2. 检测 todo_write 调用"] S3["3. persist_tasks() 持久化"] S4["4. 每 3 步 nag reminder"] S5["5. context.rs 恢复任务状态"] end RT --> TodoWrite["TodoWriteTool
(纯格式化, 并发安全, 无副作用)"] RT --> Context["context.rs
restore_tasks_from_db()
(每 turn 开始时注入)"] RT --> TaskBoard["TaskBoard
(跨 session 共享看板)
can_start() / claim_task() (原子)
list_available_tasks()"] TodoWrite --> Persist["persist_tasks
INSERT OR REPLACE INTO
agent_tasks"] Persist --> DB["SQLite: agent_tasks
(session_id, task_id UNIQUE)"] ``` --- ## 数据库 Schema ```sql -- migrations/20260616000000_agent_tasks.sql CREATE TABLE agent_tasks ( id INTEGER PRIMARY KEY AUTOINCREMENT, session_id TEXT NOT NULL, task_id TEXT NOT NULL, -- LLM 生成的任务唯一标识 content TEXT NOT NULL DEFAULT '', -- 任务描述 status TEXT NOT NULL DEFAULT 'pending' CHECK(status IN ('pending', 'in_progress', 'completed')), blocked_by TEXT NOT NULL DEFAULT '[]', -- JSON 数组: ["task_1", "task_2"] owner TEXT NOT NULL DEFAULT '', -- 归属 agent (lead / sub_xxx / teammate) created_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, updated_at DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP, FOREIGN KEY (session_id) REFERENCES agent_sessions(session_id) ON DELETE CASCADE ); -- 索引 CREATE UNIQUE INDEX idx_agent_tasks_session_task ON agent_tasks(session_id, task_id); CREATE INDEX idx_agent_tasks_session ON agent_tasks(session_id); CREATE INDEX idx_agent_tasks_status ON agent_tasks(session_id, status); ``` 关键设计点: - **(session_id, task_id) 联合唯一索引**:支持 `INSERT ... ON CONFLICT DO UPDATE` 的 upsert 语义 - **`blocked_by` 存 JSON 数组**:如 `["1", "2"]` 表示依赖任务 1 和 2 必须先完成,形成 DAG - **`owner` 字段**(`migrations/20260618000000_agent_identity.sql` 引入):支持多 Agent 场景下的任务归属 --- ## 任务状态机 ```mermaid stateDiagram-v2 [*] --> pending: 初始状态 pending --> pending: LLM 标记 completed
(回退) pending --> in_progress: claim_task()
或 LLM 标记 in_progress in_progress --> completed: LLM 标记 completed completed --> [*]: 终点状态 note right of in_progress: 同一时刻只能有一个 ``` 约束规则: 1. **最多一个 `in_progress`**:TodoWriteTool 在格式化输出时检测并警告(`tools/todo.rs:115-118`) 2. **DAG 依赖**:TaskBoard::can_start() 检查所有 blockedBy 依赖是否为 completed(`task_board.rs:33-63`) 3. **原子认领**:claim_task() 使用乐观并发控制,`WHERE owner IS NULL` 保证不被重复认领 4. **应用层验证**:persist_tasks 只做自引用检测,不做完整的循环依赖检测(文档明确标注) --- ## 核心组件 ### TodoWriteTool (`tools/todo.rs`) LLM 通过 function calling 调用的工具,声明参数: ```json { "todos": [ {"id": "1", "content": "搜索相关文献", "status": "completed"}, {"id": "2", "content": "分析论文方法", "status": "in_progress", "blockedBy": ["1"]}, {"id": "3", "content": "撰写综述", "status": "pending", "blockedBy": ["2"]} ] } ``` **设计原则 — 工具层与持久化层分离**: - `TodoWriteTool::execute()` 只做格式化和约束校验,不写数据库 - `is_concurrency_safe()` 返回 `true`(纯格式化,无副作用) - 实际的 SQLite 持久化由 `AgentRuntime::run_react_loop()` 在检测到 `todo_write` 调用后统一完成(`runtime/mod.rs:845-854`) ### persist_tasks() (`tools/todo.rs`) ```rust pub async fn persist_tasks( db: &SqlitePool, session_id: &str, todos: &[serde_json::Value], owner: &str, // "lead" / "sub_xxx" / teammate name ) -> anyhow::Result<()> ``` - 使用 `INSERT ... ON CONFLICT(session_id, task_id) DO UPDATE SET ...` 实现 upsert - 自动跳过任务对自身的引用(`blockedBy` 中包含自身 ID 时忽略并记录 warn) - 作为公开 API 导出(`tools/mod.rs:49`),允许 teammate、外部调用者直接操作 ### TaskBoard (`task_board.rs`) 共享任务看板,提供跨 Agent 的任务可见性: | 方法 | 功能 | 并发策略 | |:---|:---|:---| | `can_start(session_id, task_id)` | 遍历 blockedBy,检查所有依赖是否 completed | 只读,天然安全 | | `claim_task(session_id, task_id, claimant)` | 原子认领(status→in_progress, owner→claimant) | `WHERE owner IS NULL OR owner = ''` | | `list_available_tasks(limit)` | 列出所有可认领的 pending 任务,**跨 session** | 只读,附加 can_start 解析 | 认领 SQL 的原子性保证: ```sql UPDATE agent_tasks SET owner = ?, status = 'in_progress' WHERE session_id = ? AND task_id = ? AND (owner IS NULL OR owner = '' OR status = 'pending') ``` `rows_affected() > 0` 表示认领成功,否则已被他人认领。 --- ## ReAct 循环中的集成点 在 `AgentRuntime::run_react_loop()` 中有三个关键集成点: ### todo_write 检测与持久化 (`mod.rs:752-855`) ```rust // 检测 todo_write 调用 let called_todo_write = tool_calls.iter().any(|tc| tc.function.name == "todo_write"); if called_todo_write { steps_since_last_todo = 0; // 重置 nag 计数器 } // 工具执行完成后持久化 if called_todo_write { for prep in &prepared_calls { if prep.tool_name == "todo_write" { if let Some(todos) = prep.args.get("todos").and_then(|t| t.as_array()) { let _ = persist_tasks(db, sid, &todos_vec, "lead").await; } } } } ``` ### 进度催促 Nag Reminder (`mod.rs:589-596`) 每 3 步未调用 `todo_write`,自动向 LLM 注入提醒: ``` 提醒:你已经连续多步未更新任务计划。建议调用 todo_write 工具复盘当前进度并规划后续步骤。 ``` `nag_after_steps = 3`,`steps_since_last_todo` 在每次非 todo_write 调用后递增。 ### 跨 Turn 任务恢复 (`context.rs:32-34`) 每个新 turn 开始时,`restore_tasks_from_db()` 从 agent_tasks 表查询当前 session 的所有任务,格式化后注入 LLM 上下文: ``` [当前任务状态] 以下是上次会话中持久化的任务计划,请基于最新状态继续工作: ✅ [1] 文献检索 ✅ [2] 文献分析 🔄 [3] 撰写综述 (依赖: 1,2) ⏳ [4] 最终校对 (依赖: 3) 使用 todo_write 工具更新任务进度。 ``` System prompt 中也包含提示(`system_prompt.rs:66`): > 任务状态会在每轮开始时从数据库恢复,请基于最新状态继续工作。 --- ## 子代理与任务隔离 子代理 (`SubAgentRunner`) 与父代理在 Task 层面保持隔离: - 子代理拥有独立的 ToolRegistry 和 ReAct 循环(`subagent.rs:248-254`) - 子代理消息标记 `agent_name = "sub_xxx"`,父代理加载历史时只加载 `agent_name = "lead"` 的消息(`session.rs:78`) - 子代理不直接操作父代理的 agent_tasks,只返回文本摘要 - `agent_tasks.owner` 字段已为 teammate 场景预留 --- ## 辅助机制:工具结果持久化 (`tools/persist.rs`) 与 Task 系统协同工作的工具输出持久化: | 维度 | Task 系统 | Persist 系统 | |:---|:---|:---| | 回答的问题 | "要做什么" | "看到了什么" | | 触发条件 | LLM 调用 todo_write | 工具输出超过 max_output_chars | | 存储位置 | SQLite agent_tasks 表 | 磁盘文件 | | 幂等保证 | ON CONFLICT DO UPDATE | 独占创建 (create_new) | 当工具输出超过 `AGENT_MAX_TOOL_OUTPUT_CHARS`(默认 4000)时,完整内容写入 `{library_dir}/tool-results/{tool_call_id}.txt`,返回 `` XML 占位符,LLM 后续可通过 `read_file` 读取完整内容。 --- ## 相关文件 | 文件 | 职责 | |:---|:---| | `src/agent/tools/todo.rs` | TodoWriteTool 定义 + persist_tasks() | | `src/agent/task_board.rs` | TaskBoard — 跨 session 共享看板 | | `src/agent/runtime/mod.rs` | ReAct 循环中的 nag/persist 集成 | | `src/agent/runtime/context.rs` | restore_tasks_from_db — 跨 turn 恢复 | | `src/agent/runtime/system_prompt.rs` | 系统提示词中的 Task 指引 | | `src/agent/tools/persist.rs` | 工具输出磁盘持久化(辅助机制) | | `src/agent/subagent.rs` | 子代理中的 Task 隔离 | | `migrations/20260616000000_agent_tasks.sql` | agent_tasks 表 DDL | | `migrations/20260618000000_agent_identity.sql` | owner 字段引入 |