Overview
사용자 지정 에이전트는 세션에 연결하는 간단한 에이전트 정의입니다. 각 에이전트에는 고유한 시스템 프롬프트, 도구 제한 및 선택적 MCP 서버가 있습니다. 사용자의 요청이 에이전트의 전문 분야와 일치하면 Copilot 런타임은 해당 에이전트를 sub-agent로 자동 위임하고, 이를 격리된 컨텍스트에서 실행하는 동안 수명 주기 이벤트를 부모 세션으로 스트리밍합니다.

| Concept | Description |
|---|---|
| 사용자 지정 에이전트 | 고유한 프롬프트 및 도구 집합이 있는 명명된 에이전트 구성 |
| 하위 에이전트 | 태스크의 일부를 처리하기 위해 런타임에서 호출한 사용자 지정 에이전트 |
| 추론 | 사용자의 의도에 따라 에이전트를 자동으로 선택하는 런타임의 기능 |
| 부모 세션 | 하위 에이전트를 생성한 세션입니다. 는 모든 수명 주기 이벤트를 수신합니다. |
사용자 지정 에이전트 정의
세션을 만들 때 customAgents 를 전달합니다. 각 에이전트에는 최소한 하나의 name 및 prompt가 필요합니다.
import { CopilotClient } from "@github/copilot-sdk";
const client = new CopilotClient();
await client.start();
const session = await client.createSession({
model: "gpt-4.1",
customAgents: [
{
name: "researcher",
displayName: "Research Agent",
description: "Explores codebases and answers questions using read-only tools",
tools: ["grep", "glob", "view"],
prompt: "You are a research assistant. Analyze code and answer questions. Do not modify any files.",
},
{
name: "editor",
displayName: "Editor Agent",
description: "Makes targeted code changes",
tools: ["view", "edit", "bash"],
prompt: "You are a code editor. Make minimal, surgical changes to files as requested.",
},
],
onPermissionRequest: async () => ({ kind: "approve-once" }),
});
from copilot import CopilotClient, PermissionDecisionApproveOnce
client = CopilotClient()
await client.start()
session = await client.create_session(
on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
model="gpt-4.1",
custom_agents=[
{
"name": "researcher",
"display_name": "Research Agent",
"description": "Explores codebases and answers questions using read-only tools",
"tools": ["grep", "glob", "view"],
"prompt": "You are a research assistant. Analyze code and answer questions. Do not modify any files.",
},
{
"name": "editor",
"display_name": "Editor Agent",
"description": "Makes targeted code changes",
"tools": ["view", "edit", "bash"],
"prompt": "You are a code editor. Make minimal, surgical changes to files as requested.",
},
],
)
package main
import (
"context"
copilot "github.com/github/copilot-sdk/go"
"github.com/github/copilot-sdk/go/rpc"
)
func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
client.Start(ctx)
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "gpt-4.1",
CustomAgents: []copilot.CustomAgentConfig{
{
Name: "researcher",
DisplayName: "Research Agent",
Description: "Explores codebases and answers questions using read-only tools",
Tools: []string{"grep", "glob", "view"},
Prompt: "You are a research assistant. Analyze code and answer questions. Do not modify any files.",
},
{
Name: "editor",
DisplayName: "Editor Agent",
Description: "Makes targeted code changes",
Tools: []string{"view", "edit", "bash"},
Prompt: "You are a code editor. Make minimal, surgical changes to files as requested.",
},
},
OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {
return &rpc.PermissionDecisionApproveOnce{}, nil
},
})
_ = session
}
ctx := context.Background()
client := copilot.NewClient(nil)
client.Start(ctx)
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "gpt-4.1",
CustomAgents: []copilot.CustomAgentConfig{
{
Name: "researcher",
DisplayName: "Research Agent",
Description: "Explores codebases and answers questions using read-only tools",
Tools: []string{"grep", "glob", "view"},
Prompt: "You are a research assistant. Analyze code and answer questions. Do not modify any files.",
},
{
Name: "editor",
DisplayName: "Editor Agent",
Description: "Makes targeted code changes",
Tools: []string{"view", "edit", "bash"},
Prompt: "You are a code editor. Make minimal, surgical changes to files as requested.",
},
},
OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {
return &rpc.PermissionDecisionApproveOnce{}, nil
},
})
using GitHub.Copilot;
using GitHub.Copilot.Rpc;
await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig
{
Model = "gpt-4.1",
CustomAgents = new List<CustomAgentConfig>
{
new()
{
Name = "researcher",
DisplayName = "Research Agent",
Description = "Explores codebases and answers questions using read-only tools",
Tools = new List<string> { "grep", "glob", "view" },
Prompt = "You are a research assistant. Analyze code and answer questions. Do not modify any files.",
},
new()
{
Name = "editor",
DisplayName = "Editor Agent",
Description = "Makes targeted code changes",
Tools = new List<string> { "view", "edit", "bash" },
Prompt = "You are a code editor. Make minimal, surgical changes to files as requested.",
},
},
OnPermissionRequest = (req, inv) =>
Task.FromResult(PermissionDecision.ApproveOnce()),
});
import com.github.copilot.sdk.CopilotClient;
import com.github.copilot.sdk.events.*;
import com.github.copilot.sdk.json.*;
import java.util.List;
try (var client = new CopilotClient()) {
client.start().get();
var session = client.createSession(
new SessionConfig()
.setModel("gpt-4.1")
.setCustomAgents(List.of(
new CustomAgentConfig()
.setName("researcher")
.setDisplayName("Research Agent")
.setDescription("Explores codebases and answers questions using read-only tools")
.setTools(List.of("grep", "glob", "view"))
.setPrompt("You are a research assistant. Analyze code and answer questions. Do not modify any files."),
new CustomAgentConfig()
.setName("editor")
.setDisplayName("Editor Agent")
.setDescription("Makes targeted code changes")
.setTools(List.of("view", "edit", "bash"))
.setPrompt("You are a code editor. Make minimal, surgical changes to files as requested.")
))
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
}
구성 참조
| 재산 | Type | Required | Description |
|---|---|---|---|
name | string | ✅ | 에이전트의 고유 식별자 |
displayName | string | ||
| 이벤트에 표시된 사람이 읽을 수 있는 이름 | |||
description | string | ||
| 에이전트가 수행하는 작업 - 런타임에서 선택하는 데 도움이 됩니다. | |||
tools | |||
string[] 또는 null | |||
| 에이전트에서 사용할 수 있는 도구 이름입니다. | |||
null 또는 생략 = 모든 도구 | |||
prompt | string | ✅ | 에이전트에 대한 시스템 프롬프트 |
mcpServers | object | ||
| 이 에이전트와 관련된 MCP 서버 구성 | |||
infer | boolean | ||
런타임에서 이 에이전트를 자동으로 선택할 수 있는지 여부(기본값: true) | |||
skills | string[] | ||
| 시작할 때 에이전트의 컨텍스트에 미리 로드할 기술 이름 |
팁
좋은 description 방법은 런타임이 사용자 의도를 올바른 에이전트와 일치시킬 수 있도록 도와줍니다. 에이전트의 전문 지식과 기능에 대해 자세히 설명합니다.
위의 에이전트별 구성 외에도 agent 자체에서 설정 **** 하여 세션이 시작될 때 활성 상태인 사용자 지정 에이전트를 미리 선택할 수 있습니다. 아래 세션 만들기에서 에이전트 선택을 참조하세요.
| 세션 구성 속성 | Type | Description |
|---|---|---|
agent | string | 세션을 만들 때 미리 선택할 사용자 지정 에이전트의 이름입니다. |
name 내에서 customAgents와 일치해야 합니다. |
에이전트별 기술
skills 속성을 사용하여 에이전트의 컨텍스트에 스킬을 미리 로드할 수 있습니다. 지정된 경우 나열된 각 기술의 전체 콘텐츠 가 시작 시 에이전트의 컨텍스트에 즉시 삽입됩니다. 에이전트는 기술 도구를 호출할 필요가 없습니다. 지침이 이미 있습니다. 기술은 옵트인입니다. 에이전트는 기본적으로 기술을 받지 않으며 하위 에이전트는 부모로부터 기술을 상속하지 않습니다. 스킬 이름은 세션 수준의 skillDirectories에서 조회됩니다.
const session = await client.createSession({
skillDirectories: ["./skills"],
customAgents: [
{
name: "security-auditor",
description: "Security-focused code reviewer",
prompt: "Focus on OWASP Top 10 vulnerabilities",
skills: ["security-scan", "dependency-check"],
},
{
name: "docs-writer",
description: "Technical documentation writer",
prompt: "Write clear, concise documentation",
skills: ["markdown-lint"],
},
],
onPermissionRequest: async () => ({ kind: "approve-once" }),
});
이 예제에서는 security-auditor가 security-scan 및 dependency-check가 이미 해당 컨텍스트에 주입된 상태로 시작하는 반면, docs-writer는 markdown-lint로 시작합니다. 필드가 없는 에이전트는 skills 기술 콘텐츠를 받지 않습니다.
세션 생성 시 에이전트 선택
세션 구성을 전달 agent 하여 세션이 시작될 때 활성화해야 하는 사용자 지정 에이전트를 미리 선택할 수 있습니다. 값은 에 name 정의된 customAgents에이전트 중 하나와 일치해야 합니다.
이는 생성 후 호출 session.rpc.agent.select() 하는 것과 동일하지만 추가 API 호출을 방지하고 에이전트가 첫 번째 프롬프트에서 활성 상태인지 확인합니다.
const session = await client.createSession({
customAgents: [
{
name: "researcher",
prompt: "You are a research assistant. Analyze code and answer questions.",
},
{
name: "editor",
prompt: "You are a code editor. Make minimal, surgical changes.",
},
],
agent: "researcher", // Pre-select the researcher agent
});
session = await client.create_session(
on_permission_request=PermissionHandler.approve_all,
custom_agents=[
{
"name": "researcher",
"prompt": "You are a research assistant. Analyze code and answer questions.",
},
{
"name": "editor",
"prompt": "You are a code editor. Make minimal, surgical changes.",
},
],
agent="researcher", # Pre-select the researcher agent
)
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
CustomAgents: []copilot.CustomAgentConfig{
{
Name: "researcher",
Prompt: "You are a research assistant. Analyze code and answer questions.",
},
{
Name: "editor",
Prompt: "You are a code editor. Make minimal, surgical changes.",
},
},
Agent: "researcher", // Pre-select the researcher agent
})
var session = await client.CreateSessionAsync(new SessionConfig
{
CustomAgents = new List<CustomAgentConfig>
{
new() { Name = "researcher", Prompt = "You are a research assistant. Analyze code and answer questions." },
new() { Name = "editor", Prompt = "You are a code editor. Make minimal, surgical changes." },
},
Agent = "researcher", // Pre-select the researcher agent
});
import com.github.copilot.sdk.json.*;
import java.util.List;
var session = client.createSession(
new SessionConfig()
.setCustomAgents(List.of(
new CustomAgentConfig()
.setName("researcher")
.setPrompt("You are a research assistant. Analyze code and answer questions."),
new CustomAgentConfig()
.setName("editor")
.setPrompt("You are a code editor. Make minimal, surgical changes.")
))
.setAgent("researcher") // Pre-select the researcher agent
.setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
).get();
하위 에이전트 위임의 작동 방식
사용자 지정 에이전트를 사용하여 세션에 프롬프트를 보내면 런타임은 하위 에이전트에 위임할지 여부를 평가합니다.
- 의도 일치 - 런타임은 사용자의 프롬프트를 각 에이전트의
name및description에 대해 분석합니다. - 에이전트 선택 - 일치 항목이 발견되고
infer없는false경우 런타임에서 에이전트를 선택합니다. - 격리된 실행 - 하위 에이전트는 자체 프롬프트 및 제한된 도구 집합으로 실행됩니다.
- 이벤트 스트리밍 - 수명 주기 이벤트(
subagent.started,subagent.completed등)가 부모 세션으로 다시 스트리밍됩니다. - 결과 통합 - 하위 에이전트의 출력이 부모 에이전트의 응답에 통합됩니다.
유추 제어
기본적으로 모든 사용자 지정 에이전트는 자동 선택(infer: true)에 사용할 수 있습니다. 런타임이 에이전트를 자동으로 선택하지 않도록 infer: false를 설정합니다. 명시적 사용자 요청을 통해서만 호출하려는 에이전트에 유용합니다.
{
name: "dangerous-cleanup",
description: "Deletes unused files and dead code",
tools: ["bash", "edit", "view"],
prompt: "You clean up codebases by removing dead code and unused files.",
infer: false, // Only invoked when user explicitly asks for this agent
}
하위 에이전트 이벤트 수신 대기
하위 에이전트가 실행되면 부모 세션은 수명 주기 이벤트를 내보냅니다. 이러한 이벤트를 구독하여 에이전트 활동을 시각화하는 UI를 빌드합니다.
이벤트 유형
| 이벤트 | 내보낸 경우 | 데이터 |
|---|---|---|
subagent.selected | 런타임이 작업에 대한 에이전트를 선택합니다. | |
agentName, agentDisplayName, tools | ||
subagent.started | 하위 에이전트가 실행을 시작합니다. | |
toolCallId, agentName, , agentDisplayName, agentDescription | ||
subagent.completed | 하위 에이전트가 성공적으로 완료되었습니다. | |
toolCallId, agentName, agentDisplayName | ||
subagent.failed | 하위 에이전트에서 오류가 발생합니다. | |
toolCallId, agentName, , agentDisplayName, error | ||
subagent.deselected | 런타임이 하위 에이전트에서 멀리 전환됩니다. | — |
이벤트 구독
session.on((event) => {
switch (event.type) {
case "subagent.started":
console.log(`▶ Sub-agent started: ${event.data.agentDisplayName}`);
console.log(` Description: ${event.data.agentDescription}`);
console.log(` Tool call ID: ${event.data.toolCallId}`);
break;
case "subagent.completed":
console.log(`✅ Sub-agent completed: ${event.data.agentDisplayName}`);
break;
case "subagent.failed":
console.log(`❌ Sub-agent failed: ${event.data.agentDisplayName}`);
console.log(` Error: ${event.data.error}`);
break;
case "subagent.selected":
console.log(`🎯 Agent selected: ${event.data.agentDisplayName}`);
console.log(` Tools: ${event.data.tools?.join(", ") ?? "all"}`);
break;
case "subagent.deselected":
console.log("↩ Agent deselected, returning to parent");
break;
}
});
const response = await session.sendAndWait({
prompt: "Research how authentication works in this codebase",
});
def handle_event(event):
if event.type == "subagent.started":
print(f"▶ Sub-agent started: {event.data.agent_display_name}")
print(f" Description: {event.data.agent_description}")
elif event.type == "subagent.completed":
print(f"✅ Sub-agent completed: {event.data.agent_display_name}")
elif event.type == "subagent.failed":
print(f"❌ Sub-agent failed: {event.data.agent_display_name}")
print(f" Error: {event.data.error}")
elif event.type == "subagent.selected":
tools = event.data.tools or "all"
print(f"🎯 Agent selected: {event.data.agent_display_name} (tools: {tools})")
unsubscribe = session.on(handle_event)
response = await session.send_and_wait("Research how authentication works in this codebase")
package main
import (
"context"
"fmt"
copilot "github.com/github/copilot-sdk/go"
"github.com/github/copilot-sdk/go/rpc"
)
func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
client.Start(ctx)
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
Model: "gpt-4.1",
OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {
return &rpc.PermissionDecisionApproveOnce{}, nil
},
})
session.On(func(event copilot.SessionEvent) {
switch d := event.Data.(type) {
case *copilot.SubagentStartedData:
fmt.Printf("▶ Sub-agent started: %s\n", d.AgentDisplayName)
fmt.Printf(" Description: %s\n", d.AgentDescription)
fmt.Printf(" Tool call ID: %s\n", d.ToolCallID)
case *copilot.SubagentCompletedData:
fmt.Printf("✅ Sub-agent completed: %s\n", d.AgentDisplayName)
case *copilot.SubagentFailedData:
fmt.Printf("❌ Sub-agent failed: %s — %v\n", d.AgentDisplayName, d.Error)
case *copilot.SubagentSelectedData:
fmt.Printf("🎯 Agent selected: %s\n", d.AgentDisplayName)
}
})
_, err := session.SendAndWait(ctx, copilot.MessageOptions{
Prompt: "Research how authentication works in this codebase",
})
_ = err
}
session.On(func(event copilot.SessionEvent) {
switch d := event.Data.(type) {
case *copilot.SubagentStartedData:
fmt.Printf("▶ Sub-agent started: %s\n", d.AgentDisplayName)
fmt.Printf(" Description: %s\n", d.AgentDescription)
fmt.Printf(" Tool call ID: %s\n", d.ToolCallID)
case *copilot.SubagentCompletedData:
fmt.Printf("✅ Sub-agent completed: %s\n", d.AgentDisplayName)
case *copilot.SubagentFailedData:
fmt.Printf("❌ Sub-agent failed: %s — %v\n", d.AgentDisplayName, d.Error)
case *copilot.SubagentSelectedData:
fmt.Printf("🎯 Agent selected: %s\n", d.AgentDisplayName)
}
})
_, err := session.SendAndWait(ctx, copilot.MessageOptions{
Prompt: "Research how authentication works in this codebase",
})
using GitHub.Copilot;
public static class SubAgentEventsExample
{
public static async Task Example(CopilotSession session)
{
using var subscription = session.On<SessionEvent>(evt =>
{
switch (evt)
{
case SubagentStartedEvent started:
Console.WriteLine($"▶ Sub-agent started: {started.Data.AgentDisplayName}");
Console.WriteLine($" Description: {started.Data.AgentDescription}");
Console.WriteLine($" Tool call ID: {started.Data.ToolCallId}");
break;
case SubagentCompletedEvent completed:
Console.WriteLine($"✅ Sub-agent completed: {completed.Data.AgentDisplayName}");
break;
case SubagentFailedEvent failed:
Console.WriteLine($"❌ Sub-agent failed: {failed.Data.AgentDisplayName} — {failed.Data.Error}");
break;
case SubagentSelectedEvent selected:
Console.WriteLine($"🎯 Agent selected: {selected.Data.AgentDisplayName}");
break;
}
});
await session.SendAndWaitAsync(new MessageOptions
{
Prompt = "Research how authentication works in this codebase"
});
}
}
using var subscription = session.On<SessionEvent>(evt =>
{
switch (evt)
{
case SubagentStartedEvent started:
Console.WriteLine($"▶ Sub-agent started: {started.Data.AgentDisplayName}");
Console.WriteLine($" Description: {started.Data.AgentDescription}");
Console.WriteLine($" Tool call ID: {started.Data.ToolCallId}");
break;
case SubagentCompletedEvent completed:
Console.WriteLine($"✅ Sub-agent completed: {completed.Data.AgentDisplayName}");
break;
case SubagentFailedEvent failed:
Console.WriteLine($"❌ Sub-agent failed: {failed.Data.AgentDisplayName} — {failed.Data.Error}");
break;
case SubagentSelectedEvent selected:
Console.WriteLine($"🎯 Agent selected: {selected.Data.AgentDisplayName}");
break;
}
});
await session.SendAndWaitAsync(new MessageOptions
{
Prompt = "Research how authentication works in this codebase"
});
session.on(event -> {
if (event instanceof SubagentStartedEvent e) {
System.out.println("▶ Sub-agent started: " + e.getData().agentDisplayName());
System.out.println(" Description: " + e.getData().agentDescription());
System.out.println(" Tool call ID: " + e.getData().toolCallId());
} else if (event instanceof SubagentCompletedEvent e) {
System.out.println("✅ Sub-agent completed: " + e.getData().agentName());
} else if (event instanceof SubagentFailedEvent e) {
System.out.println("❌ Sub-agent failed: " + e.getData().agentName());
System.out.println(" Error: " + e.getData().error());
} else if (event instanceof SubagentSelectedEvent e) {
System.out.println("🎯 Agent selected: " + e.getData().agentDisplayName());
} else if (event instanceof SubagentDeselectedEvent e) {
System.out.println("↩ Agent deselected, returning to parent");
}
});
var response = session.sendAndWait(
new MessageOptions().setPrompt("Research how authentication works in this codebase")
).get();
에이전트 트리 UI 빌드
하위 에이전트 이벤트에는 실행 트리를 다시 구성할 수 있는 필드가 포함 toolCallId 됩니다. 다음은 에이전트 활동을 추적하는 패턴입니다.
interface AgentNode {
toolCallId: string;
name: string;
displayName: string;
status: "running" | "completed" | "failed";
error?: string;
startedAt: Date;
completedAt?: Date;
}
const agentTree = new Map<string, AgentNode>();
session.on((event) => {
if (event.type === "subagent.started") {
agentTree.set(event.data.toolCallId, {
toolCallId: event.data.toolCallId,
name: event.data.agentName,
displayName: event.data.agentDisplayName,
status: "running",
startedAt: new Date(event.timestamp),
});
}
if (event.type === "subagent.completed") {
const node = agentTree.get(event.data.toolCallId);
if (node) {
node.status = "completed";
node.completedAt = new Date(event.timestamp);
}
}
if (event.type === "subagent.failed") {
const node = agentTree.get(event.data.toolCallId);
if (node) {
node.status = "failed";
node.error = event.data.error;
node.completedAt = new Date(event.timestamp);
}
}
// Render your UI with the updated tree
renderAgentTree(agentTree);
});
에이전트별 범위 지정 도구
이 tools 속성을 사용하여 에이전트가 액세스할 수 있는 도구를 제한합니다. 이는 보안 및 에이전트의 집중을 유지하는 데 필수적입니다.
const session = await client.createSession({
customAgents: [
{
name: "reader",
description: "Read-only exploration of the codebase",
tools: ["grep", "glob", "view"], // No write access
prompt: "You explore and analyze code. Never suggest modifications directly.",
},
{
name: "writer",
description: "Makes code changes",
tools: ["view", "edit", "bash"], // Write access
prompt: "You make precise code changes as instructed.",
},
{
name: "unrestricted",
description: "Full access agent for complex tasks",
tools: null, // All tools available
prompt: "You handle complex multi-step tasks using any available tools.",
},
],
});
참고
tools가 null이거나 생략된 경우, 에이전트는 세션에 구성된 모든 도구에 대한 액세스를 상속받습니다. 명시적 도구 목록을 사용하여 최소 권한 원칙을 적용합니다.
에이전트 전용 도구
세션 구성의 defaultAgent 속성을 사용하여 기본 에이전트에서 특정 도구를 숨깁니다(사용자 지정 에이전트가 선택되지 않은 경우 턴을 처리하는 기본 제공 에이전트). 이렇게 하면 이러한 도구의 기능이 필요할 때 주 에이전트가 하위 에이전트에 위임되어 주 에이전트의 컨텍스트를 깨끗하게 유지합니다.
이 방법은 다음과 같은 경우 유용합니다.
- 특정 도구는 주 에이전트를 압도하는 많은 양의 컨텍스트를 생성합니다.
- 주 에이전트가 오케스트레이터 역할을 하여 특수한 하위 에이전트에 많은 작업을 위임하려고 합니다.
- 오케스트레이션과 실행 간에 엄격한 분리가 필요합니다.
import { CopilotClient, defineTool, approveAll } from "@github/copilot-sdk";
import { z } from "zod";
const heavyContextTool = defineTool("analyze-codebase", {
description: "Performs deep analysis of the codebase, generating extensive context",
parameters: z.object({ query: z.string() }),
handler: async ({ query }) => {
// ... expensive analysis that returns lots of data
return { analysis: "..." };
},
});
const session = await client.createSession({
tools: [heavyContextTool],
defaultAgent: {
excludedTools: ["analyze-codebase"],
},
customAgents: [
{
name: "researcher",
description: "Deep codebase analysis agent with access to heavy-context tools",
tools: ["analyze-codebase"],
prompt: "You perform thorough codebase analysis using the analyze-codebase tool.",
},
],
});
from copilot import CopilotClient
from copilot.tools import Tool
heavy_tool = Tool(
name="analyze-codebase",
description="Performs deep analysis of the codebase",
handler=analyze_handler,
parameters={"type": "object", "properties": {"query": {"type": "string"}}},
)
session = await client.create_session(
tools=[heavy_tool],
default_agent={"excluded_tools": ["analyze-codebase"]},
custom_agents=[
{
"name": "researcher",
"description": "Deep codebase analysis agent",
"tools": ["analyze-codebase"],
"prompt": "You perform thorough codebase analysis.",
},
],
on_permission_request=approve_all,
)
session, err := client.CreateSession(ctx, &copilot.SessionConfig{
Tools: []copilot.Tool{heavyTool},
DefaultAgent: &copilot.DefaultAgentConfig{
ExcludedTools: []string{"analyze-codebase"},
},
CustomAgents: []copilot.CustomAgentConfig{
{
Name: "researcher",
Description: "Deep codebase analysis agent",
Tools: []string{"analyze-codebase"},
Prompt: "You perform thorough codebase analysis.",
},
},
})
var session = await client.CreateSessionAsync(new SessionConfig
{
Tools = [analyzeCodebaseTool],
DefaultAgent = new DefaultAgentConfig
{
ExcludedTools = ["analyze-codebase"],
},
CustomAgents =
[
new CustomAgentConfig
{
Name = "researcher",
Description = "Deep codebase analysis agent",
Tools = ["analyze-codebase"],
Prompt = "You perform thorough codebase analysis.",
},
],
});
작동 방식
에 나열된 defaultAgent.excludedTools도구:
- 등록됨 - 해당 처리기를 실행할 수 있음
- 주 에이전트의 도구 목록에서 숨겨집니다. LLM이 직접 보거나 호출하지 않습니다.
- **
** 배열에 이를 포함하는 모든 사용자 지정 하위 에이전트에서
tools
다른 도구 필터와의 상호 작용
defaultAgent.excludedTools는 세션 수준의 availableTools 및 excludedTools와는 별개입니다:
| 필터 | Scope | 영향 |
|---|---|---|
availableTools | 세션 전체에 걸쳐 | 허용 목록 - 모든 사용자에 대해 이러한 도구만 존재합니다. |
excludedTools | 세션 전체에 걸쳐 | 차단 목록 - 이러한 도구는 모든 사용자에 대해 차단됩니다. |
defaultAgent.excludedTools | 주 에이전트만 | 이러한 도구는 주 에이전트에서 숨겨지지만 하위 에이전트에서 사용할 수 있습니다. |
우선 순위:
- 세션 수준이
availableTools/excludedTools먼저 적용됨(전역적으로) defaultAgent.excludedTools는 맨 위에 적용되어 주 에이전트만 추가로 제한합니다.
참고
도구가 둘 다 excludedTools (세션 수준)에 있고 defaultAgent.excludedTools세션 수준 제외가 우선하는 경우 모든 사용자가 도구를 사용할 수 없습니다.
에이전트에 MCP 서버 연결
각 사용자 지정 에이전트에는 고유한 MCP(모델 컨텍스트 프로토콜) 서버가 있어 특수 데이터 원본에 액세스할 수 있습니다.
const session = await client.createSession({
customAgents: [
{
name: "db-analyst",
description: "Analyzes database schemas and queries",
prompt: "You are a database expert. Use the database MCP server to analyze schemas.",
mcpServers: {
"database": {
command: "npx",
args: ["-y", "@modelcontextprotocol/server-postgres", "postgresql://localhost/mydb"],
},
},
},
],
});
패턴 및 모범 사례
연구원과 편집기 페어링
일반적인 패턴은 읽기 전용 연구원 에이전트 및 쓰기 가능 편집기 에이전트를 정의하는 것입니다. 런타임은 탐색 작업을 연구원에게 위임하고 수정 작업을 편집기로 위임합니다.
customAgents: [
{
name: "researcher",
description: "Analyzes code structure, finds patterns, and answers questions",
tools: ["grep", "glob", "view"],
prompt: "You are a code analyst. Thoroughly explore the codebase to answer questions.",
},
{
name: "implementer",
description: "Implements code changes based on analysis",
tools: ["view", "edit", "bash"],
prompt: "You make minimal, targeted code changes. Always verify changes compile.",
},
]
에이전트 설명을 구체적으로 유지
런타임은 사용자 의도와 description 일치하도록 사용합니다. 모호한 설명은 잘못된 위임으로 이어질 수 있습니다.
// ❌ Too vague — runtime can't distinguish from other agents
{ description: "Helps with code" }
// ✅ Specific — runtime knows when to delegate
{ description: "Analyzes Python test coverage and identifies untested code paths" }
오류를 정상적으로 처리
하위 에이전트는 실패할 수 있습니다. 항상 subagent.failed 이벤트를 수신 대기하고 애플리케이션에서 처리합니다.
session.on((event) => {
if (event.type === "subagent.failed") {
logger.error(`Agent ${event.data.agentName} failed: ${event.data.error}`);
// Show error in UI, retry, or fall back to parent agent
}
});