- AgentConfig/LlmClient 新增 enable_thinking 参数,前端 SSE 请求传递 thinking 开关,仅千问/DashScope 时启用 - 完善权限系统,支持细粒度的权限控制和用户权限申请 - delegate_research 工具重命名为 subagent,SubAgentTool/SubAgentRunner 重构 - 子代理消息(system/user/assistant/tool)持久化到 agent_messages 表,带 agent_name 标识 - 子代理活动日志(工具调用列表+思考摘要)注入返回结果,Hooks 获得正确 session_id 和 subagent_name - LLM 工具调用 ID 回退生成 UUID(llm.rs),ToolCall/ToolResult SSE 事件增加 id/tool_call_id 双字段 - ToolContext 扩展 session_id/sse_tx/enable_thinking 字段,executor 统一注入而非构造函数传参 - agent_messages 新增 metadata+raw_json 列,agent_sessions 暴露 summary 字段 - 删除文件级 transcript 快照(compact.rs),改为依赖 DB 持久化 - ResearchAgentPanel 重写:TimelineItem 类型替代 StreamStep,支持会话历史回放 - 新增 AgentMetricsPanel/AskUserQuestionCard/AuditLogViewer 三个前端组件,types.ts 完整类型定义 - docs/architecture/ 分层重组:概览/核心模块/核心工作流 + agent/ 子目录 11 篇专题文档 - docs/api.md 补充 RAG/Target/Agent 接口,docs/development.md 新建开发指南 - .env.example 完全重写,补充 FALLBACK_MODEL 等变量说明
234 lines
8.7 KiB
Markdown
234 lines
8.7 KiB
Markdown
# 任务系统 (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<br/>(纯格式化, 并发安全, 无副作用)"]
|
||
RT --> Context["context.rs<br/>restore_tasks_from_db()<br/>(每 turn 开始时注入)"]
|
||
RT --> TaskBoard["TaskBoard<br/>(跨 session 共享看板)<br/>can_start() / claim_task() (原子)<br/>list_available_tasks()"]
|
||
|
||
TodoWrite --> Persist["persist_tasks<br/>INSERT OR REPLACE INTO<br/>agent_tasks"]
|
||
|
||
Persist --> DB["SQLite: agent_tasks<br/>(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<br/>(回退)
|
||
pending --> in_progress: claim_task()<br/>或 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`,返回 `<persisted-output>` 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 字段引入 |
|