Skip to content

Commit f50a99e

Browse files
24anishaAnisha AgarwalVritant BhardwajrootAnisha Agarwal
authored
Search Subagent support (#2736)
* search subagent tool added * cleaning up description of search subagent * additional changes * update linting issue * Exit early on search subagent call * search subagent tool added * cleaning up description of search subagent * additional changes * update linting issue * Add fixes for subagent * describe read file tool in its prompt * fixing copilot cli issues? * resolve merge conflicts with main * explicit any pt 2 * update explicit any to unknown * demo * updating prompt to include description * fixing newline bug * added correct input params for subagent * update to add final turn warning injection * code snippet hydration * adding details to toolMetadata (untested) * commented out until testing * remove exit from main PR * actuallly terminate loop * end loop after round is added and run * remove early exit handling * add experiment flags * update code to check for exp + auto mode * update to only use gpt 5 mini for search subagent * Update src/extension/prompts/node/agent/searchSubagentPrompt.tsx Co-authored-by: Copilot <[email protected]> * Update docs/tools.md Co-authored-by: Copilot <[email protected]> * Update package.nls.json Co-authored-by: Copilot <[email protected]> * update tests to handle new prompts * Apply suggestion from @Copilot Co-authored-by: Copilot <[email protected]> * deleting vestigial file * fix merge conflict * update to default to using the main agent model as fallback for subagent, rather than hardcoding gpt4.1 * remove extra whitespace * remove runSubagent from package.json and update prompt for final snippet clarity * reset default to false * add clearer injection prompt * updating to work with main branch changes * rewrite search subagent to have its own tool calling loop file + ensure no nested loops with codebase * remove copilot-added search subagent doc * update toolResultMessage * handle exp configuration for search subagent in the right place * use searchSubagentLoop instead of subagentLoop * update to be in line with main * remove CCA agents * Some minor cleanup * Update import for ChatToolInvocationPart in SearchSubagentTool * Replace inSubAgent flag with subAgentInvocationId for tool calling loop checks --------- Co-authored-by: Anisha Agarwal <[email protected]> Co-authored-by: Vritant Bhardwaj <[email protected]> Co-authored-by: root <root@perflens-vm7.e4rbrboag42enkzhvodo1frcqh.xx.internal.cloudapp.net> Co-authored-by: Anisha Agarwal <[email protected]> Co-authored-by: Zhichao Li <[email protected]> Co-authored-by: vritant24 <[email protected]> Co-authored-by: Copilot <[email protected]> Co-authored-by: bhavyaus <[email protected]>
1 parent 8365320 commit f50a99e

30 files changed

+461
-5
lines changed

package.json

Lines changed: 46 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -164,6 +164,40 @@
164164
]
165165
}
166166
},
167+
{
168+
"name": "search_subagent",
169+
"toolReferenceName": "searchSubagent",
170+
"displayName": "%copilot.tools.searchSubagent.name%",
171+
"icon": "$(search)",
172+
"canBeReferencedInPrompt": true,
173+
"userDescription": "%copilot.tools.searchSubagent.description%",
174+
"modelDescription": "Launch an iterative search-focused subagent that orchestrates semantic_search, grep_search, file_search and read_file tool calls to explore and gather relevant workspace code for a natural language query.\n CALL THIS FOR ANY TASK THAT REQUIRES CODEBASE EXPLORATION!\n\nRemember:\n1. Perform tool calls in parallel whenever possible\n2. Avoid redundant calls -- don't repeat identical searches.\n3. Stop once you have high-confidence, non-duplicative snippets covering the query scope.\n\nReturns: A list of relevant files/snippet locations in the workspace.\n\nInput fields:\n- query: Natural language description of what to search for.\n- description: Short user-visible invocation message. \n- details: 2-3 sentences detailing the objective of the search agent.",
175+
"tags": [
176+
"vscode_codesearch"
177+
],
178+
"inputSchema": {
179+
"type": "object",
180+
"properties": {
181+
"query": {
182+
"type": "string",
183+
"description": "Natural language description of what to search for."
184+
},
185+
"description": {
186+
"type": "string",
187+
"description": "A short (3-5 word) description of the task."
188+
},
189+
"details": {
190+
"type": "string",
191+
"description": "A more detailed description of the objective for the search subagent. This helps the sub-agent remain on task and understand its purpose."
192+
}
193+
},
194+
"required": [
195+
"query",
196+
"description",
197+
"details"
198+
]
199+
}
200+
},
167201
{
168202
"name": "copilot_searchWorkspaceSymbols",
169203
"toolReferenceName": "symbols",
@@ -1234,7 +1268,8 @@
12341268
"listDirectory",
12351269
"searchResults",
12361270
"textSearch",
1237-
"usages"
1271+
"usages",
1272+
"searchSubagent"
12381273
]
12391274
},
12401275
{
@@ -3921,6 +3956,16 @@
39213956
"advanced",
39223957
"experimental"
39233958
]
3959+
},
3960+
"github.copilot.chat.searchSubagent.enabled": {
3961+
"type": "boolean",
3962+
"default": false,
3963+
"markdownDescription": "%github.copilot.config.searchSubagent.enabled%",
3964+
"tags": [
3965+
"advanced",
3966+
"experimental",
3967+
"onExp"
3968+
]
39243969
}
39253970
}
39263971
}

package.nls.json

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -377,5 +377,10 @@
377377
"github.copilot.config.githubMcpServer.enabled": "Enable built-in support for the GitHub MCP Server.",
378378
"github.copilot.config.githubMcpServer.toolsets": "Specify toolsets to use from the GitHub MCP Server. [Learn more](https://aka.ms/vscode-gh-mcp-toolsets).",
379379
"github.copilot.config.githubMcpServer.readonly": "Enable read-only mode for the GitHub MCP Server. When enabled, only read tools are available. [Learn more](https://aka.ms/vscode-gh-mcp-readonly).",
380-
"github.copilot.config.githubMcpServer.lockdown": "Enable lockdown mode for the GitHub MCP Server. When enabled, hides public issue details created by users without push access. [Learn more](https://aka.ms/vscode-gh-mcp-lockdown)."
380+
"github.copilot.config.githubMcpServer.lockdown": "Enable lockdown mode for the GitHub MCP Server. When enabled, hides public issue details created by users without push access. [Learn more](https://aka.ms/vscode-gh-mcp-lockdown).",
381+
"copilot.tools.runSubagent.name": "Run Subagent",
382+
"copilot.tools.runSubagent.description": "Runs a task within an isolated subagent context. Enables efficient organization of tasks and context window management.",
383+
"copilot.tools.searchSubagent.name": "Search Subagent",
384+
"copilot.tools.searchSubagent.description": "Launch an iterative search-focused subagent to find relevant code in your workspace.",
385+
"github.copilot.config.searchSubagent.enabled": "Enable the search subagent tool for iterative code exploration in the workspace."
381386
}

src/extension/intents/node/agentIntent.ts

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@ import { BudgetExceededError } from '@vscode/prompt-tsx/dist/base/materialized';
1010
import type * as vscode from 'vscode';
1111
import { ChatLocation, ChatResponse } from '../../../platform/chat/common/commonTypes';
1212
import { ConfigKey, IConfigurationService } from '../../../platform/configuration/common/configurationService';
13-
import { isAnthropicFamily, modelCanUseApplyPatchExclusively, modelCanUseReplaceStringExclusively, modelSupportsApplyPatch, modelSupportsMultiReplaceString, modelSupportsReplaceString, modelSupportsSimplifiedApplyPatchInstructions } from '../../../platform/endpoint/common/chatModelCapabilities';
13+
import { isAnthropicFamily, isGptFamily, modelCanUseApplyPatchExclusively, modelCanUseReplaceStringExclusively, modelSupportsApplyPatch, modelSupportsMultiReplaceString, modelSupportsReplaceString, modelSupportsSimplifiedApplyPatchInstructions } from '../../../platform/endpoint/common/chatModelCapabilities';
1414
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
1515
import { IEnvService } from '../../../platform/env/common/envService';
1616
import { ILogService } from '../../../platform/log/common/logService';
@@ -88,6 +88,7 @@ export const getAgentTools = async (accessor: ServicesAccessor, request: vscode.
8888

8989
allowTools[ToolName.CoreRunTest] = await testService.hasAnyTests();
9090
allowTools[ToolName.CoreRunTask] = tasksService.getTasks().length > 0;
91+
allowTools[ToolName.SearchSubagent] = (isGptFamily(model) || isAnthropicFamily(model)) && configurationService.getExperimentBasedConfig(ConfigKey.Advanced.SearchSubagentToolEnabled, experimentationService);
9192

9293
if (model.family.includes('grok-code')) {
9394
allowTools[ToolName.CoreManageTodoList] = false;
Lines changed: 121 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,121 @@
1+
/*---------------------------------------------------------------------------------------------
2+
* Copyright (c) Microsoft Corporation. All rights reserved.
3+
* Licensed under the MIT License. See License.txt in the project root for license information.
4+
*--------------------------------------------------------------------------------------------*/
5+
6+
import { randomUUID } from 'crypto';
7+
import type { CancellationToken, ChatRequest, ChatResponseStream, LanguageModelToolInformation, Progress } from 'vscode';
8+
import { IAuthenticationChatUpgradeService } from '../../../platform/authentication/common/authenticationUpgrade';
9+
import { ChatLocation, ChatResponse } from '../../../platform/chat/common/commonTypes';
10+
import { IConfigurationService } from '../../../platform/configuration/common/configurationService';
11+
import { IEndpointProvider } from '../../../platform/endpoint/common/endpointProvider';
12+
import { ILogService } from '../../../platform/log/common/logService';
13+
import { IRequestLogger } from '../../../platform/requestLogger/node/requestLogger';
14+
import { IExperimentationService } from '../../../platform/telemetry/common/nullExperimentationService';
15+
import { ITelemetryService } from '../../../platform/telemetry/common/telemetry';
16+
import { IInstantiationService } from '../../../util/vs/platform/instantiation/common/instantiation';
17+
import { ChatResponseProgressPart, ChatResponseReferencePart } from '../../../vscodeTypes';
18+
import { IToolCallingLoopOptions, ToolCallingLoop, ToolCallingLoopFetchOptions } from '../../intents/node/toolCallingLoop';
19+
import { SearchSubagentPrompt } from '../../prompts/node/agent/searchSubagentPrompt';
20+
import { PromptRenderer } from '../../prompts/node/base/promptRenderer';
21+
import { ToolName } from '../../tools/common/toolNames';
22+
import { IToolsService } from '../../tools/common/toolsService';
23+
import { IBuildPromptContext } from '../common/intents';
24+
import { IBuildPromptResult } from './intents';
25+
26+
export interface ISearchSubagentToolCallingLoopOptions extends IToolCallingLoopOptions {
27+
request: ChatRequest;
28+
location: ChatLocation;
29+
promptText: string;
30+
}
31+
32+
export class SearchSubagentToolCallingLoop extends ToolCallingLoop<ISearchSubagentToolCallingLoopOptions> {
33+
34+
public static readonly ID = 'searchSubagentTool';
35+
36+
constructor(
37+
options: ISearchSubagentToolCallingLoopOptions,
38+
@IInstantiationService private readonly instantiationService: IInstantiationService,
39+
@ILogService logService: ILogService,
40+
@IRequestLogger requestLogger: IRequestLogger,
41+
@IEndpointProvider private readonly endpointProvider: IEndpointProvider,
42+
@IToolsService private readonly toolsService: IToolsService,
43+
@IAuthenticationChatUpgradeService authenticationChatUpgradeService: IAuthenticationChatUpgradeService,
44+
@ITelemetryService telemetryService: ITelemetryService,
45+
@IConfigurationService configurationService: IConfigurationService,
46+
@IExperimentationService experimentationService: IExperimentationService,
47+
) {
48+
super(options, instantiationService, endpointProvider, logService, requestLogger, authenticationChatUpgradeService, telemetryService, configurationService, experimentationService);
49+
}
50+
51+
protected override createPromptContext(availableTools: LanguageModelToolInformation[], outputStream: ChatResponseStream | undefined): IBuildPromptContext {
52+
const context = super.createPromptContext(availableTools, outputStream);
53+
if (context.tools) {
54+
context.tools = {
55+
...context.tools,
56+
toolReferences: [],
57+
subAgentInvocationId: randomUUID()
58+
};
59+
}
60+
context.query = this.options.promptText;
61+
return context;
62+
}
63+
64+
private async getEndpoint(request: ChatRequest) {
65+
let endpoint = await this.endpointProvider.getChatEndpoint(this.options.request);
66+
if (!endpoint.supportsToolCalls) {
67+
endpoint = await this.endpointProvider.getChatEndpoint('gpt-4.1');
68+
}
69+
return endpoint;
70+
}
71+
72+
protected async buildPrompt(buildPromptContext: IBuildPromptContext, progress: Progress<ChatResponseReferencePart | ChatResponseProgressPart>, token: CancellationToken): Promise<IBuildPromptResult> {
73+
const endpoint = await this.getEndpoint(this.options.request);
74+
const renderer = PromptRenderer.create(
75+
this.instantiationService,
76+
endpoint,
77+
SearchSubagentPrompt,
78+
{
79+
promptContext: buildPromptContext
80+
}
81+
);
82+
return await renderer.render(progress, token);
83+
}
84+
85+
protected async getAvailableTools(): Promise<LanguageModelToolInformation[]> {
86+
const endpoint = await this.getEndpoint(this.options.request);
87+
const allTools = this.toolsService.getEnabledTools(this.options.request, endpoint);
88+
89+
// Only include tools relevant for search operations.
90+
// We include semantic_search (Codebase) and the basic search primitives.
91+
// The Codebase tool checks for inSubAgent context to prevent nested tool calling loops.
92+
const allowedSearchTools = new Set([
93+
ToolName.Codebase, // Semantic search
94+
ToolName.FindFiles,
95+
ToolName.FindTextInFiles,
96+
ToolName.ReadFile
97+
]);
98+
99+
return allTools.filter(tool => allowedSearchTools.has(tool.name as ToolName));
100+
}
101+
102+
protected async fetch({ messages, finishedCb, requestOptions }: ToolCallingLoopFetchOptions, token: CancellationToken): Promise<ChatResponse> {
103+
const endpoint = await this.getEndpoint(this.options.request);
104+
return endpoint.makeChatRequest2({
105+
debugName: SearchSubagentToolCallingLoop.ID,
106+
messages,
107+
finishedCb,
108+
location: this.options.location,
109+
requestOptions: {
110+
...requestOptions,
111+
temperature: 0
112+
},
113+
// This loop is inside a tool called from another request, so never user initiated
114+
userInitiatedRequest: false,
115+
telemetryProperties: {
116+
messageId: randomUUID(),
117+
messageSource: SearchSubagentToolCallingLoop.ID
118+
},
119+
}, token);
120+
}
121+
}

src/extension/prompts/node/agent/anthropicPrompts.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ class DefaultAnthropicAgentPrompt extends PromptElement<DefaultAgentPromptProps>
2323
<Tag name='instructions'>
2424
You are a highly sophisticated automated coding agent with expert-level knowledge across many different programming languages and frameworks.<br />
2525
The user will ask a question, or ask you to perform a task, and it may require lots of research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question.<br />
26+
{tools[ToolName.SearchSubagent] && <>For codebase exploration, prefer {ToolName.SearchSubagent} to search and gather data instead of directly calling {ToolName.FindTextInFiles}, {ToolName.Codebase} or {ToolName.FindFiles}.<br /></>}
2627
You will be given some context and attachments along with the user prompt. You can use them if they are relevant to the task, and ignore them if not.{tools[ToolName.ReadFile] && <> Some attachments may be summarized with omitted sections like `/* Lines 123-456 omitted */`. You can use the {ToolName.ReadFile} tool to read more context if needed. Never pass this omitted line marker to an edit tool.</>}<br />
2728
If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context that you have, make sure to keep them in mind when making changes.<br />
2829
{!this.props.codesearchMode && <>If the user wants you to implement a feature and they have not specified the files to edit, first break down the user's request into smaller concepts and think about the kinds of files you need to grasp each concept.<br /></>}
@@ -149,6 +150,7 @@ class Claude45DefaultPrompt extends PromptElement<DefaultAgentPromptProps> {
149150
No need to ask permission before using a tool.<br />
150151
NEVER say the name of a tool to a user. For example, instead of saying that you'll use the {ToolName.CoreRunInTerminal} tool, say "I'll run the command in a terminal".<br />
151152
If you think running multiple tools can answer the user's question, prefer calling them in parallel whenever possible{tools[ToolName.Codebase] && <>, but do not call {ToolName.Codebase} in parallel.</>}<br />
153+
{tools[ToolName.SearchSubagent] && <>For codebase exploration, prefer {ToolName.SearchSubagent} to search and gather data instead of directly calling {ToolName.FindTextInFiles}, {ToolName.Codebase} or {ToolName.FindFiles}.<br /></>}
152154
{tools[ToolName.ReadFile] && <>When using the {ToolName.ReadFile} tool, prefer reading a large section over calling the {ToolName.ReadFile} tool many times in sequence. You can also think of all the pieces you may be interested in and read them in parallel. Read large enough context to ensure you get what you need.<br /></>}
153155
{tools[ToolName.Codebase] && <>If {ToolName.Codebase} returns the full contents of the text files in the workspace, you have all the workspace context.<br /></>}
154156
{tools[ToolName.FindTextInFiles] && <>You can use the {ToolName.FindTextInFiles} to get an overview of a file by searching for a string within that one file, instead of using {ToolName.ReadFile} many times.<br /></>}

src/extension/prompts/node/agent/defaultAgentInstructions.tsx

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -115,6 +115,7 @@ export class DefaultAgentPrompt extends PromptElement<DefaultAgentPromptProps> {
115115
<Tag name='instructions'>
116116
You are a highly sophisticated automated coding agent with expert-level knowledge across many different programming languages and frameworks.<br />
117117
The user will ask a question, or ask you to perform a task, and it may require lots of research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question.<br />
118+
{tools[ToolName.SearchSubagent] && <>For any context searching, use {ToolName.SearchSubagent} to search and gather data instead of directly calling {ToolName.FindTextInFiles}, {ToolName.Codebase} or {ToolName.FindFiles}.<br /></>}
118119
You will be given some context and attachments along with the user prompt. You can use them if they are relevant to the task, and ignore them if not.{tools[ToolName.ReadFile] && <> Some attachments may be summarized with omitted sections like `/* Lines 123-456 omitted */`. You can use the {ToolName.ReadFile} tool to read more context if needed. Never pass this omitted line marker to an edit tool.</>}<br />
119120
If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context that you have, make sure to keep them in mind when making changes.<br />
120121
{!this.props.codesearchMode && <>If the user wants you to implement a feature and they have not specified the files to edit, first break down the user's request into smaller concepts and think about the kinds of files you need to grasp each concept.<br /></>}
@@ -132,6 +133,7 @@ export class DefaultAgentPrompt extends PromptElement<DefaultAgentPromptProps> {
132133
When using a tool, follow the JSON schema very carefully and make sure to include ALL required properties.<br />
133134
No need to ask permission before using a tool.<br />
134135
NEVER say the name of a tool to a user. For example, instead of saying that you'll use the {ToolName.CoreRunInTerminal} tool, say "I'll run the command in a terminal".<br />
136+
{tools[ToolName.SearchSubagent] && <>For any context searching, use {ToolName.SearchSubagent} to search and gather data instead of directly calling {ToolName.FindTextInFiles}, {ToolName.Codebase} or {ToolName.FindFiles}.<br /></>}
135137
If you think running multiple tools can answer the user's question, prefer calling them in parallel whenever possible{tools[ToolName.Codebase] && <>, but do not call {ToolName.Codebase} in parallel.</>}<br />
136138
{tools[ToolName.ReadFile] && <>When using the {ToolName.ReadFile} tool, prefer reading a large section over calling the {ToolName.ReadFile} tool many times in sequence. You can also think of all the pieces you may be interested in and read them in parallel. Read large enough context to ensure you get what you need.<br /></>}
137139
{tools[ToolName.Codebase] && <>If {ToolName.Codebase} returns the full contents of the text files in the workspace, you have all the workspace context.<br /></>}

src/extension/prompts/node/agent/openai/defaultOpenAIPrompt.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,7 @@ export class DefaultOpenAIAgentPrompt extends PromptElement<DefaultAgentPromptPr
3333
You are a highly sophisticated automated coding agent with expert-level knowledge across many different programming languages and frameworks.<br />
3434
The user will ask a question, or ask you to perform a task, and it may require lots of research to answer correctly. There is a selection of tools that let you perform actions or retrieve helpful context to answer the user's question.<br />
3535
<DefaultOpenAIKeepGoingReminder />
36+
{tools[ToolName.SearchSubagent] && <>For codebase exploration, prefer {ToolName.SearchSubagent} to search and gather data instead of directly calling {ToolName.FindTextInFiles}, {ToolName.Codebase} or {ToolName.FindFiles}.<br /></>}
3637
You will be given some context and attachments along with the user prompt. You can use them if they are relevant to the task, and ignore them if not.{tools[ToolName.ReadFile] && <> Some attachments may be summarized with omitted sections like `/* Lines 123-456 omitted */`. You can use the {ToolName.ReadFile} tool to read more context if needed. Never pass this omitted line marker to an edit tool.</>}<br />
3738
If you can infer the project type (languages, frameworks, and libraries) from the user's query or the context that you have, make sure to keep them in mind when making changes.<br />
3839
{!this.props.codesearchMode && <>If the user wants you to implement a feature and they have not specified the files to edit, first break down the user's request into smaller concepts and think about the kinds of files you need to grasp each concept.<br /></>}

src/extension/prompts/node/agent/openai/gpt51Prompt.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -144,6 +144,7 @@ class Gpt51Prompt extends PromptElement<DefaultAgentPromptProps> {
144144
- Working on the repo(s) in the current environment is allowed, even if they are proprietary.<br />
145145
- Analyzing code for vulnerabilities is allowed.<br />
146146
- Showing user code and tool call details is allowed.<br />
147+
{tools[ToolName.SearchSubagent] && <>For codebase exploration, prefer {ToolName.SearchSubagent} to search and gather data instead of directly calling {ToolName.FindTextInFiles}, {ToolName.Codebase} or {ToolName.FindFiles}.<br /></>}
147148
- Use the {ToolName.ApplyPatch} tool to edit files (NEVER try `applypatch` or `apply-patch`, only `apply_patch`): {`{"input":"*** Begin Patch\\n*** Update File: path/to/file.py\\n@@ def example():\\n- pass\\n+ return 123\\n*** End Patch"}`}.<br />
148149
<br />
149150
If completing the user's task requires writing or modifying files, your code and final answer should follow these coding guidelines, though user instructions (i.e. copilot-instructions.md) may override these guidelines:<br />

0 commit comments

Comments
 (0)