异步任务(SEP-1686):长跑工作的「先调用、后取结果」 本节摘要:真实的 Agent 工作要花几分钟到几小时:CI 运行、深度研究综合、批量导出。同步工具调用会掉线、超时或冻住 UI。SEP-1686 于 2025-11-25 合入,新增 Tasks 原语:任何请求都可被增强为 task,结果可稍后拉取或经状态通知流式获取。漂移提示:Tasks 在 2026 上半年仍是实验性的,SDK 表面仍围绕规范设计中。 学习目标 阅读完本节,你应当能够: 判断何时把工具从同步提升为任务增强(服务端工作 >30 秒)。 走完任务生命周期: → → / / 。 持久化任务状态,使崩溃不丢在途工作。 正确轮询 并取 。 一、问题与直觉 一个 工具跑一个多分钟的抽取管线。
本节摘要:真实的 Agent 工作要花几分钟到几小时:CI 运行、深度研究综合、批量导出。同步工具调用会掉线、超时或冻住 UI。SEP-1686 于 2025-11-25 合入,新增 Tasks 原语:任何请求都可被增强为 task,结果可稍后拉取或经状态通知流式获取。漂移提示:Tasks 在 2026 上半年仍是实验性的,SDK 表面仍围绕规范设计中。
阅读完本节,你应当能够:
working → input_required → completed / failed / cancelled。tasks/status 并取 tasks/result。一个 generate_report 工具跑一个多分钟的抽取管线。同步模型下三条路:
都不好。SEP-1686 加了第四条:任务增强。任何请求(典型是 tools/call)可被标记为 task,服务端立即返一个 task id,客户端轮询 tasks/status,完成时取 tasks/result,服务端状态跨重启存活。
一个请求通过设 params._meta.task.required:true(或 optional:true,服务端决定)成为 task。服务端立即响应:
{ "jsonrpc": "2.0", "id": 1, "result": { "_meta": { "task": {"id": "tsk_9f7b...", "state": "working", "ttl": 900000} } } }
ttl 是服务端保留状态的承诺;ttl 过后任务结果被丢弃。
工具注解可声明 task 支持:
taskSupport:"forbidden"——本工具始终同步,快工具安全。taskSupport:"optional"——客户端可请求任务增强。taskSupport:"required"——客户端必须用任务增强。generate_report 会是 required,notes_search 会是 forbidden。
working -> input_required -> working (经 elicitation 循环) working -> completed working -> failed working -> cancelled
状态机是只追加的:一旦 completed/failed/cancelled,任务即终态。
tasks/status {taskId}——返回当前状态与进度提示。tasks/result {taskId}——阻塞,或未完成则返 404。tasks/cancel {taskId}——幂等;终态忽略。tasks/list——可选;枚举活跃与最近完成的任务。服务端支持时,客户端可订阅状态通知:
server -> notifications/tasks/updated {taskId, state, progress?}
流式而非轮询的客户端 UX 更好。轮询始终作为最小表面被支持。
规范要求声明 task 支持的服务端持久化状态:ttl 内的崩溃不应丢已完成结果。存储从 SQLite 到 Redis 到文件系统皆可。本节脚手架用文件系统。
def save_task(task): path = f"/tmp/lesson-13-tasks/{task.id}.json" atomic_write(path, json.dumps(task.__dict__)) # 跨重启存活 def load_all_tasks(): return [Task(**json.loads(read(p))) for p in glob("/tmp/lesson-13-tasks/*.json")]
tasks/cancel 幂等。任务在执行中时,服务端尝试停止(检查执行器协作式取消);若已终态,请求是 no-op。
服务端进程重启时:
working 任务标记为 failed,错误码 CRASH_RECOVERY。completed/failed/cancelled。任务本身可调 sampling/createMessage。长跑研究任务就是这么工作的:服务端的任务线程按需采样客户端模型,同时客户端 UI 把任务显示为 working 配周期进度更新。
SEP-1686 在 2025-11-25 出货,但更广的路线图列出三个开放问题:持久订阅原语、子任务(父子任务关系)、结果 TTL 标准化。预期规范在 2026 全年演进。生产代码应只把 Tasks 当常见情况稳定,并对未来子任务的 SDK 变更加守卫。
| 维度 | 同步工具 | 任务增强(SEP-1686) |
|---|---|---|
| 连接占用 | 全程 | 仅立即返回 |
| 超时风险 | 高 | 低 |
| 跨重启 | 丢 | ttl 内存活 |
| 取消 | 难 | 幂等 tasks/cancel |
| 适合 | <30 秒 | 分钟~小时级 |
💡 心法:
taskSupport:required用在 >30 秒的工具;forbidden用在快工具;状态机只追加,终态后一切 no-op。生产持久化用 SQLite/Redis,别用裸文件系统。
本节产出 outputs/skill-task-store-designer.md——给定一个长跑工具(研究、构建、导出),它设计任务存储(状态形状、ttl、持久性)、选对 taskSupport 标志、画进度通知。
code/main.py 实现一个持久任务存储(文件系统后端)与一个后台线程跑的 generate_report 工具。客户端调工具立即拿 task id,worker 更新进度时轮询 tasks/status,完成时取 tasks/result。取消可用;崩溃恢复通过杀 worker 线程再重载状态模拟。
跑完整流:运行 code/main.py,启动一个 generate_report 任务,轮询 status,再取结果。
中途取消:运行中加一个 tasks/cancel 调用,验证 worker 遵守它且状态变 cancelled。
模拟崩溃恢复:杀 worker 线程,重启加载器,观察 CRASH_RECOVERY 失败模式。
换 SQLite:把存储扩展到 SQLite,持久性收益相同,但查询选项打开(列出会话 X 的所有任务)。
读路线图:读 MCP 2026 路线图贴,找出最可能影响来年 SDK API 设计的那个 Tasks 相关开放问题。
_meta.task.required/optional,服务端立即返 {id, state, ttl}。forbidden(快工具)、optional、required(长跑工具)。working → input_required(经 elicitation 回 working)→ completed/failed/cancelled 终态。tasks/status 轮询、tasks/result 取/404、tasks/cancel 幂等、tasks/list 可选。notifications/tasks/updated 推送 UX 更好;轮询是最小表面。working 且进程死 → failed + CRASH_RECOVERY;终态在 ttl 内保留。下一节,我们看 MCP Apps——把交互式 UI(
ui://)作为第四种内容块,让服务端能在 host 里渲染富组件。