Skip to main content

State Machines Reference

The state_machine statement implements an event-driven finite state machine (FSM) inside a workflow. Use it when your workflow needs to wait for external events and transition between named states based on those events.

For full workflowspec context, see the Workflowspec Reference.


Basic Structure

- state_machine:
name: order-fsm
initial_state: pending
timeout_sec: 300
event_source_topic: order_events

states:
- name: pending
- name: processing
- name: completed
is_terminal: true
- name: failed
is_terminal: true

transitions:
- from_state: pending
to_state: processing
trigger:
event_name: start_processing

- from_state: processing
to_state: completed
trigger:
event_name: processing_complete

- from_state: processing
to_state: failed
trigger:
event_name: processing_failed

Top-Level Parameters

ParameterTypeDescription
namestringIdentifier for this state machine
initial_statestringState to start in
timeout_secintegerMaximum total runtime before the machine times out
event_source_topicstringEvent topic to listen on for all transitions
stateslistState definitions
transitionslistTransition rules between states
global_triggerslistTransitions that apply from any state

States

State Parameters

ParameterTypeDescription
namestringUnique state name
is_terminalbooleanMark as a final state; reaching it ends the machine
timeout_secintegerMaximum time to spend in this state
on_enterstatementExecuted when entering this state
on_exitstatementExecuted when leaving this state

State Callbacks

states:
- name: processing
on_enter:
sequence:
elements:
- transform:
output_data:
- entered_at: "{{ __sys_info__.timestamp }}"
- activity:
type: process-order
input_data:
order_id: "{{ order_id }}"
output_name: process_result
- emit_event:
input_data:
topic: order_events
data:
event_name: "{{ 'processing_complete' if process_result.success else 'processing_failed' }}"

on_exit:
transform:
output_data:
- exited_at: "{{ __sys_info__.timestamp }}"

The on_enter callback typically does the work for a state and then emits an event to trigger the next transition.


Transitions

Basic Transition

transitions:
- from_state: pending
to_state: approved
trigger:
event_name: approve

Transition with Condition

Only fire the transition if the event data meets a condition:

transitions:
- from_state: pending
to_state: processing
trigger:
event_name: start_processing
condition:
- and:
- "{{ event.data.get('price') > 0 }}"
- "{{ event.data.get('inventory_available') == true }}"

The event variable inside conditions contains the full event object:

{
"data": {...},
"topic": "...",
"source_workflow_id": "...",
"metadata": {...}
}

Multiple Transitions from One State

transitions:
- from_state: processing
to_state: completed
trigger:
event_name: success

- from_state: processing
to_state: failed
trigger:
event_name: error

- from_state: processing
to_state: pending
trigger:
event_name: retry

Global Triggers

Global triggers define transitions that can fire from any state. Useful for cancellation or error escalation.

state_machine:
name: order-fsm
initial_state: pending

global_triggers:
- to_state: cancelled
trigger:
event_name: cancel

- to_state: failed
trigger:
event_name: critical_error

states:
- name: pending
- name: processing
- name: cancelled
is_terminal: true
- name: failed
is_terminal: true

Event Source Configuration

By default, state machines listen for events on a per-transition basis. Setting event_source_topic at the machine level causes all transitions to listen on that topic.

state_machine:
name: order-fsm
event_source_topic: order_events
initial_state: pending

transitions:
- from_state: pending
to_state: processing
trigger:
event_name: start # matches events on "order_events" with data.event_name == "start"

When using event_source_topic, the trigger.event_name matches against event.data.event_name in the incoming event payload.


Complete Example

A three-step order processor with validation, payment, and fulfillment stages:

body:
sequence:
elements:
- transform:
output_data:
- order_id: "{{ input.order_id }}"
- order_data: "{{ input.order_data }}"

- state_machine:
name: order-processor
initial_state: validating
timeout_sec: 600
event_source_topic: order_events

states:
- name: validating
timeout_sec: 30
on_enter:
sequence:
elements:
- activity:
type: validate-order
input_data:
order: "{{ order_data }}"
output_name: validation_result
- emit_event:
input_data:
topic: order_events
data:
event_name: "{{ 'validated' if validation_result.valid else 'validation_failed' }}"
order_id: "{{ order_id }}"

- name: processing
timeout_sec: 120
on_enter:
sequence:
elements:
- activity:
type: process-payment
input_data:
order: "{{ order_data }}"
output_name: payment_result
- emit_event:
input_data:
topic: order_events
data:
event_name: "{{ 'payment_complete' if payment_result.success else 'payment_failed' }}"

- name: completed
is_terminal: true
on_enter:
transform:
output_data:
- status: completed
- completed_at: "{{ __sys_info__.timestamp }}"

- name: failed
is_terminal: true
on_enter:
transform:
output_data:
- status: failed

transitions:
- from_state: validating
to_state: processing
trigger:
event_name: validated

- from_state: validating
to_state: failed
trigger:
event_name: validation_failed

- from_state: processing
to_state: completed
trigger:
event_name: payment_complete

- from_state: processing
to_state: failed
trigger:
event_name: payment_failed

global_triggers:
- to_state: failed
trigger:
event_name: cancel

Next Steps