Pausing and Resuming Workflows
This guide covers workflows that can pause execution and resume later, including Human-in-the-Loop (HITL) workflows and durable timers.
Overview
Flowcraft supports two primary mechanisms for pausing workflow execution:
- Wait Nodes: Pause execution until external input is provided (HITL workflows)
- Sleep Nodes: Pause execution for a specified duration (durable timers)
Wait Nodes (Human-in-the-Loop)
Wait nodes pause execution until external input is provided, enabling human intervention or external decisions.
Basic Approval Workflow
typescript
import { createFlow, FlowRuntime, ConsoleLogger } from 'flowcraft'
const flow = createFlow('approval-workflow')
.node('start', () => ({ output: { user: 'Alice', amount: 1500 } }))
.wait('wait-for-approval')
.node('process', async ({ input }) => {
if (input?.approved) {
return { output: 'Approved' }
}
return { output: 'Denied' }
})
.edge('start', 'wait-for-approval')
.edge('wait-for-approval', 'process')
const blueprint = flow.toBlueprint()
const functionRegistry = flow.getFunctionRegistry()
const runtime = new FlowRuntime({ logger: new ConsoleLogger() })
// Run until awaiting
const result = await runtime.run(blueprint, {}, { functionRegistry })
if (result.status === 'awaiting') {
// Resume with input
const finalResult = await runtime.resume(blueprint, result.serializedContext, { output: { approved: true } }, 'wait-for-approval')
console.log(finalResult.context)
}Conditional Branching with Actions
Wait nodes support conditional edges based on the action property in resume data:
typescript
const flow = createFlow('conditional-approval')
.node('start', () => ({ output: { item: 'document.pdf' } }))
.wait('review')
.node('approve', () => ({ output: 'Document approved' }))
.node('reject', () => ({ output: 'Document rejected' }))
.node('escalate', () => ({ output: 'Escalated to manager' }))
.edge('start', 'review')
.edge('review', 'approve', { action: 'approve' })
.edge('review', 'reject', { action: 'reject' })
.edge('review', 'escalate', { action: 'escalate' })
// Resume with different actions
await runtime.resume(blueprint, serializedContext, {
output: { reviewer: 'john@example.com' },
action: 'approve' // or 'reject' or 'escalate'
})Sleep Nodes (Durable Timers)
Sleep nodes pause execution for a specified duration and automatically resume when the timer expires.
Basic Timer Example
typescript
const flow = createFlow('delayed-workflow')
.node('start', () => ({ output: 'Starting delayed task' }))
.sleep('delay', { duration: 5000 }) // Sleep for 5 seconds
.node('finish', () => ({ output: 'Task completed after delay' }))
.edge('start', 'delay')
.edge('delay', 'finish')
const result = await runtime.run(flow.toBlueprint(), {}, { functionRegistry })
// After 5 seconds, workflow automatically completesCombining Sleep and Wait
typescript
const flow = createFlow('reminder-workflow')
.node('start', () => ({ output: { task: 'Review quarterly report' } }))
.wait('initial-approval')
.sleep('reminder-delay', { duration: 86400000 }) // 24 hours
.node('send-reminder', () => ({ output: 'Reminder sent' }))
.edge('start', 'initial-approval')
.edge('initial-approval', 'reminder-delay', { action: 'pending' })
.edge('reminder-delay', 'send-reminder')Key Concepts
- Wait Nodes: Pause execution for external input (HITL workflows)
- Sleep Nodes: Pause execution for a duration (durable timers)
- Resume: Provide input to continue wait nodes
- Status: Check
result.statusfor 'awaiting' - Actions: Use
actionproperty for conditional branching - Durability: Awaiting state persists across system restarts
Best Practices
- Use wait nodes for human decisions or external API calls
- Use sleep nodes for delays, retries, or scheduled tasks
- Always check
result.status === 'awaiting'before resuming - Store serialized context for durability
- Use actions for complex conditional logic