lantern

loom-workflow-guide

Loom Workflow Guide

Peregrine-style state machine orchestration for fences. Define workflows in YAML that execute as state machines with named environments, activities, and transitions.

Quick Start

initial: init

environments:
  init:
    activities:
      - type: statePoke
        args:
          key: message
          value: "Hello, World!"
      - type: stateEmit
        args:
          message: "${message}"

This creates a workflow that:

  • Starts in the init environment
  • Pokes "Hello, World!" into state at key message
  • Emits the message with variable interpolation

Core Concepts

Environments

Named states in the machine. Each has an activities list that executes sequentially.

environments:
  setup:
    activities: [...]
  
  processing:
    activities: [...]
  
  cleanup:
    activities: [...]

Transitions

Move between environments with stateTransition. Workflow ends when:

  • Transitioning to an environment that doesn't exist
  • An environment completes without transitioning

Variables

Two namespaces:

  • State (_state): Persistent data, accessed via statePoke/path
  • Variables (_vars): Temporary values from statePeek, stored with into:

Both accessible in ${...} interpolation. Variables checked first, then state.

Activity Types

statePoke

Write value to state.

- type: statePoke
  args:
    key: players
    value: [Alice, Bob, Charlie]

Supports dot-notation for nested paths:

- type: statePoke
  args:
    key: game.settings.difficulty
    value: hard

statePeek

Read value from state into a named variable.

- type: statePeek
  args:
    key: players
    into: PLAYERS  # Variable name (default: KEY.upper())

stateEmit

Output a message with ${VAR} interpolation.

- type: stateEmit
  args:
    message: "Current player: ${PLAYER}, Round: ${round}"

Variables (_vars) are checked first, then state (_state).

stateIncrement / stateDecrement

Modify numeric counters.

- type: stateIncrement
  args:
    key: round
    amount: 1  # default

- type: stateDecrement
  args:
    key: lives
    amount: 1

statePick

Randomly select from a list. Optionally remove the picked item.

- type: statePick
  args:
    from: players
    into: CHOSEN  # Variable to store picked value
    remove: true  # Remove from list (default: false)

statePop

Remove and return item from list by index.

- type: statePop
  args:
    from: queue
    into: ITEM
    index: 0  # default (first item)

stateAppend

Add value to a list.

- type: stateAppend
  args:
    key: log
    value: "Action completed"

stateGate

Conditional transition based on state value.

Empty/NotEmpty check (for lists):

- type: stateGate
  args:
    check: players
    empty: finale      # Go here if empty
    notEmpty: round    # Go here if not empty

Comparison check:

- type: stateGate
  args:
    check: score
    compare: gte       # eq, ne, gt, lt, gte, lte
    against: 100
    ifTrue: winner
    ifFalse: continue

Boolean check:

- type: stateGate
  args:
    check: is_active
    true: process
    false: skip

stateTransition

Move to another environment.

- type: stateTransition
  args:
    to: next_phase

Transition to non-existent environment ends the workflow.

Patterns

The Cookie Jar (Iterative Consumption)

Loop through a list until empty:

initial: init

environments:
  init:
    activities:
      - type: statePoke
        args:
          key: players
          value: [Alice, Bob, Charlie]
      - type: stateTransition
        args:
          to: round

  round:
    activities:
      - type: statePick
        args:
          from: players
          into: ACCUSED
          remove: true
      - type: stateEmit
        args:
          message: "${ACCUSED} stole the cookie!"
      - type: stateGate
        args:
          check: players
          empty: finale
          notEmpty: round

  finale:
    activities:
      - type: stateEmit
        args:
          message: "The cookie jar is empty!"

Counter Loop

Execute N times:

initial: init

environments:
  init:
    activities:
      - type: statePoke
        args:
          key: count
          value: 0
      - type: statePoke
        args:
          key: max
          value: 5
      - type: stateTransition
        args:
          to: loop

  loop:
    activities:
      - type: stateIncrement
        args:
          key: count
      - type: statePeek
        args:
          key: count
          into: COUNT
      - type: stateEmit
        args:
          message: "Iteration ${COUNT}"
      - type: stateGate
        args:
          check: count
          compare: gte
          against: 5
          ifTrue: done
          ifFalse: loop

  done:
    activities:
      - type: stateEmit
        args:
          message: "Finished after ${count} iterations"

Accumulator

Build up a result:

initial: init

environments:
  init:
    activities:
      - type: statePoke
        args:
          key: items
          value: [a, b, c]
      - type: statePoke
        args:
          key: result
          value: []
      - type: stateTransition
        args:
          to: process

  process:
    activities:
      - type: statePop
        args:
          from: items
          into: ITEM
      - type: stateAppend
        args:
          key: result
          value: "processed-${ITEM}"
      - type: stateGate
        args:
          check: items
          empty: done
          notEmpty: process

  done:
    activities:
      - type: statePeek
        args:
          key: result
          into: RESULT
      - type: stateEmit
        args:
          message: "Final: ${RESULT}"

Configuration

max_iterations

Prevent infinite loops:

initial: loop
max_iterations: 100  # default: 1000

environments:
  loop:
    activities:
      - type: stateTransition
        args:
          to: loop

Initial Parameters

Pass params when executing:

from loom.execute import WorkflowExecutor

spec = {...}
executor = WorkflowExecutor(spec)
executor._state["name"] = "Alice"  # Pre-seed state
executor.run()

Or via fence attrs:

attrs:
  params:
    name: Alice

Execution Output

Each step produces a YAML fence token with:

  • env: Environment name
  • type: Activity type
  • index: Activity index in environment
  • result: Activity result (if any)
  • output: Emitted message (for stateEmit)
  • transition: Target environment (for transitions)

Relationship to Peregrine

This is a subset of Peregrine pipeline syntax, adapted for fence execution:

Peregrine Loom Workflow
stages.run.activities environments.*.activities
stateEmit stateEmit
statePoke statePoke
statePeek statePeek
stateGate stateGate
stateTransition stateTransition
oculusExecute (not yet implemented)
stuffyEmit (not yet implemented)

Related

  • @cookie-jar - Working example of the Cookie Jar pattern
  • @loom - The engine that weaves streams together
  • @peregrine - Full pipeline orchestration system

Provenance

Document

  • Status: 🟒 Verified
  • Author: Claude

Changelog

  • 2026-01-12: Initial guide created

South

slots:
- context:
  - Linking workflow guide to cookie-jar example
  slug: cookie-jar

North

slots:
- context:
  - Linking workflow guide as child of main loom user guide
  slug: loom-user-guide

West

slots:
- context:
  - Sibling concepts - workflows produce edges
  slug: loom-edges-model
↑ northloom-user-guide
↓ southcookie-jar
← westloom-edges-model