Skip to content

feat: add Parallel node for AgentFlow V2#6632

Open
chogamy wants to merge 1 commit into
FlowiseAI:mainfrom
chogamy:feature/agentflow-parallel-node
Open

feat: add Parallel node for AgentFlow V2#6632
chogamy wants to merge 1 commit into
FlowiseAI:mainfrom
chogamy:feature/agentflow-parallel-node

Conversation

@chogamy

@chogamy chogamy commented Jul 16, 2026

Copy link
Copy Markdown

Summary

  • Adds a new Parallel container node to AgentFlow V2, alongside the existing Iteration node
  • Branches inside the Parallel box are auto-detected from the canvas graph structure — any child node with no incoming edge from a sibling becomes a branch root, and everything reachable from it forms that branch
  • All branches execute concurrently (Promise.allSettled) instead of sequentially; a failing branch doesn't abort the others
  • Each branch runs off an independent snapshot of flow state; after all branches settle, only the keys a branch actually changed are merged back (diff-based merge), so one branch's untouched keys don't clobber another branch's update to the same key

Changes

  • packages/components/nodes/agentflow/Parallel/Parallel.ts — new structural component (no config inputs; branches come from canvas topology)
  • packages/server/src/utils/buildAgentflow.ts — branch discovery (reuses existing constructGraphs/getStartingNode), concurrent branch execution, and state merge
  • packages/ui/src/views/agentflowsv2/ParallelNode.jsx — new resizable canvas container node
  • packages/ui/src/views/agentflowsv2/Canvas.jsx, MarketplaceCanvas.jsx — node type registration, drop/containment logic generalized to support both Iteration and Parallel containers
  • packages/ui/src/store/constant.js — icon/color registration
  • packages/ui/src/views/chatmessage/AgentExecutedDataCard.jsx, packages/ui/src/views/agentexecutions/ExecutionDetails.jsx — execution log now groups parallel branch results ("Branch #N"), mirroring existing Iteration grouping

Test plan

  • flowise-components, flowise (server), and flowise-ui all build/type-check cleanly
  • Verified NodesPool auto-discovers parallelAgentflow alongside the existing node set
  • Verified the branch-discovery algorithm against a synthetic 2-branch graph
  • Manually tested end-to-end in the browser: Start → start_time (Custom Function) → Parallel [5sec / 7sec / 10sec Custom Functions] → end_time (Custom Function) → Direct Reply
    • Each branch sleeps N seconds then writes to its own Flow State key (v1/v2/v3)
    • Result: 1 2 3 (all three branch values survived), Elapsed time: 10036ms — i.e. total time ≈ the slowest branch (10s), not the sum (22s), confirming true concurrent execution
    • Execution log correctly groups results under Branch #1 / Branch #2 / Branch #3
    • Confirmed one branch erroring doesn't stop the others

Screenshots

Added by PR author after opening this PR — canvas view of the test flow, Flow State config, one branch's Custom Function config, and the execution log showing the Branch #1/#2/#3 grouping with the elapsed-time result.

image

Related issue: #4673

🤖 Generated with Claude Code

Adds a new "Parallel" container node alongside the existing Iteration
node. Branches inside the box are auto-detected from the canvas graph
(each child with no incoming edge from a sibling becomes a branch
root), and all branches execute concurrently via Promise.allSettled
instead of sequentially.

- New Parallel.ts agentflow component (auto-discovered like Iteration)
- buildAgentflow.ts: branch discovery (constructGraphs/getStartingNode
  reuse) + concurrent execution + per-branch state snapshot with a
  diff-based merge so one branch's untouched keys don't clobber
  another branch's update to the same key
- ParallelNode.jsx canvas container, registered in Canvas/Marketplace
  canvases, drop/containment logic generalized for both container types
- Branch-aware execution log grouping in AgentExecutedDataCard/
  ExecutionDetails ("Branch #N")

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>

@gemini-code-assist gemini-code-assist 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.

Code Review

This pull request introduces a new 'Parallel' agentflow node to support concurrent branch execution, along with the corresponding backend execution logic, state merging, and UI components for visualization and resizing. The review feedback highlights several critical improvements: ensuring robust support for nested containers by merging 'iterationContext' and preserving 'parentNodeId', refining parent-matching logic in the UI tree-building, retrieving partial execution data from failed branches, and simplifying the 'ParallelNode' component's resizing implementation using percentage-based CSS dimensions.

Important

The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.

Comment on lines +1540 to +1544
} else {
const errorMessage = getErrorMessage(settled.reason)
console.error(` ❌ Error in parallel branch ${branchIndex + 1}: ${errorMessage}`)
branchResults.push({ branchIndex, error: errorMessage })
}

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.

high

If a parallel branch fails, the promise is rejected, meaning none of the executed data from that branch is merged into the parent's execution data. Retrieve the partial execution data from the rejected promise's reason (Error object) to ensure failed branches are still correctly logged and visible in the UI.

                        } else {
                            const errorMessage = getErrorMessage(settled.reason)
                            console.error(`  ❌ Error in parallel branch ${branchIndex + 1}: ${errorMessage}`)
                            branchResults.push({ branchIndex, error: errorMessage })

                            const branchExecutedData = (settled.reason as any)?.agentFlowExecutedData
                            if (branchExecutedData) {
                                const mappedData = branchExecutedData.map((data: IAgentflowExecutedData) => ({
                                    ...data,
                                    data: {
                                        ...data.data,
                                        branchIndex,
                                        parentNodeId: reactFlowNode.data.id
                                    }
                                }))
                                agentFlowExecutedData.push(...mappedData)
                            }
                        }

Comment on lines +1510 to +1517
const branchExecutedData = subFlowResult.agentFlowExecutedData.map((data: IAgentflowExecutedData) => ({
...data,
data: {
...data.data,
branchIndex,
parentNodeId: reactFlowNode.data.id
}
}))

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.

high

Preserve the existing parentNodeId if it is already set to support nested containers correctly.

                                const branchExecutedData = subFlowResult.agentFlowExecutedData.map((data: IAgentflowExecutedData) => ({
                                    ...data,
                                    data: {
                                        ...data.data,
                                        branchIndex,
                                        parentNodeId: data.data.parentNodeId || reactFlowNode.data.id
                                    }
                                }))

Comment on lines +1485 to +1489
iterationContext: {
branchIndex: branch.branchIndex,
sessionId,
agentflowRuntime: branchAgentflowRuntime
},

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.

medium

Overwriting iterationContext completely breaks variable resolution (like $iteration) for nested parallel nodes inside iteration nodes. Merge the parent iterationContext instead of overwriting it.

                                iterationContext: {
                                    ...iterationContext,
                                    branchIndex: branch.branchIndex,
                                    sessionId,
                                    agentflowRuntime: branchAgentflowRuntime
                                },

Comment on lines +702 to +705
const sameGroup =
node.data?.iterationIndex !== undefined
? potentialParent.data?.iterationIndex === node.data?.iterationIndex
: potentialParent.data?.branchIndex === node.data?.branchIndex

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.

medium

The parent matching logic only checks iterationIndex if defined, which can incorrectly match a parent from a different parallel branch in nested container scenarios. Check both indices if they are defined to ensure robust matching.

                                const sameGroup = 
                                    (node.data?.iterationIndex === undefined || potentialParent.data?.iterationIndex === node.data?.iterationIndex) &&
                                    (node.data?.branchIndex === undefined || potentialParent.data?.branchIndex === node.data?.branchIndex)

Comment on lines +677 to +680
const sameGroup =
node.data?.iterationIndex !== undefined
? potentialParent.data?.iterationIndex === node.data?.iterationIndex
: potentialParent.data?.branchIndex === node.data?.branchIndex

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.

medium

Check both iterationIndex and branchIndex if defined to ensure robust parent matching in nested container scenarios.

                                const sameGroup = 
                                    (node.data?.iterationIndex === undefined || potentialParent.data?.iterationIndex === node.data?.iterationIndex) &&
                                    (node.data?.branchIndex === undefined || potentialParent.data?.branchIndex === node.data?.branchIndex)

)

return (
<div ref={ref} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>

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.

medium

To enable smooth real-time resizing and simplify the code, set the outer container and card styles to fill 100% of the React Flow wrapper div. This eliminates the need for manual dimension tracking state, effects, and callbacks.

Suggested change
<div ref={ref} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)}>
<div ref={ref} onMouseEnter={() => setIsHovered(true)} onMouseLeave={() => setIsHovered(false)} style={{ width: '100%', height: '100%' }}>

Comment on lines +273 to +277
minHeight: Math.max(getMinimumHeight(), 250),
minWidth: 300,
width: cardDimensions.width,
height: cardDimensions.height,
backgroundColor: getBackgroundColor(),

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.

medium

Set width and height to 100% to automatically fill the wrapper div managed by React Flow.

Suggested change
minHeight: Math.max(getMinimumHeight(), 250),
minWidth: 300,
width: cardDimensions.width,
height: cardDimensions.height,
backgroundColor: getBackgroundColor(),
minHeight: Math.max(getMinimumHeight(), 250),
minWidth: 300,
width: '100%',
height: '100%',
backgroundColor: getBackgroundColor(),

Comment on lines +347 to +351
<Box
sx={{
height: `calc(${cardDimensions.height} - 20px)`,
width: `${cardDimensions.width}`,
overflow: 'hidden',

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.

medium

Use percentage-based dimensions for the inner box to ensure smooth scaling during resizing.

Suggested change
<Box
sx={{
height: `calc(${cardDimensions.height} - 20px)`,
width: `${cardDimensions.width}`,
overflow: 'hidden',
<Box
sx={{
height: 'calc(100% - 20px)',
width: '100%',
overflow: 'hidden',

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.

2 participants