Step Fanout (:fanout)
Quick Answer
Use :fanout when you need a bounded batch of child workflows that start now and collect later.
Async fanout is available only when every item is {:type :call-flow ...}.
Legacy non-child fanout items are still accepted for backward compatibility, but they run on the sequential compatibility path.
Use for bounded child-workflow spawn/collect, not as a general parallel side-effect primitive.
Canonical Shape
Core fields:
| Field | Type | Required | Notes |
|---|---|---|---|
:type | keyword | Yes | Use :fanout |
:items | vector | Yes | Runtime width is bounded to 25 items |
:max-concurrency | int | No | Requested width for async child fanout; must be 1-5 |
:on-error | keyword/string | No | :fail (default), :continue, or :collect |
:transform | form | No | SCI transform over the aggregated result map |
Async child-workflow items:
| Item field | Type | Required | Notes |
|---|---|---|---|
:type | keyword | Yes | Must be :call-flow for async child fanout |
:flow-slug | keyword/string | Yes | Child flow to start |
:input | any | No | Child input payload |
:timeout | int | No | Mapped to child workflow timeout fields |
:version | int | No | Optional child version override |
:installation-id | string | No | Optional child installation/live target override |
:workflow-id | string | No | Optional explicit child workflow id |
:parent-close-policy | keyword/string | No | :abandon, :request-cancel, or :terminate |
:retry-policy | map | No | Optional child retry override |
:profile-id is still accepted as a compatibility alias for older child-flow
items, but new source should use :installation-id for target-specific child
runs.
Child-Workflow Mode
Async :fanout is enabled only when all items are :call-flow items.
In that mode, Breyta:
- starts children up to the effective concurrency cap
- waits until child start is confirmed before counting the child as spawned
- collects results later while preserving the original item order
- records fanout lineage on child executions (
:root-workflow-id,:parent-workflow-id,:fanout-depth,:fanout-parent-step-id,:fanout-max-concurrency) - inherits draft child version
0when the parent is running from draft and the item does not specify:version,:installation-id, or live target context - treats an explicit child
:versionas a versioned child execution; run lists
show the child lineage fields so you can distinguish these from direct manual
live runs
Canonical example:
(flow/step :fanout :spawn-children
{:items [{:type :call-flow
:flow-slug :billing-apply
:input {:invoice-id "inv-1"}}
{:type :call-flow
:flow-slug :billing-apply
:input {:invoice-id "inv-2"}}]
:max-concurrency 5
:on-error :collect})
Typical parent-flow pattern:
'(let [input (flow/input)
fanout (flow/step :fanout :spawn-children
{:items (:items input)
:max-concurrency 5
:on-error :collect})]
{:item-count (count (:items input))
:fanout fanout})
Legacy Compatibility Mode
Legacy fanout items are still accepted for these step types:
:http:llm:notify:function:code
That mode is kept for compatibility only.
It executes deterministically in a loop, even if :max-concurrency is provided.
For new orchestration, prefer explicit loops or child workflows instead of adding new legacy fanout usage.
Result Shape
Default return shape:
{:successes [{:item ... :result ...}]
:failures [{:item ... :error ...}]
:total N
:success-count N
:failure-count N}
When :transform is set, the transform receives:
{:successes [...]
:failures [...]
:total N
:success-count N
:failure-count N
:items [...]}
Guardrails And Limits
- Async fanout supports only
:call-flowitems. - Do not mix legacy step items and
:call-flowitems in one:fanout. - Nested
:fanoutitems are rejected. - Nested async child fanout is rejected beyond one level.
:max-concurrencymust be at most5; validation rejects larger values.- One
:fanoutstep can include at most25items. - One root run can start at most
25child workflows total. - One run can execute at most
5fanout steps and100total fanout items across those steps.
For child flows that can produce large outputs, persist large artifacts inside the child flow and return compact refs or summaries.
Collected fanout results are still subject to inline result limits.