13 KiB
title, type, status, date
| title | type | status | date |
|---|---|---|---|
| Workflow Loop Nodes | feat | active | 2026-06-08 |
Workflow Loop Nodes
Summary
Add a first-class loop workflow node that repeats an inline node sequence until a configured exit condition is met or a configured budget expires. The loop should be available through the shared workflow IR, graph executor, editor, docs, plugin SDK exports, and published package bundle so extension workflows can use it without engine forks.
Problem Frame
Workflow IR currently supports bounded corrective loops through kind: "rework" edges and per-step iteration through foreach. Those mechanisms are not a general workflow loop: authors cannot model "run these nodes until the agent says a stop token" or "retry this agentic sequence for at most N iterations or M milliseconds" without abusing rework semantics or loosening the global cycle guard. A dedicated loop node preserves the graph's acyclic safety while making bounded repeat-until behavior explicit and inspectable.
Requirements
Loop contract
- R1. Workflow IR accepts
kind: "loop"with an inline template graph that has exactly one entry and one exit. - R2. A loop exits successfully when a configured agent-output string or regex matches the selected node output.
- R3. A loop exits with a routable budget outcome when
maxIterationsortimeoutMsis reached before the exit condition matches. - R4. Loop execution records iteration count, exit reason, final value, and per-iteration node outcomes in workflow context for downstream routing and diagnostics.
Safety and compatibility
- R5. General non-rework cycles remain illegal; loop repetition is implemented inside the loop node's bounded sub-walk, not by allowing top-level cyclic edges.
- R6. Loop templates reject unsafe nesting and invalid placement in the same parse-time style as
foreachtemplates. - R7. Existing workflows, built-ins, and v1 upgrade/downgrade behavior remain unchanged unless they explicitly use
kind: "loop". - R8. The public plugin SDK exports the loop node types so extension-authored workflows can declare loop nodes through the same package contract as core workflows.
Authoring and docs
- R9. The workflow editor can render, round-trip, copy, delete, and configure loop groups without dropping template nodes or loop settings.
- R10. Documentation explains loop semantics, exit conditions, budgets, routable outcomes, and the distinction between
loop,foreach, andrework. - R11. Because the published CLI package bundles the shared engine and dashboard, the release includes a changeset for
@runfusion/fusion.
High-Level Technical Design
flowchart TB
A[Top-level graph reaches loop node] --> B[Initialize loop state]
B --> C[Run template entry to template exit]
C --> D{Exit condition matched?}
D -->|yes| E[Emit success with exit reason matched]
D -->|no| F{Budget exhausted?}
F -->|no| G[Increment iteration and rerun template]
G --> C
F -->|iteration cap| H[Emit failure value loop-iteration-exhausted]
F -->|time cap| I[Emit failure value loop-timeout]
E --> J[Route loop outgoing edge]
H --> J
I --> J
The loop template should use the same "group node with inline subgraph" shape as foreach, but it is not step-source driven. Runtime expansion is one loop instance with repeated sub-walks, not one instance per planned task step.
Key Technical Decisions
- KTD1. Add a distinct
loopnode kind instead of overloadingforeach:foreachbinds to a collection source and per-item state, whileloopbinds to a termination policy and a single repeated region. - KTD2. Keep top-level graph cycle validation strict: the executor repeats loop templates internally, so normal edges still form an acyclic graph except existing
reworkedges. - KTD3. Model exit conditions as explicit config:
exitWhenshould support at least{ type: "output-contains", nodeId?, value }and{ type: "output-matches", nodeId?, pattern }, with the default source being the template exit node'svalue. - KTD4. Treat budget exhaustion as routable failure values:
loop-iteration-exhaustedandloop-timeoutlet authors park, escalate, or fail using existingoutcome:<value>edges. - KTD5. Reuse the editor's group-node mechanics: loop authoring should mirror
foreachtemplate rendering and round-trip behavior rather than creating a second bespoke canvas model. - KTD6. Export additive types through
@fusion/coreand@fusion/plugin-sdk: extension packages should consume the loop contract from the shared package rather than relying on local structural copies.
Implementation Units
U1. Core IR Types and Validation
- Goal: Add the
loopnode contract to shared workflow IR and validate loop templates at parse time. - Files:
packages/core/src/workflow-ir-types.ts,packages/core/src/workflow-ir.ts,packages/core/src/index.ts,packages/plugin-sdk/src/index.ts - Patterns: Follow
WorkflowForeachConfig,validateForeach,validateNoIllegalCycles,serializeWorkflowIr, and extension metadata exports. - Test Scenarios: Add
packages/core/src/__tests__/workflow-ir-loop.test.tscovering valid loop parsing, duplicate template node rejection, external template edge rejection, nested loop/foreach policy, invalid exit condition config, max iteration bounds, timeout bounds, serialization round-trip, and illegal top-level cycles still rejected. - Verification:
pnpm --filter @fusion/core exec vitest run src/__tests__/workflow-ir-loop.test.ts --silent=passed-only --reporter=dot
U2. Loop Runtime Execution
- Goal: Implement loop-node execution in the workflow graph executor with bounded repeat-until semantics.
- Files:
packages/engine/src/workflow-graph-executor.ts,packages/engine/src/workflow-graph-loop.ts - Patterns: Follow the extracted sub-walk style in
packages/engine/src/workflow-graph-foreach.tsand the top-level rework budget handling inpackages/engine/src/workflow-graph-executor.ts. - Test Scenarios: Add
packages/engine/src/__tests__/workflow-graph-loop.test.tscovering immediate match, match after multiple iterations, iteration exhaustion, timeout exhaustion with fake timers or injected clock, context patch propagation between iterations, selectednodeIdoutput source, and unchanged behavior for non-loop graphs. - Verification:
pnpm --filter @fusion/engine exec vitest run src/__tests__/workflow-graph-loop.test.ts --silent=passed-only --reporter=dot
U3. Routing and Context Semantics
- Goal: Define how loop outcomes and context keys participate in existing edge routing.
- Files:
packages/engine/src/workflow-graph-executor.ts,packages/engine/src/__tests__/workflow-graph-executor-parity.test.ts - Patterns: Follow
shouldTraverseEdge,node:<id>:outcome,node:<id>:value, and foreach context recording. - Test Scenarios: Cover
condition: "success"after a matched loop,condition: "outcome:loop-iteration-exhausted",condition: "outcome:loop-timeout", and failure propagation when a template node fails before the exit condition can be evaluated. - Verification: Include these cases in the loop runtime focused test or a small executor parity test update.
U4. Workflow Editor Loop Authoring
- Goal: Add loop group rendering, palette entry, inspector controls, flow-to-IR round-trip, delete/copy behavior, and node summary text.
- Files:
packages/dashboard/app/components/WorkflowNodeEditor.tsx,packages/dashboard/app/components/workflow-flow-mapping.ts,packages/dashboard/app/components/nodes/WorkflowNodeTypes.tsx,packages/dashboard/app/components/nodes/node-summary.ts,packages/dashboard/app/components/WorkflowNodeEditor.css - Patterns: Follow existing
foreachgroup node rendering,foreachChildFlowId, template remapping,cascadeDelete, fragment insertion, and inspector field patterns. - Test Scenarios: Extend
packages/dashboard/app/components/__tests__/workflow-flow-mapping.test.ts,packages/dashboard/app/components/__tests__/WorkflowNodeEditor.test.tsx, andpackages/dashboard/app/components/nodes/__tests__/node-summary.test.tsfor palette insertion, config editing, template child preservation, copy/remap, delete cascade, condition editability, and summary display. - Verification: Run the focused dashboard tests named above.
U5. Built-In Documentation and Authoring Reference
- Goal: Document loop node semantics and update authoring guidance so workflow authors use the right repeat primitive.
- Files:
docs/workflow-steps.md,docs/PLUGIN_AUTHORING.md,docs/cli-reference.md - Patterns: Follow the existing node sections for
foreach,step-review, andcode. - Test Scenarios: Documentation-only assertions should rely on existing docs tests if present; otherwise no new test is needed.
- Verification:
pnpm lintshould catch markdown-adjacent import/doc inventory issues if any are covered by lint.
U6. Packaging and Release Metadata
- Goal: Ensure the loop contract ships through the published package and remains available to extension-authored workflows.
- Files:
.changeset/<loop-node-name>.md,packages/cli/srcbundle entry points if needed,packages/plugin-sdk/src/index.ts - Patterns: Follow
.changeset/workflow-extension-plugins.mdand the current plugin SDK export surface. - Test Scenarios: Existing build coverage should prove package exports compile; add a small SDK type-export assertion only if current tests do not cover exported workflow IR types.
- Verification:
pnpm --filter @fusion/plugin-sdk typecheck,pnpm build
U7. Extension Workflow Consumption Pass
- Goal: Update extension-authored workflow templates to use
loopwhere they currently need bounded repeat-until behavior. - Files: Extension package workflow template files that declare repeat-until review or response regions.
- Patterns: Consume
WorkflowLoopConfigand related exported types from@fusion/plugin-sdk; do not duplicate loop config shapes locally. - Test Scenarios: Add template parse/round-trip tests in the extension package and a focused runtime dispatch test for a loop-backed extension workflow.
- Verification: Run that package's typecheck/build and focused tests after the shared engine change is available.
Acceptance Examples
- AE1. Given a loop template whose final prompt node returns
DONE, whenexitWhen.valueisDONE, then the loop runs once, recordsmatched, and follows its success edge. - AE2. Given a loop template that returns
KEEP_GOINGtwice andDONEon the third run, whenmaxIterationsis at least 3, then the loop runs three iterations and exits successfully. - AE3. Given a loop template that never emits the configured stop string, when
maxIterationsis 2, then the loop emitsvalue: "loop-iteration-exhausted"and follows anoutcome:loop-iteration-exhaustededge if present. - AE4. Given a loop template that runs longer than
timeoutMs, when the timeout elapses, then the loop emitsvalue: "loop-timeout"without running another iteration. - AE5. Given a workflow with a top-level non-rework cycle, when it is parsed, then parse still rejects it even if the workflow also contains a valid loop node.
Scope Boundaries
- This plan does not add unbounded graph cycles.
- This plan does not change
foreach(source:"task-steps")semantics. - This plan does not replace existing PR review
reworkloops; it adds a separate authoring primitive for repeat-until workflows. - This plan does not introduce persistent loop-instance tables unless implementation discovers a restart-resume requirement that cannot be met from existing run context.
Risks and Dependencies
- Loop output source ambiguity: Authors may expect any agent text to count as loop output. The initial contract should define one source clearly: the selected template node's
WorkflowNodeResult.value, defaulting to the template exit node. - Timeout testing risk: Real timers would slow the suite. Use fake timers or an injectable clock/deadline helper for timeout exhaustion coverage.
- Editor complexity:
foreachalready has careful group-node round-trip behavior. Loop should reuse that mapping machinery or small generalized helpers to avoid a second drift-prone group implementation. - Extension timing: Extension workflow templates can adopt
loopafter the shared package exports land; until then they should continue to parse under the current engine contract.
Sources
packages/core/src/workflow-ir-types.tsdefines current node kinds,WorkflowForeachConfig, and rework budget helpers.packages/core/src/workflow-ir.tsvalidatesforeachtemplates and rejects non-rework cycles.packages/engine/src/workflow-graph-executor.tsowns graph traversal, top-level rework handling, and node outcome routing.packages/engine/src/workflow-graph-foreach.tsprovides the closest runtime pattern for a bounded inline sub-walk.packages/dashboard/app/components/workflow-flow-mapping.tsandpackages/dashboard/app/components/WorkflowNodeEditor.tsxprovide the existing group-node authoring pattern.docs/workflow-steps.mddocuments currentforeach,rework, andcodenode semantics.