From 4c0ac8844cb17ace5120877edf25a54f3ac8be9a Mon Sep 17 00:00:00 2001 From: Danny Avila Date: Wed, 22 Jul 2026 09:30:53 -0400 Subject: [PATCH] =?UTF-8?q?=F0=9F=AA=86=20fix:=20Preserve=20Nested=20Subag?= =?UTF-8?q?ent=20Delegation=20(#14392)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Sien Nuyens --- .../__tests__/run-summarization.test.ts | 32 ++++++++++++++++++- packages/api/src/agents/run.ts | 4 +++ 2 files changed, 35 insertions(+), 1 deletion(-) diff --git a/packages/api/src/agents/__tests__/run-summarization.test.ts b/packages/api/src/agents/__tests__/run-summarization.test.ts index 5f8fc78eda..83ee5f6959 100644 --- a/packages/api/src/agents/__tests__/run-summarization.test.ts +++ b/packages/api/src/agents/__tests__/run-summarization.test.ts @@ -81,7 +81,7 @@ jest.mock('~/agents/checkpointer', () => ({ getAgentCheckpointer: jest.fn().mockResolvedValue({}), })); -import { Run } from '@librechat/agents'; +import { Run, buildChildInputs } from '@librechat/agents'; /** Minimal RunAgent factory */ function makeAgent( @@ -1020,6 +1020,36 @@ describe('subagentConfigs', () => { expect(configs[0].self).toBeUndefined(); }); + it('preserves explicit nested subagents across the SDK child graph boundary', async () => { + const grandchild = makeAgent({ id: 'agent_grandchild', name: 'Grandchild' }); + const child = makeAgent({ + id: 'agent_child', + name: 'Child', + subagents: { enabled: true, allowSelf: false, agent_ids: ['agent_grandchild'] }, + subagentAgentConfigs: [grandchild], + }); + const agents = await callAndCapture({ + agents: [ + makeAgent({ + subagents: { enabled: true, allowSelf: false, agent_ids: ['agent_child'] }, + subagentAgentConfigs: [child], + }), + ], + }); + + expect(agents[0].maxSubagentDepth).toBe(MAX_SUBAGENT_DEPTH); + const childConfig = (agents[0].subagentConfigs as Parameters[0][])[0]; + expect(childConfig.allowNested).toBe(true); + + const childInputs = buildChildInputs(childConfig, 'agent_child', MAX_SUBAGENT_DEPTH); + expect(childInputs.maxSubagentDepth).toBe(MAX_SUBAGENT_DEPTH - 1); + expect(childInputs.subagentConfigs).toHaveLength(1); + expect(childInputs.subagentConfigs?.[0]).toMatchObject({ + type: 'agent_grandchild', + allowNested: true, + }); + }); + it('combines self-spawn and explicit subagents when both enabled', async () => { const child = makeAgent({ id: 'agent_child', name: 'Helper' }); const agents = await callAndCapture({ diff --git a/packages/api/src/agents/run.ts b/packages/api/src/agents/run.ts index f063fecdf0..e69142d36c 100644 --- a/packages/api/src/agents/run.ts +++ b/packages/api/src/agents/run.ts @@ -991,6 +991,8 @@ function buildSubagentConfigs( child.description ?? `Delegate a subtask to the ${child.name ?? child.id} agent in an isolated context.`, agentInputs: childInputs, + /** Preserve the child's resolved subagent configs when the SDK builds its isolated graph. */ + allowNested: true, /** Honor each child agent's own resolved recursion limit. */ maxTurns: resolveSubagentMaxTurns(agentsEConfig, child), }); @@ -1350,6 +1352,8 @@ export async function createRun({ ); if (subagentConfigs.length > 0) { agentInput.subagentConfigs = subagentConfigs; + /** Seed the SDK countdown that bounds nested delegation across isolated child graphs. */ + agentInput.maxSubagentDepth = MAX_SUBAGENT_DEPTH; } agentInputs.push(agentInput); }