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
initenvironment - 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 viastatePoke/path - Variables (
_vars): Temporary values fromstatePeek, stored withinto:
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: hardstatePeek
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: 1statePick
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 emptyComparison check:
- type: stateGate
args:
check: score
compare: gte # eq, ne, gt, lt, gte, lte
against: 100
ifTrue: winner
ifFalse: continueBoolean check:
- type: stateGate
args:
check: is_active
true: process
false: skipstateTransition
Move to another environment.
- type: stateTransition
args:
to: next_phaseTransition 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: loopInitial 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: AliceExecution Output
Each step produces a YAML fence token with:
env: Environment nametype: Activity typeindex: Activity index in environmentresult: 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