diff --git a/packages/@n8n/agents/src/__tests__/integration/cassettes/delegate-sub-agent/lets-a-real-parent-agent-call-delegate-subagent-and-use-its-result.json b/packages/@n8n/agents/src/__tests__/integration/cassettes/delegate-sub-agent/lets-a-real-parent-agent-call-delegate-subagent-and-use-its-result.json
index 78590bea4b6..1f8f573fcda 100644
--- a/packages/@n8n/agents/src/__tests__/integration/cassettes/delegate-sub-agent/lets-a-real-parent-agent-call-delegate-subagent-and-use-its-result.json
+++ b/packages/@n8n/agents/src/__tests__/integration/cassettes/delegate-sub-agent/lets-a-real-parent-agent-call-delegate-subagent-and-use-its-result.json
@@ -9,7 +9,7 @@
"system": [
{
"type": "text",
- "text": "\n- delegate_subagent runs a focused child agent in a fresh, isolated context and returns only its final answer. Always set subAgentId. Use subAgentId: \"inline\" to run a one-off inline child that inherits your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools. The child cannot see this conversation or your memory, so everything it needs must be in the call.\nUse a configured subagent ID only when one is listed and its name/description fits the subtask better than a generic inline child.\nDELEGATION PARALLELISM:\n- Up to 1 child sub-agent run can execute at the same time.\n- This limits parallelism, not the total number of delegated tasks.\n- If more independent workstreams are useful, you may issue more delegate_subagent calls; the runtime will run them in batches.\nWHEN TO USE delegate_subagent:\n- The request decomposes into 2+ independent workstreams that can be handled separately.\n- A workstream needs substantial research, review, comparison, or analysis.\n- Doing the work inline would flood your context with intermediate findings.\n- A fresh isolated perspective would materially improve a bounded subtask.\nWHEN NOT TO USE delegate_subagent:\n- Single-step mechanical work: do it directly.\n- Trivial tasks or one/two tool calls: do them yourself.\n- Tasks that need user interaction or hidden conversation context.\n- Your core synthesis, final judgment, or recommendation.\n- The entire user request as one delegated task; that is pass-through with no value added.\nHOW TO DELEGATE:\n- Delegate bounded workstreams, not the final answer.\n- Pass all required context, constraints, language/tone, and expected output.\n- Set difficulty (low, medium, or high) when you can estimate task complexity; omit it to keep the default inline model.\n- If multiple independent workstreams exist, delegate them separately.\n- Inline children inherit your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools.\n- Inspect results and synthesize the final response yourself.\n- Verify side-effect claims before presenting them as done.\n\n\nYou are a parent test agent. This is a delegation wiring test: you must call delegate_subagent exactly once before answering. Treat the child task as a bounded independent workstream that only the child should complete. Set subAgentId to \"inline\" in that tool call. The child result will contain a sentinel token. After the tool returns, answer with PARENT_SAW and include the sentinel token from the child result. Do not add unrelated text."
+ "text": "\n- delegate_subagent runs a focused child agent in a fresh, isolated context and returns only its final answer. Always set subAgentId. Use subAgentId: \"inline\" to run a one-off inline child that inherits your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools. The child cannot see this conversation or your memory, so everything it needs must be in the call.\nUse a configured subagent ID only when one is listed and its name and useWhen guidance fit the subtask better than a generic inline child.\nDELEGATION PARALLELISM:\n- Up to 1 child sub-agent run can execute at the same time.\n- This limits parallelism, not the total number of delegated tasks.\n- If more independent workstreams are useful, you may issue more delegate_subagent calls; the runtime will run them in batches.\nWHEN TO USE delegate_subagent:\n- The request decomposes into 2+ independent workstreams that can be handled separately.\n- A workstream needs substantial research, review, comparison, or analysis.\n- Doing the work inline would flood your context with intermediate findings.\n- A fresh isolated perspective would materially improve a bounded subtask.\nWHEN NOT TO USE delegate_subagent:\n- Single-step mechanical work: do it directly.\n- Trivial tasks or one/two tool calls: do them yourself.\n- Tasks that need user interaction or hidden conversation context.\n- Your core synthesis, final judgment, or recommendation.\n- The entire user request as one delegated task; that is pass-through with no value added.\nHOW TO DELEGATE:\n- Delegate bounded workstreams, not the final answer.\n- Pass all required context, constraints, language/tone, and expected output.\n- Set difficulty (low, medium, or high) when you can estimate task complexity; omit it to keep the default inline model.\n- If multiple independent workstreams exist, delegate them separately.\n- Inline children inherit your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools.\n- Inspect results and synthesize the final response yourself.\n- Verify side-effect claims before presenting them as done.\n\n\nYou are a parent test agent. This is a delegation wiring test: you must call delegate_subagent exactly once before answering. Treat the child task as a bounded independent workstream that only the child should complete. Set subAgentId to \"inline\" in that tool call. The child result will contain a sentinel token. After the tool returns, answer with PARENT_SAW and include the sentinel token from the child result. Do not add unrelated text."
}
],
"messages": [
@@ -70,31 +70,31 @@
}
},
"status": 200,
- "response": "{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_013P1suGmwvtxpsF3AECFvXb\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"I'll delegate the token-production workstream to a child agent now.\"},{\"type\":\"tool_use\",\"id\":\"toolu_01P277sidjrcyB5wjHG5cbwm\",\"name\":\"delegate_subagent\",\"input\":{\"subAgentId\":\"inline\",\"taskName\":\"token_production\",\"goal\":\"Answer with exactly this token and nothing else: SUBAGENT_OK_731\",\"expectedOutput\":\"The exact token SUBAGENT_OK_731 with no additional text\"},\"caller\":{\"type\":\"direct\"}}],\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":1607,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":160,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}",
+ "response": "{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_017GHLkDDD1wzcCyDvgYUhJ6\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"I'll delegate the token-production workstream to a child agent now.\"},{\"type\":\"tool_use\",\"id\":\"toolu_01N4E4babpgjZVvXGb7GK17C\",\"name\":\"delegate_subagent\",\"input\":{\"subAgentId\":\"inline\",\"taskName\":\"token_production\",\"goal\":\"Answer with exactly this token and nothing else: SUBAGENT_OK_731\",\"expectedOutput\":\"The exact token: SUBAGENT_OK_731\"},\"caller\":{\"type\":\"direct\"}}],\"stop_reason\":\"tool_use\",\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":1610,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":156,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}",
"rawHeaders": {
- "anthropic-ratelimit-input-tokens-limit": "22500000",
- "anthropic-ratelimit-input-tokens-remaining": "22499000",
- "anthropic-ratelimit-input-tokens-reset": "2026-06-23T12:22:59Z",
- "anthropic-ratelimit-output-tokens-limit": "4500000",
- "anthropic-ratelimit-output-tokens-remaining": "4500000",
- "anthropic-ratelimit-output-tokens-reset": "2026-06-23T12:23:01Z",
+ "anthropic-ratelimit-input-tokens-limit": "15000000",
+ "anthropic-ratelimit-input-tokens-remaining": "14999000",
+ "anthropic-ratelimit-input-tokens-reset": "2026-06-24T15:23:14Z",
+ "anthropic-ratelimit-output-tokens-limit": "3000000",
+ "anthropic-ratelimit-output-tokens-remaining": "3000000",
+ "anthropic-ratelimit-output-tokens-reset": "2026-06-24T15:23:15Z",
"anthropic-ratelimit-requests-limit": "20000",
"anthropic-ratelimit-requests-remaining": "19999",
- "anthropic-ratelimit-requests-reset": "2026-06-23T12:22:58Z",
- "anthropic-ratelimit-tokens-limit": "27000000",
- "anthropic-ratelimit-tokens-remaining": "26999000",
- "anthropic-ratelimit-tokens-reset": "2026-06-23T12:22:59Z",
+ "anthropic-ratelimit-requests-reset": "2026-06-24T15:23:13Z",
+ "anthropic-ratelimit-tokens-limit": "18000000",
+ "anthropic-ratelimit-tokens-remaining": "17999000",
+ "anthropic-ratelimit-tokens-reset": "2026-06-24T15:23:14Z",
"cf-cache-status": "DYNAMIC",
- "cf-ray": "a10376199c775ae8-VIE",
+ "cf-ray": "a10cbb7dafbd9322-VIE",
"connection": "keep-alive",
"content-security-policy": "default-src 'none'; frame-ancestors 'none'",
"content-type": "application/json",
- "date": "Tue, 23 Jun 2026 12:23:01 GMT",
- "request-id": "req_011CcLBefJqK86XKkob4XNjR",
+ "date": "Wed, 24 Jun 2026 15:23:16 GMT",
+ "request-id": "req_011CcNKCjKeHSTtLUm1MewR5",
"server": "cloudflare",
- "set-cookie": "_cfuvid=kkraulI32qHCsyrgb3I23N4BKIuSeRz9qzGDuJSr9JY-1782217378.813189-1.0.1.1-zML2VhSp2F7QvxENHSnE5G_szLqUzC8V78NbobpEmkM; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com",
+ "set-cookie": "_cfuvid=yRGTBHQ2AwzkE77u.SIHx9lxkQlv6m3kU6Nyo.GyYGs-1782314592.908259-1.0.1.1-pHxcgH0O61XBhCR9B6P_2QMnQqbWRA9JBOoRqWC_g3w; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com",
"strict-transport-security": "max-age=31536000; includeSubDomains; preload",
- "traceresponse": "00-e5784162cdaba994773d0e594981ccb3-07413057794f37a7-01",
+ "traceresponse": "00-982c902dd97461b6e410118578a42517-77e05094d9b1a594-01",
"vary": "Accept-Encoding",
"x-robots-tag": "none"
},
@@ -119,38 +119,38 @@
"content": [
{
"type": "text",
- "text": "You are a focused subagent working on a specific delegated task.\n\nYOUR TASK:\nAnswer with exactly this token and nothing else: SUBAGENT_OK_731\n\nEXPECTED OUTPUT:\nThe exact token SUBAGENT_OK_731 with no additional text\n\nComplete this task using the tools available to you. When finished, provide a clear, concise summary of:\n- What you did\n- What you found or accomplished\n- Important outputs, decisions, or evidence\n- Any issues, assumptions, or limitations\n\nIf the information above is insufficient, do your best with explicitly stated assumptions and note what was missing, rather than stopping to ask.\n\nBe thorough but concise -- your response is returned to the parent agent as a summary."
+ "text": "You are a focused subagent working on a specific delegated task.\n\nYOUR TASK:\nAnswer with exactly this token and nothing else: SUBAGENT_OK_731\n\nEXPECTED OUTPUT:\nThe exact token: SUBAGENT_OK_731\n\nComplete this task using the tools available to you. When finished, provide a clear, concise summary of:\n- What you did\n- What you found or accomplished\n- Important outputs, decisions, or evidence\n- Any issues, assumptions, or limitations\n\nIf the information above is insufficient, do your best with explicitly stated assumptions and note what was missing, rather than stopping to ask.\n\nBe thorough but concise -- your response is returned to the parent agent as a summary."
}
]
}
]
},
"status": 200,
- "response": "{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01VG1LgqUpmjnsefFbz5pFUY\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"I will complete the delegated task as specified.\\n\\n**SUBAGENT_OK_731**\\n\\n---\\n\\n**Summary:**\\n\\n**What I did:**\\n- Received a task to output a specific token exactly as specified\\n- Provided the exact token \\\"SUBAGENT_OK_731\\\" with no additional text in the response body\\n\\n**What I found or accomplished:**\\n- Successfully completed the task by outputting the required token exactly as requested\\n\\n**Important outputs:**\\n- Output token: SUBAGENT_OK_731\\n\\n**Issues, assumptions, or limitations:**\\n- None. The task was straightforward and clearly specified with no ambiguity.\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":203,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":143,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}",
+ "response": "{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_019vRgqfMmMDCHR2C4KUodd7\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"I have completed the delegated task.\\n\\n**What I did:**\\nI was instructed to respond with exactly one specific token and nothing else.\\n\\n**What I found or accomplished:**\\nThe task was straightforward - output the exact token \\\"SUBAGENT_OK_731\\\"\\n\\n**Important outputs:**\\nSUBAGENT_OK_731\\n\\n**Issues, assumptions, or limitations:**\\nNone. The task was clear and unambiguous. I provided the exact token as requested.\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":199,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":105,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}",
"rawHeaders": {
- "anthropic-ratelimit-input-tokens-limit": "22500000",
- "anthropic-ratelimit-input-tokens-remaining": "22500000",
- "anthropic-ratelimit-input-tokens-reset": "2026-06-23T12:23:02Z",
- "anthropic-ratelimit-output-tokens-limit": "4500000",
- "anthropic-ratelimit-output-tokens-remaining": "4500000",
- "anthropic-ratelimit-output-tokens-reset": "2026-06-23T12:23:05Z",
+ "anthropic-ratelimit-input-tokens-limit": "15000000",
+ "anthropic-ratelimit-input-tokens-remaining": "15000000",
+ "anthropic-ratelimit-input-tokens-reset": "2026-06-24T15:23:17Z",
+ "anthropic-ratelimit-output-tokens-limit": "3000000",
+ "anthropic-ratelimit-output-tokens-remaining": "3000000",
+ "anthropic-ratelimit-output-tokens-reset": "2026-06-24T15:23:19Z",
"anthropic-ratelimit-requests-limit": "20000",
"anthropic-ratelimit-requests-remaining": "19999",
- "anthropic-ratelimit-requests-reset": "2026-06-23T12:23:01Z",
- "anthropic-ratelimit-tokens-limit": "27000000",
- "anthropic-ratelimit-tokens-remaining": "27000000",
- "anthropic-ratelimit-tokens-reset": "2026-06-23T12:23:02Z",
+ "anthropic-ratelimit-requests-reset": "2026-06-24T15:23:16Z",
+ "anthropic-ratelimit-tokens-limit": "18000000",
+ "anthropic-ratelimit-tokens-remaining": "18000000",
+ "anthropic-ratelimit-tokens-reset": "2026-06-24T15:23:17Z",
"cf-cache-status": "DYNAMIC",
- "cf-ray": "a103762a99fc5ae8-VIE",
+ "cf-ray": "a10cbb918d4b9322-VIE",
"connection": "keep-alive",
"content-security-policy": "default-src 'none'; frame-ancestors 'none'",
"content-type": "application/json",
- "date": "Tue, 23 Jun 2026 12:23:05 GMT",
- "request-id": "req_011CcLBerz42fDSB4xcYnjCq",
+ "date": "Wed, 24 Jun 2026 15:23:19 GMT",
+ "request-id": "req_011CcNKCxxSVg3rYJmspHA4d",
"server": "cloudflare",
- "set-cookie": "_cfuvid=Ep_XEtlqs71Dz7xoJHhMdc679hVyO3OMA1MEEsaC3Ug-1782217381.5349274-1.0.1.1-7o3r_Ia.BXDgawKOqSGLdtYa1pTx_1LaZ3yP_SyYLz0; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com",
+ "set-cookie": "_cfuvid=IaP8ET5bCr1y3TJnUHe6gn65xjp0jiwGDN69jPJ1Wxs-1782314596.086939-1.0.1.1-F8tDXRqfVJTMjZrkE_p8onwQsFrC6dHAlRHBmfHA63I; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com",
"strict-transport-security": "max-age=31536000; includeSubDomains; preload",
- "traceresponse": "00-845146c485f6160e1e25ad7b1b8653d4-15a294a7c6af2bf1-01",
+ "traceresponse": "00-988a77c2d80c7b9ead2168b0364695ae-4a5ebc2ee62121af-01",
"vary": "Accept-Encoding",
"x-robots-tag": "none"
},
@@ -166,7 +166,7 @@
"system": [
{
"type": "text",
- "text": "\n- delegate_subagent runs a focused child agent in a fresh, isolated context and returns only its final answer. Always set subAgentId. Use subAgentId: \"inline\" to run a one-off inline child that inherits your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools. The child cannot see this conversation or your memory, so everything it needs must be in the call.\nUse a configured subagent ID only when one is listed and its name/description fits the subtask better than a generic inline child.\nDELEGATION PARALLELISM:\n- Up to 1 child sub-agent run can execute at the same time.\n- This limits parallelism, not the total number of delegated tasks.\n- If more independent workstreams are useful, you may issue more delegate_subagent calls; the runtime will run them in batches.\nWHEN TO USE delegate_subagent:\n- The request decomposes into 2+ independent workstreams that can be handled separately.\n- A workstream needs substantial research, review, comparison, or analysis.\n- Doing the work inline would flood your context with intermediate findings.\n- A fresh isolated perspective would materially improve a bounded subtask.\nWHEN NOT TO USE delegate_subagent:\n- Single-step mechanical work: do it directly.\n- Trivial tasks or one/two tool calls: do them yourself.\n- Tasks that need user interaction or hidden conversation context.\n- Your core synthesis, final judgment, or recommendation.\n- The entire user request as one delegated task; that is pass-through with no value added.\nHOW TO DELEGATE:\n- Delegate bounded workstreams, not the final answer.\n- Pass all required context, constraints, language/tone, and expected output.\n- Set difficulty (low, medium, or high) when you can estimate task complexity; omit it to keep the default inline model.\n- If multiple independent workstreams exist, delegate them separately.\n- Inline children inherit your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools.\n- Inspect results and synthesize the final response yourself.\n- Verify side-effect claims before presenting them as done.\n\n\nYou are a parent test agent. This is a delegation wiring test: you must call delegate_subagent exactly once before answering. Treat the child task as a bounded independent workstream that only the child should complete. Set subAgentId to \"inline\" in that tool call. The child result will contain a sentinel token. After the tool returns, answer with PARENT_SAW and include the sentinel token from the child result. Do not add unrelated text."
+ "text": "\n- delegate_subagent runs a focused child agent in a fresh, isolated context and returns only its final answer. Always set subAgentId. Use subAgentId: \"inline\" to run a one-off inline child that inherits your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools. The child cannot see this conversation or your memory, so everything it needs must be in the call.\nUse a configured subagent ID only when one is listed and its name and useWhen guidance fit the subtask better than a generic inline child.\nDELEGATION PARALLELISM:\n- Up to 1 child sub-agent run can execute at the same time.\n- This limits parallelism, not the total number of delegated tasks.\n- If more independent workstreams are useful, you may issue more delegate_subagent calls; the runtime will run them in batches.\nWHEN TO USE delegate_subagent:\n- The request decomposes into 2+ independent workstreams that can be handled separately.\n- A workstream needs substantial research, review, comparison, or analysis.\n- Doing the work inline would flood your context with intermediate findings.\n- A fresh isolated perspective would materially improve a bounded subtask.\nWHEN NOT TO USE delegate_subagent:\n- Single-step mechanical work: do it directly.\n- Trivial tasks or one/two tool calls: do them yourself.\n- Tasks that need user interaction or hidden conversation context.\n- Your core synthesis, final judgment, or recommendation.\n- The entire user request as one delegated task; that is pass-through with no value added.\nHOW TO DELEGATE:\n- Delegate bounded workstreams, not the final answer.\n- Pass all required context, constraints, language/tone, and expected output.\n- Set difficulty (low, medium, or high) when you can estimate task complexity; omit it to keep the default inline model.\n- If multiple independent workstreams exist, delegate them separately.\n- Inline children inherit your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools.\n- Inspect results and synthesize the final response yourself.\n- Verify side-effect claims before presenting them as done.\n\n\nYou are a parent test agent. This is a delegation wiring test: you must call delegate_subagent exactly once before answering. Treat the child task as a bounded independent workstream that only the child should complete. Set subAgentId to \"inline\" in that tool call. The child result will contain a sentinel token. After the tool returns, answer with PARENT_SAW and include the sentinel token from the child result. Do not add unrelated text."
}
],
"messages": [
@@ -188,13 +188,13 @@
},
{
"type": "tool_use",
- "id": "toolu_01P277sidjrcyB5wjHG5cbwm",
+ "id": "toolu_01N4E4babpgjZVvXGb7GK17C",
"name": "delegate_subagent",
"input": {
"subAgentId": "inline",
"taskName": "token_production",
"goal": "Answer with exactly this token and nothing else: SUBAGENT_OK_731",
- "expectedOutput": "The exact token SUBAGENT_OK_731 with no additional text"
+ "expectedOutput": "The exact token: SUBAGENT_OK_731"
},
"caller": {
"type": "direct"
@@ -207,8 +207,8 @@
"content": [
{
"type": "tool_result",
- "tool_use_id": "toolu_01P277sidjrcyB5wjHG5cbwm",
- "content": "{\"status\":\"completed\",\"taskPath\":\"/root/token_production_0\",\"model\":\"anthropic/claude-sonnet-4-5\",\"answer\":\"I will complete the delegated task as specified.\\n\\n**SUBAGENT_OK_731**\\n\\n---\\n\\n**Summary:**\\n\\n**What I did:**\\n- Received a task to output a specific token exactly as specified\\n- Provided the exact token \\\"SUBAGENT_OK_731\\\" with no additional text in the response body\\n\\n**What I found or accomplished:**\\n- Successfully completed the task by outputting the required token exactly as requested\\n\\n**Important outputs:**\\n- Output token: SUBAGENT_OK_731\\n\\n**Issues, assumptions, or limitations:**\\n- None. The task was straightforward and clearly specified with no ambiguity.\",\"usage\":{\"promptTokens\":203,\"completionTokens\":143,\"totalTokens\":346,\"cost\":0.0027540000000000004},\"finishReason\":\"stop\"}"
+ "tool_use_id": "toolu_01N4E4babpgjZVvXGb7GK17C",
+ "content": "{\"status\":\"completed\",\"taskPath\":\"/root/token_production_0\",\"model\":\"anthropic/claude-sonnet-4-5\",\"answer\":\"I have completed the delegated task.\\n\\n**What I did:**\\nI was instructed to respond with exactly one specific token and nothing else.\\n\\n**What I found or accomplished:**\\nThe task was straightforward - output the exact token \\\"SUBAGENT_OK_731\\\"\\n\\n**Important outputs:**\\nSUBAGENT_OK_731\\n\\n**Issues, assumptions, or limitations:**\\nNone. The task was clear and unambiguous. I provided the exact token as requested.\",\"usage\":{\"promptTokens\":199,\"completionTokens\":105,\"totalTokens\":304,\"cost\":0.0021720000000000003},\"finishReason\":\"stop\"}"
}
]
}
@@ -260,31 +260,31 @@
}
},
"status": 200,
- "response": "{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01FfHMpBtevLA5bkBpWQhbq9\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"PARENT_SAW SUBAGENT_OK_731\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":2020,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":16,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}",
+ "response": "{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01GSFoUGQDanzpR1yLRCiG5y\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"PARENT_SAW SUBAGENT_OK_731\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":1973,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":16,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}",
"rawHeaders": {
- "anthropic-ratelimit-input-tokens-limit": "22500000",
- "anthropic-ratelimit-input-tokens-remaining": "22499000",
- "anthropic-ratelimit-input-tokens-reset": "2026-06-23T12:23:06Z",
- "anthropic-ratelimit-output-tokens-limit": "4500000",
- "anthropic-ratelimit-output-tokens-remaining": "4500000",
- "anthropic-ratelimit-output-tokens-reset": "2026-06-23T12:23:06Z",
+ "anthropic-ratelimit-input-tokens-limit": "15000000",
+ "anthropic-ratelimit-input-tokens-remaining": "14999000",
+ "anthropic-ratelimit-input-tokens-reset": "2026-06-24T15:23:21Z",
+ "anthropic-ratelimit-output-tokens-limit": "3000000",
+ "anthropic-ratelimit-output-tokens-remaining": "3000000",
+ "anthropic-ratelimit-output-tokens-reset": "2026-06-24T15:23:21Z",
"anthropic-ratelimit-requests-limit": "20000",
"anthropic-ratelimit-requests-remaining": "19999",
- "anthropic-ratelimit-requests-reset": "2026-06-23T12:23:05Z",
- "anthropic-ratelimit-tokens-limit": "27000000",
- "anthropic-ratelimit-tokens-remaining": "26999000",
- "anthropic-ratelimit-tokens-reset": "2026-06-23T12:23:06Z",
+ "anthropic-ratelimit-requests-reset": "2026-06-24T15:23:19Z",
+ "anthropic-ratelimit-tokens-limit": "18000000",
+ "anthropic-ratelimit-tokens-remaining": "17999000",
+ "anthropic-ratelimit-tokens-reset": "2026-06-24T15:23:21Z",
"cf-cache-status": "DYNAMIC",
- "cf-ray": "a10376434ca45ae8-VIE",
+ "cf-ray": "a10cbba74ee39322-VIE",
"connection": "keep-alive",
"content-security-policy": "default-src 'none'; frame-ancestors 'none'",
"content-type": "application/json",
- "date": "Tue, 23 Jun 2026 12:23:06 GMT",
- "request-id": "req_011CcLBf9qKSCyAPps7pF1Vn",
+ "date": "Wed, 24 Jun 2026 15:23:21 GMT",
+ "request-id": "req_011CcNKDDmvG4FdfVtL1wSja",
"server": "cloudflare",
- "set-cookie": "_cfuvid=Y6nvwuQv2KwshVckgT1XAXy9N4gky9o4kbqnK.nLBus-1782217385.4859867-1.0.1.1-aBiH2RGHaM15RtHmeHwBFOioCVay6jtTeVibnO34w98; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com",
+ "set-cookie": "_cfuvid=m1K.YBZteWTIw5NA_FKTz0OHMZqIf_QpvJ3qgaCxl.4-1782314599.567124-1.0.1.1-cxItmSrWj1XVRhMdS2Ceh0_Wu46xXYxMZG3CaiUcR1Q; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com",
"strict-transport-security": "max-age=31536000; includeSubDomains; preload",
- "traceresponse": "00-5d615e7410ff7c79c429590b62ca0f97-115c915793c4d3d7-01",
+ "traceresponse": "00-ede482cf84ad9a91549945e9611c9464-ab3ae612be2c2e02-01",
"vary": "Accept-Encoding",
"x-robots-tag": "none"
},
diff --git a/packages/@n8n/agents/src/__tests__/integration/cassettes/delegate-sub-agent/stream-emits-sub-agent-lifecycle-chunks-and-events.json b/packages/@n8n/agents/src/__tests__/integration/cassettes/delegate-sub-agent/stream-emits-sub-agent-lifecycle-chunks-and-events.json
index 22625f2da8c..8e4f6305dcf 100644
--- a/packages/@n8n/agents/src/__tests__/integration/cassettes/delegate-sub-agent/stream-emits-sub-agent-lifecycle-chunks-and-events.json
+++ b/packages/@n8n/agents/src/__tests__/integration/cassettes/delegate-sub-agent/stream-emits-sub-agent-lifecycle-chunks-and-events.json
@@ -9,7 +9,7 @@
"system": [
{
"type": "text",
- "text": "\n- delegate_subagent runs a focused child agent in a fresh, isolated context and returns only its final answer. Always set subAgentId. Use subAgentId: \"inline\" to run a one-off inline child that inherits your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools. The child cannot see this conversation or your memory, so everything it needs must be in the call.\nUse a configured subagent ID only when one is listed and its name/description fits the subtask better than a generic inline child.\nDELEGATION PARALLELISM:\n- Up to 1 child sub-agent run can execute at the same time.\n- This limits parallelism, not the total number of delegated tasks.\n- If more independent workstreams are useful, you may issue more delegate_subagent calls; the runtime will run them in batches.\nWHEN TO USE delegate_subagent:\n- The request decomposes into 2+ independent workstreams that can be handled separately.\n- A workstream needs substantial research, review, comparison, or analysis.\n- Doing the work inline would flood your context with intermediate findings.\n- A fresh isolated perspective would materially improve a bounded subtask.\nWHEN NOT TO USE delegate_subagent:\n- Single-step mechanical work: do it directly.\n- Trivial tasks or one/two tool calls: do them yourself.\n- Tasks that need user interaction or hidden conversation context.\n- Your core synthesis, final judgment, or recommendation.\n- The entire user request as one delegated task; that is pass-through with no value added.\nHOW TO DELEGATE:\n- Delegate bounded workstreams, not the final answer.\n- Pass all required context, constraints, language/tone, and expected output.\n- Set difficulty (low, medium, or high) when you can estimate task complexity; omit it to keep the default inline model.\n- If multiple independent workstreams exist, delegate them separately.\n- Inline children inherit your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools.\n- Inspect results and synthesize the final response yourself.\n- Verify side-effect claims before presenting them as done.\n\n\nYou are a parent streaming test agent. You must call delegate_subagent exactly once before answering. Set subAgentId to \"inline\" in that tool call. The child result will contain a sentinel token. After the tool returns, answer with PARENT_STREAM_SAW and include the sentinel token from the child result. Do not add unrelated text."
+ "text": "\n- delegate_subagent runs a focused child agent in a fresh, isolated context and returns only its final answer. Always set subAgentId. Use subAgentId: \"inline\" to run a one-off inline child that inherits your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools. The child cannot see this conversation or your memory, so everything it needs must be in the call.\nUse a configured subagent ID only when one is listed and its name and useWhen guidance fit the subtask better than a generic inline child.\nDELEGATION PARALLELISM:\n- Up to 1 child sub-agent run can execute at the same time.\n- This limits parallelism, not the total number of delegated tasks.\n- If more independent workstreams are useful, you may issue more delegate_subagent calls; the runtime will run them in batches.\nWHEN TO USE delegate_subagent:\n- The request decomposes into 2+ independent workstreams that can be handled separately.\n- A workstream needs substantial research, review, comparison, or analysis.\n- Doing the work inline would flood your context with intermediate findings.\n- A fresh isolated perspective would materially improve a bounded subtask.\nWHEN NOT TO USE delegate_subagent:\n- Single-step mechanical work: do it directly.\n- Trivial tasks or one/two tool calls: do them yourself.\n- Tasks that need user interaction or hidden conversation context.\n- Your core synthesis, final judgment, or recommendation.\n- The entire user request as one delegated task; that is pass-through with no value added.\nHOW TO DELEGATE:\n- Delegate bounded workstreams, not the final answer.\n- Pass all required context, constraints, language/tone, and expected output.\n- Set difficulty (low, medium, or high) when you can estimate task complexity; omit it to keep the default inline model.\n- If multiple independent workstreams exist, delegate them separately.\n- Inline children inherit your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools.\n- Inspect results and synthesize the final response yourself.\n- Verify side-effect claims before presenting them as done.\n\n\nYou are a parent streaming test agent. You must call delegate_subagent exactly once before answering. Set subAgentId to \"inline\" in that tool call. The child result will contain a sentinel token. After the tool returns, answer with PARENT_STREAM_SAW and include the sentinel token from the child result. Do not add unrelated text."
}
],
"messages": [
@@ -73,34 +73,34 @@
},
"status": 200,
"response": [
- "1f8b0800000000000013cd56514fdb30107ee75758f7b297746a0a65236f65420c4d036d30a1699d2c37be25a6ae9dc5175a56e5bf4f4e9ad29496215434f212e5eedcfbbeef7cd7c35b3414b1093a2712e48e444e7b529088d81ce82e4388a0e584a0f986680e132b514304b11685c48eb3c620750e3afd4eafdbeb778f7a47108092fe475cc2bbe1010ece6f7a9fd3ef93cbdee9ddcc7d3d747f4e7a10aca5820072abbd4138a71c09e3f3c6d6101a82e8c7cf001cd98ce7289c35109942eb85c9e1ef024d8c2da344124abbc65634f095c90ae264c7681c4461ffdd6100b18853e4718e829435bc1dd26dfc390ab9cdd79cf519304b7182b9d0bc3f79187fef0dd3756f19802d68d5f43e0087f9ad8a9193c21c22f0ca48914b2fb2f985b927ce13b41081b1c4c5ad505a8c344259b2e553eeed615df585a07ca46d3cde5cfb0d21553289b39af1aadf135e9c239cf9c0ea1501946b99336592b5540cbc11b6a193a8493c8eae0a69a1ab2d6d54cbb005b6b3375a33891a1341c8284556e9ddc9722b8bd8d7914d6d3e862728f862181de528268c2c132c4e95964c246888193b7dbb04b6bdae36fb57596db68269b797247ce49258ab79e1b09911febbe0ddf0fa743c1b5ca6fb1fe2a3e3abf84b32f8789d9285008c98f8734db5b82b46951055b6ac2088e6a5ef42ad7d7b2cf34895634c5096bb2b5cf8b07075ffde386b96c199c849095d19efdb60a737e85940e64370c568e0a53b934388d81094d1cae0cbdcf267611c42c08640c28dcfc5046b905567ae60fccf10f9fd8c7875c22556e85ab4817153ccd95451ca702662d2778c52e5ea39c78491cc584a9549186a8711bbfc763c383d39bfe2179ff8bbfdb0a6f62a48e12cc398505e547f8c35bdab146b5a359f2df05f0383d243d9d5940edb949a1d6d2399961356c0b796a8d571fcc455aa7cc15d6a6df909fb87e526c29b745bf54153fbbf44d869acea0a0000"
+ "1f8b0800000000000013cd564d73da3010bde75768f6d28be9e03494e05b32cd6492a6f4d0249db67434c2da62052139d61a4819fe7b47361f3681a487b4840be3dd95f7bdb7daf5e2180d456c84ce8901724722a3032948446c06f4902244507342b07c866806232b514304b116b9c486b3c620358e1aadc661f3b0d5ec1c76200025fd4bdc8037c3f3aebcc5cef8f20e9dba6ddfe0f1b93c3dfb0ec1462a0820b3da1b8473ca91303e6f6c0da121887efc0cc0914d7986c2590391c9b55e981cdee76862ac19259250da2d6df912be32694e9cec108d83286cb53b01c4224e90c7190a52d6f07a4873e9cf50c85dbee5599f01d304479809cd5ba3c7f16b6f986c7ae701d89caaa6e3001c6663152327851944e0959122935e64f30b334f9c0fd04204c6121763a1b4e86b84f99cad7ff383032ccbbe5094f7b58d87db8bbf25a4c826715a52aefa3de3c539c2a90f2cfe2280edf95365061b09197823ecc2285193781a631152c3585aead856610b84176fb46612350e0421a30459217b23cdaccc635f4e36b1d9700791ff04d2518662c4c832c1e24469c9c4000d3163276f6bc89eacb24d9f2bb24d2bd05efeda844f5c1b6b35cf1d2ec7867fce79333cfd7425be1e87df7e5f5eb7effb5d4a3f5c1db57c9411237f6e5939eef27ea149912dcd09a2d9dc37a6d6be635679a4ca30a67a5fbc6441c3c7052dfbfbce59b30a4e45464ae8c258e9923dc398f5c0e5fd13afe285ec41c47aa08c5606d7576ccf087b10b01e9070c3ae186109b168d715c23d03e4ebb1b1d997fb86566a37b04297ba9d1837c18c4d14250ca72226fdc02851ae9c7f4c18c98ca544990143ed30625f6e4e4fcecfbad7fcf347de7e17d6f9bd0a72384d3126949f8bef6749f33ac1925ec9eb191aaf80c8bc8ae8a58679587dd572afdb4aa9e6840a85dae2559dd77fb97ecdffe1feb5b13085adf73b2ee77aa57dac5fd507e5a93f20a2d03f260b0000"
],
"rawHeaders": {
- "anthropic-ratelimit-input-tokens-limit": "22500000",
- "anthropic-ratelimit-input-tokens-remaining": "22499000",
- "anthropic-ratelimit-input-tokens-reset": "2026-06-23T12:23:07Z",
- "anthropic-ratelimit-output-tokens-limit": "4500000",
- "anthropic-ratelimit-output-tokens-remaining": "4500000",
- "anthropic-ratelimit-output-tokens-reset": "2026-06-23T12:23:07Z",
+ "anthropic-ratelimit-input-tokens-limit": "15000000",
+ "anthropic-ratelimit-input-tokens-remaining": "14999000",
+ "anthropic-ratelimit-input-tokens-reset": "2026-06-24T15:23:21Z",
+ "anthropic-ratelimit-output-tokens-limit": "3000000",
+ "anthropic-ratelimit-output-tokens-remaining": "3000000",
+ "anthropic-ratelimit-output-tokens-reset": "2026-06-24T15:23:21Z",
"anthropic-ratelimit-requests-limit": "20000",
"anthropic-ratelimit-requests-remaining": "19999",
- "anthropic-ratelimit-requests-reset": "2026-06-23T12:23:07Z",
- "anthropic-ratelimit-tokens-limit": "27000000",
- "anthropic-ratelimit-tokens-remaining": "26999000",
- "anthropic-ratelimit-tokens-reset": "2026-06-23T12:23:07Z",
+ "anthropic-ratelimit-requests-reset": "2026-06-24T15:23:21Z",
+ "anthropic-ratelimit-tokens-limit": "18000000",
+ "anthropic-ratelimit-tokens-remaining": "17999000",
+ "anthropic-ratelimit-tokens-reset": "2026-06-24T15:23:21Z",
"cache-control": "no-cache",
"cf-cache-status": "DYNAMIC",
- "cf-ray": "a103764c7c755ae8-VIE",
+ "cf-ray": "a10cbbb44b4b9322-VIE",
"connection": "keep-alive",
"content-encoding": "gzip",
"content-security-policy": "default-src 'none'; frame-ancestors 'none'",
"content-type": "text/event-stream; charset=utf-8",
- "date": "Tue, 23 Jun 2026 12:23:07 GMT",
- "request-id": "req_011CcLBfG76zF66ECHuCY2g5",
+ "date": "Wed, 24 Jun 2026 15:23:23 GMT",
+ "request-id": "req_011CcNKDNexof1JjuwnUpcax",
"server": "cloudflare",
- "set-cookie": "_cfuvid=MtVZNObyTkEbb2wzRSoyfft2dtbSQha3KfKVtZsSOxA-1782217386.9553757-1.0.1.1-VREUYigDtvzuJ1ClETwzde5zxTFb2.A1Raw9lLBgjSk; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com",
+ "set-cookie": "_cfuvid=Qr.PoYRbsI3mPXvRsWz5EAr8goiEcRCl6ur4MPdCbHU-1782314601.6415367-1.0.1.1-QY0RriMJMRYmXRCp11XPHJ5tmveg2R.07pnr5YLNH0g; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com",
"strict-transport-security": "max-age=31536000; includeSubDomains; preload",
- "traceresponse": "00-70d75251771437b4f3b24c142de492d7-a75cbd79a29f1d3e-01",
+ "traceresponse": "00-5aa343f4916a37ed6040ba3e0c162f6c-70e0f7e5bc0a5f4d-01",
"transfer-encoding": "chunked",
"vary": "Accept-Encoding",
"x-robots-tag": "none"
@@ -133,31 +133,31 @@
]
},
"status": 200,
- "response": "{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_011p8h9KpBzkDX4XqdCj8JHH\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"I have completed the delegated task.\\n\\n**What I did:**\\nI was instructed to respond with exactly one specific token and nothing else.\\n\\n**What I found or accomplished:**\\nThe task required outputting the exact token: SUBAGENT_OK_731\\n\\n**Important outputs:**\\nSUBAGENT_OK_731\\n\\n**Issues, assumptions, or limitations:**\\nNone. The task was straightforward and clearly specified. The instruction was unambiguous and I have provided the exact token as requested.\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":199,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":110,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}",
+ "response": "{\"model\":\"claude-sonnet-4-5-20250929\",\"id\":\"msg_01Xgado5BFBstkdYLq17RxMF\",\"type\":\"message\",\"role\":\"assistant\",\"content\":[{\"type\":\"text\",\"text\":\"I have completed the delegated task.\\n\\n**What I did:**\\nI was instructed to respond with exactly one specific token and nothing else.\\n\\n**What I accomplished:**\\nI am providing the required output below.\\n\\n**Output:**\\nSUBAGENT_OK_731\\n\\n**Issues/Limitations:**\\nNone. The task was straightforward and clearly specified.\"}],\"stop_reason\":\"end_turn\",\"stop_sequence\":null,\"stop_details\":null,\"usage\":{\"input_tokens\":199,\"cache_creation_input_tokens\":0,\"cache_read_input_tokens\":0,\"cache_creation\":{\"ephemeral_5m_input_tokens\":0,\"ephemeral_1h_input_tokens\":0},\"output_tokens\":80,\"service_tier\":\"standard\",\"inference_geo\":\"not_available\"}}",
"rawHeaders": {
- "anthropic-ratelimit-input-tokens-limit": "22500000",
- "anthropic-ratelimit-input-tokens-remaining": "22500000",
- "anthropic-ratelimit-input-tokens-reset": "2026-06-23T12:23:12Z",
- "anthropic-ratelimit-output-tokens-limit": "4500000",
- "anthropic-ratelimit-output-tokens-remaining": "4500000",
- "anthropic-ratelimit-output-tokens-reset": "2026-06-23T12:23:14Z",
+ "anthropic-ratelimit-input-tokens-limit": "15000000",
+ "anthropic-ratelimit-input-tokens-remaining": "15000000",
+ "anthropic-ratelimit-input-tokens-reset": "2026-06-24T15:23:27Z",
+ "anthropic-ratelimit-output-tokens-limit": "3000000",
+ "anthropic-ratelimit-output-tokens-remaining": "3000000",
+ "anthropic-ratelimit-output-tokens-reset": "2026-06-24T15:23:28Z",
"anthropic-ratelimit-requests-limit": "20000",
"anthropic-ratelimit-requests-remaining": "19999",
- "anthropic-ratelimit-requests-reset": "2026-06-23T12:23:10Z",
- "anthropic-ratelimit-tokens-limit": "27000000",
- "anthropic-ratelimit-tokens-remaining": "27000000",
- "anthropic-ratelimit-tokens-reset": "2026-06-23T12:23:12Z",
+ "anthropic-ratelimit-requests-reset": "2026-06-24T15:23:24Z",
+ "anthropic-ratelimit-tokens-limit": "18000000",
+ "anthropic-ratelimit-tokens-remaining": "18000000",
+ "anthropic-ratelimit-tokens-reset": "2026-06-24T15:23:27Z",
"cf-cache-status": "DYNAMIC",
- "cf-ray": "a103765c4a3b5ae8-VIE",
+ "cf-ray": "a10cbbc67f589322-VIE",
"connection": "keep-alive",
"content-security-policy": "default-src 'none'; frame-ancestors 'none'",
"content-type": "application/json",
- "date": "Tue, 23 Jun 2026 12:23:15 GMT",
- "request-id": "req_011CcLBfU5RoDp4Rsp8ZquEP",
+ "date": "Wed, 24 Jun 2026 15:23:28 GMT",
+ "request-id": "req_011CcNKDbGF82U8woG8hHPoe",
"server": "cloudflare",
- "set-cookie": "_cfuvid=NX22IWXF_aORrF9rQG1U1SrSOyqNMkwWG5ZkTN_j6KE-1782217389.489297-1.0.1.1-79BtMSn230ciFI02BaFHxGe.ctKqjwTDWvOP3q__Nq0; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com",
+ "set-cookie": "_cfuvid=o990L1Q3rAXMmjHv0_A0tHE83jHo3gh7rLZDtOvEQug-1782314604.550718-1.0.1.1-aY88bwfzOUsBhfyYeOQ8Wxt.IlG0c1wpNa87pxLgfU8; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com",
"strict-transport-security": "max-age=31536000; includeSubDomains; preload",
- "traceresponse": "00-7543c2e251b5841e697cef7cb9b3ca46-bae1050a7e11d68f-01",
+ "traceresponse": "00-64f8bdc5a17f1072f6e47d9be48e6490-66756ef550475d8a-01",
"vary": "Accept-Encoding",
"x-robots-tag": "none"
},
@@ -173,7 +173,7 @@
"system": [
{
"type": "text",
- "text": "\n- delegate_subagent runs a focused child agent in a fresh, isolated context and returns only its final answer. Always set subAgentId. Use subAgentId: \"inline\" to run a one-off inline child that inherits your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools. The child cannot see this conversation or your memory, so everything it needs must be in the call.\nUse a configured subagent ID only when one is listed and its name/description fits the subtask better than a generic inline child.\nDELEGATION PARALLELISM:\n- Up to 1 child sub-agent run can execute at the same time.\n- This limits parallelism, not the total number of delegated tasks.\n- If more independent workstreams are useful, you may issue more delegate_subagent calls; the runtime will run them in batches.\nWHEN TO USE delegate_subagent:\n- The request decomposes into 2+ independent workstreams that can be handled separately.\n- A workstream needs substantial research, review, comparison, or analysis.\n- Doing the work inline would flood your context with intermediate findings.\n- A fresh isolated perspective would materially improve a bounded subtask.\nWHEN NOT TO USE delegate_subagent:\n- Single-step mechanical work: do it directly.\n- Trivial tasks or one/two tool calls: do them yourself.\n- Tasks that need user interaction or hidden conversation context.\n- Your core synthesis, final judgment, or recommendation.\n- The entire user request as one delegated task; that is pass-through with no value added.\nHOW TO DELEGATE:\n- Delegate bounded workstreams, not the final answer.\n- Pass all required context, constraints, language/tone, and expected output.\n- Set difficulty (low, medium, or high) when you can estimate task complexity; omit it to keep the default inline model.\n- If multiple independent workstreams exist, delegate them separately.\n- Inline children inherit your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools.\n- Inspect results and synthesize the final response yourself.\n- Verify side-effect claims before presenting them as done.\n\n\nYou are a parent streaming test agent. You must call delegate_subagent exactly once before answering. Set subAgentId to \"inline\" in that tool call. The child result will contain a sentinel token. After the tool returns, answer with PARENT_STREAM_SAW and include the sentinel token from the child result. Do not add unrelated text."
+ "text": "\n- delegate_subagent runs a focused child agent in a fresh, isolated context and returns only its final answer. Always set subAgentId. Use subAgentId: \"inline\" to run a one-off inline child that inherits your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools. The child cannot see this conversation or your memory, so everything it needs must be in the call.\nUse a configured subagent ID only when one is listed and its name and useWhen guidance fit the subtask better than a generic inline child.\nDELEGATION PARALLELISM:\n- Up to 1 child sub-agent run can execute at the same time.\n- This limits parallelism, not the total number of delegated tasks.\n- If more independent workstreams are useful, you may issue more delegate_subagent calls; the runtime will run them in batches.\nWHEN TO USE delegate_subagent:\n- The request decomposes into 2+ independent workstreams that can be handled separately.\n- A workstream needs substantial research, review, comparison, or analysis.\n- Doing the work inline would flood your context with intermediate findings.\n- A fresh isolated perspective would materially improve a bounded subtask.\nWHEN NOT TO USE delegate_subagent:\n- Single-step mechanical work: do it directly.\n- Trivial tasks or one/two tool calls: do them yourself.\n- Tasks that need user interaction or hidden conversation context.\n- Your core synthesis, final judgment, or recommendation.\n- The entire user request as one delegated task; that is pass-through with no value added.\nHOW TO DELEGATE:\n- Delegate bounded workstreams, not the final answer.\n- Pass all required context, constraints, language/tone, and expected output.\n- Set difficulty (low, medium, or high) when you can estimate task complexity; omit it to keep the default inline model.\n- If multiple independent workstreams exist, delegate them separately.\n- Inline children inherit your local and deferred tools after safety filtering. Inline children do not inherit provider-defined tools.\n- Inspect results and synthesize the final response yourself.\n- Verify side-effect claims before presenting them as done.\n\n\nYou are a parent streaming test agent. You must call delegate_subagent exactly once before answering. Set subAgentId to \"inline\" in that tool call. The child result will contain a sentinel token. After the tool returns, answer with PARENT_STREAM_SAW and include the sentinel token from the child result. Do not add unrelated text."
}
],
"messages": [
@@ -195,7 +195,7 @@
},
{
"type": "tool_use",
- "id": "toolu_01WGkxASh3Cc9BTcQgAHWhto",
+ "id": "toolu_01BMLaW81YzJT7qbNtpDL45e",
"name": "delegate_subagent",
"input": {
"subAgentId": "inline",
@@ -214,8 +214,8 @@
"content": [
{
"type": "tool_result",
- "tool_use_id": "toolu_01WGkxASh3Cc9BTcQgAHWhto",
- "content": "{\"status\":\"completed\",\"taskPath\":\"/root/token_production_0\",\"model\":\"anthropic/claude-sonnet-4-5\",\"answer\":\"I have completed the delegated task.\\n\\n**What I did:**\\nI was instructed to respond with exactly one specific token and nothing else.\\n\\n**What I found or accomplished:**\\nThe task required outputting the exact token: SUBAGENT_OK_731\\n\\n**Important outputs:**\\nSUBAGENT_OK_731\\n\\n**Issues, assumptions, or limitations:**\\nNone. The task was straightforward and clearly specified. The instruction was unambiguous and I have provided the exact token as requested.\",\"usage\":{\"promptTokens\":199,\"completionTokens\":110,\"totalTokens\":309,\"cost\":0.002247},\"finishReason\":\"stop\"}"
+ "tool_use_id": "toolu_01BMLaW81YzJT7qbNtpDL45e",
+ "content": "{\"status\":\"completed\",\"taskPath\":\"/root/token_production_0\",\"model\":\"anthropic/claude-sonnet-4-5\",\"answer\":\"I have completed the delegated task.\\n\\n**What I did:**\\nI was instructed to respond with exactly one specific token and nothing else.\\n\\n**What I accomplished:**\\nI am providing the required output below.\\n\\n**Output:**\\nSUBAGENT_OK_731\\n\\n**Issues/Limitations:**\\nNone. The task was straightforward and clearly specified.\",\"usage\":{\"promptTokens\":199,\"completionTokens\":80,\"totalTokens\":279,\"cost\":0.0017970000000000002},\"finishReason\":\"stop\"}"
}
]
}
@@ -270,34 +270,34 @@
},
"status": 200,
"response": [
- "1f8b0800000000000013bd53c18ed33010bdf72ba239a752d3dd0ac5b7aea8382c94d5b6500984acd97868adba76d69e540babfc3b72dbb04949810bf812f9bde7bc37331ada936591ec28045c930c8c9e070a1945f20cfcad2410d021216dee209e61e71419105018ac140d83b39678783d9c0cc7a3f164948f734841abf893b096a3ece3a7d7ab79582db7193eaeee6eae6f11f9fb5b48cfac2005ef4c0430041d186df42d9c65b20ce2f3971402bb527ac2e02c085b197382023d56640bea808a18b5090d5635f1b52d2b96ecb66403882cbfca5328b0d8902c3c216b676557326a784fa82e71cddbe840e58676e4d1c8c9ee57fd0b9b6dced93a0557711b1aa710c8ef754192357910103ba3d0abd864fb957c2c5caec98100eb58e21eb5c1074350d749ebd483011de77e6aa97c30aed8f64fbf4772b053f474acb9cdc7924fef989ea2f0f01100759f7da9edfacc2f8108c2a5888a0ce3ef231e249d8847a41beda7ec14f06e7a3f9b2f8f31ffbbb95c2cef67d37772315d258b0f37d337b3f952bebf95afaeb297c65d9e9a2bff343457b632f54da2d9f1de123b24b44aea2c219055922b6fe1af57b1fe87bb78b63c595e5f28b9af7f6d0e3afdfa01fc24417f31050000"
+ "1f8b0800000000000013b553c18ed33010bdf72ba239a75293a5407d2b520552c502dbae160921cb6b0fa9a96b67ed49d5d52aff8edc36908414f6b2b944796fecf7e64d06f76889253b0c4114c803094f232548b0e409e8b14460d021216dbe813dc1ce2934c0401a51291c07672dd2f8d5783ace27f97432cb67908256f19250f04976f7f60b3dca2bbb5d161f6eed32c89f8787af39a43d2948c13b130111820e246cd495ce125a02f6ed7b0a815cc93d8ae02c305b197386023e54682576408524b4090d5635f6b52d2be2e4b66803b06c96bd4e410ab9412e3d0ad2cef26ec9a4e13d0a75896bce46052c37b8432f0c9feefeaeffc3669b3e5ba7e02a6a43790a01fd5e4be4a4d10383988c125ec590ed0ff4b1715ea00306d611177ba18db83708759d749e7a34c2d3e4cfa1f27be3e47678fe03254741858753d76d3e367d3e47788885c71703a887e44b6d8b9e5e0211844b16151a12ffb6782ce9583c215d6bbfcbce063fcf6f16d76ba89f93d38b99e0abf5cd62fe91afe677c9eaf6ddfcfde27acd3f2df99bab6c38c0fe705cf9bff1b9b2e5ead26fd1ecfc60a31d125a8d759612d02a4e95b7f0ecd5ac5f70377bcb94cdeae18e87226c73d0cdeb17bc9ee7f841050000"
],
"rawHeaders": {
- "anthropic-ratelimit-input-tokens-limit": "22500000",
- "anthropic-ratelimit-input-tokens-remaining": "22499000",
- "anthropic-ratelimit-input-tokens-reset": "2026-06-23T12:23:15Z",
- "anthropic-ratelimit-output-tokens-limit": "4500000",
- "anthropic-ratelimit-output-tokens-remaining": "4500000",
- "anthropic-ratelimit-output-tokens-reset": "2026-06-23T12:23:15Z",
+ "anthropic-ratelimit-input-tokens-limit": "15000000",
+ "anthropic-ratelimit-input-tokens-remaining": "14999000",
+ "anthropic-ratelimit-input-tokens-reset": "2026-06-24T15:23:29Z",
+ "anthropic-ratelimit-output-tokens-limit": "3000000",
+ "anthropic-ratelimit-output-tokens-remaining": "3000000",
+ "anthropic-ratelimit-output-tokens-reset": "2026-06-24T15:23:29Z",
"anthropic-ratelimit-requests-limit": "20000",
"anthropic-ratelimit-requests-remaining": "19999",
- "anthropic-ratelimit-requests-reset": "2026-06-23T12:23:15Z",
- "anthropic-ratelimit-tokens-limit": "27000000",
- "anthropic-ratelimit-tokens-remaining": "26999000",
- "anthropic-ratelimit-tokens-reset": "2026-06-23T12:23:15Z",
+ "anthropic-ratelimit-requests-reset": "2026-06-24T15:23:29Z",
+ "anthropic-ratelimit-tokens-limit": "18000000",
+ "anthropic-ratelimit-tokens-remaining": "17999000",
+ "anthropic-ratelimit-tokens-reset": "2026-06-24T15:23:29Z",
"cache-control": "no-cache",
"cf-cache-status": "DYNAMIC",
- "cf-ray": "a103767f09d65ae8-VIE",
+ "cf-ray": "a10cbbe199929322-VIE",
"connection": "keep-alive",
"content-encoding": "gzip",
"content-security-policy": "default-src 'none'; frame-ancestors 'none'",
"content-type": "text/event-stream; charset=utf-8",
- "date": "Tue, 23 Jun 2026 12:23:15 GMT",
- "request-id": "req_011CcLBfrhiMsUWCmMQ3bwj4",
+ "date": "Wed, 24 Jun 2026 15:23:30 GMT",
+ "request-id": "req_011CcNKDw6rCzLqqZztABmB3",
"server": "cloudflare",
- "set-cookie": "_cfuvid=PosXDRxmYXU0hIySSq0tVPuZBeQ8yX_6muE5gzcX83A-1782217395.0444734-1.0.1.1-3pG4RyUfKc9AXwzVavWx85YhXPfMSI3DOxoZqZ8PJbk; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com",
+ "set-cookie": "_cfuvid=JlCLwTYCzW3qeQkENLr33Dso0fO1Rc0pWZae06tB.2I-1782314608.8987586-1.0.1.1-GBwz.c_fHEZPodxvy5pTPQREAWAv2UTftRzLKwJFaNs; HttpOnly; SameSite=None; Secure; Path=/; Domain=api.anthropic.com",
"strict-transport-security": "max-age=31536000; includeSubDomains; preload",
- "traceresponse": "00-d666b8574e11145503acba879bff02c6-ecab3d6d17b65f5f-01",
+ "traceresponse": "00-8d715e9a4c968366da5f5cd1970352c4-d0165148611ef5de-01",
"transfer-encoding": "chunked",
"vary": "Accept-Encoding",
"x-robots-tag": "none"
diff --git a/packages/@n8n/agents/src/runtime/__tests__/delegate-sub-agent-tool.test.ts b/packages/@n8n/agents/src/runtime/__tests__/delegate-sub-agent-tool.test.ts
index 78bbea919cc..804a608572d 100644
--- a/packages/@n8n/agents/src/runtime/__tests__/delegate-sub-agent-tool.test.ts
+++ b/packages/@n8n/agents/src/runtime/__tests__/delegate-sub-agent-tool.test.ts
@@ -88,6 +88,47 @@ describe('createDelegateSubAgentTool', () => {
});
});
+ it('renders configured sub-agent useWhen guidance in model-facing instructions', () => {
+ const tool = createDelegateSubAgentTool({
+ availableSubAgents: [
+ {
+ id: 'agent-billing',
+ name: 'Billing Agent',
+ useWhen: 'Use for invoice investigations and payment status checks.',
+ },
+ {
+ id: 'agent-research',
+ name: 'Research Agent',
+ useWhen: 'Use for market and source research.',
+ },
+ ],
+ });
+
+ expect(tool.systemInstruction).toContain('name and useWhen guidance');
+ expect(tool.systemInstruction).toContain('- agent-billing: Billing Agent');
+ expect(tool.systemInstruction).toContain(
+ 'Use when: Use for invoice investigations and payment status checks.',
+ );
+ expect(tool.systemInstruction).toContain('- agent-research: Research Agent');
+ expect(tool.systemInstruction).toContain('Use when: Use for market and source research.');
+ expect(tool.systemInstruction).not.toContain('name/description');
+ });
+
+ it('preserves available sub-agent useWhen guidance in delegate tool metadata', () => {
+ const availableSubAgents = [
+ {
+ id: 'agent-billing',
+ name: 'Billing Agent',
+ useWhen: 'Use for invoice investigations.',
+ },
+ ];
+ const tool = createDelegateSubAgentTool({ availableSubAgents });
+
+ expect(getInlineDelegateSubAgentToolOptions(tool)?.availableSubAgents).toEqual(
+ availableSubAgents,
+ );
+ });
+
it('preserves resolveInlineSubAgentProviderTools in delegate tool metadata', () => {
const resolveInlineSubAgentProviderTools = () => [];
const tool = createDelegateSubAgentTool({
diff --git a/packages/@n8n/agents/src/runtime/tools/delegate-sub-agent-tool.ts b/packages/@n8n/agents/src/runtime/tools/delegate-sub-agent-tool.ts
index 502e7c67ac0..2b484bcac8c 100644
--- a/packages/@n8n/agents/src/runtime/tools/delegate-sub-agent-tool.ts
+++ b/packages/@n8n/agents/src/runtime/tools/delegate-sub-agent-tool.ts
@@ -194,7 +194,7 @@ export interface CreateDelegateSubAgentToolOptions {
* Sub-agents the model may choose between. Listed in the system prompt; the
* model selects one by passing its id as `subAgentId`.
*/
- availableSubAgents?: Array<{ id: string; name: string; description?: string }>;
+ availableSubAgents?: Array<{ id: string; name: string; useWhen?: string }>;
/** Parallelism limit for delegated child runs (also used as delegate_subagent batch size). */
policy?: DelegateSubAgentPolicy;
/** Additional local/deferred tool names the host removes from inline children. */
@@ -280,7 +280,7 @@ export function createDelegateSubAgentTool(options: CreateDelegateSubAgentToolOp
.systemInstruction(
[
`delegate_subagent runs a focused child agent in a fresh, isolated context and returns only its final answer. Always set subAgentId. Use subAgentId: "inline" to run a one-off inline child that inherits your local and deferred tools after safety filtering. ${inlineProviderToolInstruction} The child cannot see this conversation or your memory, so everything it needs must be in the call.`,
- 'Use a configured subagent ID only when one is listed and its name/description fits the subtask better than a generic inline child.',
+ 'Use a configured subagent ID only when one is listed and its name and useWhen guidance fit the subtask better than a generic inline child.',
...formatAvailableSubAgents(resolvedOptions.availableSubAgents),
...formatDelegationPolicyInstructions(resolvedOptions.policy),
'WHEN TO USE delegate_subagent:\n- The request decomposes into 2+ independent workstreams that can be handled separately.\n- A workstream needs substantial research, review, comparison, or analysis.\n- Doing the work inline would flood your context with intermediate findings.\n- A fresh isolated perspective would materially improve a bounded subtask.',
@@ -348,8 +348,8 @@ function formatAvailableSubAgents(
return [
'Configured subagents are available as specialist options. Use subAgentId: "inline" for the default inline child; pass one of these exact IDs only when that specialist is a better fit:',
...availableSubAgents.map((subAgent) => {
- const description = subAgent.description ? ` - ${subAgent.description}` : '';
- return `- ${subAgent.id}: ${subAgent.name}${description}`;
+ const useWhen = subAgent.useWhen ? `\n Use when: ${subAgent.useWhen}` : '';
+ return `- ${subAgent.id}: ${subAgent.name}${useWhen}`;
}),
];
}
diff --git a/packages/@n8n/api-types/src/agents/__tests__/sanitize-agent-json-config.test.ts b/packages/@n8n/api-types/src/agents/__tests__/sanitize-agent-json-config.test.ts
index 978dd5d2836..1c257ace3e4 100644
--- a/packages/@n8n/api-types/src/agents/__tests__/sanitize-agent-json-config.test.ts
+++ b/packages/@n8n/api-types/src/agents/__tests__/sanitize-agent-json-config.test.ts
@@ -50,7 +50,13 @@ describe('sanitizeAgentJsonConfig', () => {
...baseConfig,
subAgents: {
maxChildren: 3,
- agents: [{ agentId: 'agent-2', legacyLabel: 'Research' }],
+ agents: [
+ {
+ agentId: 'agent-2',
+ useWhen: 'Use for research tasks.',
+ legacyLabel: 'Research',
+ },
+ ],
legacyBudget: 99,
},
config: {
@@ -65,7 +71,7 @@ describe('sanitizeAgentJsonConfig', () => {
...baseConfig,
subAgents: {
maxChildren: 3,
- agents: [{ agentId: 'agent-2' }],
+ agents: [{ agentId: 'agent-2', useWhen: 'Use for research tasks.' }],
},
config: {
toolCallConcurrency: 2,
@@ -372,7 +378,7 @@ describe('sanitizeAgentJsonConfig', () => {
...baseConfig,
subAgents: {
maxChildren: 3,
- agents: [{ agentId: 'agent-2' }],
+ agents: [{ agentId: 'agent-2', useWhen: 'Use for billing questions.' }],
},
});
const parsed = AgentJsonConfigSchema.safeParse(sanitized);
@@ -380,6 +386,35 @@ describe('sanitizeAgentJsonConfig', () => {
expect(parsed.success).toBe(true);
if (!parsed.success) return;
expect(parsed.data.subAgents?.maxChildren).toBe(3);
+ expect(parsed.data.subAgents?.agents?.[0]?.useWhen).toBe('Use for billing questions.');
+ });
+
+ it.each(['', ' ', 'Too short'])('accepts optional sub-agent useWhen value %p', (useWhen) => {
+ const sanitized = sanitizeAgentJsonConfig({
+ ...baseConfig,
+ subAgents: {
+ agents: [{ agentId: 'agent-2', useWhen }],
+ },
+ });
+
+ expect(sanitized).toEqual({
+ ...baseConfig,
+ subAgents: {
+ agents: [{ agentId: 'agent-2', useWhen }],
+ },
+ });
+ expect(AgentJsonConfigSchema.safeParse(sanitized).success).toBe(true);
+ });
+
+ it('rejects sub-agent useWhen values over 512 characters', () => {
+ expect(
+ AgentJsonConfigSchema.safeParse({
+ ...baseConfig,
+ subAgents: {
+ agents: [{ agentId: 'agent-2', useWhen: 'a'.repeat(513) }],
+ },
+ }).success,
+ ).toBe(false);
});
it.each([SUB_AGENT_MAX_CHILDREN_MIN, SUB_AGENT_MAX_CHILDREN_MAX])(
@@ -411,7 +446,7 @@ describe('sanitizeAgentJsonConfig', () => {
...baseConfig,
subAgents: {
maxChildren: 3,
- agents: [{ agentId: 'agent-2' }],
+ agents: [{ agentId: 'agent-2', useWhen: 'Use for billing escalations.' }],
modelsByDifficulty: {
low: { model: 'openai/gpt-4o-mini', credential: 'cred-openai' },
medium: { model: 'anthropic/claude-haiku-4-5', credential: 'cred-anthropic' },
@@ -533,7 +568,7 @@ describe('sanitizeAgentJsonConfig', () => {
...baseConfig,
subAgents: {
maxChildren: 4,
- agents: [{ agentId: 'agent-2' }],
+ agents: [{ agentId: 'agent-2', useWhen: 'Use for support escalation.' }],
modelsByDifficulty: {
high: { model: 'anthropic/claude-sonnet-4-5', credential: 'cred-anthropic' },
},
@@ -544,7 +579,7 @@ describe('sanitizeAgentJsonConfig', () => {
...baseConfig,
subAgents: {
maxChildren: 4,
- agents: [{ agentId: 'agent-2' }],
+ agents: [{ agentId: 'agent-2', useWhen: 'Use for support escalation.' }],
modelsByDifficulty: {
high: { model: 'anthropic/claude-sonnet-4-5', credential: 'cred-anthropic' },
},
diff --git a/packages/@n8n/api-types/src/agents/agent-json-config.schema.ts b/packages/@n8n/api-types/src/agents/agent-json-config.schema.ts
index 67015a36745..e121b7359de 100644
--- a/packages/@n8n/api-types/src/agents/agent-json-config.schema.ts
+++ b/packages/@n8n/api-types/src/agents/agent-json-config.schema.ts
@@ -69,9 +69,14 @@ const WebSearchConfigSchema = z.object({
credential: z.string().optional(),
});
-const SubAgentConfigSchema = z.object({
- agentId: z.string().trim().min(1),
-});
+export const SUB_AGENT_USE_WHEN_MAX_LENGTH = 512;
+
+const SubAgentConfigSchema = z
+ .object({
+ agentId: z.string().trim().min(1),
+ useWhen: z.string().trim().max(SUB_AGENT_USE_WHEN_MAX_LENGTH).optional(),
+ })
+ .strict();
export const SUB_AGENT_TASK_DIFFICULTIES = ['low', 'medium', 'high'] as const;
const SubAgentTaskDifficultySchema = z.enum(SUB_AGENT_TASK_DIFFICULTIES);
diff --git a/packages/cli/src/modules/agents/__tests__/agent-config.service.test.ts b/packages/cli/src/modules/agents/__tests__/agent-config.service.test.ts
index a6332ba492c..53e271686d4 100644
--- a/packages/cli/src/modules/agents/__tests__/agent-config.service.test.ts
+++ b/packages/cli/src/modules/agents/__tests__/agent-config.service.test.ts
@@ -278,13 +278,17 @@ describe('AgentConfigService', () => {
...baseConfig,
subAgents: {
maxChildren: 3,
- agents: [{ agentId: 'missing-agent' }, { agentId: 'agent-2' }, { agentId: 'agent-2' }],
+ agents: [
+ { agentId: 'missing-agent', useWhen: 'Use for missing work.' },
+ { agentId: 'agent-2', useWhen: 'Use for billing escalations.' },
+ { agentId: 'agent-2', useWhen: 'Use for duplicate work.' },
+ ],
},
});
expect((agentRepository.save.mock.calls[0][0] as Agent).schema?.subAgents).toEqual({
maxChildren: 3,
- agents: [{ agentId: 'agent-2' }],
+ agents: [{ agentId: 'agent-2', useWhen: 'Use for billing escalations.' }],
});
expect(
agentRepository.findByIdAndProjectId.mock.calls.filter(([id]) => id === 'agent-2'),
@@ -293,14 +297,16 @@ describe('AgentConfigService', () => {
await expect(
service.updateConfig(agentId, projectId, {
...baseConfig,
- subAgents: { agents: [{ agentId: 'agent-3' }] },
+ subAgents: {
+ agents: [{ agentId: 'agent-3', useWhen: 'Use for unpublished work.' }],
+ },
}),
).rejects.toThrow('must be published');
await expect(
service.updateConfig(agentId, projectId, {
...baseConfig,
- subAgents: { agents: [{ agentId }] },
+ subAgents: { agents: [{ agentId, useWhen: 'Use for self-delegation.' }] },
}),
).rejects.toThrow('cannot use itself');
});
diff --git a/packages/cli/src/modules/agents/__tests__/agent-json-config.test.ts b/packages/cli/src/modules/agents/__tests__/agent-json-config.test.ts
index 9f0b494c35f..f0b3f0f8393 100644
--- a/packages/cli/src/modules/agents/__tests__/agent-json-config.test.ts
+++ b/packages/cli/src/modules/agents/__tests__/agent-json-config.test.ts
@@ -36,7 +36,7 @@ describe('AgentJsonConfigSchema — skill refs', () => {
});
describe('AgentJsonConfigSchema — subAgents', () => {
- it('accepts saved agent references', () => {
+ it('accepts legacy saved agent references without useWhen routing guidance', () => {
const parsed = AgentJsonConfigSchema.safeParse({
...baseConfig,
subAgents: { agents: [{ agentId: 'agent-1' }] },
@@ -44,6 +44,40 @@ describe('AgentJsonConfigSchema — subAgents', () => {
expect(parsed.success).toBe(true);
});
+ it('accepts saved agent references with useWhen routing guidance', () => {
+ const parsed = AgentJsonConfigSchema.safeParse({
+ ...baseConfig,
+ subAgents: {
+ agents: [
+ {
+ agentId: 'agent-1',
+ useWhen: 'Use for billing-policy questions and invoice investigations.',
+ },
+ ],
+ },
+ });
+
+ expect(parsed.success).toBe(true);
+ });
+
+ it.each(['', ' ', 'Too short'])('accepts optional useWhen value %p', (useWhen) => {
+ const parsed = AgentJsonConfigSchema.safeParse({
+ ...baseConfig,
+ subAgents: { agents: [{ agentId: 'agent-1', useWhen }] },
+ });
+
+ expect(parsed.success).toBe(true);
+ });
+
+ it('rejects useWhen values over 512 characters', () => {
+ const parsed = AgentJsonConfigSchema.safeParse({
+ ...baseConfig,
+ subAgents: { agents: [{ agentId: 'agent-1', useWhen: 'a'.repeat(513) }] },
+ });
+
+ expect(parsed.success).toBe(false);
+ });
+
it('accepts an empty saved-agent reference list', () => {
expect(
AgentJsonConfigSchema.safeParse({ ...baseConfig, subAgents: { agents: [] } }).success,
diff --git a/packages/cli/src/modules/agents/__tests__/agent-telemetry.test.ts b/packages/cli/src/modules/agents/__tests__/agent-telemetry.test.ts
index 35df8ad454d..5d7289b6aa3 100644
--- a/packages/cli/src/modules/agents/__tests__/agent-telemetry.test.ts
+++ b/packages/cli/src/modules/agents/__tests__/agent-telemetry.test.ts
@@ -41,7 +41,7 @@ describe('agent telemetry', () => {
'openai.web_search': { enabled: true },
},
subAgents: {
- agents: [{ agentId: 'agent-2' }],
+ agents: [{ agentId: 'agent-2', useWhen: 'Use for research tasks.' }],
},
config: {
webSearch: { enabled: true },
diff --git a/packages/cli/src/modules/agents/__tests__/agents-service-reconstruct-gating.test.ts b/packages/cli/src/modules/agents/__tests__/agents-service-reconstruct-gating.test.ts
index 26c9a6f1094..16eb41dff88 100644
--- a/packages/cli/src/modules/agents/__tests__/agents-service-reconstruct-gating.test.ts
+++ b/packages/cli/src/modules/agents/__tests__/agents-service-reconstruct-gating.test.ts
@@ -87,6 +87,7 @@ function makeReconstructionService(
modules: string[] = [],
overrides: {
logger?: Logger;
+ agentRepository?: AgentRepository;
agentsConfig?: Partial;
n8nCheckpointStorage?: N8NCheckpointStorage;
} = {},
@@ -99,7 +100,7 @@ function makeReconstructionService(
outboundHttp.transport.mockReturnValue(transport);
return new AgentRuntimeReconstructionService(
overrides.logger ?? mock(),
- mock(),
+ overrides.agentRepository ?? mock(),
mock(),
mock(),
mock(),
@@ -222,7 +223,9 @@ describe('AgentRuntimeReconstructionService.reconstructFromAgentEntity — sub-a
},
{
name: 'saved-agent references',
- subAgents: { agents: [{ agentId: 'agent-2' }] },
+ subAgents: {
+ agents: [{ agentId: 'agent-2', useWhen: 'Use for research tasks.' }],
+ },
},
])('always injects delegation tools for $name', async ({ subAgents }) => {
const { service, credentialProvider } = setup();
@@ -247,6 +250,18 @@ describe('AgentRuntimeReconstructionService.reconstructFromAgentEntity — sub-a
return undefined;
}
+ function getInjectedAvailableSubAgents() {
+ for (const call of builtAgent.tool.mock.calls) {
+ for (const item of Array.isArray(call[0]) ? call[0] : [call[0]]) {
+ const tool = item as BuiltTool;
+ if (tool.name === DELEGATE_SUB_AGENT_TOOL_NAME) {
+ return getInlineDelegateSubAgentToolOptions(tool)?.availableSubAgents;
+ }
+ }
+ }
+ return undefined;
+ }
+
function getInjectedInlineSubAgentModelsByDifficulty() {
for (const call of builtAgent.tool.mock.calls) {
for (const item of Array.isArray(call[0]) ? call[0] : [call[0]]) {
@@ -298,6 +313,37 @@ describe('AgentRuntimeReconstructionService.reconstructFromAgentEntity — sub-a
});
});
+ it('passes saved sub-agent useWhen guidance into delegate tool metadata', async () => {
+ const credentialProvider = mock();
+ const agentRepository = mock();
+ agentRepository.findByIdAndProjectId.mockResolvedValue({
+ id: 'agent-billing',
+ name: 'Billing Agent',
+ activeVersionId: 'version-billing',
+ } as Agent);
+ const service = makeReconstructionService([], { agentRepository });
+ const entity = makeAgentEntity(undefined, {
+ subAgents: {
+ agents: [
+ {
+ agentId: 'agent-billing',
+ useWhen: 'Use for invoice investigations and payment status checks.',
+ },
+ ],
+ },
+ });
+
+ await service.reconstructFromAgentEntity(entity, credentialProvider, 'user-1');
+
+ expect(getInjectedAvailableSubAgents()).toEqual([
+ {
+ id: 'agent-billing',
+ name: 'Billing Agent',
+ useWhen: 'Use for invoice investigations and payment status checks.',
+ },
+ ]);
+ });
+
it('resolves subAgents.modelsByDifficulty into delegate tool metadata', async () => {
const credentialProvider = mock();
credentialProvider.resolve.mockImplementation(async (credentialId: string) => {
diff --git a/packages/cli/src/modules/agents/agent-config.service.ts b/packages/cli/src/modules/agents/agent-config.service.ts
index e5e665bbd23..11a6b4ffdfa 100644
--- a/packages/cli/src/modules/agents/agent-config.service.ts
+++ b/packages/cli/src/modules/agents/agent-config.service.ts
@@ -23,7 +23,7 @@ import { AgentTaskRepository } from './repositories/agent-task.repository';
import { AgentRepository } from './repositories/agent.repository';
import { createAgentCredentialProvider } from './utils/agent-credential-provider';
import { markAgentDraftDirty } from './utils/agent-draft.utils';
-import { resolveUniqueSubAgents } from './utils/sub-agent-resolver';
+import { resolveUniqueSubAgents, type ResolvedSubAgentRef } from './utils/sub-agent-resolver';
@Service()
export class AgentConfigService {
@@ -256,7 +256,7 @@ export class AgentConfigService {
config: AgentJsonConfig,
entity: Agent,
existingTaskIds: ReadonlySet,
- ): Promise> {
+ ): Promise {
if (config.skills !== undefined) {
const skills = entity.skills ?? {};
config.skills = config.skills.filter((ref) => Boolean(skills[ref.id]));
@@ -277,22 +277,19 @@ export class AgentConfigService {
projectId: entity.projectId,
agentRepository: this.agentRepository,
});
- const existingSubAgentIds = new Set(
- resolvedSubAgents.filter(({ agent }) => agent !== null).map(({ agentId }) => agentId),
- );
config.subAgents.agents = resolvedSubAgents
- .filter(({ agentId }) => existingSubAgentIds.has(agentId))
- .map(({ agentId }) => ({ agentId }));
+ .filter(({ agent }) => agent !== null)
+ .map(({ agentId, useWhen }) => ({
+ agentId,
+ ...(useWhen ? { useWhen } : {}),
+ }));
return resolvedSubAgents;
}
return [];
}
- private validateSubAgentRefs(
- resolvedSubAgents: Array<{ agentId: string; agent: Agent | null }>,
- entity: Agent,
- ) {
+ private validateSubAgentRefs(resolvedSubAgents: ResolvedSubAgentRef[], entity: Agent) {
for (const { agentId, agent } of resolvedSubAgents) {
if (!agent) continue;
if (agentId === entity.id) {
diff --git a/packages/cli/src/modules/agents/agent-runtime-reconstruction.service.ts b/packages/cli/src/modules/agents/agent-runtime-reconstruction.service.ts
index caef5554f9d..d1f5e5b4914 100644
--- a/packages/cli/src/modules/agents/agent-runtime-reconstruction.service.ts
+++ b/packages/cli/src/modules/agents/agent-runtime-reconstruction.service.ts
@@ -69,7 +69,7 @@ export type AgentRuntimeProfile = 'top-level' | 'sub-agent';
export interface SubAgentDelegationConfig {
sourcesById: Record;
- availableSubAgents: Array<{ id: string; name: string }>;
+ availableSubAgents: Array<{ id: string; name: string; useWhen?: string }>;
}
export interface ReconstructAgentRuntimeParams {
@@ -286,7 +286,7 @@ export class AgentRuntimeReconstructionService {
const sourcesById: Record = {};
const availableSubAgents: SubAgentDelegationConfig['availableSubAgents'] = [];
- for (const { agentId, agent } of await resolveUniqueSubAgents({
+ for (const { agentId, agent, useWhen } of await resolveUniqueSubAgents({
refs: configuredAgents,
projectId,
agentRepository: this.agentRepository,
@@ -297,6 +297,7 @@ export class AgentRuntimeReconstructionService {
availableSubAgents.push({
id: agentId,
name: agent.name,
+ ...(useWhen ? { useWhen } : {}),
});
}
diff --git a/packages/cli/src/modules/agents/builder/__tests__/agents-builder-prompts.test.ts b/packages/cli/src/modules/agents/builder/__tests__/agents-builder-prompts.test.ts
index ec629781927..e0d8ee7a368 100644
--- a/packages/cli/src/modules/agents/builder/__tests__/agents-builder-prompts.test.ts
+++ b/packages/cli/src/modules/agents/builder/__tests__/agents-builder-prompts.test.ts
@@ -43,6 +43,13 @@ describe('sub-agent skill availability', () => {
expect(skill?.instructions).toContain('`allowMultiple: true`');
expect(skill?.instructions).toContain('subAgentId: "inline"');
expect(skill?.instructions).toContain('`subAgents.maxChildren`');
+ expect(skill?.instructions).toContain(
+ '{ "agentId": "", "useWhen": "Use for ..." }',
+ );
+ expect(skill?.instructions).toContain(
+ 'If it is unclear when a selected saved subagent should be used, ask the user',
+ );
+ expect(skill?.instructions).toContain('Do not write vague values');
expect(skill?.instructions).toContain(
`from ${SUB_AGENT_MAX_CHILDREN_MIN} to ${SUB_AGENT_MAX_CHILDREN_MAX}`,
);
diff --git a/packages/cli/src/modules/agents/builder/agents-builder-tools.service.ts b/packages/cli/src/modules/agents/builder/agents-builder-tools.service.ts
index a0d66593d10..1ea45daca30 100644
--- a/packages/cli/src/modules/agents/builder/agents-builder-tools.service.ts
+++ b/packages/cli/src/modules/agents/builder/agents-builder-tools.service.ts
@@ -592,7 +592,8 @@ export class AgentsBuilderToolsService {
.description(
'List published agents in the same project that can be added to the target agent as subagents. ' +
'Excludes the target agent itself and unpublished agents. Use before asking the user which ' +
- 'subagents to add. Returned `agentId` values are the only valid values to write into `subAgents.agents[].agentId`.',
+ 'subagents to add. Returned `agentId` values are the only valid values to write into `subAgents.agents[].agentId`; ' +
+ 'write parent-owned routing guidance into `subAgents.agents[].useWhen`; ask a follow-up first when it is unclear when that parent should use the subagent.',
)
.input(z.object({}))
.handler(async () => {
diff --git a/packages/cli/src/modules/agents/builder/skills/sub-agents.skill.ts b/packages/cli/src/modules/agents/builder/skills/sub-agents.skill.ts
index eaea8327fc0..99f51fe2102 100644
--- a/packages/cli/src/modules/agents/builder/skills/sub-agents.skill.ts
+++ b/packages/cli/src/modules/agents/builder/skills/sub-agents.skill.ts
@@ -48,30 +48,47 @@ subagent.
added. Do not write agent ids from memory, prose, or user-entered free text.
2. If published agents are available and the user has not named exact agents,
call \`ask_question\` with \`allowMultiple: true\`. Use each option's
- \`value\` as the returned \`agentId\`, and include descriptions when present.
+ \`value\` as the returned \`agentId\`.
3. If no published agents are available, do not configure saved subagents.
Inline delegation still works without saved-agent refs.
-4. Call \`read_config\`.
-5. Patch selected saved agents into \`subAgents.agents\` as
- \`{ "agentId": "" }\`. Avoid duplicates.
+4. Determine the parent-owned routing guidance for each selected saved
+ subagent. Store it as \`useWhen\`, for example
+ \`{ "agentId": "", "useWhen": "Use for billing-policy questions and invoice investigations." }\`.
+5. If it is unclear when a selected saved subagent should be used, ask the user
+ a follow-up before patching \`subAgents.agents\`. Do not invent vague routing
+ guidance.
+6. Call \`read_config\`.
+7. Patch selected saved agents into \`subAgents.agents\`. Avoid duplicates.
Example patch flow:
1. \`list_sub_agents()\`.
2. If it returns one or more agents and the user has not named exact ones, call
\`ask_question({ allowMultiple: true, ... })\` with those agents as options.
-3. \`read_config()\`.
-4. \`patch_config(...)\` adding selected \`{ "agentId": "" }\`
- refs to \`/subAgents/agents\`.
+3. If the user's request does not make the routing rule clear, ask when each
+ selected saved subagent should be used.
+4. \`read_config()\`.
+5. \`patch_config(...)\` adding selected
+ \`{ "agentId": "", "useWhen": "Use for ..." }\` refs to
+ \`/subAgents/agents\`.
## Rules
- If the resumed values include text that is not one of the listed agent ids,
do not persist it as an agent id; ask a follow-up.
+- \`useWhen\` is relationship-specific routing guidance owned by this parent
+ config. It is not the child agent's global description.
+- Write \`useWhen\` only when the intended routing is clear from the user's
+ request or an explicit follow-up answer.
+- Every new or updated saved subagent ref must include \`useWhen\`.
+- Good \`useWhen\` values are concrete and intent-oriented, such as
+ "Use for invoice investigations and payment status checks."
+- Do not write vague values such as "Use when helpful", "Use for tasks", or
+ "Use for this agent."
- Do not add custom tools, custom instructions, or custom schema fields to
simulate subagents.
- Preserve existing \`subAgents.agents\` refs unless the user explicitly asks to
- change saved subagents.
+ change saved subagents. Preserve existing \`useWhen\` values when keeping refs.
## Inline model mappings
@@ -99,6 +116,7 @@ Example shape:
- Inline delegation still works even when \`subAgents.agents\` is absent.
- Saved refs use only returned same-project published agent ids.
+- New or updated saved refs include concrete \`useWhen\` guidance.
- Any \`subAgents.maxChildren\` value stays within ${SUB_AGENT_MAX_CHILDREN_MIN} to ${SUB_AGENT_MAX_CHILDREN_MAX}.`,
};
}
diff --git a/packages/cli/src/modules/agents/sub-agents/__tests__/delegate-sub-agent-tool.test.ts b/packages/cli/src/modules/agents/sub-agents/__tests__/delegate-sub-agent-tool.test.ts
index 9cc9eb63cc8..95023809e77 100644
--- a/packages/cli/src/modules/agents/sub-agents/__tests__/delegate-sub-agent-tool.test.ts
+++ b/packages/cli/src/modules/agents/sub-agents/__tests__/delegate-sub-agent-tool.test.ts
@@ -186,7 +186,13 @@ describe('createN8nDelegateSubAgentTool', () => {
sourcesById: {
'agent-2': selectedSource,
},
- availableSubAgents: [{ id: 'agent-2', name: 'Research Agent' }],
+ availableSubAgents: [
+ {
+ id: 'agent-2',
+ name: 'Research Agent',
+ useWhen: 'Use for research tasks.',
+ },
+ ],
projectId,
userId,
credentialProvider,
diff --git a/packages/cli/src/modules/agents/sub-agents/delegate-sub-agent-tool.ts b/packages/cli/src/modules/agents/sub-agents/delegate-sub-agent-tool.ts
index aca7ffddb4d..90218e28739 100644
--- a/packages/cli/src/modules/agents/sub-agents/delegate-sub-agent-tool.ts
+++ b/packages/cli/src/modules/agents/sub-agents/delegate-sub-agent-tool.ts
@@ -18,7 +18,7 @@ import type {
export interface CreateN8nDelegateSubAgentToolOptions extends SubAgentForegroundRunContext {
runner: SubAgentForegroundRunner;
sourcesById: Record;
- availableSubAgents?: Array<{ id: string; name: string }>;
+ availableSubAgents?: Array<{ id: string; name: string; useWhen?: string }>;
policy?: SubAgentRunPolicy;
inlineSubAgentModelsByDifficulty?: Partial>;
resolveInlineSubAgentProviderTools?: InlineSubAgentProviderToolsResolver;
diff --git a/packages/cli/src/modules/agents/utils/sub-agent-resolver.ts b/packages/cli/src/modules/agents/utils/sub-agent-resolver.ts
index 5c7b04072a9..f57a119c292 100644
--- a/packages/cli/src/modules/agents/utils/sub-agent-resolver.ts
+++ b/packages/cli/src/modules/agents/utils/sub-agent-resolver.ts
@@ -1,24 +1,33 @@
import type { Agent } from '../entities/agent.entity';
import type { AgentRepository } from '../repositories/agent.repository';
+export type SubAgentConfigRef = { agentId: string; useWhen?: string };
+
+export type ResolvedSubAgentRef = SubAgentConfigRef & { agent: Agent | null };
+
export async function resolveUniqueSubAgents({
refs,
projectId,
agentRepository,
}: {
- refs: Array<{ agentId: string }>;
+ refs: SubAgentConfigRef[];
projectId: string;
agentRepository: Pick;
-}): Promise> {
+}): Promise {
const seen = new Set();
- const resolved: Array<{ agentId: string; agent: Agent | null }> = [];
- for (const { agentId } of refs) {
+ const resolved: ResolvedSubAgentRef[] = [];
+ for (const ref of refs) {
+ const { agentId } = ref;
if (seen.has(agentId)) continue;
seen.add(agentId);
- resolved.push({
+ const resolvedRef: ResolvedSubAgentRef = {
agentId,
agent: await agentRepository.findByIdAndProjectId(agentId, projectId),
- });
+ };
+ if (ref.useWhen !== undefined) {
+ resolvedRef.useWhen = ref.useWhen;
+ }
+ resolved.push(resolvedRef);
}
return resolved;
}
diff --git a/packages/frontend/@n8n/i18n/src/locales/en.json b/packages/frontend/@n8n/i18n/src/locales/en.json
index 2e61f4cab99..dfee4089edf 100644
--- a/packages/frontend/@n8n/i18n/src/locales/en.json
+++ b/packages/frontend/@n8n/i18n/src/locales/en.json
@@ -6711,11 +6711,18 @@
"agents.builder.files.size.bytes": "{bytes} B",
"agents.builder.files.size.kilobytes": "{kilobytes} KB",
"agents.builder.files.size.megabytes": "{megabytes} MB",
- "agents.builder.subAgents.title": "Sub-agents",
+ "agents.builder.subAgents.title": "Agents",
"agents.builder.subAgents.description": "This agent can delegate focused subtasks. Add published agents from this project when you want reusable specialists.",
+ "agents.builder.subAgents.settings.title": "Sub-agent settings",
+ "agents.builder.subAgents.settings.description": "Configure delegation limits and inline sub-agent model choices.",
"agents.builder.subAgents.add": "Add agent",
"agents.builder.subAgents.loadError": "Could not load project agents",
"agents.builder.subAgents.remove": "Remove {name}",
+ "agents.builder.subAgents.useWhen.label": "When should this agent be used?",
+ "agents.builder.subAgents.useWhen.hint": "Tell the parent agent when to delegate work to this agent.",
+ "agents.builder.subAgents.useWhen.placeholder": "Use for billing questions, invoice investigations, and payment status checks.",
+ "agents.builder.subAgents.useWhen.characterCount": "{count}/{max}",
+ "agents.builder.subAgents.useWhen.validation.maxLength": "Use when must be {max} characters or less.",
"agents.builder.subAgents.maxChildren.label": "Max parallel sub-agents",
"agents.builder.subAgents.maxChildren.hint": "How many sub-agents this agent can run at the same time ({min}-{max}).",
"agents.builder.subAgents.modelsByDifficulty.title": "Models by difficulty",
@@ -6727,13 +6734,17 @@
"agents.builder.subAgents.modelsByDifficulty.high.label": "High",
"agents.builder.subAgents.modelsByDifficulty.high.description": "Complex tasks that need deeper reasoning or detailed synthesis.",
"agents.builder.subAgents.modelsByDifficulty.clear": "Use parent model",
- "agents.builder.subAgents.modal.title": "Add sub-agents",
+ "agents.builder.subAgents.modal.title": "Sub-agents",
"agents.builder.subAgents.modal.description": "Select published agents from this project to let this agent delegate work to them.",
"agents.builder.subAgents.modal.selectAgent": "Select {name}",
"agents.builder.subAgents.modal.empty.title": "No agents to add",
"agents.builder.subAgents.modal.empty.description": "Published agents from this project show here. Already added agents are hidden.",
+ "agents.builder.subAgents.modal.search.placeholder": "Search agents",
+ "agents.builder.subAgents.modal.noResults.title": "No matching agents",
+ "agents.builder.subAgents.modal.noResults.description": "Try another search term.",
"agents.builder.subAgents.modal.cancel": "Cancel",
- "agents.builder.subAgents.modal.add": "Add agents",
+ "agents.builder.subAgents.modal.add": "Add agent",
+ "agents.builder.subAgents.modal.remove": "Remove sub agent",
"agents.builder.memory.recallModel.label": "Memory model",
"agents.builder.memory.recallModel.hint": "Choose the model that creates, reviews, and retrieves memories. Uses the agent model by default.",
"agents.builder.episodicMemoryCredentialModal.title": "Episodic Memory",
diff --git a/packages/frontend/editor-ui/src/features/agents/__tests__/AgentCapabilitiesSection.test.ts b/packages/frontend/editor-ui/src/features/agents/__tests__/AgentCapabilitiesSection.test.ts
index f1fafc36c17..0e7b6864a82 100644
--- a/packages/frontend/editor-ui/src/features/agents/__tests__/AgentCapabilitiesSection.test.ts
+++ b/packages/frontend/editor-ui/src/features/agents/__tests__/AgentCapabilitiesSection.test.ts
@@ -1,11 +1,12 @@
import { flushPromises, mount } from '@vue/test-utils';
import type { AgentJsonTaskConfig, AgentTaskDto } from '@n8n/api-types';
+import { ref } from 'vue';
import { beforeEach, describe, expect, it, vi } from 'vitest';
import type { SimplifiedNodeType } from '@/Interface';
import AgentCapabilitiesSection from '../components/AgentCapabilitiesSection.vue';
-import type { AgentJsonConfig, AgentJsonToolRef, CustomToolEntry } from '../types';
-import { AGENT_TASK_MODAL_KEY } from '../constants';
+import type { AgentJsonConfig, AgentJsonToolRef, AgentResource, CustomToolEntry } from '../types';
+import { AGENT_SUB_AGENTS_MODAL_KEY, AGENT_TASK_MODAL_KEY } from '../constants';
const getNodeType = vi.fn<(type: string, version?: number) => SimplifiedNodeType | null>(
() => null,
@@ -42,6 +43,20 @@ vi.mock('@/app/stores/ui.store', () => ({
useUIStore: () => ({ openModalWithData: openModalWithDataSpy }),
}));
+const showErrorSpy = vi.fn();
+vi.mock('@/app/composables/useToast', () => ({
+ useToast: () => ({ showError: showErrorSpy }),
+}));
+
+const projectAgentsListRef = ref([]);
+const ensureProjectAgentsLoadedSpy = vi.fn();
+vi.mock('../composables/useProjectAgentsList', () => ({
+ useProjectAgentsList: () => ({
+ list: projectAgentsListRef,
+ ensureLoaded: ensureProjectAgentsLoadedSpy,
+ }),
+}));
+
const getAgentTasksSpy = vi.fn();
vi.mock('../composables/useAgentApi', () => ({
getAgentTasks: (...args: unknown[]) => getAgentTasksSpy(...args),
@@ -64,7 +79,10 @@ function mountSection(
customTools: Record = {},
config: AgentJsonConfig | null = null,
taskRefs: AgentJsonTaskConfig[] = [],
+ projectAgents: AgentResource[] = [],
) {
+ projectAgentsListRef.value = projectAgents;
+
return mount(AgentCapabilitiesSection, {
props: {
config,
@@ -112,6 +130,24 @@ function makeTask(overrides: Partial = {}): AgentTaskDto {
};
}
+function makeAgent(overrides: Partial = {}): AgentResource {
+ return {
+ id: 'agent-2',
+ name: 'Helper Agent',
+ projectId: 'project-id',
+ resourceType: 'agent',
+ isCompiled: true,
+ createdAt: '2026-01-01T00:00:00.000Z',
+ updatedAt: '2026-01-01T00:00:00.000Z',
+ versionId: 'version-2',
+ activeVersionId: 'version-2',
+ tools: {},
+ skills: {},
+ activeVersion: null,
+ ...overrides,
+ };
+}
+
function taskRef(id = 'task-1', enabled = true): AgentJsonTaskConfig {
return { type: 'task', id, enabled };
}
@@ -132,6 +168,8 @@ describe('AgentCapabilitiesSection', () => {
beforeEach(() => {
vi.clearAllMocks();
getAgentTasksSpy.mockResolvedValue([]);
+ projectAgentsListRef.value = [];
+ ensureProjectAgentsLoadedSpy.mockResolvedValue([]);
});
it('formats node and custom tool chip labels for display', () => {
@@ -307,6 +345,202 @@ describe('AgentCapabilitiesSection', () => {
expect(wrapper.findAll('[data-testid="agent-capabilities-tool-row"]').length).toBe(1);
});
+ it('renders selected sub-agents as chips and opens the add modal from capabilities', async () => {
+ const config: AgentJsonConfig = {
+ name: 'Test Agent',
+ model: '',
+ instructions: '',
+ tools: [],
+ subAgents: {
+ maxChildren: 7,
+ agents: [{ agentId: 'agent-2', useWhen: 'Use for billing support requests.' }],
+ },
+ };
+ const wrapper = mountSection(
+ [],
+ {},
+ config,
+ [],
+ [
+ makeAgent(),
+ makeAgent({ id: 'agent-3', name: 'Research Agent', versionId: 'version-3' }),
+ makeAgent({
+ id: 'agent-4',
+ name: 'Draft Agent',
+ versionId: 'version-4',
+ activeVersionId: null,
+ }),
+ makeAgent({ id: 'agent-id', name: 'Current Agent', versionId: 'version-current' }),
+ ],
+ );
+ await flushPromises();
+
+ expect(wrapper.text()).toContain('Helper Agent');
+ expect(wrapper.findAll('[data-testid="agent-capabilities-sub-agent-row"]').length).toBe(1);
+
+ await wrapper.find('[data-testid="agent-capabilities-add-sub-agent"]').trigger('click');
+ await flushPromises();
+
+ expect(openModalWithDataSpy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ name: AGENT_SUB_AGENTS_MODAL_KEY,
+ data: expect.objectContaining({
+ agents: [{ id: 'agent-3', name: 'Research Agent' }],
+ }),
+ }),
+ );
+
+ const modalCall = openModalWithDataSpy.mock.calls[0]?.[0] as {
+ data: { onConfirm: (payload: { agentId: string; useWhen?: string }) => void };
+ };
+ modalCall.data.onConfirm({
+ agentId: 'agent-3',
+ useWhen: 'Use for research requests.',
+ });
+
+ expect(wrapper.emitted('update:config')?.[0]).toEqual([
+ {
+ subAgents: {
+ maxChildren: 7,
+ agents: [
+ { agentId: 'agent-2', useWhen: 'Use for billing support requests.' },
+ { agentId: 'agent-3', useWhen: 'Use for research requests.' },
+ ],
+ },
+ },
+ ]);
+ });
+
+ it('opens an existing sub-agent chip for editing and removal', async () => {
+ const config: AgentJsonConfig = {
+ name: 'Test Agent',
+ model: '',
+ instructions: '',
+ tools: [],
+ subAgents: {
+ maxChildren: 7,
+ agents: [
+ { agentId: 'agent-2', useWhen: 'Use for billing support requests.' },
+ { agentId: 'agent-3', useWhen: 'Use for research tasks.' },
+ ],
+ },
+ };
+ const wrapper = mountSection(
+ [],
+ {},
+ config,
+ [],
+ [makeAgent(), makeAgent({ id: 'agent-3', name: 'Research Agent', versionId: 'version-3' })],
+ );
+ await flushPromises();
+
+ await wrapper.findAll('[data-testid="agent-capabilities-sub-agent-row"]')[0].trigger('click');
+
+ expect(openModalWithDataSpy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ name: AGENT_SUB_AGENTS_MODAL_KEY,
+ data: expect.objectContaining({
+ selectedAgent: { id: 'agent-2', name: 'Helper Agent' },
+ useWhen: 'Use for billing support requests.',
+ }),
+ }),
+ );
+
+ const modalCall = openModalWithDataSpy.mock.calls[0]?.[0] as {
+ data: {
+ onConfirm: (payload: { agentId: string; useWhen?: string }) => void;
+ onRemove: (agentId: string) => void;
+ };
+ };
+ modalCall.data.onConfirm({
+ agentId: 'agent-2',
+ });
+
+ expect(wrapper.emitted('update:config')?.[0]).toEqual([
+ {
+ subAgents: {
+ maxChildren: 7,
+ agents: [
+ { agentId: 'agent-2' },
+ { agentId: 'agent-3', useWhen: 'Use for research tasks.' },
+ ],
+ },
+ },
+ ]);
+
+ modalCall.data.onRemove('agent-2');
+
+ expect(wrapper.emitted('update:config')?.[1]).toEqual([
+ {
+ subAgents: {
+ maxChildren: 7,
+ agents: [{ agentId: 'agent-3', useWhen: 'Use for research tasks.' }],
+ },
+ },
+ ]);
+ });
+
+ it('keeps legacy sub-agent refs without useWhen editable and removable', async () => {
+ const config: AgentJsonConfig = {
+ name: 'Test Agent',
+ model: '',
+ instructions: '',
+ tools: [],
+ subAgents: {
+ maxChildren: 7,
+ agents: [{ agentId: 'agent-2' }],
+ },
+ };
+ const wrapper = mountSection([], {}, config, [], [makeAgent()]);
+ await flushPromises();
+
+ expect(wrapper.text()).toContain('Helper Agent');
+ expect(wrapper.findAll('[data-testid="agent-capabilities-sub-agent-row"]').length).toBe(1);
+
+ await wrapper.find('[data-testid="agent-capabilities-sub-agent-row"]').trigger('click');
+
+ expect(openModalWithDataSpy).toHaveBeenCalledWith(
+ expect.objectContaining({
+ name: AGENT_SUB_AGENTS_MODAL_KEY,
+ data: expect.objectContaining({
+ selectedAgent: { id: 'agent-2', name: 'Helper Agent' },
+ useWhen: '',
+ }),
+ }),
+ );
+
+ const modalCall = openModalWithDataSpy.mock.calls[0]?.[0] as {
+ data: {
+ onConfirm: (payload: { agentId: string; useWhen?: string }) => void;
+ onRemove: (agentId: string) => void;
+ };
+ };
+ modalCall.data.onConfirm({
+ agentId: 'agent-2',
+ useWhen: 'Use for billing support requests.',
+ });
+
+ expect(wrapper.emitted('update:config')?.[0]).toEqual([
+ {
+ subAgents: {
+ maxChildren: 7,
+ agents: [{ agentId: 'agent-2', useWhen: 'Use for billing support requests.' }],
+ },
+ },
+ ]);
+
+ modalCall.data.onRemove('agent-2');
+
+ expect(wrapper.emitted('update:config')?.[1]).toEqual([
+ {
+ subAgents: {
+ maxChildren: 7,
+ agents: [],
+ },
+ },
+ ]);
+ });
+
it('renders task chips from task refs and fetched bodies', async () => {
getAgentTasksSpy.mockResolvedValue([makeTask()]);
diff --git a/packages/frontend/editor-ui/src/features/agents/__tests__/AgentSubAgentsModal.spec.ts b/packages/frontend/editor-ui/src/features/agents/__tests__/AgentSubAgentsModal.spec.ts
new file mode 100644
index 00000000000..ee1d3f398f8
--- /dev/null
+++ b/packages/frontend/editor-ui/src/features/agents/__tests__/AgentSubAgentsModal.spec.ts
@@ -0,0 +1,256 @@
+/* eslint-disable import-x/no-extraneous-dependencies -- test-only Vue mounting */
+import { mount } from '@vue/test-utils';
+import { describe, it, expect, vi, beforeEach } from 'vitest';
+
+import AgentSubAgentsModal from '../components/AgentSubAgentsModal.vue';
+
+const closeModalMock = vi.fn();
+
+vi.mock('@n8n/i18n', () => ({
+ useI18n: () => ({
+ baseText: (key: string, options?: { interpolate?: Record }) =>
+ ({
+ 'agents.builder.subAgents.modal.title': 'Sub-agents',
+ 'agents.builder.subAgents.modal.description': 'Select published agents',
+ 'agents.builder.subAgents.modal.selectAgent': `Select ${options?.interpolate?.name ?? ''}`,
+ 'agents.builder.subAgents.modal.empty.title': 'No agents to add',
+ 'agents.builder.subAgents.modal.empty.description': 'Published agents show here',
+ 'agents.builder.subAgents.modal.search.placeholder': 'Search agents',
+ 'agents.builder.subAgents.modal.noResults.title': 'No matching agents',
+ 'agents.builder.subAgents.modal.noResults.description': 'Try another search term.',
+ 'agents.builder.subAgents.modal.add': 'Add agent',
+ 'agents.builder.subAgents.modal.remove': 'Remove sub agent',
+ 'agents.builder.subAgents.useWhen.label': 'When should this agent be used?',
+ 'agents.builder.subAgents.useWhen.hint': 'Tell the parent agent when to delegate work.',
+ 'agents.builder.subAgents.useWhen.placeholder': 'Use for billing questions',
+ 'agents.builder.subAgents.useWhen.characterCount': '0/512',
+ 'agents.builder.subAgents.useWhen.validation.maxLength': `Use when must be ${options?.interpolate?.max ?? ''} characters or less`,
+ 'generic.back': 'Back',
+ 'generic.cancel': 'Cancel',
+ 'generic.save': 'Save',
+ })[key] ?? key,
+ }),
+}));
+
+vi.mock('@/app/stores/ui.store', () => ({
+ useUIStore: () => ({ closeModal: closeModalMock }),
+}));
+
+vi.mock('@/app/components/Modal.vue', () => ({
+ default: {
+ name: 'Modal',
+ props: ['name', 'width', 'customClass'],
+ template:
+ '',
+ },
+}));
+
+vi.mock('@n8n/design-system', () => ({
+ N8nActionBox: {
+ props: ['heading', 'description'],
+ template: '