{"meta":{"title":"自定义代理和子代理编排","intro":"使用限定范围的工具和提示来定义专用代理，然后让 Copilot 在单个会话中将它们编排为子代理。 有关并行调度多个子代理，请参阅 机队模式。","product":"GitHub Copilot","breadcrumbs":[{"href":"/zh/enterprise-cloud@latest/copilot","title":"GitHub Copilot"},{"href":"/zh/enterprise-cloud@latest/copilot/how-tos","title":"操作方法"},{"href":"/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk","title":"Copilot SDK"},{"href":"/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/features","title":"功能"},{"href":"/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/features/custom-agents","title":"自定义代理"}],"documentType":"article"},"body":"# 自定义代理和子代理编排\n\n使用限定范围的工具和提示来定义专用代理，然后让 Copilot 在单个会话中将它们编排为子代理。 有关并行调度多个子代理，请参阅 机队模式。\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\n## 概述\n\n自定义代理是附加到会话的轻型代理定义。 每个代理都有自己的系统提示、工具限制和可选的 MCP 服务器。 当用户的请求与某个代理的专长相匹配时，Copilot 运行时会自动将任务委派给该代理，使其作为 **sub-agent** 在隔离的上下文中运行，同时将生命周期事件持续回传到父会话。\n\n![图示：显示所述过程的流程图。](/assets/images/help/copilot/copilot-sdk/features-custom-agents-diagram-0.png)\n\n| 概念        | Description           |\n| --------- | --------------------- |\n| **自定义代理** | 具名称的代理配置，具有其自己的提示和工具集 |\n| **子代理**   | 运行时调用的用于处理部分任务的自定义代理  |\n| **推理**    | 运行时能够根据用户的意图自动选择代理    |\n| **父会话**   | 生成子代理的会话;接收所有生命周期事件   |\n\n## 定义自定义代理\n\n创建会话时传递 `customAgents` 。 每个代理至少需要一个 `name` 和一个 `prompt`。\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\nimport { CopilotClient } from \"@github/copilot-sdk\";\n\nconst client = new CopilotClient();\nawait client.start();\n\nconst session = await client.createSession({\n    model: \"gpt-5.4\",\n    customAgents: [\n        {\n            name: \"researcher\",\n            displayName: \"Research Agent\",\n            description: \"Explores codebases and answers questions using read-only tools\",\n            tools: [\"grep\", \"glob\", \"view\"],\n            prompt: \"You are a research assistant. Analyze code and answer questions. Do not modify any files.\",\n        },\n        {\n            name: \"editor\",\n            displayName: \"Editor Agent\",\n            description: \"Makes targeted code changes\",\n            tools: [\"view\", \"edit\", \"bash\"],\n            prompt: \"You are a code editor. Make minimal, surgical changes to files as requested.\",\n        },\n    ],\n    onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nfrom copilot import CopilotClient, PermissionDecisionApproveOnce\n\nclient = CopilotClient()\nawait client.start()\n\nsession = await client.create_session(\n    on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),\n    model=\"gpt-5.4\",\n    custom_agents=[\n        {\n            \"name\": \"researcher\",\n            \"display_name\": \"Research Agent\",\n            \"description\": \"Explores codebases and answers questions using read-only tools\",\n            \"tools\": [\"grep\", \"glob\", \"view\"],\n            \"prompt\": \"You are a research assistant. Analyze code and answer questions. Do not modify any files.\",\n        },\n        {\n            \"name\": \"editor\",\n            \"display_name\": \"Editor Agent\",\n            \"description\": \"Makes targeted code changes\",\n            \"tools\": [\"view\", \"edit\", \"bash\"],\n            \"prompt\": \"You are a code editor. Make minimal, surgical changes to files as requested.\",\n        },\n    ],\n)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n```golang\nctx := context.Background()\nclient := copilot.NewClient(nil)\nclient.Start(ctx)\n\nsession, _ := client.CreateSession(ctx, &copilot.SessionConfig{\n    Model: \"gpt-5.4\",\n    CustomAgents: []copilot.CustomAgentConfig{\n        {\n            Name:        \"researcher\",\n            DisplayName: \"Research Agent\",\n            Description: \"Explores codebases and answers questions using read-only tools\",\n            Tools:       []string{\"grep\", \"glob\", \"view\"},\n            Prompt:      \"You are a research assistant. Analyze code and answer questions. Do not modify any files.\",\n        },\n        {\n            Name:        \"editor\",\n            DisplayName: \"Editor Agent\",\n            Description: \"Makes targeted code changes\",\n            Tools:       []string{\"view\", \"edit\", \"bash\"},\n            Prompt:      \"You are a code editor. Make minimal, surgical changes to files as requested.\",\n        },\n    },\n    OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {\n        return &rpc.PermissionDecisionApproveOnce{}, nil\n    },\n})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n```csharp\nusing GitHub.Copilot;\nusing GitHub.Copilot.Rpc;\n\nawait using var client = new CopilotClient();\nawait using var session = await client.CreateSessionAsync(new SessionConfig\n{\n    Model = \"gpt-5.4\",\n    CustomAgents = new List<CustomAgentConfig>\n    {\n        new()\n        {\n            Name = \"researcher\",\n            DisplayName = \"Research Agent\",\n            Description = \"Explores codebases and answers questions using read-only tools\",\n            Tools = new List<string> { \"grep\", \"glob\", \"view\" },\n            Prompt = \"You are a research assistant. Analyze code and answer questions. Do not modify any files.\",\n        },\n        new()\n        {\n            Name = \"editor\",\n            DisplayName = \"Editor Agent\",\n            Description = \"Makes targeted code changes\",\n            Tools = new List<string> { \"view\", \"edit\", \"bash\" },\n            Prompt = \"You are a code editor. Make minimal, surgical changes to files as requested.\",\n        },\n    },\n    OnPermissionRequest = (req, inv) =>\n        Task.FromResult(PermissionDecision.ApproveOnce()),\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n```java\nimport com.github.copilot.CopilotClient;\nimport com.github.copilot.rpc.*;\nimport java.util.List;\n\ntry (var client = new CopilotClient()) {\n    client.start().get();\n\n    var session = client.createSession(\n        new SessionConfig()\n            .setModel(\"gpt-5.4\")\n            .setCustomAgents(List.of(\n                new CustomAgentConfig()\n                    .setName(\"researcher\")\n                    .setDisplayName(\"Research Agent\")\n                    .setDescription(\"Explores codebases and answers questions using read-only tools\")\n                    .setTools(List.of(\"grep\", \"glob\", \"view\"))\n                    .setPrompt(\"You are a research assistant. Analyze code and answer questions. Do not modify any files.\"),\n                new CustomAgentConfig()\n                    .setName(\"editor\")\n                    .setDisplayName(\"Editor Agent\")\n                    .setDescription(\"Makes targeted code changes\")\n                    .setTools(List.of(\"view\", \"edit\", \"bash\"))\n                    .setPrompt(\"You are a code editor. Make minimal, surgical changes to files as requested.\")\n            ))\n            .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n    ).get();\n}\n```\n\n</div>\n\n</div>\n\n## 配置参考\n\n| 财产                                                      | 类型         | 必需 | Description |\n| ------------------------------------------------------- | ---------- | -- | ----------- |\n| `name`                                                  | `string`   | ✅  | 代理的唯一标识符    |\n| `displayName`                                           | `string`   |    |             |\n| 事件中显示的人类可读名称                                            |            |    |             |\n| `description`                                           | `string`   |    |             |\n| 代理的作用是帮助运行时环境选择它                                        |            |    |             |\n| `tools`                                                 |            |    |             |\n| `string[]` 或 `null`                                     |            |    |             |\n| 代理可以使用的工具名称。                                            |            |    |             |\n| `null` 或省略（= 所有工具）                                      |            |    |             |\n| `prompt`                                                | `string`   | ✅  | 代理的系统提示     |\n| `mcpServers`                                            | `object`   |    |             |\n| 特定于此代理的 MCP 服务器配置                                       |            |    |             |\n| `infer`                                                 | `boolean`  |    |             |\n| 运行时是否可以自动选择此代理（默认值： `true`                              |            |    |             |\n| `skills`                                                | `string[]` |    |             |\n| 启动时预加载到智能体上下文中的技能名称                                     |            |    |             |\n| `model`                                                 | `string`   |    |             |\n| 此代理运行时要使用的模型标识符                                         |            |    |             |\n| `reasoningEffort`                                       | `string`   |    |             |\n| 此代理运行时使用的推理工作量。 如果省略，SDK 不会发送代理级覆盖，运行时将解析推理工作量（请参阅下方注释） |            |    |             |\n\n> \\[!TIP]\n> 一个好 `description` 方法有助于运行时将用户意向与正确的代理匹配。 具体介绍代理的专业知识和功能。\n\n设置 `model` 和 `reasoningEffort`，以在自定义代理运行时覆盖父会话的模型设置。 如果`reasoningEffort`省略，SDK 不会发送代理级覆盖，运行时将按照自身的优先顺序解析推理工作量：每次调用的客户端选项、解析后模型的默认值或代理定义均具有优先级；否则，仅当子代理运行的模型与父代理相同时，运行时才会继承父会话的推理工作量。 当子代理解析为其他模型时，将回退到该模型的默认推理工作量，而不是继承父代理的推理工作量。 Python使用`reasoning_effort`、.NET使用`ReasoningEffort`、Go 使用`ReasoningEffort`、Java用法`setReasoningEffort`和 Rust 使用`with_reasoning_effort`。\n\n除了上述每个代理配置之外，还可以在`agent`本身上设置\\*\\*\\*\\*，以在会话启动时预先选择哪个自定义代理处于活动状态。 请参阅下面的 [会话创建时选择代理](#selecting-an-agent-at-session-creation) 。\n\n| 会话配置属性  | 类型       | Description                                          |\n| ------- | -------- | ---------------------------------------------------- |\n| `agent` | `string` | 在创建会话时预选择的自定义代理的名称。 必须与 `name` 中的 `customAgents` 匹配。 |\n\n## 每个代理的技能\n\n可以使用该 `skills` 属性将技能预加载到代理的上下文中。 指定后，每个列出技能的**完整内容**都会在启动时直接注入代理的上下文中——代理无需调用技能工具，因为相关指令已已存在。 技能是**可选启用**的：代理默认不具备任何技能，子代理也不会从父代理继承技能。 技能名称从会话级 `skillDirectories` 中解析。\n\n```typescript\nconst session = await client.createSession({\n    skillDirectories: [\"./skills\"],\n    customAgents: [\n        {\n            name: \"security-auditor\",\n            description: \"Security-focused code reviewer\",\n            prompt: \"Focus on OWASP Top 10 vulnerabilities\",\n            skills: [\"security-scan\", \"dependency-check\"],\n        },\n        {\n            name: \"docs-writer\",\n            description: \"Technical documentation writer\",\n            prompt: \"Write clear, concise documentation\",\n            skills: [\"markdown-lint\"],\n        },\n    ],\n    onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n```\n\n在此示例中，`security-auditor` 启动时其上下文中已注入 `security-scan` 和 `dependency-check`，而 `docs-writer` 启动时其上下文中已注入 `markdown-lint`。 没有 `skills` 字段的代理不会收到技能内容。\n\n## 在创建会话时选择代理\n\n可以传入 `agent` 会话配置，以便预先选择会话启动时应处于活动状态的自定义代理。 该值必须与`name`中定义的代理之一的`customAgents`匹配。\n\n这相当于在创建后调用 `session.rpc.agent.select()` ，但会避免额外的 API 调用，并确保代理在首次提示时处于活动状态。\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n<!-- docs-validate: skip -->\n\n```typescript\nconst session = await client.createSession({\n    customAgents: [\n        {\n            name: \"researcher\",\n            prompt: \"You are a research assistant. Analyze code and answer questions.\",\n        },\n        {\n            name: \"editor\",\n            prompt: \"You are a code editor. Make minimal, surgical changes.\",\n        },\n    ],\n    agent: \"researcher\", // Pre-select the researcher agent\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n<!-- docs-validate: skip -->\n\n```python\nsession = await client.create_session(\n    on_permission_request=PermissionHandler.approve_all,\n    custom_agents=[\n        {\n            \"name\": \"researcher\",\n            \"prompt\": \"You are a research assistant. Analyze code and answer questions.\",\n        },\n        {\n            \"name\": \"editor\",\n            \"prompt\": \"You are a code editor. Make minimal, surgical changes.\",\n        },\n    ],\n    agent=\"researcher\",  # Pre-select the researcher agent\n)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n<!-- docs-validate: skip -->\n\n```golang\nsession, _ := client.CreateSession(ctx, &copilot.SessionConfig{\n    CustomAgents: []copilot.CustomAgentConfig{\n        {\n            Name:   \"researcher\",\n            Prompt: \"You are a research assistant. Analyze code and answer questions.\",\n        },\n        {\n            Name:   \"editor\",\n            Prompt: \"You are a code editor. Make minimal, surgical changes.\",\n        },\n    },\n    Agent: \"researcher\", // Pre-select the researcher agent\n})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n<!-- docs-validate: skip -->\n\n```csharp\nvar session = await client.CreateSessionAsync(new SessionConfig\n{\n    CustomAgents = new List<CustomAgentConfig>\n    {\n        new() { Name = \"researcher\", Prompt = \"You are a research assistant. Analyze code and answer questions.\" },\n        new() { Name = \"editor\", Prompt = \"You are a code editor. Make minimal, surgical changes.\" },\n    },\n    Agent = \"researcher\", // Pre-select the researcher agent\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```java\nimport com.github.copilot.rpc.*;\nimport java.util.List;\n\nvar session = client.createSession(\n    new SessionConfig()\n        .setCustomAgents(List.of(\n            new CustomAgentConfig()\n                .setName(\"researcher\")\n                .setPrompt(\"You are a research assistant. Analyze code and answer questions.\"),\n            new CustomAgentConfig()\n                .setName(\"editor\")\n                .setPrompt(\"You are a code editor. Make minimal, surgical changes.\")\n        ))\n        .setAgent(\"researcher\") // Pre-select the researcher agent\n        .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n).get();\n```\n\n</div>\n\n</div>\n\n## 子代理委派的工作原理\n\n向具有自定义代理的会话发送提示时，运行时将评估是否委托给子代理：\n\n1. **意图匹配**—运行时会分析用户的提示以匹配每个代理的`name`和`description`\n2. **代理选择** - 如果找到匹配项且 `infer` 不等于 `false`，则运行时选择代理。\n3. **独立执行** - 子代理使用自己的提示和受限工具集运行\n4. **事件流式处理** - 生命周期事件（`subagent.started``subagent.completed`等）流式传输到父会话\n5. **结果集成** - 子代理的输出合并到父代理的响应中\n\n### 控制推理\n\n默认情况下，所有自定义代理都可用于自动选择（`infer: true`）。 设置为 `infer: false` 防止运行时自动选择代理- 对于仅希望通过显式用户请求调用的代理非常有用：\n\n```typescript\n{\n    name: \"dangerous-cleanup\",\n    description: \"Deletes unused files and dead code\",\n    tools: [\"bash\", \"edit\", \"view\"],\n    prompt: \"You clean up codebases by removing dead code and unused files.\",\n    infer: false, // Only invoked when user explicitly asks for this agent\n}\n```\n\n## 侦听子代理事件\n\n子代理运行时，父会话会发出生命周期事件。 订阅这些事件以生成可视化代理活动的 UI。\n\n由子代理发起的会话事件与父会话共享同一事件流，并包含信封级的 `agentId`。 根/主代理事件和会话级事件省略 `agentId`，因此呈现器可以通过检查事件信封将父响应与子代理跟踪分开。\n\n### 事件类型\n\n| 事件                                                                                                          | 以下情况下发出       | Data |\n| ----------------------------------------------------------------------------------------------------------- | ------------- | ---- |\n| `subagent.selected`                                                                                         | 运行时为任务选择代理    |      |\n| `agentName`、`agentDisplayName`、`tools`                                                                      |               |      |\n| `subagent.started`                                                                                          | 子代理开始执行       |      |\n| `toolCallId`、`agentName`、`agentDisplayName`、`agentDescription`、`model?`                                     |               |      |\n| `subagent.completed`                                                                                        | 子代理任务成功完成     |      |\n| `toolCallId`、`agentName`、`agentDisplayName`、`model?`、`durationMs?`、`totalTokens?`、`totalToolCalls?`         |               |      |\n| `subagent.failed`                                                                                           | 子代理遇到错误       |      |\n| `toolCallId`、`agentName`、`agentDisplayName`、`error`、`model?`、`durationMs?`、`totalTokens?`、`totalToolCalls?` |               |      |\n| `subagent.deselected`                                                                                       | 运行时从子代理中切换到别处 | —    |\n\n### 订阅事件\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\nsession.on((event) => {\n    switch (event.type) {\n        case \"subagent.started\":\n            console.log(`▶ Sub-agent started: ${event.data.agentDisplayName}`);\n            console.log(`  Description: ${event.data.agentDescription}`);\n            console.log(`  Tool call ID: ${event.data.toolCallId}`);\n            break;\n\n        case \"subagent.completed\":\n            console.log(`✅ Sub-agent completed: ${event.data.agentDisplayName}`);\n            if (event.data.durationMs !== undefined) console.log(`  Duration: ${event.data.durationMs}ms`);\n            if (event.data.totalTokens !== undefined) console.log(`  Tokens: ${event.data.totalTokens}`);\n            if (event.data.totalToolCalls !== undefined) console.log(`  Tool calls: ${event.data.totalToolCalls}`);\n            break;\n\n        case \"subagent.failed\":\n            console.log(`❌ Sub-agent failed: ${event.data.agentDisplayName}`);\n            console.log(`  Error: ${event.data.error}`);\n            if (event.data.durationMs !== undefined) console.log(`  Duration: ${event.data.durationMs}ms`);\n            break;\n\n        case \"subagent.selected\":\n            console.log(`🎯 Agent selected: ${event.data.agentDisplayName}`);\n            console.log(`  Tools: ${event.data.tools?.join(\", \") ?? \"all\"}`);\n            break;\n\n        case \"subagent.deselected\":\n            console.log(\"↩ Agent deselected, returning to parent\");\n            break;\n    }\n});\n\nconst response = await session.sendAndWait({\n    prompt: \"Research how authentication works in this codebase\",\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\ndef handle_event(event):\n    if event.type == \"subagent.started\":\n        print(f\"▶ Sub-agent started: {event.data.agent_display_name}\")\n        print(f\"  Description: {event.data.agent_description}\")\n    elif event.type == \"subagent.completed\":\n        print(f\"✅ Sub-agent completed: {event.data.agent_display_name}\")\n    elif event.type == \"subagent.failed\":\n        print(f\"❌ Sub-agent failed: {event.data.agent_display_name}\")\n        print(f\"  Error: {event.data.error}\")\n    elif event.type == \"subagent.selected\":\n        tools = event.data.tools or \"all\"\n        print(f\"🎯 Agent selected: {event.data.agent_display_name} (tools: {tools})\")\n\nunsubscribe = session.on(handle_event)\n\nresponse = await session.send_and_wait(\"Research how authentication works in this codebase\")\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n```golang\nsession.On(func(event copilot.SessionEvent) {\n    switch d := event.Data.(type) {\n    case *copilot.SubagentStartedData:\n        fmt.Printf(\"▶ Sub-agent started: %s\\n\", d.AgentDisplayName)\n        fmt.Printf(\"  Description: %s\\n\", d.AgentDescription)\n        fmt.Printf(\"  Tool call ID: %s\\n\", d.ToolCallID)\n    case *copilot.SubagentCompletedData:\n        fmt.Printf(\"✅ Sub-agent completed: %s\\n\", d.AgentDisplayName)\n    case *copilot.SubagentFailedData:\n        fmt.Printf(\"❌ Sub-agent failed: %s — %v\\n\", d.AgentDisplayName, d.Error)\n    case *copilot.SubagentSelectedData:\n        fmt.Printf(\"🎯 Agent selected: %s\\n\", d.AgentDisplayName)\n    }\n})\n\n_, err := session.SendAndWait(ctx, copilot.MessageOptions{\n    Prompt: \"Research how authentication works in this codebase\",\n})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"dotnet\" data-label=\".NET\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">.NET</div>\n\n```csharp\nusing var subscription = session.On<SessionEvent>(evt =>\n{\n    switch (evt)\n    {\n        case SubagentStartedEvent started:\n            Console.WriteLine($\"▶ Sub-agent started: {started.Data.AgentDisplayName}\");\n            Console.WriteLine($\"  Description: {started.Data.AgentDescription}\");\n            Console.WriteLine($\"  Tool call ID: {started.Data.ToolCallId}\");\n            break;\n        case SubagentCompletedEvent completed:\n            Console.WriteLine($\"✅ Sub-agent completed: {completed.Data.AgentDisplayName}\");\n            break;\n        case SubagentFailedEvent failed:\n            Console.WriteLine($\"❌ Sub-agent failed: {failed.Data.AgentDisplayName} — {failed.Data.Error}\");\n            break;\n        case SubagentSelectedEvent selected:\n            Console.WriteLine($\"🎯 Agent selected: {selected.Data.AgentDisplayName}\");\n            break;\n    }\n});\n\nawait session.SendAndWaitAsync(new MessageOptions\n{\n    Prompt = \"Research how authentication works in this codebase\"\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"java\" data-label=\"Java\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Java</div>\n\n<!-- docs-validate: skip -->\n\n```java\nsession.on(event -> {\n    if (event instanceof SubagentStartedEvent e) {\n        System.out.println(\"▶ Sub-agent started: \" + e.getData().agentDisplayName());\n        System.out.println(\"  Description: \" + e.getData().agentDescription());\n        System.out.println(\"  Tool call ID: \" + e.getData().toolCallId());\n    } else if (event instanceof SubagentCompletedEvent e) {\n        System.out.println(\"✅ Sub-agent completed: \" + e.getData().agentName());\n    } else if (event instanceof SubagentFailedEvent e) {\n        System.out.println(\"❌ Sub-agent failed: \" + e.getData().agentName());\n        System.out.println(\"  Error: \" + e.getData().error());\n    } else if (event instanceof SubagentSelectedEvent e) {\n        System.out.println(\"🎯 Agent selected: \" + e.getData().agentDisplayName());\n    } else if (event instanceof SubagentDeselectedEvent e) {\n        System.out.println(\"↩ Agent deselected, returning to parent\");\n    }\n});\n\nvar response = session.sendAndWait(\n    new MessageOptions().setPrompt(\"Research how authentication works in this codebase\")\n).get();\n```\n\n</div>\n\n</div>\n\n## 构建代理树用户界面\n\n子代理事件包括 `toolCallId` 用于重新构造执行树的字段。 下面是跟踪代理活动的模式：\n\n```typescript\ninterface AgentNode {\n    toolCallId: string;\n    name: string;\n    displayName: string;\n    status: \"running\" | \"completed\" | \"failed\";\n    error?: string;\n    startedAt: Date;\n    completedAt?: Date;\n}\n\nconst agentTree = new Map<string, AgentNode>();\n\nsession.on((event) => {\n    if (event.type === \"subagent.started\") {\n        agentTree.set(event.data.toolCallId, {\n            toolCallId: event.data.toolCallId,\n            name: event.data.agentName,\n            displayName: event.data.agentDisplayName,\n            status: \"running\",\n            startedAt: new Date(event.timestamp),\n        });\n    }\n\n    if (event.type === \"subagent.completed\") {\n        const node = agentTree.get(event.data.toolCallId);\n        if (node) {\n            node.status = \"completed\";\n            node.completedAt = new Date(event.timestamp);\n        }\n    }\n\n    if (event.type === \"subagent.failed\") {\n        const node = agentTree.get(event.data.toolCallId);\n        if (node) {\n            node.status = \"failed\";\n            node.error = event.data.error;\n            node.completedAt = new Date(event.timestamp);\n        }\n    }\n\n    // Render your UI with the updated tree\n    renderAgentTree(agentTree);\n});\n```\n\n## 每个代理的范围工具\n\n使用 `tools` 属性限制代理可以访问的工具。 这对于安全性和使代理保持专注至关重要：\n\n```typescript\nconst session = await client.createSession({\n    customAgents: [\n        {\n            name: \"reader\",\n            description: \"Read-only exploration of the codebase\",\n            tools: [\"grep\", \"glob\", \"view\"],  // No write access\n            prompt: \"You explore and analyze code. Never suggest modifications directly.\",\n        },\n        {\n            name: \"writer\",\n            description: \"Makes code changes\",\n            tools: [\"view\", \"edit\", \"bash\"],   // Write access\n            prompt: \"You make precise code changes as instructed.\",\n        },\n        {\n            name: \"unrestricted\",\n            description: \"Full access agent for complex tasks\",\n            tools: null,                        // All tools available\n            prompt: \"You handle complex multi-step tasks using any available tools.\",\n        },\n    ],\n});\n```\n\n> \\[!NOTE]\n> 如果 `tools` 为 `null` 或省略，代理将继承对会话上配置的所有工具的访问权限。 使用显式工具列表强制实施最低特权原则。\n\n## 代理专属工具\n\n`defaultAgent`使用会话配置上的属性隐藏默认代理中的特定工具（在未选择自定义代理时处理轮次的内置代理）。 这强制主代理在需要这些工具的功能时委托给子代理，使主代理的上下文保持干净。\n\n这在以下情况下非常有用：\n\n* 某些工具生成大量上下文，使主代理不知所措\n* 你希望主代理充当协调者，将繁重的工作委托给专门化的子代理\n* 需要在编排和执行之间严格分离\n\n<div class=\"ghd-codetabs\">\n<div class=\"ghd-codetab\" data-lang=\"typescript\" data-label=\"TypeScript\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">TypeScript</div>\n\n```typescript\nimport { CopilotClient, defineTool, approveAll } from \"@github/copilot-sdk\";\nimport { z } from \"zod\";\n\nconst heavyContextTool = defineTool(\"analyze-codebase\", {\n    description: \"Performs deep analysis of the codebase, generating extensive context\",\n    parameters: z.object({ query: z.string() }),\n    handler: async ({ query }) => {\n        // ... expensive analysis that returns lots of data\n        return { analysis: \"...\" };\n    },\n});\n\nconst session = await client.createSession({\n    tools: [heavyContextTool],\n    defaultAgent: {\n        excludedTools: [\"analyze-codebase\"],\n    },\n    customAgents: [\n        {\n            name: \"researcher\",\n            description: \"Deep codebase analysis agent with access to heavy-context tools\",\n            tools: [\"analyze-codebase\"],\n            prompt: \"You perform thorough codebase analysis using the analyze-codebase tool.\",\n        },\n    ],\n});\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"python\" data-label=\"Python\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Python</div>\n\n```python\nfrom copilot import CopilotClient\nfrom copilot.tools import Tool\n\nheavy_tool = Tool(\n    name=\"analyze-codebase\",\n    description=\"Performs deep analysis of the codebase\",\n    handler=analyze_handler,\n    parameters={\"type\": \"object\", \"properties\": {\"query\": {\"type\": \"string\"}}},\n)\n\nsession = await client.create_session(\n    tools=[heavy_tool],\n    default_agent={\"excluded_tools\": [\"analyze-codebase\"]},\n    custom_agents=[\n        {\n            \"name\": \"researcher\",\n            \"description\": \"Deep codebase analysis agent\",\n            \"tools\": [\"analyze-codebase\"],\n            \"prompt\": \"You perform thorough codebase analysis.\",\n        },\n    ],\n    on_permission_request=approve_all,\n)\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"go\" data-label=\"Go\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">Go</div>\n\n<!-- docs-validate: skip -->\n\n```golang\nsession, err := client.CreateSession(ctx, &copilot.SessionConfig{\n    Tools: []copilot.Tool{heavyTool},\n    DefaultAgent: &copilot.DefaultAgentConfig{\n        ExcludedTools: []string{\"analyze-codebase\"},\n    },\n    CustomAgents: []copilot.CustomAgentConfig{\n        {\n            Name:        \"researcher\",\n            Description: \"Deep codebase analysis agent\",\n            Tools:       []string{\"analyze-codebase\"},\n            Prompt:      \"You perform thorough codebase analysis.\",\n        },\n    },\n})\n```\n\n</div>\n\n<div class=\"ghd-codetab\" data-lang=\"csharp\" data-label=\"C#\"><div class=\"ghd-codetab-fallback-label\" role=\"heading\" aria-level=\"3\">C#</div>\n\n<!-- docs-validate: skip -->\n\n```csharp\nvar session = await client.CreateSessionAsync(new SessionConfig\n{\n    Tools = [analyzeCodebaseTool],\n    DefaultAgent = new DefaultAgentConfig\n    {\n        ExcludedTools = [\"analyze-codebase\"],\n    },\n    CustomAgents =\n    [\n        new CustomAgentConfig\n        {\n            Name = \"researcher\",\n            Description = \"Deep codebase analysis agent\",\n            Tools = [\"analyze-codebase\"],\n            Prompt = \"You perform thorough codebase analysis.\",\n        },\n    ],\n});\n```\n\n</div>\n\n</div>\n\n### 工作原理\n\n`defaultAgent.excludedTools`中列出的工具：\n\n1. **已注册**——其处理程序可用于执行\n2. **隐藏** 在主代理的工具列表中 - LLM 不会直接查看或调用它们\n3. ```\n             对任何将其包含在 **** 数组中的自定义子智能体`tools`\n   ```\n\n### 与其他工具筛选器交互\n\n`defaultAgent.excludedTools` 与会话级别的 `availableTools` 和 `excludedTools` 相互独立：\n\n| 过滤器                          | Scope | Effect              |\n| ---------------------------- | ----- | ------------------- |\n| `availableTools`             | 全会话范围 | 允许列表——仅对所有人开放这些工具   |\n| `excludedTools`              | 全会话范围 | 阻止名单——这些工具对所有人被屏蔽   |\n| `defaultAgent.excludedTools` | 仅限主代理 | 这些工具对主代理隐藏，但可供子代理使用 |\n\n优先：\n\n1. 会话级别 `availableTools`/`excludedTools` 会最先在全局范围内应用\n2. `defaultAgent.excludedTools` 作为最外层规则应用，仅对主智能体施加额外限制\n\n> \\[!NOTE]\n> 如果某个工具同时位于 `excludedTools`（会话级别）和 `defaultAgent.excludedTools` 中，则会话级别的排除优先生效——该工具对所有人都不可用。\n\n## 将 MCP 服务器附加到代理\n\n每个自定义代理可以有自己的 MCP（模型上下文协议）服务器，使它能够访问专用数据源：\n\n```typescript\nconst session = await client.createSession({\n    customAgents: [\n        {\n            name: \"db-analyst\",\n            description: \"Analyzes database schemas and queries\",\n            prompt: \"You are a database expert. Use the database MCP server to analyze schemas.\",\n            mcpServers: {\n                \"database\": {\n                    command: \"npx\",\n                    args: [\"-y\", \"@modelcontextprotocol/server-postgres\", \"postgresql://localhost/mydb\"],\n                },\n            },\n        },\n    ],\n});\n```\n\n## 模式和最佳做法\n\n### 将研究人员与编辑配对\n\n常见的模式是定义只读的研究人员代理和支持写入的编辑器代理。 运行时将探索任务委托给研究人员，并将修改任务委托给编辑器：\n\n```typescript\ncustomAgents: [\n    {\n        name: \"researcher\",\n        description: \"Analyzes code structure, finds patterns, and answers questions\",\n        tools: [\"grep\", \"glob\", \"view\"],\n        prompt: \"You are a code analyst. Thoroughly explore the codebase to answer questions.\",\n    },\n    {\n        name: \"implementer\",\n        description: \"Implements code changes based on analysis\",\n        tools: [\"view\", \"edit\", \"bash\"],\n        prompt: \"You make minimal, targeted code changes. Always verify changes compile.\",\n    },\n]\n```\n\n### 使代理说明保持明确\n\n运行时使用 `description` 来匹配用户意图。 模糊描述会导致委派效果不佳\n\n```typescript\n// ❌ Too vague — runtime can't distinguish from other agents\n{ description: \"Helps with code\" }\n\n// ✅ Specific — runtime knows when to delegate\n{ description: \"Analyzes Python test coverage and identifies untested code paths\" }\n```\n\n### 妥善处理故障\n\n子代理可能会失败。 始终监听 `subagent.failed` 事件，并在您的应用程序中进行处理。\n\n```typescript\nsession.on((event) => {\n    if (event.type === \"subagent.failed\") {\n        logger.error(`Agent ${event.data.agentName} failed: ${event.data.error}`);\n        // Show error in UI, retry, or fall back to parent agent\n    }\n});\n```"}