KEEL · 龙骨 · A CURRICULUM FOR THE AI ERA
02. Wake-up、Signal、Timer 和 Task 有什么区别? — keel 龙骨
长周期 Agent 不能靠一个常驻 Python 线程“等着”。进程会重启、机器会缩容、用户输入会延迟,等待本身必须成为持久化状态。课程把各种继续运行的原因统一建模为 WakeupRequest,但保留它们的来源和取消语义。
长周期 Agent 不能靠一个常驻 Python 线程“等着”。进程会重启、机器会缩容、用户输入会延迟,等待本身必须成为持久化状态。课程把各种继续运行的原因统一建模为 WakeupRequest,但保留它们的来源和取消语义。
四种继续原因
| 类型 | 触发者 | 典型用途 | 关键字段 |
|---|---|---|---|
| Timer | 调度器 | 10 分钟后轮询部署状态 | due_at、时区/时钟版本 |
| Signal | 用户或外部系统 | 审批、补充参数、webhook | signal_id、来源身份、payload |
| Task completion | 异步工具/服务 | 查询报表、批量导出完成 | task_id、状态、结果引用 |
| Retry | 运行时 | 暂时性网络或限流错误 | attempt、退避策略、deadline |
它们都能进入 pending -> claimed -> fired/failed,但不应把外部 signal 伪装成 timer,也不应把尚未完成的 task 当成成功结果。LangGraph interrupts强调恢复点会保存状态并等待外部输入;当前 MCP Tasks 扩展则用 durable task ID、轮询/通知和明确终态表达 deferred result。Tasks 是需要双方协商支持的官方扩展,不是所有 MCP 客户端都具备的核心能力。
唤醒契约
最小请求至少包含:wakeup_id、run_id、kind、due_at、来源、去重键、取消策略和 payload schema。wakeup_id 是业务意图的身份,不是每次投递的消息 ID;消息重复投递仍应指向同一个请求。
配套项目的 SQLiteEventStore.schedule() 对相同 ID 的相同载荷幂等,对不同载荷报冲突;claim_due() 才会产生一次 delivery attempt 和 lease。运行:
python courses/advanced/advanced-agent-systems/course/project/examples/03_wakeup_and_lease.py
观察 worker A 持有 lease 时 worker B 无法领取,过期后 B 才能接管。这个结果比“线程睡醒了”更接近队列、webhook 和人工审批的共同协议。
取消与迟到输入
取消不是简单删除 wakeup:需要记录请求者、时间、原因和当前状态。已经 fired 的请求不能被取消来掩盖已发生的副作用;迟到 signal 必须依据 run 的终态和版本拒绝或转成新的业务请求。
本章检查点
- 同一个业务意图重试时,哪个字段保持不变?
- worker 在进程重启后如何知道自己之前是否已经处理?
- task 返回
working时,为什么不能产生run.completed?