跳到主要内容

Workflow 状态生命周期

这章讲:一个 workflow 实例有哪些 status、它们怎么流转、为什么必须调用 acknowledgeStartOfProcessing,以及四个收尾/续命动作各自的语义。读完你会明白:workflow 的成败不是由「处理函数有没有抛异常」决定的。

1. 八种 status

一个实例的 status 取值在 SDK 里有完整文档(listInstances 的 JSDoc,packages/sdk/src/bot/workflow-proxy/types.ts:24-35):

status含义
pending已创建,但还没开始干活
in_progress正在运行(由 acknowledge 置入)
completed成功收尾
failed出错收尾
cancelled通过 API 取消
timedout因超时收尾
listening在等一个事件以继续
paused通过 API 暂停

注:具体的 status 联合类型定义在生成的 @botpress/client 包里(SDK 用 client.Workflow['status'],见 types.ts:36),源码里看不到该枚举的字面定义文件,但上面这份语义是 SDK 自带的权威 JSDoc。

2. 状态怎么流转

怎么读这张图:实例总是从 pending 出生;你的处理函数通过动作把它推向终态。方向从上往下。

createWorkflow


┌──────────┐
│ pending │ ← 刚创建,还没人干活
└────┬─────┘
acknowledge │
StartOfProcessing

┌────────────┐
│ in_progress│ ← 干活中
└────┬───────┘
┌───────────┼─────────────┬───────────────┐
▼ ▼ ▼ ▼
setCompleted setFailed cancel (后端超时)
│ │ │ │
▼ ▼ ▼ ▼
completed failed cancelled timedout

两个关键事实:

  • pending → in_progress 必须你来推(acknowledgeStartOfProcessing)。后端不会因为「事件投递成功」就自动认为你开始干了。
  • 终态由动作决定,不是由处理函数正常返回决定。处理函数跑完了但没调 setCompleted,实例可能还停在 in_progress / pending——dispatcher 会专门警告这种情况(见 §02 章)。

3. 为什么必须 acknowledge:看门狗 + 重试

这是整个 workflow 模型最容易踩的坑,也是它最妙的设计。acknowledgeStartOfProcessing 的 JSDoc 把规则写死了(packages/sdk/src/bot/workflow-proxy/types.ts:69-81):

「This method should be called in every workflow handler as soon as the workflow starts doing work. ... Should a workflow not be acknowledged in a timely fashion, it will be retriggered 3 times before being marked as failed.」

白话拆解:

  • 后端把 workflow 当看门狗任务:投了事件给你,期待你很快把它从 pending 推到 in_progress(= ack)。
  • 没及时 ack → 后端认为这次唤醒「卡死了」→ 重投;累计 3 次都没 ack → 直接判 failed
  • 所以 ack 不是「可选的礼貌」,而是**告诉看门狗「我活着、我接手了」**的心跳。

它的真实实现很克制——只在「有事件、当前是 pending、且本进程还没 ack 过」时才真去打后端,否则是 no-op:

// packages/sdk/src/bot/workflow-proxy/proxy.ts:93-110(真实源码,节选)
async acknowledgeStartOfProcessing() {
if (!props.event || props.workflow.status !== 'pending' || isAcknowledged) {
return { workflow: wrapWorkflowInstance(props) } // no-op
}
const { workflow } = await props.client.updateWorkflow({
id: props.workflow.id,
status: 'in_progress',
eventId: props.event.id,
})
isAcknowledged = true
// ...
}

这段在干嘛:把 status 置 in_progress,并带上 eventId(让后端知道是哪次唤醒 ack 的);isAcknowledged 闭包变量保证一次 handler 内重复调用是幂等的。

4. 四个收尾/续命动作

都在 wrapWorkflowInstance 里(packages/sdk/src/bot/workflow-proxy/proxy.ts:112-136),实现高度一致:都是「打 updateWorkflow 改 status,再回调 onWorkflowUpdate,返回新包装的实例」。

动作status 结果额外参数用途
setCompleted({ output? })completed可带类型化 output成功收尾
setFailed({ failureReason })failed必填 failureReason出错收尾
cancel()cancelled丢弃所有 output 并取消
update({ timeoutAt? / tags? / output? / userId? })不变至少一项续命(改 timeoutAt)/ 改 tags / 写中途 output

续命的真实用法(枚举没跑完就把超时推后 5 分钟,等下次 continued):

// plugins/file-synchronizer/src/hooks/workflow-continued/build-queue.ts(真实源码,节选)
if (enumerationState !== undefined) {
const timeIn5Minutes = new Date(Date.now() + 300_000).toISOString()
await props.workflow.update({ timeoutAt: timeIn5Minutes })
await props.states.workflow.buildQueueRuntimeState.set(props.workflow.id, { ...workflowState, enumerationState })
return
}

这段在干嘛:活没干完 → 用 updatetimeoutAt 往后推、把当前进度存进 workflow-scoped state(以 workflow.id 为 key)、然后直接 return。后端到点会再发一个 continued 事件,下次接着跑。重点看:进度不在内存,而在 state,因为下一次是全新的 handler 调用。

5. 进度存哪:workflow-scoped state

因为同一实例会被多次唤醒,跨唤醒的进度要存进 type: 'workflow' 的 state,key 就是 workflow.id。bugbuster 用它存「上次 lint 到哪个 issue」:

// bots/bugbuster/src/handlers/lint-all.ts:18-25(真实源码,节选)
await client.getOrSetState({
id: workflow.id, // ← 以实例 id 为 key
name: 'lastLintedId',
type: 'workflow',
payload: {},
})

对应的 state 在定义里声明为 type: 'workflow'(bots/bugbuster/bot.definition.ts:25-30)。心智模型:workflow state 之于 workflow 实例,就像「这次后台任务的草稿纸」,实例结束即作废。

6. 关键细节 / 坑

  • 不 ack = 被重试 3 次后 failed:最常见的「我的 workflow 莫名失败」就是处理函数没在开头 ack。
  • 处理函数 return 不等于 completed:必须显式 setCompleted / setFailed,否则实例悬在非终态(dispatcher 会警告,见 §02)。
  • setFailedfailureReason 必填:类型上是 Required<Pick<..., 'failureReason'>>(types.ts:86-88),逼你给出失败原因。
  • 续命靠 update({ timeoutAt }):这是长任务「分片跑」的官方手段,配合 workflow state 存进度。

代码地图

主题文件路径符号名
status 语义文档packages/sdk/src/bot/workflow-proxy/types.tsWorkflowProxy(listInstances JSDoc)
ack 的契约文档packages/sdk/src/bot/workflow-proxy/types.tsacknowledgeStartOfProcessing(JSDoc)
ack 真实实现packages/sdk/src/bot/workflow-proxy/proxy.tsacknowledgeStartOfProcessing
收尾动作实现packages/sdk/src/bot/workflow-proxy/proxy.tssetCompleted / setFailed / cancel / update
续命 + 存进度示例plugins/file-synchronizer/src/hooks/workflow-continued/build-queue.tshandleEvent
workflow-scoped state 示例bots/bugbuster/src/handlers/lint-all.tshandleLintAll