Workflows & Steps
Understanding the workflow lifecycle and step execution model
Workflows & Steps
A workflow in Stevora is a sequence of steps executed durably. Each run is persisted to PostgreSQL, so if a process crashes mid-execution, the workflow resumes exactly where it left off.
Workflow Lifecycle
Every workflow run moves through a finite set of statuses. The engine enforces these transitions — invalid transitions are rejected.
┌────────────────────────────────────┐
│ v
PENDING ──> RUNNING ──> WAITING ──> RUNNING ──> COMPLETED
│ │ │ │
│ │ │ └──> FAILED
│ │ │ │
│ │ └──> CANCELLED └──> RUNNING (retry)
│ │ │
│ └──> PAUSED ──> RUNNING │
│ │ │
│ └──> FAILED ──────────────────────────-┘
│ │
│ └──> CANCELLED
│
└──> CANCELLEDThe valid transitions are defined explicitly in the state machine:
const workflowTransitions: Record<WorkflowRunStatus, WorkflowRunStatus[]> = {
PENDING: ['RUNNING', 'CANCELLED'],
RUNNING: ['WAITING', 'PAUSED', 'COMPLETED', 'FAILED', 'CANCELLED'],
WAITING: ['RUNNING', 'CANCELLED', 'FAILED'],
PAUSED: ['RUNNING', 'CANCELLED'],
COMPLETED: [],
FAILED: ['RUNNING'], // allow retry from failed
CANCELLED: [],
};Terminal states are COMPLETED, FAILED, and CANCELLED. Once a workflow reaches a terminal state, the engine will not process it further (unless explicitly retried from FAILED).
Step Lifecycle
Steps follow a similar state machine:
const stepTransitions: Record<StepRunStatus, StepRunStatus[]> = {
PENDING: ['RUNNING', 'SKIPPED', 'CANCELLED'],
RUNNING: ['WAITING', 'COMPLETED', 'FAILED', 'CANCELLED'],
WAITING: ['RUNNING', 'COMPLETED', 'FAILED', 'CANCELLED'],
COMPLETED: [],
FAILED: ['RUNNING'], // allow retry
SKIPPED: [],
CANCELLED: [],
};When a step completes, the engine automatically advances to the next step in the definition. When the last step completes, the workflow transitions to COMPLETED.
The 6 Step Types
Stevora provides six step types that serve as building blocks for any workflow.
Task
A general-purpose step that runs a registered handler function. Use this for custom business logic, API calls, data transformations, or anything that does not fit the other types.
{
"type": "task",
"name": "enrich-lead",
"handler": "enrichLeadData",
"config": { "source": "clearbit" }
}Wait
Pauses the workflow for a duration or until a specific timestamp. The engine schedules a delayed job and resumes automatically.
{
"type": "wait",
"name": "cooldown-period",
"durationMs": 86400000
}You can also wait until an absolute time:
{
"type": "wait",
"name": "wait-until-market-open",
"untilTimestamp": "2025-01-06T14:30:00Z"
}Condition
Evaluates an expression against the current workflow state and branches to different steps based on the result.
{
"type": "condition",
"name": "check-qualification",
"expression": "leadScore",
"trueStep": "send-premium-offer",
"falseStep": "send-nurture-email"
}The expression is evaluated by walking the workflow state object. If the resolved value is truthy, execution jumps to trueStep; otherwise it jumps to falseStep.
External Event
Pauses the workflow and waits for an external signal (webhook callback, user action, third-party event). Optionally includes a timeout.
{
"type": "external_event",
"name": "wait-for-payment",
"signalType": "payment.received",
"timeoutMs": 3600000
}Resume the workflow by sending a signal through the API:
POST /api/v1/workflow-runs/:id/resume
{ "signalType": "payment.received", "payload": { "amount": 99.00 } }LLM
Calls a language model with first-class support for tool calling, model fallback, guardrails, and structured output.
{
"type": "llm",
"name": "draft-email",
"model": "gpt-4o",
"fallbackModels": ["claude-sonnet-4-20250514"],
"systemPrompt": "You are a sales assistant.",
"messages": [
{ "role": "user", "content": "Draft an outreach email for {{state.leadName}}." }
],
"responseFormat": "json",
"temperature": 0.7,
"maxToolRounds": 5
}Approval
Pauses the workflow for human review. A reviewer can approve, reject, or edit the content before execution continues.
{
"type": "approval",
"name": "review-email",
"contentKey": "draftEmail",
"prompt": "Review this email before sending.",
"timeoutMs": 86400000
}Step Execution Flow
When the engine picks up a workflow run from the queue, it follows this sequence:
- Fetch the workflow run with its definition and existing step runs.
- Guard against terminal states — skip if already
COMPLETED,FAILED, orCANCELLED. - Resolve the next step — either an explicit target, the
__nextStepset by a condition, or the next step in definition order. - Idempotency check — if the step is already
COMPLETEDorSKIPPED, advance immediately. - Transition the workflow and step to
RUNNING(with optimistic locking). - Dispatch to the appropriate step handler based on the step type.
- Handle the result:
completed— persist output and state updates, enqueue the next step.waiting— transition toWAITING, schedule a delayed resume job if applicable.failed— evaluate the retry policy. If retries remain, schedule a retry. Otherwise, fail the workflow.
Defining a Workflow
A workflow definition is a named, versioned list of steps validated with Zod:
const workflowDefinitionInputSchema = z.object({
name: z.string().min(1).max(200),
version: z.string().min(1).max(50),
description: z.string().max(1000).optional(),
steps: z.array(stepDefinitionSchema).min(1),
});Every step inherits a base schema with a name and optional retry policy, then extends it with type-specific fields.