diff --git a/packages/@n8n/ai-workflow-builder.ee/evaluations/core/environment.ts b/packages/@n8n/ai-workflow-builder.ee/evaluations/core/environment.ts
index 0eb7e6582ad..309d2b6d360 100644
--- a/packages/@n8n/ai-workflow-builder.ee/evaluations/core/environment.ts
+++ b/packages/@n8n/ai-workflow-builder.ee/evaluations/core/environment.ts
@@ -88,7 +88,6 @@ export function createAgent(
llmSimpleTask: llm,
llmComplexTask: llm,
checkpointer: new MemorySaver(),
- enableMultiAgent: true,
tracer,
});
}
diff --git a/packages/@n8n/ai-workflow-builder.ee/src/agents/responder.agent.ts b/packages/@n8n/ai-workflow-builder.ee/src/agents/responder.agent.ts
index 5f3419bc193..6efd85d0aa1 100644
--- a/packages/@n8n/ai-workflow-builder.ee/src/agents/responder.agent.ts
+++ b/packages/@n8n/ai-workflow-builder.ee/src/agents/responder.agent.ts
@@ -77,6 +77,8 @@ export interface ResponderContext {
discoveryContext?: DiscoveryContext | null;
/** Current workflow state */
workflowJSON: SimpleWorkflow;
+ /** Summary of previous conversation (from compaction) */
+ previousSummary?: string;
}
/**
@@ -98,7 +100,20 @@ export class ResponderAgent {
private buildContextMessage(context: ResponderContext): HumanMessage | null {
const contextParts: string[] = [];
- // Check for errors first - if there's an error, surface it prominently
+ // Previous conversation summary (from compaction)
+ if (context.previousSummary) {
+ contextParts.push(`**Previous Conversation Summary:**\n${context.previousSummary}`);
+ }
+
+ // Check for state management actions (compact/clear)
+ const stateManagementEntry = context.coordinationLog.find(
+ (e) => e.phase === 'state_management',
+ );
+ if (stateManagementEntry) {
+ contextParts.push(`**State Management:** ${stateManagementEntry.summary}`);
+ }
+
+ // Check for errors - if there's an error, surface it prominently
const errorEntry = getErrorEntry(context.coordinationLog);
if (errorEntry) {
contextParts.push(
diff --git a/packages/@n8n/ai-workflow-builder.ee/src/agents/supervisor.agent.ts b/packages/@n8n/ai-workflow-builder.ee/src/agents/supervisor.agent.ts
index 626775767f7..448df76e89a 100644
--- a/packages/@n8n/ai-workflow-builder.ee/src/agents/supervisor.agent.ts
+++ b/packages/@n8n/ai-workflow-builder.ee/src/agents/supervisor.agent.ts
@@ -94,6 +94,8 @@ export interface SupervisorContext {
workflowJSON: SimpleWorkflow;
/** Coordination log tracking subgraph completion */
coordinationLog: CoordinationLogEntry[];
+ /** Summary of previous conversation (from compaction) */
+ previousSummary?: string;
}
/**
@@ -115,14 +117,21 @@ export class SupervisorAgent {
private buildContextMessage(context: SupervisorContext): HumanMessage | null {
const contextParts: string[] = [];
- // 1. Workflow summary (node count and names only)
+ // 1. Previous conversation summary (from compaction)
+ if (context.previousSummary) {
+ contextParts.push('');
+ contextParts.push(context.previousSummary);
+ contextParts.push('');
+ }
+
+ // 2. Workflow summary (node count and names only)
if (context.workflowJSON.nodes.length > 0) {
contextParts.push('');
contextParts.push(buildWorkflowSummary(context.workflowJSON));
contextParts.push('');
}
- // 2. Coordination log summary (what phases completed)
+ // 3. Coordination log summary (what phases completed)
if (context.coordinationLog.length > 0) {
contextParts.push('');
contextParts.push(summarizeCoordinationLog(context.coordinationLog));
diff --git a/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts b/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts
index f01970a721e..db80d6f58f4 100644
--- a/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts
+++ b/packages/@n8n/ai-workflow-builder.ee/src/ai-workflow-builder-agent.service.ts
@@ -12,7 +12,11 @@ import type { IUser, INodeTypeDescription, ITelemetryTrackProperties } from 'n8n
import { LLMServiceError } from '@/errors';
import { anthropicClaudeSonnet45 } from '@/llm-config';
import { SessionManagerService } from '@/session-manager.service';
-import { WorkflowBuilderAgent, type ChatPayload } from '@/workflow-builder-agent';
+import {
+ BuilderFeatureFlags,
+ WorkflowBuilderAgent,
+ type ChatPayload,
+} from '@/workflow-builder-agent';
type OnCreditsUpdated = (userId: string, creditsQuota: number, creditsClaimed: number) => void;
@@ -29,6 +33,7 @@ export class AiWorkflowBuilderService {
private readonly logger?: Logger,
private readonly instanceId?: string,
private readonly instanceUrl?: string,
+ private readonly n8nVersion?: string,
private readonly onCreditsUpdated?: OnCreditsUpdated,
private readonly onTelemetryEvent?: OnTelemetryEvent,
) {
@@ -153,7 +158,7 @@ export class AiWorkflowBuilderService {
});
}
- private async getAgent(user: IUser) {
+ private async getAgent(user: IUser, featureFlags?: BuilderFeatureFlags) {
const { anthropicClaude, tracingClient, authHeaders } = await this.setupModels(user);
const agent = new WorkflowBuilderAgent({
@@ -162,7 +167,6 @@ export class AiWorkflowBuilderService {
llmSimpleTask: anthropicClaude,
llmComplexTask: anthropicClaude,
logger: this.logger,
- enableMultiAgent: process.env.N8N_ENABLE_MULTI_AGENT === 'true',
checkpointer: this.sessionManager.getCheckpointer(),
tracer: tracingClient
? new LangChainTracer({ client: tracingClient, projectName: 'n8n-workflow-builder' })
@@ -171,6 +175,10 @@ export class AiWorkflowBuilderService {
onGenerationSuccess: async () => {
await this.onGenerationSuccess(user, authHeaders);
},
+ runMetadata: {
+ n8nVersion: this.n8nVersion,
+ featureFlags: featureFlags ?? {},
+ },
});
return agent;
@@ -200,7 +208,7 @@ export class AiWorkflowBuilderService {
}
async *chat(payload: ChatPayload, user: IUser, abortSignal?: AbortSignal) {
- const agent = await this.getAgent(user);
+ const agent = await this.getAgent(user, payload.featureFlags);
const userId = user?.id?.toString();
const workflowId = payload.workflowContext?.currentWorkflow?.id;
diff --git a/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/examples/basic/switch-node-examples.ts b/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/examples/basic/switch-node-examples.ts
new file mode 100644
index 00000000000..c6fcb8d7fb9
--- /dev/null
+++ b/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/examples/basic/switch-node-examples.ts
@@ -0,0 +1,124 @@
+export const SWITCH_NODE_EXAMPLES = `
+### Switch Node Examples
+
+#### Example 1: Route by Amount Tiers (Purchase Approval)
+Current Parameters: { "mode": "rules" }
+Requested Changes: Route under $100 to auto-approve, $100-$1000 to manager, over $1000 to finance
+
+Expected Output:
+{
+ "mode": "rules",
+ "rules": {
+ "values": [
+ {
+ "conditions": {
+ "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
+ "conditions": [
+ {
+ "leftValue": "={{ $json.amount }}",
+ "rightValue": 100,
+ "operator": { "type": "number", "operation": "lt" }
+ }
+ ],
+ "combinator": "and"
+ },
+ "renameOutput": true,
+ "outputKey": "Auto-Approve"
+ },
+ {
+ "conditions": {
+ "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
+ "conditions": [
+ {
+ "leftValue": "={{ $json.amount }}",
+ "rightValue": 100,
+ "operator": { "type": "number", "operation": "gte" }
+ },
+ {
+ "leftValue": "={{ $json.amount }}",
+ "rightValue": 1000,
+ "operator": { "type": "number", "operation": "lte" }
+ }
+ ],
+ "combinator": "and"
+ },
+ "renameOutput": true,
+ "outputKey": "Manager Review"
+ },
+ {
+ "conditions": {
+ "options": { "caseSensitive": true, "leftValue": "", "typeValidation": "strict" },
+ "conditions": [
+ {
+ "leftValue": "={{ $json.amount }}",
+ "rightValue": 1000,
+ "operator": { "type": "number", "operation": "gt" }
+ }
+ ],
+ "combinator": "and"
+ },
+ "renameOutput": true,
+ "outputKey": "Finance Review"
+ }
+ ]
+ }
+}
+
+#### Example 2: Route by Status String
+Current Parameters: { "mode": "rules" }
+Requested Changes: Route by order status - pending, processing, completed
+
+Expected Output:
+{
+ "mode": "rules",
+ "rules": {
+ "values": [
+ {
+ "conditions": {
+ "options": { "caseSensitive": false, "leftValue": "", "typeValidation": "loose" },
+ "conditions": [
+ {
+ "leftValue": "={{ $json.status }}",
+ "rightValue": "pending",
+ "operator": { "type": "string", "operation": "equals" }
+ }
+ ],
+ "combinator": "and"
+ },
+ "renameOutput": true,
+ "outputKey": "Pending"
+ },
+ {
+ "conditions": {
+ "options": { "caseSensitive": false, "leftValue": "", "typeValidation": "loose" },
+ "conditions": [
+ {
+ "leftValue": "={{ $json.status }}",
+ "rightValue": "processing",
+ "operator": { "type": "string", "operation": "equals" }
+ }
+ ],
+ "combinator": "and"
+ },
+ "renameOutput": true,
+ "outputKey": "Processing"
+ },
+ {
+ "conditions": {
+ "options": { "caseSensitive": false, "leftValue": "", "typeValidation": "loose" },
+ "conditions": [
+ {
+ "leftValue": "={{ $json.status }}",
+ "rightValue": "completed",
+ "operator": { "type": "string", "operation": "equals" }
+ }
+ ],
+ "combinator": "and"
+ },
+ "renameOutput": true,
+ "outputKey": "Completed"
+ }
+ ]
+ }
+}
+`;
diff --git a/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/node-types/switch-node.ts b/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/node-types/switch-node.ts
new file mode 100644
index 00000000000..f7caeae7772
--- /dev/null
+++ b/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/node-types/switch-node.ts
@@ -0,0 +1,68 @@
+export const SWITCH_NODE_GUIDE = `
+### Switch Node Configuration Guide
+
+The Switch node routes items to different outputs based on conditions. Uses the same filter structure as IF node but for multi-way branching.
+
+#### Switch Node Structure (mode: 'rules')
+\`\`\`json
+{
+ "mode": "rules",
+ "rules": {
+ "values": [
+ {
+ "conditions": {
+ "options": {
+ "caseSensitive": true,
+ "leftValue": "",
+ "typeValidation": "strict"
+ },
+ "conditions": [
+ {
+ "leftValue": "={{ $json.amount }}",
+ "rightValue": 100,
+ "operator": {
+ "type": "number",
+ "operation": "lt"
+ }
+ }
+ ],
+ "combinator": "and"
+ },
+ "renameOutput": true,
+ "outputKey": "Under $100"
+ }
+ ]
+ }
+}
+\`\`\`
+
+#### Key Points:
+1. Each entry in rules.values[] creates ONE output
+2. Conditions use the same filter structure as IF node
+3. Multiple conditions per rule are combined with combinator ("and" or "or")
+4. Use renameOutput: true + outputKey to label outputs descriptively
+
+#### Numeric Operators
+- lt: Less than
+- gt: Greater than
+- lte: Less than or equal
+- gte: Greater than or equal
+- equals: Equal to
+
+#### String Operators
+- equals: Exact match
+- contains: Contains substring
+- startsWith: Starts with
+- endsWith: Ends with
+
+#### Common Patterns:
+
+**Numeric Range Routing** (for ranges like $100-$1000):
+Use two conditions with combinator: "and":
+- First condition: gte (greater than or equal to lower bound)
+- Second condition: lte (less than or equal to upper bound)
+
+**String-Based Routing** (status/type values):
+- Use type: "string" with operation: "equals"
+- Set caseSensitive: false in options for case-insensitive matching
+`;
diff --git a/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/prompt-builder.ts b/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/prompt-builder.ts
index 665e13f1b41..70f7f3c249b 100644
--- a/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/prompt-builder.ts
+++ b/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/prompt-builder.ts
@@ -9,9 +9,11 @@ import { TOOL_NODE_EXAMPLES } from './examples/advanced/tool-node-examples';
import { IF_NODE_EXAMPLES } from './examples/basic/if-node-examples';
import { SET_NODE_EXAMPLES } from './examples/basic/set-node-examples';
import { SIMPLE_UPDATE_EXAMPLES } from './examples/basic/simple-updates';
+import { SWITCH_NODE_EXAMPLES } from './examples/basic/switch-node-examples';
import { HTTP_REQUEST_GUIDE } from './node-types/http-request';
import { IF_NODE_GUIDE } from './node-types/if-node';
import { SET_NODE_GUIDE } from './node-types/set-node';
+import { SWITCH_NODE_GUIDE } from './node-types/switch-node';
import { TOOL_NODES_GUIDE } from './node-types/tool-nodes';
import { RESOURCE_LOCATOR_GUIDE } from './parameter-types/resource-locator';
import { SYSTEM_MESSAGE_GUIDE } from './parameter-types/system-message';
@@ -42,6 +44,8 @@ export class ParameterUpdatePromptBuilder {
sections.push(SET_NODE_GUIDE);
} else if (this.isIfNode(context.nodeType)) {
sections.push(IF_NODE_GUIDE);
+ } else if (this.isSwitchNode(context.nodeType)) {
+ sections.push(SWITCH_NODE_GUIDE);
} else if (this.isHttpRequestNode(context.nodeType)) {
sections.push(HTTP_REQUEST_GUIDE);
}
@@ -130,6 +134,14 @@ export class ParameterUpdatePromptBuilder {
return category === 'if';
}
+ /**
+ * Checks if node is a Switch node
+ */
+ private static isSwitchNode(nodeType: string): boolean {
+ const category = getNodeTypeCategory(nodeType);
+ return category === 'switch';
+ }
+
/**
* Checks if node is an HTTP Request node
*/
@@ -179,6 +191,8 @@ export class ParameterUpdatePromptBuilder {
examples.push(SET_NODE_EXAMPLES);
} else if (this.isIfNode(context.nodeType)) {
examples.push(IF_NODE_EXAMPLES);
+ } else if (this.isSwitchNode(context.nodeType)) {
+ examples.push(SWITCH_NODE_EXAMPLES);
}
// Add resource locator examples if needed
if (context.hasResourceLocatorParams) {
diff --git a/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/prompt-config.ts b/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/prompt-config.ts
index 39e0067b22b..31a3976215b 100644
--- a/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/prompt-config.ts
+++ b/packages/@n8n/ai-workflow-builder.ee/src/chains/prompts/prompt-config.ts
@@ -4,6 +4,7 @@ export const DEFAULT_PROMPT_CONFIG: NodePromptConfig = {
nodeTypePatterns: {
set: ['n8n-nodes-base.set', 'set'],
if: ['n8n-nodes-base.if', 'if', 'filter'],
+ switch: ['n8n-nodes-base.switch', 'switch'],
httpRequest: ['n8n-nodes-base.httpRequest', 'httprequest', 'webhook', 'n8n-nodes-base.webhook'],
tool: ['Tool', '.tool'],
},
diff --git a/packages/@n8n/ai-workflow-builder.ee/src/multi-agent-workflow-subgraphs.ts b/packages/@n8n/ai-workflow-builder.ee/src/multi-agent-workflow-subgraphs.ts
index fbeb617aaa0..189e629a73d 100644
--- a/packages/@n8n/ai-workflow-builder.ee/src/multi-agent-workflow-subgraphs.ts
+++ b/packages/@n8n/ai-workflow-builder.ee/src/multi-agent-workflow-subgraphs.ts
@@ -7,6 +7,7 @@ import type { INodeTypeDescription } from 'n8n-workflow';
import { ResponderAgent } from './agents/responder.agent';
import { SupervisorAgent } from './agents/supervisor.agent';
import {
+ DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS,
MAX_BUILDER_ITERATIONS,
MAX_CONFIGURATOR_ITERATIONS,
MAX_DISCOVERY_ITERATIONS,
@@ -20,6 +21,13 @@ import type { SubgraphPhase } from './types/coordination';
import { createErrorMetadata } from './types/coordination';
import { getNextPhaseFromLog } from './utils/coordination-log';
import { processOperations } from './utils/operations-processor';
+import {
+ determineStateAction,
+ handleCleanupDangling,
+ handleCompactMessages,
+ handleCreateWorkflowName,
+ handleDeleteMessages,
+} from './utils/state-modifier';
import type { BuilderFeatureFlags } from './workflow-builder-agent';
/**
@@ -43,6 +51,8 @@ export interface MultiAgentSubgraphConfig {
logger?: Logger;
instanceUrl?: string;
checkpointer?: MemorySaver;
+ /** Token threshold for auto-compaction. Defaults to DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS */
+ autoCompactThresholdTokens?: number;
featureFlags?: BuilderFeatureFlags;
}
@@ -107,8 +117,15 @@ function createSubgraphNodeHandler<
* Parent graph orchestrates between subgraphs with minimal shared state.
*/
export function createMultiAgentWorkflowWithSubgraphs(config: MultiAgentSubgraphConfig) {
- const { parsedNodeTypes, llmComplexTask, logger, instanceUrl, checkpointer, featureFlags } =
- config;
+ const {
+ parsedNodeTypes,
+ llmComplexTask,
+ logger,
+ instanceUrl,
+ checkpointer,
+ autoCompactThresholdTokens = DEFAULT_AUTO_COMPACT_THRESHOLD_TOKENS,
+ featureFlags,
+ } = config;
const supervisorAgent = new SupervisorAgent({ llm: llmComplexTask });
const responderAgent = new ResponderAgent({ llm: llmComplexTask });
@@ -142,6 +159,7 @@ export function createMultiAgentWorkflowWithSubgraphs(config: MultiAgentSubgraph
messages: state.messages,
workflowJSON: state.workflowJSON,
coordinationLog: state.coordinationLog,
+ previousSummary: state.previousSummary,
});
return {
@@ -155,6 +173,7 @@ export function createMultiAgentWorkflowWithSubgraphs(config: MultiAgentSubgraph
coordinationLog: state.coordinationLog,
discoveryContext: state.discoveryContext,
workflowJSON: state.workflowJSON,
+ previousSummary: state.previousSummary,
});
return {
@@ -171,6 +190,31 @@ export function createMultiAgentWorkflowWithSubgraphs(config: MultiAgentSubgraph
workflowOperations: [], // Clear operations after processing
};
})
+ // State modification nodes (preprocessing)
+ .addNode('check_state', (state) => ({
+ nextPhase: determineStateAction(state, autoCompactThresholdTokens),
+ }))
+ .addNode('cleanup_dangling', (state) => handleCleanupDangling(state.messages, logger))
+ .addNode('compact_messages', async (state) => {
+ const isAutoCompact = state.messages[state.messages.length - 1]?.content !== '/compact';
+ return await handleCompactMessages(
+ state.messages,
+ state.previousSummary ?? '',
+ llmComplexTask,
+ isAutoCompact,
+ );
+ })
+ .addNode('delete_messages', (state) => handleDeleteMessages(state.messages))
+ .addNode(
+ 'create_workflow_name',
+ async (state) =>
+ await handleCreateWorkflowName(
+ state.messages,
+ state.workflowJSON,
+ llmComplexTask,
+ logger,
+ ),
+ )
// Add Subgraph Nodes (using helper to reduce duplication)
.addNode(
'discovery_subgraph',
@@ -206,8 +250,31 @@ export function createMultiAgentWorkflowWithSubgraphs(config: MultiAgentSubgraph
.addEdge('discovery_subgraph', 'process_operations')
.addEdge('builder_subgraph', 'process_operations')
.addEdge('configurator_subgraph', 'process_operations')
- // Start flows to supervisor (initial routing only)
- .addEdge(START, 'supervisor')
+ // Start flows to check_state (preprocessing)
+ .addEdge(START, 'check_state')
+ // Conditional routing from check_state
+ .addConditionalEdges('check_state', (state) => {
+ const routes: Record = {
+ cleanup_dangling: 'cleanup_dangling',
+ compact_messages: 'compact_messages',
+ delete_messages: 'delete_messages',
+ create_workflow_name: 'create_workflow_name',
+ auto_compact_messages: 'compact_messages', // Reuse same node
+ continue: 'supervisor',
+ };
+ return routes[state.nextPhase] ?? 'supervisor';
+ })
+ // Route after state modification nodes
+ .addEdge('cleanup_dangling', 'check_state') // Re-check after cleanup
+ .addEdge('delete_messages', 'responder') // Clear → responder for acknowledgment
+ .addEdge('create_workflow_name', 'supervisor') // Continue after naming
+ // Compact has conditional routing: auto → continue, manual → responder
+ .addConditionalEdges('compact_messages', (state) => {
+ // Auto-compact preserves the last user message, manual /compact clears all
+ // If messages remain after compaction, it's auto-compact → continue processing
+ const hasMessages = state.messages.length > 0;
+ return hasMessages ? 'check_state' : 'responder';
+ })
// Conditional Edge for Supervisor (initial routing via LLM)
.addConditionalEdges('supervisor', (state) => routeToNode(state.nextPhase))
// Deterministic routing after subgraphs complete (based on coordination log)
diff --git a/packages/@n8n/ai-workflow-builder.ee/src/parent-graph-state.ts b/packages/@n8n/ai-workflow-builder.ee/src/parent-graph-state.ts
index 33741b88dca..053a9717943 100644
--- a/packages/@n8n/ai-workflow-builder.ee/src/parent-graph-state.ts
+++ b/packages/@n8n/ai-workflow-builder.ee/src/parent-graph-state.ts
@@ -1,5 +1,5 @@
import type { BaseMessage } from '@langchain/core/messages';
-import { Annotation } from '@langchain/langgraph';
+import { Annotation, messagesStateReducer } from '@langchain/langgraph';
import type { CoordinationLogEntry } from './types/coordination';
import type { DiscoveryContext } from './types/discovery-types';
@@ -17,7 +17,7 @@ import type { ChatPayload } from './workflow-builder-agent';
export const ParentGraphState = Annotation.Root({
// Shared: User's conversation history (for responder)
messages: Annotation({
- reducer: (x, y) => x.concat(y),
+ reducer: messagesStateReducer,
default: () => [],
}),
@@ -56,6 +56,12 @@ export const ParentGraphState = Annotation.Root({
default: () => [],
}),
+ // For conversation compaction - stores summarized history
+ previousSummary: Annotation({
+ reducer: (x, y) => y ?? x,
+ default: () => '',
+ }),
+
// Template IDs fetched from workflow examples for telemetry
templateIds: Annotation({
reducer: appendArrayReducer,
diff --git a/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/builder.subgraph.ts b/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/builder.subgraph.ts
index aee4cee8973..91aef3faa13 100644
--- a/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/builder.subgraph.ts
+++ b/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/builder.subgraph.ts
@@ -55,6 +55,7 @@ STEP 3: VALIDATE (REQUIRED)
- After ALL nodes and connections are created, call validate_structure
- This step is MANDATORY - you cannot finish without it
- If validation finds issues (missing trigger, invalid connections), fix them and validate again
+- MAXIMUM 3 VALIDATION ATTEMPTS: After 3 calls to validate_structure, proceed to respond regardless of remaining issues
STEP 4: RESPOND TO USER
- Only after validation passes, provide your brief summary
@@ -83,6 +84,12 @@ Placement rules:
For AI-generated structured data, prefer Structured Output Parser nodes over Code nodes.
For binary file data, use Extract From File node to extract content from files before processing.
Use Code nodes only for custom business logic beyond parsing.
+
+STRUCTURED OUTPUT PARSER RULE:
+When Discovery results include Structured Output Parser:
+1. Create the Structured Output Parser node
+2. Set AI Agent's hasOutputParser: true in connectionParameters
+3. Connect: Structured Output Parser → AI Agent (ai_outputParser connection)
@@ -108,9 +115,19 @@ ALWAYS check node details and set connectionParameters explicitly.
CONNECTION PARAMETERS EXAMPLES:
- Static nodes (HTTP Request, Set, Code): reasoning="Static inputs/outputs", parameters={{}}
-- AI Agent with parser: reasoning="hasOutputParser creates additional input", parameters={{ hasOutputParser: true }}
+- AI Agent with structured output: reasoning="hasOutputParser enables ai_outputParser input for Structured Output Parser", parameters={{ hasOutputParser: true }}
- Vector Store insert: reasoning="Insert mode requires document input", parameters={{ mode: "insert" }}
- Document Loader custom: reasoning="Custom mode enables text splitter input", parameters={{ textSplittingMode: "custom" }}
+- Switch with routing rules: reasoning="Switch needs N outputs, creating N rules.values entries with outputKeys", parameters={{ mode: "rules", rules: {{ values: [...] }} }} - see for full structure
+
+
+WHEN TO SET hasOutputParser: true on AI Agent:
+- Discovery found Structured Output Parser node → MUST set hasOutputParser: true
+- AI output will be used in conditions (IF/Switch nodes checking $json.field)
+- AI output will be formatted/displayed (HTML emails, reports with specific sections)
+- AI output will be stored in database/data tables with specific fields
+- AI is classifying, scoring, or extracting specific data fields
+
n8n connections flow from SOURCE (output) to TARGET (input).
@@ -158,6 +175,51 @@ Common mistake to avoid:
- Document Loader is an AI sub-node that gives Vector Store document processing capability
+
+For Switch nodes with multiple routing paths:
+- The number of outputs is determined by the number of entries in rules.values[]
+- You MUST create the rules.values[] array with placeholder entries for each output branch
+- Each entry needs: conditions structure (with empty leftValue/rightValue) + renameOutput: true + descriptive outputKey
+- Configurator will fill in the actual condition values later
+- Use descriptive node names like "Route by Amount" or "Route by Status"
+
+Example connectionParameters for 3-way routing:
+{{
+ "mode": "rules",
+ "rules": {{
+ "values": [
+ {{
+ "conditions": {{
+ "options": {{ "caseSensitive": true, "leftValue": "", "typeValidation": "strict" }},
+ "conditions": [{{ "leftValue": "", "rightValue": "", "operator": {{ "type": "string", "operation": "equals" }} }}],
+ "combinator": "and"
+ }},
+ "renameOutput": true,
+ "outputKey": "Output 1 Name"
+ }},
+ {{
+ "conditions": {{
+ "options": {{ "caseSensitive": true, "leftValue": "", "typeValidation": "strict" }},
+ "conditions": [{{ "leftValue": "", "rightValue": "", "operator": {{ "type": "string", "operation": "equals" }} }}],
+ "combinator": "and"
+ }},
+ "renameOutput": true,
+ "outputKey": "Output 2 Name"
+ }},
+ {{
+ "conditions": {{
+ "options": {{ "caseSensitive": true, "leftValue": "", "typeValidation": "strict" }},
+ "conditions": [{{ "leftValue": "", "rightValue": "", "operator": {{ "type": "string", "operation": "equals" }} }}],
+ "combinator": "and"
+ }},
+ "renameOutput": true,
+ "outputKey": "Output 3 Name"
+ }}
+ ]
+ }}
+}}
+
+
**Main Connections** (regular data flow):
- Trigger → HTTP Request → Set → Email
diff --git a/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/configurator.subgraph.ts b/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/configurator.subgraph.ts
index 8c74cd8db86..3d9c83a562d 100644
--- a/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/configurator.subgraph.ts
+++ b/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/configurator.subgraph.ts
@@ -50,6 +50,7 @@ STEP 2: VALIDATE (REQUIRED)
- After ALL configurations complete, call validate_configuration
- This step is MANDATORY - you cannot finish without it
- If validation finds issues, fix them and validate again
+- MAXIMUM 3 VALIDATION ATTEMPTS: After 3 calls to validate_configuration, proceed to respond regardless of remaining issues
STEP 3: RESPOND TO USER
- Only after validation passes, provide your response
@@ -85,6 +86,7 @@ CRITICAL PARAMETERS TO ALWAYS SET:
- Set node: Fields to set with values
- Code node: The actual code to execute
- IF node: Conditions to check
+- Switch node: Configure rules.values[] with conditions for each output branch (uses same filter structure as IF node)
- Document Loader: dataType parameter ('binary' for files like PDF, 'json' for JSON data)
- AI nodes: Prompts, models, configurations
- Tool nodes: Use $fromAI for dynamic recipient/subject/message fields
@@ -95,6 +97,34 @@ Defaults are traps that cause runtime failures. Examples:
- HTTP Request defaults to GET but APIs often need POST
- Vector Store mode affects available connections - set explicitly (retrieve-as-tool when using with AI Agent)
+
+Switch nodes require configuring rules.values[] array - each entry creates one output:
+
+Structure per rule:
+{{
+ "conditions": {{
+ "options": {{ "caseSensitive": true, "leftValue": "", "typeValidation": "strict" }},
+ "conditions": [
+ {{
+ "leftValue": "={{{{ $json.fieldName }}}}",
+ "rightValue": ,
+ "operator": {{ "type": "number|string", "operation": "lt|gt|equals|etc" }}
+ }}
+ ],
+ "combinator": "and"
+ }},
+ "renameOutput": true,
+ "outputKey": "Descriptive Label"
+}}
+
+For numeric ranges (e.g., $100-$1000):
+- Use TWO conditions with combinator: "and"
+- First: gte (greater than or equal)
+- Second: lte (less than or equal)
+
+Always set renameOutput: true and provide descriptive outputKey labels.
+
+
After validation passes, provide a concise summary:
- List any placeholders requiring user configuration (e.g., "URL placeholder needs actual endpoint")
diff --git a/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/discovery.subgraph.ts b/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/discovery.subgraph.ts
index 60148969262..d0d9534011d 100644
--- a/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/discovery.subgraph.ts
+++ b/packages/@n8n/ai-workflow-builder.ee/src/subgraphs/discovery.subgraph.ts
@@ -325,11 +325,38 @@ A parameter is connection-changing ONLY IF it appears in or