Skip to content

Commit cfbea10

Browse files
authored
Replace chat.subagents.maxDepth with chat.subagents.allowInvocationsF… (#304435)
Replace chat.subagents.maxDepth with chat.subagents.allowInvocationsFromSubagents
1 parent ab39f16 commit cfbea10

File tree

4 files changed

+32
-28
lines changed

4 files changed

+32
-28
lines changed

src/vs/workbench/contrib/chat/browser/chat.contribution.ts

Lines changed: 5 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1307,12 +1307,11 @@ configurationRegistry.registerConfiguration({
13071307
mode: 'auto'
13081308
}
13091309
},
1310-
[ChatConfiguration.SubagentsMaxDepth]: {
1311-
type: 'number',
1312-
description: nls.localize('chat.subagents.maxDepth', "Maximum nesting depth for subagents. Set to 0 to disable nested subagents. A subagent at this depth will not be able to launch further subagents."),
1313-
default: 0,
1314-
minimum: 0,
1315-
maximum: 20,
1310+
[ChatConfiguration.SubagentsAllowInvocationsFromSubagents]: {
1311+
type: 'boolean',
1312+
description: nls.localize('chat.subagents.allowInvocationsFromSubagents', "Allow subagents to invoke subagents."),
1313+
markdownDescription: nls.localize('chat.subagents.allowInvocationsFromSubagents.md', "Controls whether subagents can invoke other subagents. When enabled, nesting is limited to a maximum depth of 5."),
1314+
default: false,
13161315
experiment: {
13171316
mode: 'auto'
13181317
}

src/vs/workbench/contrib/chat/common/constants.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,7 +48,7 @@ export enum ChatConfiguration {
4848
ChatViewProgressBadgeEnabled = 'chat.viewProgressBadge.enabled',
4949
ChatContextUsageEnabled = 'chat.contextUsage.enabled',
5050
SubagentToolCustomAgents = 'chat.customAgentInSubagent.enabled',
51-
SubagentsMaxDepth = 'chat.subagents.maxDepth',
51+
SubagentsAllowInvocationsFromSubagents = 'chat.subagents.allowInvocationsFromSubagents',
5252
ShowCodeBlockProgressAnimation = 'chat.agent.codeBlockProgress',
5353
RestoreLastPanelSession = 'chat.restoreLastPanelSession',
5454
ExitAfterDelegation = 'chat.exitAfterDelegation',

src/vs/workbench/contrib/chat/common/tools/builtinTools/runSubagentTool.ts

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,8 @@ export interface IRunSubagentToolInputParams {
5858
agentName?: string;
5959
}
6060

61+
export const RUN_SUBAGENT_MAX_NESTING_DEPTH = 5;
62+
6163
export class RunSubagentTool extends Disposable implements IToolImpl {
6264

6365
static readonly Id = 'runSubagent';
@@ -250,7 +252,8 @@ export class RunSubagentTool extends Disposable implements IToolImpl {
250252
};
251253

252254
// Determine whether the subagent should be allowed to spawn its own subagents.
253-
const maxDepth = this.configurationService.getValue<number>(ChatConfiguration.SubagentsMaxDepth) ?? 0;
255+
const allowInvocationsFromSubagents = this.configurationService.getValue<boolean>(ChatConfiguration.SubagentsAllowInvocationsFromSubagents) ?? false;
256+
const maxDepth = allowInvocationsFromSubagents ? RUN_SUBAGENT_MAX_NESTING_DEPTH : 0;
254257
const sessionKey = invocation.context.sessionResource.toString();
255258
const currentDepth = this._sessionDepth.get(sessionKey) ?? 0;
256259
const depthAllowed = currentDepth + 1 <= maxDepth;
@@ -270,7 +273,7 @@ export class RunSubagentTool extends Disposable implements IToolImpl {
270273
modeTools['copilot_askQuestions'] = false;
271274

272275
if (maxDepth > 0) {
273-
this.logService.debug(`RunSubagentTool: Nested subagents enabling ${modeTools[RunSubagentTool.Id]}: session ${sessionKey}, currentDepth: ${currentDepth}, maxDepth: ${maxDepth}`);
276+
this.logService.debug(`RunSubagentTool: Nested subagents enabling ${modeTools[RunSubagentTool.Id]}: session ${sessionKey}, currentDepth: ${currentDepth}, maxDepth: ${maxDepth}, allowInvocationsFromSubagents: ${allowInvocationsFromSubagents}`);
274277
}
275278

276279
const variableSet = new ChatRequestVariableSet();

src/vs/workbench/contrib/chat/test/common/tools/builtinTools/runSubagentTool.test.ts

Lines changed: 21 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ import { URI } from '../../../../../../../base/common/uri.js';
99
import { ensureNoDisposablesAreLeakedInTestSuite } from '../../../../../../../base/test/common/utils.js';
1010
import { NullLogService } from '../../../../../../../platform/log/common/log.js';
1111
import { TestConfigurationService } from '../../../../../../../platform/configuration/test/common/testConfigurationService.js';
12-
import { RunSubagentTool } from '../../../../common/tools/builtinTools/runSubagentTool.js';
12+
import { RUN_SUBAGENT_MAX_NESTING_DEPTH, RunSubagentTool } from '../../../../common/tools/builtinTools/runSubagentTool.js';
1313
import { MockLanguageModelToolsService } from '../mockLanguageModelToolsService.js';
1414
import { IChatAgentHistoryEntry, IChatAgentRequest, IChatAgentResult, IChatAgentService, UserSelectedTools } from '../../../../common/participants/chatAgents.js';
1515
import { IChatProgress, IChatService } from '../../../../common/chatService/chatService.js';
@@ -502,12 +502,12 @@ suite('RunSubagentTool', () => {
502502
*/
503503
let callIdCounter = 0;
504504
function createInvokableTool(opts: {
505-
maxDepth: number;
505+
allowInvocationsFromSubagents: boolean;
506506
capturedRequests: IChatAgentRequest[];
507507
}) {
508508
const mockToolsService = testDisposables.add(new MockLanguageModelToolsService());
509509
const configService = new TestConfigurationService({
510-
[ChatConfiguration.SubagentsMaxDepth]: opts.maxDepth,
510+
[ChatConfiguration.SubagentsAllowInvocationsFromSubagents]: opts.allowInvocationsFromSubagents,
511511
});
512512
const promptsService = new MockPromptsService();
513513

@@ -565,9 +565,9 @@ suite('RunSubagentTool', () => {
565565
const countTokens = async () => 0;
566566
const noProgress: ToolProgress = { report() { } };
567567

568-
test('disables runSubagent tool when maxDepth is 0', async () => {
568+
test('disables runSubagent tool when nesting is disabled', async () => {
569569
const capturedRequests: IChatAgentRequest[] = [];
570-
const { tool } = createInvokableTool({ maxDepth: 0, capturedRequests });
570+
const { tool } = createInvokableTool({ allowInvocationsFromSubagents: false, capturedRequests });
571571
const sessionUri = URI.parse('test://session/depth0');
572572

573573
await tool.invoke(createInvocation(sessionUri), countTokens, noProgress, CancellationToken.None);
@@ -576,9 +576,9 @@ suite('RunSubagentTool', () => {
576576
assert.strictEqual(capturedRequests[0].userSelectedTools?.['runSubagent'], false);
577577
});
578578

579-
test('enables runSubagent tool at depth 0 when maxDepth >= 1', async () => {
579+
test('enables runSubagent tool at depth 0 when nesting is enabled', async () => {
580580
const capturedRequests: IChatAgentRequest[] = [];
581-
const { tool } = createInvokableTool({ maxDepth: 3, capturedRequests });
581+
const { tool } = createInvokableTool({ allowInvocationsFromSubagents: true, capturedRequests });
582582
const sessionUri = URI.parse('test://session/depth-enabled');
583583

584584
await tool.invoke(createInvocation(sessionUri), countTokens, noProgress, CancellationToken.None);
@@ -587,38 +587,40 @@ suite('RunSubagentTool', () => {
587587
assert.strictEqual(capturedRequests[0].userSelectedTools?.['runSubagent'], true);
588588
});
589589

590-
test('disables runSubagent tool when depth reaches maxDepth', async () => {
590+
test('disables runSubagent tool when depth reaches hard limit', async () => {
591591
const capturedRequests: IChatAgentRequest[] = [];
592592
const sessionUri = URI.parse('test://session/depth-limit');
593593

594-
// maxDepth=1, so the first invoke (depth 0→1) should allow nesting,
595-
// but the second invoke (depth 1→2) should not since 1+1 <= 1 is false.
596-
const { tool, mockChatAgentService } = createInvokableTool({ maxDepth: 1, capturedRequests });
594+
// When nesting is enabled, the tool enforces a hardcoded maximum depth of 5.
595+
// Simulate nested invocation until we exceed the limit and ensure it disables nesting.
596+
const { tool, mockChatAgentService } = createInvokableTool({ allowInvocationsFromSubagents: true, capturedRequests });
597597

598598
// Simulate nested invocation: the first invoke's invokeAgent callback
599599
// triggers a second invoke on the same tool (same session).
600600
capturedRequests.length = 0;
601+
let nestedInvocations = 0;
601602
mockChatAgentService.invokeAgent = async (_id: string, request: IChatAgentRequest) => {
602603
capturedRequests.push(request);
603-
// On the first call (depth 0), simulate a nested subagent call
604-
if (capturedRequests.length === 1) {
604+
// Keep nesting until we go beyond the hardcoded maxDepth
605+
if (nestedInvocations++ < RUN_SUBAGENT_MAX_NESTING_DEPTH + 1) {
605606
await tool.invoke(createInvocation(sessionUri), countTokens, noProgress, CancellationToken.None);
606607
}
607608
return {};
608609
};
609610

610611
await tool.invoke(createInvocation(sessionUri), countTokens, noProgress, CancellationToken.None);
611612

612-
assert.strictEqual(capturedRequests.length, 2);
613-
// First call at depth 0: should enable (0 + 1 <= 1)
614-
assert.strictEqual(capturedRequests[0].userSelectedTools?.['runSubagent'], true);
615-
// Second call at depth 1: should disable (1 + 1 <= 1 is false)
616-
assert.strictEqual(capturedRequests[1].userSelectedTools?.['runSubagent'], false);
613+
assert.ok(capturedRequests.length >= 2);
614+
// At depth 0..(maxDepth-1), nesting is allowed. Once depth reaches maxDepth, the next call should disable nesting.
615+
const enabledFlags = capturedRequests.map(r => r.userSelectedTools?.['runSubagent']);
616+
assert.strictEqual(enabledFlags[0], true);
617+
assert.strictEqual(enabledFlags[1], true);
618+
assert.strictEqual(enabledFlags[RUN_SUBAGENT_MAX_NESTING_DEPTH], false);
617619
});
618620

619621
test('depth is decremented after invoke completes', async () => {
620622
const capturedRequests: IChatAgentRequest[] = [];
621-
const { tool } = createInvokableTool({ maxDepth: 2, capturedRequests });
623+
const { tool } = createInvokableTool({ allowInvocationsFromSubagents: true, capturedRequests });
622624
const sessionUri = URI.parse('test://session/depth-decrement');
623625

624626
// First invoke

0 commit comments

Comments
 (0)