Parallel Nodes
fanout / fanin parallel execution nodes.
Partially implemented. Concurrent branch execution, the canvas group editor, branch ordering, and scope isolation are shipped. Fan-in output aggregation is not: parallel.fanin is a graph-shape marker that completes with an empty output - it does not collect branch results into a results array. Downstream steps cannot yet consume a merged per-branch output. Aggregation is tracked as a known gap on the roadmap.
Parallel nodes let a workflow execute multiple branches concurrently and then converge on a single continuation point. They appear as a matched pair: parallel.fanout starts the concurrent branches and parallel.fanin waits for all of them to complete before the workflow continues.
The pair forms a parallel group on the canvas - a visual container with a fan-out entry node, one or more branch subgraphs inside the container, and a fan-in exit node. The workflow definition serializes this as a parallel-group step type.
parallel.fanout
Starts concurrent execution of all branches defined in the parallel group. The executor fires all branches simultaneously at the same DAG tier.
Canvas behavior: Dropping parallel.fanout onto the canvas creates an empty parallel group container. Use the Parallel branches editor in the config panel to add, rename, and reorder branches. Each branch is an independent sub-graph within the container scope.
Port behavior: parallel.fanout has no typed input ports in isolation - upstream nodes wire to the parallel group boundary ports (the entry points of each branch). The fanout node itself acts as the scope entry signal.
Bespoke config panel: parallel.fanout uses an auto-discovered bespoke config panel that renders the branch definition editor. From the panel you can:
- Add new branches with a label
- Rename existing branches
- Reorder branches
- Remove branches (edges from removed branches are deleted)
parallel.fanin
Waits for all branches in the parallel group to complete before routing execution out of the container. The workflow instance remains in running status while branches execute concurrently. Once all branches complete, execution continues past the fan-in.
Merge behavior (not yet implemented): parallel.fanin does not aggregate branch outputs today. The executor treats the fan-in as a passthrough that completes with an empty output ({}) - fan-out/fan-in semantics live entirely in graph topology, with branches fired at the same DAG tier via Promise.allSettled. To consume a branch's result downstream, reference the branch step directly with a template expression - {{ steps.<stepId>.output.<field> }} - rather than the fan-in's output; a merged results array on the fan-in port is a known gap.
Port behavior: parallel.fanin exposes a single output port for downstream wiring, but its runtime value is an empty object until output aggregation ships.
Error handling: If a branch step fails (after retries), execution stops after the current DAG tier completes - steps already in flight in that tier run to completion (they are not cancelled), and no later tier (including steps past the fan-in) executes.
Parallel group anatomy
[upstream node]
│
▼
parallel.fanout
┌────┴────────────────────┐
│ Branch A │ Branch B │
│ [node A1] → [node A2] │ [node B1] │
└────────────────────────┘ └────────────────────────┘
│ │
└──────────┬───────────────────┘
▼
parallel.fanin
│
▼
[downstream node]YAML representation
The workflow definition serializes a parallel group as a single parallel-group step containing an ordered branches array. The executor uses Kahn-BFS tier computation to fire all branches at the same tier.
steps:
- id: parallel_research
type: parallel-group
branches:
- id: branch_linear
steps:
- id: read_linear
type: action
nodeId: linear.issue.read
config:
issueId: "{{ $trigger.data.issueId }}"
- id: branch_github
steps:
- id: read_pr
type: action
nodeId: github.file.read
config:
owner: "{{ $trigger.data.repository.owner }}"
repo: "{{ $trigger.data.repository.name }}"
path: CONTRIBUTING.md
- id: continue_after_parallel
type: action
nodeId: linear.comment.create
config:
issueId: "{{ $trigger.data.issueId }}"
body: "Research complete. Linear issue and PR context loaded."Usage patterns
Multi-tracker research: Fan out to read an issue from both Linear and GitHub Issues simultaneously, then fan in before dispatching the agent with both contexts.
Concurrent agent dispatch: Dispatch multiple agents in parallel - for example, a security scanner and a code reviewer - and wait for both before advancing the SDLC stage.
Multi-repo operations: Apply the same operation (create a branch, post a comment) to multiple repositories concurrently.
Independent setup steps: Provision multiple resources in parallel (create a branch, add a label, acknowledge the session) before the main agent dispatch.
Scope isolation
Steps inside a parallel group branch can only wire to other steps in the same branch or to the parallel group boundary ports. Cross-branch wiring is not allowed - this is enforced at canvas validation time. Because fan-in output aggregation has not shipped, data cannot flow between branches or out through the fan-in port; downstream steps that need a branch's data must reference the branch step's output directly via {{ steps.<stepId>.output }} template expressions.
Related pages
- Runtime: DAG Executor - how the BFS tier computation runs parallel branches
- Condition Nodes - branching before a fan-out for conditional parallelism
- Agent Action Nodes -
agent.invokein parallel branches for concurrent agent dispatch - Canvas: Port Types - scope isolation rules for parallel group boundaries