Skip to content

Commit e5617b2

Browse files
CopilotsunbryeCopilotvgrl
authored
Add "Use Copilot SDK" map category (#60275)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com> Co-authored-by: sunbrye <56200261+sunbrye@users.noreply.github.com> Co-authored-by: sunbrye <sunbrye@github.com> Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com> Co-authored-by: Vanessa <vgrl@github.com>
1 parent 1aed3b5 commit e5617b2

File tree

11 files changed

+2619
-1
lines changed

11 files changed

+2619
-1
lines changed

content/copilot/how-tos/copilot-sdk/index.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ versions:
66
feature: copilot
77
children:
88
- /sdk-getting-started
9+
- /use-copilot-sdk
910
- /use-hooks
1011
- /observability
1112
contentType: how-tos
Lines changed: 327 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,327 @@
1+
---
2+
title: Custom agents and sub-agent orchestration
3+
shortTitle: Custom agents
4+
intro: Define specialized agents with scoped tools and prompts, and let {% data variables.product.prodname_copilot_short %} orchestrate them as sub-agents within a single session.
5+
product: '{% data reusables.gated-features.copilot-sdk %}'
6+
versions:
7+
feature: copilot
8+
contentType: how-tos
9+
category:
10+
- Author and optimize with Copilot
11+
---
12+
13+
{% data reusables.copilot.copilot-sdk.technical-preview-note %}
14+
15+
Custom agents are lightweight agent definitions you attach to a session. Each agent has its own system prompt, tool restrictions, and optional MCP servers. When a user's request matches an agent's expertise, the {% data variables.copilot.copilot_sdk_short %} runtime automatically delegates to that agent as a sub-agent—running it in an isolated context while streaming lifecycle events back to the parent session. For a visual overview of the delegation flow, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/custom-agents.md#overview).
16+
17+
| Concept | Description |
18+
|---------|-------------|
19+
| **Custom agent** | A named agent config with its own prompt and tool set |
20+
| **Sub-agent** | A custom agent invoked by the runtime to handle part of a task |
21+
| **Inference** | The runtime's ability to auto-select an agent based on the user's intent |
22+
| **Parent session** | The session that spawned the sub-agent; receives all lifecycle events |
23+
24+
## Defining custom agents
25+
26+
Pass `customAgents` when creating a session. At minimum, each agent needs a `name` and `prompt`.
27+
28+
```typescript
29+
import { CopilotClient } from "@github/copilot-sdk";
30+
31+
const client = new CopilotClient();
32+
await client.start();
33+
34+
const session = await client.createSession({
35+
model: "gpt-4.1",
36+
customAgents: [
37+
{
38+
name: "researcher",
39+
displayName: "Research Agent",
40+
description: "Explores codebases and answers questions using read-only tools",
41+
tools: ["grep", "glob", "view"],
42+
prompt: "You are a research assistant. Analyze code and answer questions. Do not modify any files.",
43+
},
44+
{
45+
name: "editor",
46+
displayName: "Editor Agent",
47+
description: "Makes targeted code changes",
48+
tools: ["view", "edit", "bash"],
49+
prompt: "You are a code editor. Make minimal, surgical changes to files as requested.",
50+
},
51+
],
52+
onPermissionRequest: async () => ({ kind: "approved" }),
53+
});
54+
```
55+
56+
For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/custom-agents.md#defining-custom-agents).
57+
58+
## Configuration reference
59+
60+
| Property | Type | Required | Description |
61+
|----------|------|----------|-------------|
62+
| `name` | `string` || Unique identifier for the agent |
63+
| `displayName` | `string` | | Human-readable name shown in events |
64+
| `description` | `string` | | What the agent does—helps the runtime select it |
65+
| `tools` | `string[]` or `null` | | Names of tools the agent can use. `null` or omitted = all tools |
66+
| `prompt` | `string` || System prompt for the agent |
67+
| `mcpServers` | `object` | | MCP server configurations specific to this agent |
68+
| `infer` | `boolean` | | Whether the runtime can auto-select this agent (default: `true`) |
69+
70+
> [!TIP]
71+
> A good `description` helps the runtime match user intent to the right agent. Be specific about the agent's expertise and capabilities.
72+
73+
In addition to per-agent configuration, you can set `agent` on the **session config** to pre-select which custom agent is active when the session starts.
74+
75+
| Session config property | Type | Description |
76+
|-------------------------|------|-------------|
77+
| `agent` | `string` | Name of the custom agent to pre-select at session creation. Must match a `name` in `customAgents`. |
78+
79+
## Selecting an agent at session creation
80+
81+
You can pass `agent` in the session config to pre-select which custom agent should be active when the session starts. The value must match the `name` of one of the agents defined in `customAgents`.
82+
83+
```typescript
84+
const session = await client.createSession({
85+
customAgents: [
86+
{
87+
name: "researcher",
88+
prompt: "You are a research assistant. Analyze code and answer questions.",
89+
},
90+
{
91+
name: "editor",
92+
prompt: "You are a code editor. Make minimal, surgical changes.",
93+
},
94+
],
95+
agent: "researcher", // Pre-select the researcher agent
96+
});
97+
```
98+
99+
For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/custom-agents.md#selecting-an-agent-at-session-creation).
100+
101+
## How sub-agent delegation works
102+
103+
When you send a prompt to a session with custom agents, the runtime evaluates whether to delegate to a sub-agent:
104+
105+
1. **Intent matching**—The runtime analyzes the user's prompt against each agent's `name` and `description`
106+
1. **Agent selection**—If a match is found and `infer` is not `false`, the runtime selects the agent
107+
1. **Isolated execution**—The sub-agent runs with its own prompt and restricted tool set
108+
1. **Event streaming**—Lifecycle events (`subagent.started`, `subagent.completed`, etc.) stream back to the parent session
109+
1. **Result integration**—The sub-agent's output is incorporated into the parent agent's response
110+
111+
### Controlling inference
112+
113+
By default, all custom agents are available for automatic selection (`infer: true`). Set `infer: false` to prevent the runtime from auto-selecting an agent—useful for agents you only want invoked through explicit user requests:
114+
115+
```typescript
116+
{
117+
name: "dangerous-cleanup",
118+
description: "Deletes unused files and dead code",
119+
tools: ["bash", "edit", "view"],
120+
prompt: "You clean up codebases by removing dead code and unused files.",
121+
infer: false, // Only invoked when user explicitly asks for this agent
122+
}
123+
```
124+
125+
## Listening to sub-agent events
126+
127+
When a sub-agent runs, the parent session emits lifecycle events. Subscribe to these events to build UIs that visualize agent activity.
128+
129+
### Event types
130+
131+
| Event | Emitted when | Data |
132+
|-------|-------------|------|
133+
| `subagent.selected` | Runtime selects an agent for the task | `agentName`, `agentDisplayName`, `tools` |
134+
| `subagent.started` | Sub-agent begins execution | `toolCallId`, `agentName`, `agentDisplayName`, `agentDescription` |
135+
| `subagent.completed` | Sub-agent finishes successfully | `toolCallId`, `agentName`, `agentDisplayName` |
136+
| `subagent.failed` | Sub-agent encounters an error | `toolCallId`, `agentName`, `agentDisplayName`, `error` |
137+
| `subagent.deselected` | Runtime switches away from the sub-agent ||
138+
139+
### Subscribing to events
140+
141+
```typescript
142+
session.on((event) => {
143+
switch (event.type) {
144+
case "subagent.started":
145+
console.log(`▶ Sub-agent started: ${event.data.agentDisplayName}`);
146+
console.log(` Description: ${event.data.agentDescription}`);
147+
console.log(` Tool call ID: ${event.data.toolCallId}`);
148+
break;
149+
150+
case "subagent.completed":
151+
console.log(`✅ Sub-agent completed: ${event.data.agentDisplayName}`);
152+
break;
153+
154+
case "subagent.failed":
155+
console.log(`❌ Sub-agent failed: ${event.data.agentDisplayName}`);
156+
console.log(` Error: ${event.data.error}`);
157+
break;
158+
159+
case "subagent.selected":
160+
console.log(`🎯 Agent selected: ${event.data.agentDisplayName}`);
161+
console.log(` Tools: ${event.data.tools?.join(", ") ?? "all"}`);
162+
break;
163+
164+
case "subagent.deselected":
165+
console.log("↩ Agent deselected, returning to parent");
166+
break;
167+
}
168+
});
169+
170+
const response = await session.sendAndWait({
171+
prompt: "Research how authentication works in this codebase",
172+
});
173+
```
174+
175+
For examples in Python, Go, and .NET, see the [`github/copilot-sdk` repository](https://github.com/github/copilot-sdk/blob/main/docs/features/custom-agents.md#listening-to-sub-agent-events).
176+
177+
## Building an agent tree UI
178+
179+
Sub-agent events include `toolCallId` fields that let you reconstruct the execution tree. Here's a pattern for tracking agent activity:
180+
181+
```typescript
182+
interface AgentNode {
183+
toolCallId: string;
184+
name: string;
185+
displayName: string;
186+
status: "running" | "completed" | "failed";
187+
error?: string;
188+
startedAt: Date;
189+
completedAt?: Date;
190+
}
191+
192+
const agentTree = new Map<string, AgentNode>();
193+
194+
session.on((event) => {
195+
if (event.type === "subagent.started") {
196+
agentTree.set(event.data.toolCallId, {
197+
toolCallId: event.data.toolCallId,
198+
name: event.data.agentName,
199+
displayName: event.data.agentDisplayName,
200+
status: "running",
201+
startedAt: new Date(event.timestamp),
202+
});
203+
}
204+
205+
if (event.type === "subagent.completed") {
206+
const node = agentTree.get(event.data.toolCallId);
207+
if (node) {
208+
node.status = "completed";
209+
node.completedAt = new Date(event.timestamp);
210+
}
211+
}
212+
213+
if (event.type === "subagent.failed") {
214+
const node = agentTree.get(event.data.toolCallId);
215+
if (node) {
216+
node.status = "failed";
217+
node.error = event.data.error;
218+
node.completedAt = new Date(event.timestamp);
219+
}
220+
}
221+
222+
// Render your UI with the updated tree
223+
renderAgentTree(agentTree);
224+
});
225+
```
226+
227+
## Scoping tools per agent
228+
229+
Use the `tools` property to restrict which tools an agent can access. This is essential for security and for keeping agents focused:
230+
231+
```typescript
232+
const session = await client.createSession({
233+
customAgents: [
234+
{
235+
name: "reader",
236+
description: "Read-only exploration of the codebase",
237+
tools: ["grep", "glob", "view"], // No write access
238+
prompt: "You explore and analyze code. Never suggest modifications directly.",
239+
},
240+
{
241+
name: "writer",
242+
description: "Makes code changes",
243+
tools: ["view", "edit", "bash"], // Write access
244+
prompt: "You make precise code changes as instructed.",
245+
},
246+
{
247+
name: "unrestricted",
248+
description: "Full access agent for complex tasks",
249+
tools: null, // All tools available
250+
prompt: "You handle complex multi-step tasks using any available tools.",
251+
},
252+
],
253+
});
254+
```
255+
256+
> [!NOTE]
257+
> When `tools` is `null` or omitted, the agent inherits access to all tools configured on the session. Use explicit tool lists to enforce the principle of least privilege.
258+
259+
## Attaching MCP servers to agents
260+
261+
Each custom agent can have its own MCP (Model Context Protocol) servers, giving it access to specialized data sources:
262+
263+
```typescript
264+
const session = await client.createSession({
265+
customAgents: [
266+
{
267+
name: "db-analyst",
268+
description: "Analyzes database schemas and queries",
269+
prompt: "You are a database expert. Use the database MCP server to analyze schemas.",
270+
mcpServers: {
271+
"database": {
272+
command: "npx",
273+
args: ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"],
274+
},
275+
},
276+
},
277+
],
278+
});
279+
```
280+
281+
## Patterns and best practices
282+
283+
### Pair a researcher with an editor
284+
285+
A common pattern is to define a read-only researcher agent and a write-capable editor agent. The runtime delegates exploration tasks to the researcher and modification tasks to the editor:
286+
287+
```typescript
288+
customAgents: [
289+
{
290+
name: "researcher",
291+
description: "Analyzes code structure, finds patterns, and answers questions",
292+
tools: ["grep", "glob", "view"],
293+
prompt: "You are a code analyst. Thoroughly explore the codebase to answer questions.",
294+
},
295+
{
296+
name: "implementer",
297+
description: "Implements code changes based on analysis",
298+
tools: ["view", "edit", "bash"],
299+
prompt: "You make minimal, targeted code changes. Always verify changes compile.",
300+
},
301+
]
302+
```
303+
304+
### Keep agent descriptions specific
305+
306+
The runtime uses the `description` to match user intent. Vague descriptions lead to poor delegation:
307+
308+
```typescript
309+
// ❌ Too vague — runtime can't distinguish from other agents
310+
{ description: "Helps with code" }
311+
312+
// ✅ Specific — runtime knows when to delegate
313+
{ description: "Analyzes Python test coverage and identifies untested code paths" }
314+
```
315+
316+
### Handle failures gracefully
317+
318+
Sub-agents can fail. Always listen for `subagent.failed` events and handle them in your application:
319+
320+
```typescript
321+
session.on((event) => {
322+
if (event.type === "subagent.failed") {
323+
logger.error(`Agent ${event.data.agentName} failed: ${event.data.error}`);
324+
// Show error in UI, retry, or fall back to parent agent
325+
}
326+
});
327+
```

0 commit comments

Comments
 (0)