Fan-out & Node Kinds — the n8n-Parity Ladder
"Catch up with n8n" is three differently-sized asks wearing one name. Most of the breadth needs no engine work at all; fan/merge needs neutron's decider to grow a frontier; and only n8n's per-item data model would be a true redesign — which we deliberately decline. The durable assay engine underneath is expected to change for none of it.
1 · Execution today: one cursor
The decider replays history and follows a single cursor (currentId = outs[0].to).
A branch evaluates its condition and picks one arm; the other never runs. Arms may converge
(fan-in works); fan-out does not exist.
flowchart LR
T[trigger] --> B{branch}
B -->|true ✓| A[agent turn]
B -.->|false — never runs| N[notify]
A --> D[done]
N -.-> D
Everything durable — retries, timers, approval parking, deterministic replay — is the assay engine's event log. The decider is a pure function: history in, commands out. That separation is what keeps this upgrade contained.
2 · The ladder
| Tier | What | Touches | Epic |
|---|---|---|---|
| 0 | transform · http_request · wait · sub_workflow · error edges · per-node retry/timeout · typed approval responses | new node kinds only — current walk executes them as ordinary activities | #462 |
| 1 | fan (multi-out = all arms run) + join (wait-all, input = {predecessor: output}) | decider (cursor → frontier) · NEP-0006 addendum · validation · run overlay | #463 |
| 2 | n8n item model (wires carry arrays, implicit per-item loops) | every node, every expression, the run UI — declined; a loop-over-items node covers most of the need | — |
3 · Tier 1: the frontier walk
flowchart LR T[trigger] --> F[fan] F --> P[draft post] F --> E[draft email] P --> J[join
waits for all] E --> J J --> A[approve]
| Piece | Change |
|---|---|
| decider | Walk a ready-set; several activities in flight; a node is ready when ALL inbound arms settled. Stays a pure replay function — ready-order derives from the existing topo order, so determinism holds. |
| NEP-0006 | Fan semantics, join node (wait-all first), joined-output naming for {{ node.x.y }}. Versioned graphs make it additive — published v1 graphs replay untouched. |
| assay engine | Expected: nothing. It logs per-seq activities; nothing in the command contract says one-at-a-time. A two-pending-activities spike is the hard gate before building. |
| run UI | Several live nodes at once; step rail groups arms; node lifecycle vocabulary INACTIVE·PENDING·RUNNING·WAITING·COMPLETED·FAILED·CANCELLED. |
| validation | Join reachability — an arm that can skip a join must be declared or the join deadlocks. |
4 · The honest n8n comparison
| Gap | Reality | Cost |
|---|---|---|
| breadth of leaves | 400+ integrations — neutron's equivalent is the capability registry; the missing pieces are the generic kinds (tier 0) | tier 0 |
| fan / merge | n8n runs arms interleaved in one process — the win is the shape, not raw speed | tier 1 |
| item model | n8n's true engine-level difference, and its main source of user confusion | tier 2 — declined |
5 · What the reference study already shipped
From the seven-tool set (~/forks/agentic-workflows): dify's floating config
panel; n8n's quick-add "+", insert-on-wire and JSON import; langflow's edge reconnection;
trigger-first starter tiles; canvas fit/center control. All live in v0.6.1-b21. Google ADK's
contributions are PD008's subject.