背景
当前 Pause / Resume 的主要实现是:
- Pause:把 scan job 状态改为
paused,停止当前进程内的 pipeline runtime
- Resume:把状态改回
running,基于已有队列重新启动 pipeline runtime
这种方式没有完整处理正在启动或正在运行的 task。Pause 后,task 容器、Agent turn、并发槽位和迟到结果可能继续存在;Resume 时也无法保证这些 task 从头重新执行且只执行一次。
本 issue 的目标不是保存 task 的执行进度,而是提供明确、简单、可靠的强制暂停语义。
核心语义
Pause
Pause 必须立即:
- 持久化 job 的 pause intent,并阻止 worker 领取新 task、阻止 pipeline 创建下游 task。
- 停止所有
starting 和 running task 对应的 Agent turn、ACP process、容器及运行时。
- 将这些 task 当前 attempt 标记为
interrupted。
- 丢弃当前 attempt 的执行进度、部分输出、临时 artifact 和未提交结果。
- 记录需要在 Resume 时重新执行的 logical task。
- 保留 Pause 前已经成功提交的 completed task 和有效结果。
- 等待所有运行时确认终止后,job 才从
pausing 进入 paused。
不提供 graceful drain,不等待正在运行的 Agent turn 完成,也不尝试保存半途 checkpoint。
Resume
Resume 必须:
- 为 Pause 时被中断的 task 创建全新的 attempt。
- 使用原 task 的不可变 input、pipeline definition 和 runtime config snapshot 从头执行。
- 为新 attempt 创建新的 Agent session,不恢复被中断的 ACP session。
- 将新 attempt 与原本尚未启动的 queued task 一起重新放入队列。
- 重新启动 pipeline runtime,并继续正常的 edge/fan-out 调度。
- 保证重复 Resume 请求不会重复创建 attempt 或重复入队。
状态模型
Job 状态:
pending/running -> pausing -> paused -> resuming -> running
Task attempt 状态至少区分:
queued -> starting -> running -> completed
\-> interrupted
interrupted 是终态记录,不会被改回 running。Resume 创建新的 attempt,保留旧 attempt 供审计。
Pause Fence 与竞态处理
Pause 请求在数据库中原子写入:
pauseRequestedAt
- 递增的
executionEpoch
- pause request ID
所有 worker、queue consumer、Agent driver、artifact/result writer 和 edge fan-out 在启动或提交前检查 epoch:
- Pause intent 写入前已经成功提交的 completed task 保留。
- Pause intent 写入后才到达的旧 attempt 结果一律拒绝并丢弃。
- 旧 worker 即使未及时退出,也不能覆盖 Resume 后的新 attempt。
- Pause 与 task start、task complete、下游 enqueue 同时发生时,以数据库中的 epoch 和提交顺序为准。
运行时终止与清理
Pause 应统一终止并回收:
- 正在启动和正在运行的 scan task container
- ACP
session/prompt 和 agent process
- task runtime、terminal 和 monitoring subscription
- BullMQ active job 对应的执行租约
- 当前 attempt 的临时目录和部分输出
- 尚未提交的 candidate/result projection
终止操作必须幂等。容器已经退出、进程不存在或 worker 重启时,reconciler 仍应能把 attempt 收敛为 interrupted。
Resume Reconciliation
Resume 由单一 reconciler 获取 job lease,并按数据库状态恢复:
- completed task 保持不变,不重新执行
- 原本 queued、尚未启动的 task 恢复调度
- interrupted task 创建新 attempt 并重新入队
- 孤儿 container、旧 runtime 和旧 epoch queue item 被清理
- 通过稳定 idempotency key 防止 task、edge、candidate 和结果重复
如果部分 task 入队失败,job 保持 resuming 或明确的恢复失败状态,可安全重试;不能表面显示为 running。
UI 与可观测性
- UI 区分
pausing、paused、resuming 和恢复失败
pausing 时展示仍待终止的 starting/running task 数量
- task detail 展示被 Pause 中断的 attempt 及其 replacement attempt
- 记录 pause/resume request、execution epoch、终止原因、重新入队结果和耗时
- 明确提示用户:Pause 会丢弃所有运行中 task 的当前进度,Resume 后从头执行
验收标准
- Pause 后不再有新的 task 或下游 edge 开始执行
- 所有 starting/running task 被立即终止,当前 attempt 标记为 interrupted
- 被中断 attempt 的部分输出和迟到结果不会生效
- Pause 前已完成并提交的 task 不被重新执行
- Resume 为每个 interrupted task 创建且只创建一个新 attempt,并重新入队
- Resume 使用原输入和配置从头运行,不恢复旧 ACP session 或半途进度
- 重复 Pause / Resume 请求保持幂等
- Vulseek、BullMQ 或容器运行时重启后仍能收敛到一致状态
- 覆盖 Pause/start、Pause/complete、Pause/fan-out、迟到提交、孤儿容器、重复 Resume 和部分入队失败测试
背景
当前 Pause / Resume 的主要实现是:
paused,停止当前进程内的 pipeline runtimerunning,基于已有队列重新启动 pipeline runtime这种方式没有完整处理正在启动或正在运行的 task。Pause 后,task 容器、Agent turn、并发槽位和迟到结果可能继续存在;Resume 时也无法保证这些 task 从头重新执行且只执行一次。
本 issue 的目标不是保存 task 的执行进度,而是提供明确、简单、可靠的强制暂停语义。
核心语义
Pause
Pause 必须立即:
starting和runningtask 对应的 Agent turn、ACP process、容器及运行时。interrupted。pausing进入paused。不提供 graceful drain,不等待正在运行的 Agent turn 完成,也不尝试保存半途 checkpoint。
Resume
Resume 必须:
状态模型
Job 状态:
Task attempt 状态至少区分:
interrupted是终态记录,不会被改回 running。Resume 创建新的 attempt,保留旧 attempt 供审计。Pause Fence 与竞态处理
Pause 请求在数据库中原子写入:
pauseRequestedAtexecutionEpoch所有 worker、queue consumer、Agent driver、artifact/result writer 和 edge fan-out 在启动或提交前检查 epoch:
运行时终止与清理
Pause 应统一终止并回收:
session/prompt和 agent process终止操作必须幂等。容器已经退出、进程不存在或 worker 重启时,reconciler 仍应能把 attempt 收敛为
interrupted。Resume Reconciliation
Resume 由单一 reconciler 获取 job lease,并按数据库状态恢复:
如果部分 task 入队失败,job 保持
resuming或明确的恢复失败状态,可安全重试;不能表面显示为 running。UI 与可观测性
pausing、paused、resuming和恢复失败pausing时展示仍待终止的 starting/running task 数量验收标准