Choose the Right Primitive

Choose the smallest primitive that expresses the automation clearly and provides the durability you actually need. More structure is not automatically better: every durable node creates identity, events, presentation, and recovery behavior.

Decision table

NeedUse
One unit of work<step> and <script>
Independent single steps<parallel>
Same durable work for every item<for-each>
Boolean decision<choose>
Exact string routing<switch>
Independent multi-step lanes<fork> and <branch>
Human decision<approval>
Wait for another workflowservices.workflows.call()
Start another workflow and continueservices.workflows.start()
Notify every subscriberservices.events.emit()

Begin with sequential steps

Most workflows should start as a short sequence:

WOML
<steps>
  <step id="load">...</step>
  <step id="transform">...</step>
  <step id="save">...</step>
</steps>

Document order already expresses dependency. Do not introduce parallelism or forks until work is truly independent.

Durable loop or JavaScript loop

Use <for-each> when every runtime item deserves durable identity, bounded concurrency, ordered aggregate results, recovery, or inspection.

WOML
<for-each id="processOrders" items="{{context.steps.load.orders}}" concurrency="4">
  ...
</for-each>

Use an ordinary JavaScript loop inside one step for small pure transformations that should succeed or fail as one operation:

JavaScript
return {
  names: context.payload.users.map(user => user.name.trim())
};

A JavaScript loop is not a durable per-item workflow. WOML cannot independently retry or inspect each iteration inside that one script.

Parallel or fork

<parallel> is for independent direct step children that all rejoin before the next main step:

WOML
<parallel id="checks" concurrency="2">
  <step id="inventory">...</step>
  <step id="risk">...</step>
</parallel>

A <fork> contains independent multi-step branches. Its join attribute selects which branches must finish before the main continuation proceeds. Non-joined branches remain owned durable work, but they do not block that continuation.

Use parallel for “do these checks together.” Use fork for “start these independent lanes, each with its own sequence.”

Choose or switch

Use <choose> for a strict boolean decision. Compute the boolean in a step and reference it from <when test>.

Use <switch> when one exact string selects a route, such as standard, express, or pickup. Switch has no JavaScript-style fallthrough.

Both structures should publish a stable <result> when later steps need the selected route's output.

Approval or ordinary condition

Use a choice when the answer already exists in workflow data. Use <approval> when the workflow must pause durably for an external human decision. Approval is not a boolean expression; it has notification delivery, a capability token, timeout behavior, and approved or rejected continuations.

Call, start, event, or webhook

Use services.workflows.call() for one child whose result is required now. Use .start() when the parent should continue with a run ID. Use events to broadcast a fact to every matching active subscriber. Use a webhook at a public system boundary where an external caller needs HTTP admission behavior.

When uncertain, choose the simpler primitive and keep identities explicit. You can add concurrency or composition later without hiding the workflow inside a script.