本示例演示框架内置的 Task 工具族(task_create / task_update / task_get / task_list),对齐 Claude Code v2.1.142+ 的结构化 Task 能力。与 TodoWriteTool 的「整表替换」不同,Task 工具族采用按 taskId 增量更新模型:任务由服务端分配 id,支持 blockedBy / blocks 依赖编排。任务看板存放在会话级 state(key 前缀 tasks,不用 temp:),因此可以跨轮(跨 Runner.run_async 调用)存活。
- 增量更新语义:
task_create创建任务并返回服务端分配的id;task_update按taskId局部 patch 状态 / 字段 / 依赖,不必重传整表。 - 依赖编排:
task_update的addBlockedBy/removeBlockedBy(及addBlocks/removeBlocks)维护双向依赖边;上游任务completed时自动从下游blockedBy中移除并报告unblocked。 - Token 优化:
task_list只返回摘要(id/subject/status/owner/blockedBy),刻意省略description;需要完整详情用task_get。 - 会话级持久化:整个看板序列化为单个 JSON blob 写入
tool_context.state["tasks[:<branch>]"],随 function-response 事件的 state delta 自动落库,跨轮存活。注意:框架会剥离temp:前缀的 state,因此默认使用无前缀的tasks。 - 子 Agent 隔离:state key 追加
:<branch>,不同 branch(父 / 子 Agent)各自维护独立看板;branch 为空时回退到 agent 名。 - 硬契约校验(代码强制):
subject非空、状态合法、依赖引用存在、依赖无环(detect_cycle)、默认至多一个in_progress(enforce_single_in_progress,可关);违反返回INVALID_ARGS/INVALID_DEPENDENCY/INVALID_STATUS/NOT_FOUND。 - ID 不重用:
highwatermark记录曾分配过的最高 id,软删除(status: deleted)后也不会复用。 - Prompt 引导分层:使用时机与状态机建议放在
DEFAULT_TASK_PROMPT,由工具process_request自动注入(多工具挂载只注入一次),与硬契约清晰分层。
| 维度 | todo_write |
Task 工具族 |
|---|---|---|
| 工具数量 | 1 | 4 |
| 更新方式 | 整表替换 | 按 taskId 增量 |
| 单项标识 | content(唯一键) |
id(服务端分配) |
| 依赖 | 无 | blockedBy / blocks |
| state key | todos[:branch] |
tasks[:branch] |
| 适用场景 | 短清单、token 不敏感 | 长任务板、多 Agent、依赖编排 |
建议二选一挂载;同时挂载易让模型混用。
本例只有一个 LlmAgent,挂载 TaskToolSet 与文件/Shell 执行工具;root_agent 指向 task_planner:
task_planner (LlmAgent)
├── model: OpenAIModel
├── instruction: 工程助手人设(DEFAULT_TASK_PROMPT 由工具 process_request 自动注入)
├── tools:
│ ├── TaskToolSet() → task_create / task_update / task_get / task_list
│ ├── BashTool(cwd=work_dir)
│ ├── WriteTool(cwd=work_dir)
│ └── ReadTool(cwd=work_dir)
└── session: InMemorySessionService(单一 session 跨轮复用)
关键文件:
- examples/task_tools/agent/agent.py:构建
task_planner,挂载TaskToolSet - examples/task_tools/agent/prompts.py:规划型人设 instruction(
DEFAULT_TASK_PROMPT由process_request自动追加) - examples/task_tools/agent/config.py:环境变量读取(LLM 凭据)
- examples/task_tools/run_agent.py:测试入口,在同一个 session 内驱动两轮静态站点搭建/优化;
task_update将状态设为in_progress/completed时实时渲染看板,每轮结束后用get_task_store读回持久化看板
TaskToolSet()即可直接用,工具名task_create/task_update/task_get/task_list(snake_case,满足部分 provider 的^[a-zA-Z0-9_-]+$命名约束)。- 构造参数:
state_key_prefix(默认tasks):状态 key 前缀;勿用temp:,该前缀不会被 SessionService 持久化。enforce_single_in_progress(默认True):设置某任务in_progress时若已有其他in_progress则拒绝。inject_prompt(默认True):把DEFAULT_TASK_PROMPT注入 system instruction(多工具挂载只注入一次)。
- 所有轮次共用同一个
session_id,每轮一次runner.run_async。 - 工具把看板写进
tasks:<branch>,随事件 state delta 落库。 - 实时看板:每次
task_update成功且状态变为in_progress或completed时,demo 从 session 读回get_task_store并打印📋 Current task board:(仅改依赖边的task_update不触发)。 - 轮末看板:每轮结束后再次用
get_task_store(session, branch=agent.name)读回持久化结果,打印📋 Persisted task board:,证明跨轮存活。 - 渲染符号与
render_task_list一致:✅已完成(completed)🔄进行中(in_progress,显示activeForm)⬜待办(pending),并标注blocked by: <ids>
- 硬契约(代码强制):
subject非空、状态合法、依赖存在且无环、至多一个in_progress;违反返回明确错误码。 - Prompt 引导(鼓励不强制):
DEFAULT_TASK_PROMPT在挂载工具时经process_request自动追加到 system instruction。 - 原则:要强制就加 validator,不要把约束塞进 prompt,两层保持可区分。
- Python 3.12
git clone https://github.com/trpc-group/trpc-agent-python.git
cd trpc-agent-python
python3 -m venv .venv
source .venv/bin/activate
pip3 install -e .在 examples/task_tools/.env 中配置(或通过 export):
TRPC_AGENT_API_KEYTRPC_AGENT_BASE_URLTRPC_AGENT_MODEL_NAME
cd examples/task_tools
python3 run_agent.pyrun_agent.py 在同一个 session 内驱动 2 轮对话:Turn 1 用 task_create 规划静态站点并逐项执行;Turn 2 追加 CSS/JS 相关任务并更新 README。典型工具链:
Turn 1 搭建静态站点
task_create ×3 → task_update addBlockedBy → task_update in_progress
→ Bash / Write 执行 → task_update completed(每步 in_progress / completed 后打印 Current task board)
Turn 2 优化静态站点
task_create ×4(id 从 #4 起)→ task_update 依赖与状态 → Bash / Write 执行
→ task_update completed(轮末 Persisted task board 含 #1–#7)
🆔 Session ID: 71bb460a... (shared across all turns)
📂 Work dir: /path/to/examples/task_tools
========== 搭建静态站点 ==========
📝 User: 请帮我在当前目录搭建 demo 静态站点 ...
🔧 [Invoke Tool: task_create({'subject': '创建 demo/ 及子目录 css/、js/', ...})]
📊 [Tool Result: created id=1 subject='创建 demo/ 及子目录 css/、js/']
🔧 [Invoke Tool: task_update({'taskId': '2', 'addBlockedBy': ['1']})]
📊 [Tool Result: updated id=2 status=pending]
🔧 [Invoke Tool: task_update({'taskId': '1', 'status': 'in_progress'})]
📊 [Tool Result: updated id=1 status=in_progress]
📋 Current task board:
🔄 #1 创建 demo/ 及子目录 css/、js/
⬜ #2 创建 demo/index.html (blocked by: 1)
⬜ #3 创建 demo/README.md (blocked by: 1)
🔧 [Invoke Tool: task_update({'taskId': '1', 'status': 'completed'})]
📊 [Tool Result: updated id=1 status=completed unblocked=['2', '3']]
📋 Current task board:
✅ #1 创建 demo/ 及子目录 css/、js/
⬜ #2 创建 demo/index.html
⬜ #3 创建 demo/README.md
📋 Persisted task board:
✅ #1 创建 demo/ 及子目录 css/、js/
✅ #2 创建 demo/index.html
✅ #3 创建 demo/README.md
----------------------------------------
========== 优化静态站点 ==========
🔧 [Invoke Tool: task_create({'subject': '创建 demo/css/style.css', ...})]
📊 [Tool Result: created id=4 subject='创建 demo/css/style.css']
... (Turn 2 追加 #4–#7,id 延续 Turn 1)
📋 Persisted task board:
✅ #1 创建 demo/ 及子目录 css/、js/
✅ #2 创建 demo/index.html
✅ #3 创建 demo/README.md
✅ #4 创建 demo/css/style.css
✅ #5 创建 demo/js/app.js
✅ #6 更新 demo/index.html
✅ #7 更新 demo/README.md
----------------------------------------
- 长任务板、需要显式依赖编排或跨多轮跟踪:用
TaskToolSet。 - 需要服务端 / REST / 审计读取当前看板:调用
get_task_store(session, branch)。
- task_tools_parallel — 验证
parallel_tool_calls=True与task_store_lock下的并行task_create/task_update(Phase 1–2 无需 API Key)。