Skip to main content

사용자 정의 에이전트 및 하위 에이전트 오케스트레이션

범위가 지정된 도구 및 프롬프트를 사용하여 특수 에이전트를 정의한 다음, Copilot 단일 세션 내에서 하위 에이전트로 오케스트레이션하도록 합니다.

Overview

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

다이어그램: 설명된 프로세스를 보여 주는 순서도입니다.

ConceptDescription
사용자 지정 에이전트고유한 프롬프트 및 도구 집합이 있는 명명된 에이전트 구성
하위 에이전트태스크의 일부를 처리하기 위해 런타임에서 호출한 사용자 지정 에이전트
추론사용자의 의도에 따라 에이전트를 자동으로 선택하는 런타임의 기능
부모 세션하위 에이전트를 생성한 세션입니다. 는 모든 수명 주기 이벤트를 수신합니다.

사용자 지정 에이전트 정의

세션을 만들 때 customAgents 를 전달합니다. 각 에이전트에는 최소한 하나의 nameprompt가 필요합니다.

TypeScript
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" }),
});
Python
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.",
        },
    ],
)
Go
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
    },
})
.NET
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()),
});
Java
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();
}

구성 참조

재산TypeRequiredDescription
namestring에이전트의 고유 식별자
displayNamestring
이벤트에 표시된 사람이 읽을 수 있는 이름
descriptionstring
에이전트가 수행하는 작업 - 런타임에서 선택하는 데 도움이 됩니다.
tools
string[] 또는 null
에이전트에서 사용할 수 있는 도구 이름입니다.
null 또는 생략 = 모든 도구
promptstring에이전트에 대한 시스템 프롬프트
mcpServersobject
이 에이전트와 관련된 MCP 서버 구성
inferboolean
런타임에서 이 에이전트를 자동으로 선택할 수 있는지 여부(기본값: true)
skillsstring[]
시작할 때 에이전트의 컨텍스트에 미리 로드할 기술 이름

좋은 description 방법은 런타임이 사용자 의도를 올바른 에이전트와 일치시킬 수 있도록 도와줍니다. 에이전트의 전문 지식과 기능에 대해 자세히 설명합니다.

위의 에이전트별 구성 외에도 agent 자체에서 설정 **** 하여 세션이 시작될 때 활성 상태인 사용자 지정 에이전트를 미리 선택할 수 있습니다. 아래 세션 만들기에서 에이전트 선택을 참조하세요.

세션 구성 속성TypeDescription
agentstring세션을 만들 때 미리 선택할 사용자 지정 에이전트의 이름입니다.
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-auditorsecurity-scandependency-check가 이미 해당 컨텍스트에 주입된 상태로 시작하는 반면, docs-writermarkdown-lint로 시작합니다. 필드가 없는 에이전트는 skills 기술 콘텐츠를 받지 않습니다.

세션 생성 시 에이전트 선택

세션 구성을 전달 agent 하여 세션이 시작될 때 활성화해야 하는 사용자 지정 에이전트를 미리 선택할 수 있습니다. 값은 에 name 정의된 customAgents에이전트 중 하나와 일치해야 합니다.

이는 생성 후 호출 session.rpc.agent.select() 하는 것과 동일하지만 추가 API 호출을 방지하고 에이전트가 첫 번째 프롬프트에서 활성 상태인지 확인합니다.

TypeScript
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
});
Python
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
)
Go
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
})
.NET
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
});
Java
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();

하위 에이전트 위임의 작동 방식

사용자 지정 에이전트를 사용하여 세션에 프롬프트를 보내면 런타임은 하위 에이전트에 위임할지 여부를 평가합니다.

  1. 의도 일치 - 런타임은 사용자의 프롬프트를 각 에이전트의 namedescription에 대해 분석합니다.
  2. 에이전트 선택 - 일치 항목이 발견되고 infer 없는 false경우 런타임에서 에이전트를 선택합니다.
  3. 격리된 실행 - 하위 에이전트는 자체 프롬프트 및 제한된 도구 집합으로 실행됩니다.
  4. 이벤트 스트리밍 - 수명 주기 이벤트(subagent.started, subagent.completed등)가 부모 세션으로 다시 스트리밍됩니다.
  5. 결과 통합 - 하위 에이전트의 출력이 부모 에이전트의 응답에 통합됩니다.

유추 제어

기본적으로 모든 사용자 지정 에이전트는 자동 선택(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런타임이 하위 에이전트에서 멀리 전환됩니다.

이벤트 구독

TypeScript
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",
});
Python
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")
Go
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",
})
.NET
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"
});
Java
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.",
        },
    ],
});

참고

toolsnull이거나 생략된 경우, 에이전트는 세션에 구성된 모든 도구에 대한 액세스를 상속받습니다. 명시적 도구 목록을 사용하여 최소 권한 원칙을 적용합니다.

에이전트 전용 도구

세션 구성의 defaultAgent 속성을 사용하여 기본 에이전트에서 특정 도구를 숨깁니다(사용자 지정 에이전트가 선택되지 않은 경우 턴을 처리하는 기본 제공 에이전트). 이렇게 하면 이러한 도구의 기능이 필요할 때 주 에이전트가 하위 에이전트에 위임되어 주 에이전트의 컨텍스트를 깨끗하게 유지합니다.

이 방법은 다음과 같은 경우 유용합니다.

  • 특정 도구는 주 에이전트를 압도하는 많은 양의 컨텍스트를 생성합니다.
  • 주 에이전트가 오케스트레이터 역할을 하여 특수한 하위 에이전트에 많은 작업을 위임하려고 합니다.
  • 오케스트레이션과 실행 간에 엄격한 분리가 필요합니다.
TypeScript
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.",
        },
    ],
});
Python
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,
)
Go
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.",
        },
    },
})
C#
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도구:

  1. 등록됨 - 해당 처리기를 실행할 수 있음
  2. 주 에이전트의 도구 목록에서 숨겨집니다. LLM이 직접 보거나 호출하지 않습니다.
  3. ** ** 배열에 이를 포함하는 모든 사용자 지정 하위 에이전트에서 tools

다른 도구 필터와의 상호 작용

defaultAgent.excludedTools는 세션 수준의 availableToolsexcludedTools와는 별개입니다:

필터Scope영향
availableTools세션 전체에 걸쳐허용 목록 - 모든 사용자에 대해 이러한 도구만 존재합니다.
excludedTools세션 전체에 걸쳐차단 목록 - 이러한 도구는 모든 사용자에 대해 차단됩니다.
defaultAgent.excludedTools주 에이전트만이러한 도구는 주 에이전트에서 숨겨지지만 하위 에이전트에서 사용할 수 있습니다.

우선 순위:

  1. 세션 수준이 availableTools/excludedTools 먼저 적용됨(전역적으로)
  2. 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
    }
});