Skip to content

epic: unify composition primitives and graph execution model #640

@nextlevelshit

Description

@nextlevelshit

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

  1. 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?

  2. 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.

  3. 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.

  4. What do users lose in each unification direction?

  5. Migration path: 6 composition pipelines + 6 graph pipelines need migration or the old system needs maintained indefinitely.

Scope

Phase 1: Research

  • Map every composition primitive to its graph equivalent (or document why no equivalent exists)
  • Interview pipeline authors (check existing pipeline patterns) — which model do they reach for?
  • Benchmark: does iterate parallel fan-out have performance characteristics that graph mode can't match?
  • Survey other tools: how do Fabro, GitHub Actions, Argo Workflows handle this?

Phase 2: ADR

  • Write ADR documenting the chosen direction (unify to graph, unify to composition, or formalize boundary)
  • Define migration plan for affected pipelines

Phase 3: Implementation

  • Execute the chosen direction
  • Migrate affected pipelines
  • Update WebUI (compose page and DAG view converge or differentiate)
  • Update documentation

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or request

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions