🧶 fix: Untangle Parallel Subagent Descriptor Paths (#15492)

* fix: isolate parallel subagent descriptor walks

* fix: preserve canonical subagent config identity

* fix: separate subagent graph assembly from path validation
This commit is contained in:
Danny Avila 2026-09-01 21:48:15 -04:00 • committed by GitHub
parent 6d56d3b6fe
commit 6372cb08ee
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
3 changed files with 111 additions and 7 deletions

View file

@ -1199,13 +1199,10 @@ const initializeClient = async ({
subagentGraphIds.add(subagentId);
}
countExpandedSubagentDescriptor(subagentId);
const existingChildren = await buildLazySubagentDescriptors(
existing,
depth + 1,
nextAncestors,
);
existing.lazySubagentConfigs = existingChildren.filter((child) => child.configId);
existing.subagentAgentConfigs = existingChildren.filter((child) => !child.configId);
/** Every initialized config is expanded independently in `rootSubagentConfigs`.
* Descend here only to enforce path-sensitive depth and size limits; mutating the
* shared object from this ancestor-specific walk would race its root expansion. */
await buildLazySubagentDescriptors(existing, depth + 1, nextAncestors);
return existing;
}
const metadata = await loadSubagentMetadata(subagentId);

View file

@ -2479,6 +2479,45 @@ describe('initializeClient — subagent loading', () => {
expect(agentClientArgs.agentConfigs.has(HANDOFF_AND_SUB_ID)).toBe(true);
});
it('preserves shared initialized agent children across parallel walkers', async () => {
const leftId = 'agent_parallel_left';
const rightId = 'agent_parallel_right';
const sharedId = 'agent_parallel_shared';
const primaryConfig = makePrimaryConfig({
subagents: { enabled: true, allowSelf: false, agent_ids: [leftId, rightId] },
});
const leftConfig = makeNestedSubagentConfig(leftId, [sharedId]);
const rightConfig = makeNestedSubagentConfig(rightId, [sharedId]);
const sharedConfig = makeNestedSubagentConfig(sharedId, [leftId]);
mockInitializeAgent.mockResolvedValue(primaryConfig);
processAddedConvo.mockImplementationOnce(async ({ agentConfigs }) => {
agentConfigs.set(leftId, leftConfig);
agentConfigs.set(rightId, rightConfig);
agentConfigs.set(sharedId, sharedConfig);
return { userMCPAuthMap: undefined };
});
await initializeClient({
req: makeSubagentReq(),
res: {},
signal: new AbortController().signal,
endpointOption: makeEndpointOption(),
});
const subagentsById = new Map(
agentClientArgs.agent.subagentAgentConfigs.map((config) => [config.id, config]),
);
const left = subagentsById.get(leftId);
const right = subagentsById.get(rightId);
const leftShared = left.subagentAgentConfigs[0];
const rightShared = right.subagentAgentConfigs[0];
expect(leftShared).toBe(sharedConfig);
expect(rightShared).toBe(sharedConfig);
expect(leftShared.subagentAgentConfigs.map((child) => child.id)).toEqual([leftId]);
expect(rightShared.subagentAgentConfigs.map((child) => child.id)).toEqual([leftId]);
expect(agentClientArgs.agentConfigs.get(sharedId)).toBe(sharedConfig);
});
it('clears subagents config on primary when the capability is disabled', async () => {
/** Admin can turn subagents off at the endpoint level even if an agent was
* configured for them. The primary's `subagents` field should be

View file

@ -2022,6 +2022,74 @@ describe('subagentConfigs', () => {
});
});
it('prunes shared-agent cycles per traversal path without dropping valid edges', async () => {
const left = makeAgent({
id: 'agent_left',
name: 'Left',
subagents: { enabled: true, allowSelf: false, agent_ids: ['agent_shared'] },
}) as TestRunAgent;
const right = makeAgent({
id: 'agent_right',
name: 'Right',
subagents: { enabled: true, allowSelf: false, agent_ids: ['agent_shared'] },
}) as TestRunAgent;
const shared = makeAgent({
id: 'agent_shared',
name: 'Shared',
subagents: { enabled: true, allowSelf: false, agent_ids: ['agent_left'] },
}) as TestRunAgent;
left.subagentAgentConfigs = [shared];
right.subagentAgentConfigs = [shared];
shared.subagentAgentConfigs = [left];
const agents = await callAndCapture({
agents: [
makeAgent({
subagents: {
enabled: true,
allowSelf: false,
agent_ids: ['agent_left', 'agent_right'],
},
subagentAgentConfigs: [left, right],
}),
],
});
const rootConfigs = agents[0].subagentConfigs as Parameters<typeof buildChildInputs>[0][];
const rootConfigsByType = new Map(rootConfigs.map((config) => [config.type, config]));
const leftConfig = rootConfigsByType.get('agent_left');
const rightConfig = rootConfigsByType.get('agent_right');
if (!leftConfig || !rightConfig) {
throw new Error('Expected both root subagent configs');
}
const leftInputs = buildChildInputs(leftConfig, 'agent_left', MAX_SUBAGENT_DEPTH);
const rightInputs = buildChildInputs(rightConfig, 'agent_right', MAX_SUBAGENT_DEPTH);
const leftShared = leftInputs.subagentConfigs?.[0];
const rightShared = rightInputs.subagentConfigs?.[0];
if (
!leftShared ||
!rightShared ||
leftInputs.maxSubagentDepth == null ||
rightInputs.maxSubagentDepth == null
) {
throw new Error('Expected both shared subagent configs');
}
const leftSharedInputs = buildChildInputs(
leftShared,
'agent_shared',
leftInputs.maxSubagentDepth,
);
const rightSharedInputs = buildChildInputs(
rightShared,
'agent_shared',
rightInputs.maxSubagentDepth,
);
expect(leftSharedInputs.subagentConfigs).toBeUndefined();
expect(rightSharedInputs.subagentConfigs).toHaveLength(1);
expect(rightSharedInputs.subagentConfigs?.[0]).toMatchObject({ type: 'agent_left' });
});
it('combines self-spawn and explicit subagents when both enabled', async () => {
const child = makeAgent({ id: 'agent_child', name: 'Helper' });
const agents = await callAndCapture({