Skip to content

Add bounded workflow loop nodes - #1508

Merged
gsxdsm merged 2 commits into
mainfrom
feature/workflow-loop-nodes
Jun 8, 2026
Merged

Add bounded workflow loop nodes#1508
gsxdsm merged 2 commits into
mainfrom
feature/workflow-loop-nodes

Conversation

@gsxdsm

@gsxdsm gsxdsm commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator

Summary

  • add first-class loop workflow IR nodes with bounded template execution, output-based exit conditions, and failure values for timeout/iteration exhaustion
  • wire loop execution through the workflow graph executor while preserving plugin node-handler execution inside loop templates
  • add dashboard authoring, mapping, summaries, docs, SDK type exports, and a changeset

Validation

  • pnpm --filter @fusion/core exec vitest run src/__tests__/workflow-ir-loop.test.ts --silent=passed-only --reporter=dot
  • pnpm --filter @fusion/engine exec vitest run src/__tests__/workflow-graph-loop.test.ts --silent=passed-only --reporter=dot
  • pnpm --filter @fusion/dashboard exec vitest run app/components/__tests__/workflow-flow-mapping.test.ts app/components/nodes/__tests__/node-summary.test.ts --silent=passed-only --reporter=dot
  • pnpm --filter @fusion/core typecheck
  • pnpm --filter @fusion/engine typecheck
  • pnpm --filter @fusion/dashboard typecheck
  • pnpm --filter @fusion/plugin-sdk typecheck
  • pnpm lint
  • pnpm build
  • pnpm test:gate

Summary by CodeRabbit

  • New Features

    • First-class workflow loop nodes: bounded iterative template execution with configurable exit conditions (output-contains / output-matches), maxIterations and timeoutMs
    • Editor support: create loop nodes, configure exit condition/watch node, see iteration/time badges and concise loop summaries
    • Runtime routing: explicit failure outcomes for iteration exhaustion and timeout
  • Documentation

    • Added detailed design, validation rules, examples, and authoring guide for loop nodes

@coderabbitai

coderabbitai Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: 23bda170-467b-4aba-bb98-5a613c50a8ff

📥 Commits

Reviewing files that changed from the base of the PR and between a504238 and 75867c4.

📒 Files selected for processing (8)
  • docs/workflow-steps.md
  • packages/core/src/__tests__/workflow-ir-loop.test.ts
  • packages/core/src/workflow-ir.ts
  • packages/dashboard/app/components/WorkflowNodeEditor.tsx
  • packages/dashboard/app/components/__tests__/workflow-flow-mapping.test.ts
  • packages/dashboard/app/components/nodes/node-summary.ts
  • packages/dashboard/app/components/workflow-flow-mapping.ts
  • packages/engine/src/workflow-graph-loop.ts
✅ Files skipped from review due to trivial changes (1)
  • docs/workflow-steps.md
🚧 Files skipped from review as they are similar to previous changes (6)
  • packages/core/src/workflow-ir.ts
  • packages/dashboard/app/components/nodes/node-summary.ts
  • packages/dashboard/app/components/tests/workflow-flow-mapping.test.ts
  • packages/core/src/tests/workflow-ir-loop.test.ts
  • packages/dashboard/app/components/WorkflowNodeEditor.tsx
  • packages/dashboard/app/components/workflow-flow-mapping.ts

📝 Walkthrough

Walkthrough

This PR introduces first-class loop workflow nodes with bounded template repetition, exit conditions, and editor support. It adds IR types and validation, executor runtime execution, dashboard authoring UI, bidirectional flow mapping, and comprehensive documentation.

Changes

Workflow Loop Nodes

Layer / File(s) Summary
Loop type contract and core IR validation
packages/core/src/workflow-ir-types.ts, packages/core/src/index.ts, packages/plugin-sdk/src/index.ts, packages/core/src/workflow-ir.ts, packages/core/src/__tests__/workflow-ir-loop.test.ts
WorkflowLoopExitCondition and WorkflowLoopConfig types define loop structure with template subgraph, exitWhen matching criteria, and maxIterations/timeoutMs bounds. validateLoop enforces template schema, constraints on nesting and rework, single entry/exit, and illegal-cycle detection. Tests verify round-tripping and rejection of invalid configurations.
Executor runtime for loop execution
packages/engine/src/workflow-graph-loop.ts, packages/engine/src/workflow-graph-executor.ts, packages/engine/src/__tests__/workflow-graph-loop.test.ts
runLoop executes loop templates iteratively with deadline-bounded execution, exit condition matching (output-contains, output-matches), and context recording. WorkflowGraphExecutor routes loop nodes through runLoop, merges outcomes/values into shared context, and traverses outgoing edges. Tests cover matched exits, iteration exhaustion, timeout, and regex-based conditions.
Dashboard node authoring UI
packages/dashboard/app/components/nodes/WorkflowNodeTypes.tsx, packages/dashboard/app/components/WorkflowNodeEditor.tsx, packages/dashboard/app/components/nodes/node-summary.ts, packages/dashboard/app/components/nodes/__tests__/node-summary.test.ts
LoopGroupNode React component renders loop with configurable maxIterations/timeoutMs badges and empty-state hint. Workflow editor palette includes loop node creation, addNode generates child prompt with default loop config, inspector exposes exit condition controls and budget settings. Node summary displays "until … · Nx" format for exit condition and iteration budget.
Dashboard IR↔Flow bidirectional mapping for loops
packages/dashboard/app/components/workflow-flow-mapping.ts, packages/dashboard/app/components/__tests__/workflow-flow-mapping.test.ts
Generalizes template-group handling from foreach to include loop via loopConfigOf/groupTemplateConfigOf helpers. irToFlow expands loop templates as parented child nodes; flowToIr reassembles loop config and template with correct kind preservation. cascadeDelete expands to loop template children; fragment insertion and deep-copy utilities handle loop internals with id/layout remapping. Tests verify round-trip fidelity and fragment insertion.
Feature documentation and release metadata
docs/plans/2026-06-08-002-feat-workflow-loop-nodes-plan.md, docs/workflow-steps.md, .changeset/workflow-loop-nodes.md
Feature plan specifies loop requirements (template repetition, exit conditions, budget exhaustion as routable outcomes), safety constraints (no new top-level cycles, bounded repetition), implementation units, and acceptance examples. Workflow IR v2 documentation describes loop configuration schema, template constraints (single entry/exit, no nested loops), and outcome/failure routing. Changeset documents minor release of loop node support.

Sequence Diagram(s)

sequenceDiagram
  participant Author as Workflow Author
  participant Editor as WorkflowNodeEditor
  participant Mapping as workflow-flow-mapping
  participant Executor as WorkflowGraphExecutor
  participant LoopRunner as runLoop
  participant Context as Workflow Context

  Author->>Editor: Configure loop node and template
  Editor->>Mapping: flowToIr(loop node)
  Mapping->>Executor: Provide IR with loop node
  Executor->>LoopRunner: runLoop(loopNode, env)
  LoopRunner->>Context: Publish iteration outcomes and values
  LoopRunner->>Executor: Return outcome, value, visitedNodeIds
  Executor->>Context: Store node:{id}:outcome and node:{id}:value
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~75 minutes

Possibly related PRs

  • Runfusion/Fusion#1424: Adds step-inversion/template-group machinery that this loop feature generalizes and builds upon.
  • Runfusion/Fusion#1433: Touches related IR template recursion/stripApprovalBypassFlags handling referenced by loop template changes.

🐰 A loop node hops into the flow,
Round and round, not too slow,
With exit conditions set just right,
Iterations bounded tight,
Templates dance till they ignite!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 46.88% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Add bounded workflow loop nodes' accurately and concisely summarizes the main change—introducing first-class loop workflow IR nodes with bounded execution—which aligns with the changeset's primary objective.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feature/workflow-loop-nodes

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@greptile-apps

greptile-apps Bot commented Jun 8, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR introduces first-class loop workflow IR nodes with bounded repeat-until semantics, wired end-to-end through validation, the graph executor, and the dashboard editor. The previously-flagged P1 (loop nodes silently accepted inside foreach templates) is correctly addressed: validateForeach now rejects inner.kind === \"loop\" alongside \"foreach\", and the test suite covers that case.

  • Core (workflow-ir.ts): validateLoop enforces single entry/exit, no nested template groups, no rework edges, topology checks, and an assertSafeLoopRegexPattern guard; clampForeachConfigs is extended to clamp maxIterations > 50.
  • Engine (workflow-graph-loop.ts): Sequential template sub-walk per iteration, deadline/abort checks between nodes, publishIterationContext merging iteration results into the parent context, and failure-value routing for timeout/exhaustion/node-failure outcomes.
  • Dashboard: Loop node palette entry, LoopGroupNode React Flow component, full inspector panel (exit type, value/pattern, optional watch nodeId, max iterations, timeout), and generalised groupTemplateConfigOf abstraction in workflow-flow-mapping.ts.

Confidence Score: 5/5

Safe to merge; the previously-flagged blocking issue (loop nodes silently permitted inside foreach templates) is fully addressed with both a guard and a test.

All execution paths through the loop node are covered by tests (success exit, multi-iteration convergence, exhaustion routing, timeout routing, regex nodeId). The IR validation is thorough, the foreach/loop nesting symmetry is correctly handled in both directions, and the context merge/publish pattern follows the established foreach executor model.

packages/engine/src/workflow-graph-loop.ts — local cap constants duplicate core values without the import-from-core guard that the foreach executor uses; worth a follow-up to export a resolver from @fusion/core.

Important Files Changed

Filename Overview
packages/engine/src/workflow-graph-loop.ts New loop executor — correct sequential template walk, but embeds cap constants locally (MAX_ITERATIONS_CAP, MAX_TIMEOUT_MS) instead of importing from @fusion/core, unlike the foreach executor's resolveMaxReworkCycles pattern
packages/core/src/workflow-ir.ts Adds validateLoop with thorough guards; the LOOP_REGEX_NESTED_QUANTIFIER heuristic misses alternation-based ReDoS patterns like (a
packages/core/src/workflow-ir-types.ts Adds WorkflowLoopConfig and WorkflowLoopExitCondition types; well-structured with optional nodeId and flags fields
packages/engine/src/workflow-graph-executor.ts Wires runLoop into the graph walk, threading context and visitedNodeIds correctly; runLoopNowForTests seam enables deterministic timeout tests
packages/dashboard/app/components/workflow-flow-mapping.ts Generalises foreach group handling to a shared groupTemplateConfigOf / groupIds abstraction; loop round-trips and cascade-delete both correctly extended
packages/dashboard/app/components/WorkflowNodeEditor.tsx Adds loop palette entry and full inspector panel; exit-type toggle preserves text across switches; numeric inputs use parseInt with isNaN guard
packages/dashboard/app/components/nodes/WorkflowNodeTypes.tsx Adds LoopGroupNode component mirroring ForeachGroupNode; displays iteration count badge and optional timeout badge
packages/engine/src/tests/workflow-graph-loop.test.ts Covers success exit, multi-iteration convergence, exhaustion routing, timeout routing, and regex nodeId; no test for mid-template node-failure propagation
packages/core/src/tests/workflow-ir-loop.test.ts Thorough IR validation tests including foreach-with-nested-loop (previously P1 fix), seam rejection, cycle rejection, and clamp/reject budget checks

Sequence Diagram

sequenceDiagram
    participant E as WorkflowGraphExecutor
    participant L as runLoop
    participant T as runTemplateNode
    participant C as Context

    E->>L: runLoop(loopNode, env)
    loop for iteration 1..maxIterations
        L->>L: check deadline / abort signal
        L->>T: runTemplateNode(entry, signal, iterationContext)
        T-->>L: WorkflowNodeResult
        alt node failure
            L->>C: "publishIterationContext + exitReason=node-failure"
            L-->>E: "{outcome: failure, value: node.value}"
        else walk next edge
            L->>T: runTemplateNode(next, ...)
            T-->>L: WorkflowNodeResult
        end
        L->>L: matchesExit(config, sourceNodeValue)
        alt exit condition met
            L->>C: "context[node:loop:loop] = {exitReason: matched}"
            L-->>E: "{outcome: success}"
        end
    end
    L->>C: "context[node:loop:loop] = {exitReason: iteration-exhausted | timeout}"
    L-->>E: "{outcome: failure, value: loop-iteration-exhausted | loop-timeout}"
Loading

Reviews (2): Last reviewed commit: "fix(FN-0000): address workflow loop revi..." | Re-trigger Greptile

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 3

Caution

Some comments are outside the diff and can’t be posted inline due to platform limitations.

⚠️ Outside diff range comments (1)
packages/dashboard/app/components/workflow-flow-mapping.ts (1)

1002-1019: ⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Preserve persisted child layout when inserting template-group fragments.

Inserted foreach/loop fragments currently discard the saved positions of their template children and reset every child to the default horizontal layout. The fragment layout already carries namespaced child keys, so authors will lose any hand-arranged template body every time they insert one of these fragments.

Proposed fix
     if (groupCfg) {
       const template = groupCfg.template;
       const groupKind = editorKind(node);
       template.nodes.forEach((inner, innerIdx) => {
         const innerKind = editorKind(inner);
+        const childPos =
+          layout?.[foreachChildFlowId(node.id, inner.id)] ?? {
+            x: FOREACH_CHILD_X + innerIdx * FOREACH_CHILD_STEP_X,
+            y: FOREACH_CHILD_Y,
+          };
         childNodes.push({
           id: foreachChildFlowId(id, inner.id),
           type: innerKind,
-          position: { x: FOREACH_CHILD_X + innerIdx * FOREACH_CHILD_STEP_X, y: FOREACH_CHILD_Y },
+          position: childPos,
           parentId: id,
           extent: "parent",
           data: { kind: innerKind, label: nodeLabel(inner), config: { ...(inner.config ?? {}) } },
           deletable: true,
         });
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/dashboard/app/components/workflow-flow-mapping.ts` around lines 1002
- 1019, The foreach/loop fragment insertion is overwriting saved child positions
by always assigning default FOREACH_CHILD_X/STEP positions; update the creation
of child nodes in the block that calls groupTemplateConfigOf(node) and iterates
template.nodes (the code that uses foreachChildFlowId, editorKind, nodeLabel) to
first look up any persisted per-child layout in node.layout (the namespaced
child keys the fragment carries) and, if present, use those x/y (and extent if
stored) for position/extent instead of the default horizontal layout; fall back
to the existing FOREACH_CHILD_X + innerIdx * FOREACH_CHILD_STEP_X and
FOREACH_CHILD_Y when no saved layout exists. Ensure irEdgeToFlow call that
namespaces edge ids continues to use the same id prefix so layout keys match.
🧹 Nitpick comments (1)
docs/workflow-steps.md (1)

122-141: ⚡ Quick win

Add language tag to code fence for proper syntax highlighting.

The fenced code block on line 126 should specify ts or typescript for proper syntax highlighting in rendered documentation.

📝 Proposed fix
-```
+```ts
 { template: { nodes, edges },
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@docs/workflow-steps.md` around lines 122 - 141, The fenced code block showing
the `loop` node config should include a language tag for TypeScript; update the
code fence that begins with "{ template: { nodes, edges }," to use ```ts (or
```typescript) so the snippet for keys like `template`, `exitWhen`,
`maxIterations`, and `timeoutMs` is syntax-highlighted in the rendered docs.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/dashboard/app/components/nodes/node-summary.ts`:
- Around line 161-177: The loop case in node-summary.ts contains hardcoded
English strings ("until matches", "until contains", "3x") that bypass
localization. Pass these string literals through the translator function `t` to
enable proper localization of loop summaries. This includes the string templates
for the pattern matching phrase and the value contains phrase, as well as the
default iteration count string. Update all three string literals returned in the
conditional expression and the default maxIterations fallback to use the `t`
function for consistency with the rest of the node summary localization.

In `@packages/dashboard/app/components/WorkflowNodeEditor.tsx`:
- Around line 3196-3216: The onChange handler for the exit type select (using
exitType and updateSelectedData) rebuilds exitWhen from only type +
pattern/value and drops other sibling fields (e.g., nodeId, regex flags); update
the handler so when computing the new exitWhen you copy existing keys from the
current exitWhen object and only override type and the pattern/value field
(preserving nodeId and any other fields), e.g., read the current exitWhen via
prev.exitWhen, merge its properties into the new object, and set the new type
plus pattern or value while leaving other keys intact.

In `@packages/engine/src/workflow-graph-loop.ts`:
- Around line 90-96: The matchesExit function currently compiles a RegExp per
iteration; instead precompile and validate the regex once when the loop config
is resolved (the WorkflowLoopConfig.exitWhen object) and store the compiled
RegExp on that config so matchesExit can reuse it; additionally run a safety
check (e.g., use a safe-regex-like heuristic or RE2-compatible validation)
during validation/creation of the loop config (in the workflow config
resolution/validation path that consumes WorkflowLoopConfig) and reject or
sanitize pathological patterns so no hazardous pattern reaches the runtime loop.

---

Outside diff comments:
In `@packages/dashboard/app/components/workflow-flow-mapping.ts`:
- Around line 1002-1019: The foreach/loop fragment insertion is overwriting
saved child positions by always assigning default FOREACH_CHILD_X/STEP
positions; update the creation of child nodes in the block that calls
groupTemplateConfigOf(node) and iterates template.nodes (the code that uses
foreachChildFlowId, editorKind, nodeLabel) to first look up any persisted
per-child layout in node.layout (the namespaced child keys the fragment carries)
and, if present, use those x/y (and extent if stored) for position/extent
instead of the default horizontal layout; fall back to the existing
FOREACH_CHILD_X + innerIdx * FOREACH_CHILD_STEP_X and FOREACH_CHILD_Y when no
saved layout exists. Ensure irEdgeToFlow call that namespaces edge ids continues
to use the same id prefix so layout keys match.

---

Nitpick comments:
In `@docs/workflow-steps.md`:
- Around line 122-141: The fenced code block showing the `loop` node config
should include a language tag for TypeScript; update the code fence that begins
with "{ template: { nodes, edges }," to use ```ts (or ```typescript) so the
snippet for keys like `template`, `exitWhen`, `maxIterations`, and `timeoutMs`
is syntax-highlighted in the rendered docs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: fb864583-7407-497f-83b1-dd525761f928

📥 Commits

Reviewing files that changed from the base of the PR and between e0ab3b5 and a504238.

📒 Files selected for processing (17)
  • .changeset/workflow-loop-nodes.md
  • docs/plans/2026-06-08-002-feat-workflow-loop-nodes-plan.md
  • docs/workflow-steps.md
  • packages/core/src/__tests__/workflow-ir-loop.test.ts
  • packages/core/src/index.ts
  • packages/core/src/workflow-ir-types.ts
  • packages/core/src/workflow-ir.ts
  • packages/dashboard/app/components/WorkflowNodeEditor.tsx
  • packages/dashboard/app/components/__tests__/workflow-flow-mapping.test.ts
  • packages/dashboard/app/components/nodes/WorkflowNodeTypes.tsx
  • packages/dashboard/app/components/nodes/__tests__/node-summary.test.ts
  • packages/dashboard/app/components/nodes/node-summary.ts
  • packages/dashboard/app/components/workflow-flow-mapping.ts
  • packages/engine/src/__tests__/workflow-graph-loop.test.ts
  • packages/engine/src/workflow-graph-executor.ts
  • packages/engine/src/workflow-graph-loop.ts
  • packages/plugin-sdk/src/index.ts

Comment thread packages/dashboard/app/components/nodes/node-summary.ts
Comment thread packages/dashboard/app/components/WorkflowNodeEditor.tsx
Comment thread packages/engine/src/workflow-graph-loop.ts Outdated
@gsxdsm

gsxdsm commented Jun 8, 2026

Copy link
Copy Markdown
Collaborator Author

Inserted foreach/loop fragments currently discard the saved positions of their template children.

Addressed: insertFragment now reads namespaced child layout entries and preserves child positions when inserting template-group fragments. Added coverage for loop fragment child layout preservation.

The fenced code block showing the loop node config should include a language tag for TypeScript.

Addressed: updated the loop config fence to ts.

validateForeach silently allows loop nodes inside foreach templates.

Addressed: validateForeach now rejects nested loop/foreach template groups symmetrically with validateLoop, with regression coverage.

The function name clampForeachConfigs is now misleading... / stripApprovalBypassFlags JSDoc mentions only foreach-in-foreach.

Addressed: updated the bounded-config clamp JSDoc and the approval-bypass stripping comment to reflect loop/template-group behavior.

@gsxdsm
gsxdsm merged commit c9b7802 into main Jun 8, 2026
6 checks passed
@gsxdsm
gsxdsm deleted the feature/workflow-loop-nodes branch June 8, 2026 07:33
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant