Context
Wave has two overlapping flow control systems that evolved independently:
System 1: Composition Primitives (pre-epic #589)
iterate — fan-out over items (sequential or parallel)
branch — route based on context variable
loop — repeat steps until condition met
aggregate — merge results from fan-out
sub_pipeline (via pipeline: field) — invoke child pipeline
Used by: audit-quality-loop, ops-epic-runner, ops-implement-epic, ops-parallel-audit, ops-release-harden
Visible in: WebUI /compose page
System 2: Graph Execution Model (epic #589)
type: conditional — route based on step outcome via edges
type: command — shell script execution
max_visits — loop safety limit
edges with backward targets — creates cycles
type: pipeline — sub-pipeline invocation
Used by: impl-hotfix, wave-bugfix, ops-debug, test-gen, wave-test-hardening, wave-validate
Visible in: WebUI DAG with step type shapes
The Overlap
| Capability |
Composition |
Graph |
| Conditional routing |
branch: with cases |
type: conditional + edges |
| Looping |
loop: with until |
edges pointing backward + max_visits |
| Sub-pipeline |
pipeline: field |
type: pipeline |
| Fan-out/parallel |
iterate: with mode: parallel |
(no equivalent) |
| Aggregation |
aggregate: |
(no equivalent) |
| Shell execution |
(no equivalent) |
type: command |
Why This Matters
- Pipeline authors must learn two models
- Validator needs to handle both
- WebUI renders them differently (compose page vs DAG step types)
- New features must be implemented in both systems or one gets left behind
- The executor has two code paths (
executeStep vs executeGraphPipeline)
Research Questions
-
Can graph features subsume composition primitives? Graph edges + conditional routing could replace branch. max_visits + backward edges could replace loop. But what about iterate (parallel fan-out) and aggregate?
-
Can composition primitives subsume graph features? loop could replace max_visits + edges. branch could replace type: conditional. But composition primitives are higher-level — they don't support arbitrary DAG shapes.
-
Should we keep both and formalize the boundary? Composition = orchestration layer (pipeline-of-pipelines). Graph = step layer (within a single pipeline). Clear separation, no overlap.
-
What do users lose in each unification direction?
-
Migration path: 6 composition pipelines + 6 graph pipelines need migration or the old system needs maintained indefinitely.
Scope
Phase 1: Research
Phase 2: ADR
Phase 3: Implementation
Validation Criteria
- One clear model for pipeline authors (or two with a documented boundary)
- All existing pipelines continue to work
- WebUI reflects the unified/clarified model
- Documentation explains when to use what
Context
Wave has two overlapping flow control systems that evolved independently:
System 1: Composition Primitives (pre-epic #589)
iterate— fan-out over items (sequential or parallel)branch— route based on context variableloop— repeat steps until condition metaggregate— merge results from fan-outsub_pipeline(viapipeline:field) — invoke child pipelineUsed by: audit-quality-loop, ops-epic-runner, ops-implement-epic, ops-parallel-audit, ops-release-harden
Visible in: WebUI /compose page
System 2: Graph Execution Model (epic #589)
type: conditional— route based on step outcome viaedgestype: command— shell script executionmax_visits— loop safety limitedgeswith backward targets — creates cyclestype: pipeline— sub-pipeline invocationUsed by: impl-hotfix, wave-bugfix, ops-debug, test-gen, wave-test-hardening, wave-validate
Visible in: WebUI DAG with step type shapes
The Overlap
branch:with casestype: conditional+edgesloop:withuntiledgespointing backward +max_visitspipeline:fieldtype: pipelineiterate:withmode: parallelaggregate:type: commandWhy This Matters
executeStepvsexecuteGraphPipeline)Research Questions
Can graph features subsume composition primitives? Graph edges + conditional routing could replace
branch.max_visits+ backward edges could replaceloop. But what aboutiterate(parallel fan-out) andaggregate?Can composition primitives subsume graph features?
loopcould replacemax_visits+ edges.branchcould replacetype: conditional. But composition primitives are higher-level — they don't support arbitrary DAG shapes.Should we keep both and formalize the boundary? Composition = orchestration layer (pipeline-of-pipelines). Graph = step layer (within a single pipeline). Clear separation, no overlap.
What do users lose in each unification direction?
Migration path: 6 composition pipelines + 6 graph pipelines need migration or the old system needs maintained indefinitely.
Scope
Phase 1: Research
iterateparallel fan-out have performance characteristics that graph mode can't match?Phase 2: ADR
Phase 3: Implementation
Validation Criteria