Primitives.org.ai

State Machines

Explicit state management for complex business logic

State Machines

State machines provide explicit state management with guarded transitions, making complex business logic predictable and auditable.

Defining a State Machine

import { stateMachine, state, transition } from 'ai-workflows'

const orderMachine = stateMachine({
  name: 'order-lifecycle',
  initial: 'draft',
  context: {
    orderId: '',
    items: [],
    total: 0,
    paidAt: null,
    shippedAt: null,
  },

  states: {
    draft: state({
      on: {
        SUBMIT: transition('pending'),
        CANCEL: transition('cancelled'),
      },
    }),

    pending: state({
      on: {
        PAYMENT_RECEIVED: transition('paid', {
          action: (ctx) => {
            ctx.paidAt = new Date()
          },
        }),
        CANCEL: transition('cancelled'),
      },
    }),

    paid: state({
      onEnter: async (ctx) => {
        await notifyWarehouse(ctx.orderId)
      },
      on: {
        SHIP: transition('shipped'),
        REFUND: transition('refunded'),
      },
    }),

    shipped: state({
      onEnter: async (ctx) => {
        ctx.shippedAt = new Date()
        await sendTrackingEmail(ctx.orderId)
      },
      on: {
        DELIVER: transition('delivered'),
        RETURN: transition('returning'),
      },
    }),

    delivered: state({ type: 'final' }),
    cancelled: state({ type: 'final' }),
    refunded: state({ type: 'final' }),
  },
})

Guarded Transitions

Transitions can have guards that must pass:

pending: state({
  on: {
    PAYMENT_RECEIVED: transition('paid', {
      guard: (ctx, event) => {
        // Only transition if payment amount matches
        return event.amount >= ctx.total
      },
    }),

    APPLY_DISCOUNT: transition('pending', {
      guard: (ctx, event) => event.code !== undefined,
      action: (ctx, event) => {
        ctx.total = ctx.total * (1 - event.discount)
      },
    }),
  },
}),

Entry and Exit Actions

Execute code when entering or leaving states:

processing: state({
  onEnter: async (ctx) => {
    // Runs when entering 'processing'
    await startProcessing(ctx.orderId)
    ctx.processingStartedAt = new Date()
  },

  onExit: async (ctx) => {
    // Runs when leaving 'processing'
    await logProcessingDuration(
      ctx.orderId,
      Date.now() - ctx.processingStartedAt
    )
  },

  on: {
    COMPLETE: transition('completed'),
  },
}),

Sending Events

const instance = await orderMachine.create({
  orderId: 'order_123',
  items: [{ name: 'Widget', price: 100 }],
  total: 100,
})

// Send events to transition
await instance.send('SUBMIT')
console.log(instance.state) // 'pending'

await instance.send('PAYMENT_RECEIVED', { amount: 100 })
console.log(instance.state) // 'paid'

// Check if transition is possible
const canShip = instance.can('SHIP')

Hierarchical States

States can be nested:

const machine = stateMachine({
  states: {
    active: state({
      initial: 'idle',

      states: {
        idle: state({
          on: { START: transition('running') },
        }),
        running: state({
          on: { PAUSE: transition('paused') },
        }),
        paused: state({
          on: { RESUME: transition('running') },
        }),
      },

      on: {
        STOP: transition('stopped'),  // Exits all nested states
      },
    }),

    stopped: state({ type: 'final' }),
  },
})

Parallel States

Run multiple states simultaneously:

const machine = stateMachine({
  type: 'parallel',

  states: {
    upload: state({
      initial: 'pending',
      states: {
        pending: state({ on: { START: transition('uploading') } }),
        uploading: state({ on: { COMPLETE: transition('done') } }),
        done: state({ type: 'final' }),
      },
    }),

    validate: state({
      initial: 'pending',
      states: {
        pending: state({ on: { START: transition('validating') } }),
        validating: state({ on: { PASS: transition('valid') } }),
        valid: state({ type: 'final' }),
      },
    }),
  },
})

History States

Remember previous state when re-entering:

const machine = stateMachine({
  states: {
    editing: state({
      initial: 'text',
      states: {
        text: state({}),
        formatting: state({}),
        preview: state({}),
      },
      history: 'shallow',  // Remember last child state
    }),

    saving: state({
      on: {
        DONE: transition('editing'),  // Returns to last editing state
      },
    }),
  },
})

Visualization

Generate diagrams from state machine definitions:

import { visualize } from 'ai-workflows'

const diagram = visualize(orderMachine, {
  format: 'mermaid',
})

console.log(diagram)
// stateDiagram-v2
//   [*] --> draft
//   draft --> pending: SUBMIT
//   draft --> cancelled: CANCEL
//   pending --> paid: PAYMENT_RECEIVED
//   ...

Persistence

State machines are automatically persisted:

// Create with ID for persistence
const instance = await orderMachine.create(
  { orderId: 'order_123' },
  { id: 'order_123' }
)

// Later, restore from ID
const restored = await orderMachine.get('order_123')
console.log(restored.state)  // Current state
console.log(restored.context)  // Current context

Event History

Track all transitions:

const history = instance.getHistory()
// [
//   { from: 'draft', to: 'pending', event: 'SUBMIT', timestamp: ... },
//   { from: 'pending', to: 'paid', event: 'PAYMENT_RECEIVED', timestamp: ... },
// ]
Was this page helpful?

On this page