Skip to content
预计 1 分钟右侧目录可快速定位

Run 等几个小时还能继续吗:Durable Wait、Timer 与 External Event ​

前置:多个 Worker 抢同一个 Run 怎么办:Claim、Lease、Heartbeat 与 Fencing
核心问题:Run 等待几小时的审批或外部事件时,怎样释放 Worker,又怎样保证事件到来后只唤醒正确的等待实例?

“等待”不能靠一个 Python Task 睡着 ​

Agent 发起异步部署后,第三方系统可能 40 分钟后才通过 webhook 返回结果;另一个 Run 需要“明天 09:00 再检查”;审批则可能两天后才有人处理。

如果实现是:

python
await asyncio.sleep(60 * 60 * 24)

或一直把 HTTP/worker 进程挂着等待,那么一次重启、扩缩容或部署就会丢失等待位置。

长任务中的等待应该被建模成 durable fact,而不是进程栈状态。

Wait Condition 是 Runtime 状态的一部分 ​

Run/Node 进入 WAITING 时至少持久化:

text
wait_id
kind = TIMER | APPROVAL | EXTERNAL_EVENT
correlation_key
due_at / expected_event
resume_node
created_at
status = WAITING

然后当前 Worker 可以完全退出。系统恢复后仍知道“在等什么”和“满足条件后从哪里继续”。

这就是 durable wait 与普通 sleep() 的区别。

Timer:时间到了只是产生一个可处理事件 ​

Timer 不应该直接修改业务状态。更稳妥的模型是:

text
persist TIMER(due_at)
→ timer service / scheduler detects due
→ emit wake event(wait_id)
→ Runtime validates current state
→ WAITING → READY/RUNNING

这样 timer 重复触发也可以通过 wait_id 去重;服务停机一小时后重启,也可以扫描 overdue timer 补发 wake-up,而不是永久丢失。

External Event:先相关,再恢复 ​

Webhook、消息队列、另一个 Workflow 的结果本质上都是外部事件。Runtime 需要稳定 correlation key,例如:

text
run_id + wait_id
external_job_id
approval_id
business_operation_id

外部事件到达后先写入 inbox/event record,再尝试推进状态。不要让 HTTP handler 直接从内存找到某个 Future 并 set_result();进程重启后那个 Future 根本不存在。

典型流程:

text
POST /webhook/deployment
→ verify/authenticate
→ persist ExternalEvent(event_id, correlation_key, payload)
→ dedup
→ match WAITING record
→ transition WAITING → READY
→ enqueue/notify scheduler

Lost Wake-up 与 Early Event ​

最容易漏掉的不是“事件没来”,而是事件到达时序:

  • Runtime 还没把 WAITING 提交,external event 已经先到;
  • WAITING 已提交,但 wake event 在 enqueue 前 crash;
  • provider retry webhook,事件重复到达。

因此 wait registration 与 event inbox 都必须 durable。恢复/对账逻辑应能匹配“先来的 event”和“后注册的 wait”,而不是要求事件刚好在进程监听期间到达。

与 HITL 的关系 ​

HITL 不是一套特殊 Runtime。它只是:

text
kind = APPROVAL
expected_event = approval.decision

用户批准、定时器到期、第三方 job 完成,都可以共用同一个 wait/resume state machine;不同的是事件来源、鉴权和 payload validation。

Failure Modes ​

等待期间必须占住 Worker ​

会造成线程/连接/内存长期占用,并让部署重启变成数据丢失。

处理: 写 checkpoint + wait record 后释放执行资源。

Webhook 直接“唤醒内存对象” ​

多实例或重启后无法找到原 Future,事件也难以重放。

处理: 先持久化 external event,再由 Runtime 根据 correlation 和 state transition 恢复。

如何验证 ​

场景必须成立
WAITING 后整个服务重启Timer/approval/external event 到来后仍能 resume
event 在 wait registration 前到达后续注册时能够匹配,不丢事件
同一 webhook 重试 3 次只发生一次有效 state transition
timer 到期但 enqueue 前 crash重启扫描后仍能补唤醒

自测 ​

  1. Run 要等 6 小时人工回调。为什么不能让一个线程 sleep 6 小时保持执行上下文?
  2. External event 重复投递时,怎样用 stable wait/event identity 保证同一个等待只被满足一次?

Sources ​

AI Engineering · Agent · Harness · Backend Systems