{"meta":{"title":"Пользовательские агенты и оркестровка субагентов","intro":"Определите специализированных агентов с ограниченными инструментами и подсказками, а затем позвольте Copilot оркестровать их как субагентов в течение одной сессии. Для параллельной отправки нескольких субагентов см. Режим флота.","product":"GitHub Copilot","breadcrumbs":[{"href":"/ru/enterprise-cloud@latest/copilot","title":"GitHub Copilot"},{"href":"/ru/enterprise-cloud@latest/copilot/how-tos","title":"Инструкции"},{"href":"/ru/enterprise-cloud@latest/copilot/how-tos/copilot-sdk","title":"Второй пилот SDK"},{"href":"/ru/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/features","title":"Возможности"},{"href":"/ru/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 runtime автоматически делегирует данные этому агенту как **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` при создании сессии. Каждому агенту нужно минимум a `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`и использует `with_reasoning_effort`Rust.\n\nВ дополнение к конфигурации для каждого агента, вы можете заранее `agent` выбрать в самой **сессионной** конфигурации заранее выбрать пользовательский агент при начале сессии. См. [раздел «Выбор агента» в разделе «Создание сессии](#selecting-an-agent-at-session-creation) » ниже.\n\n| Свойство конфигурации сессии | Тип      | Description                                                                                                                 |\n| ---------------------------- | -------- | --------------------------------------------------------------------------------------------------------------------------- |\n| `agent`                      | `string` | Имя пользовательского агента для предварительного выбора при создании сессии. Должно совпадать с a `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. **Сопоставление намерений** — Runtime анализирует подсказки пользователя по сравнению с запросами `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Когда работает субагент, родительская сессия генерирует события жизненного цикла. Подпишитесь на эти события, чтобы создавать интерфейсы, визуализирующие активность агентов.\n\nСобытия сеанса, возникающие в подагене, совместно используют родительский поток сеансов и включают уровень `agentId`конверта. События корневого или основного агента и события уровня сеанса опущены `agentId`, поэтому отрисовщики могут хранить родительский ответ отдельно от трассировок субагента, проверяя конверт события.\n\n### Типы событий\n\n| Событие                                                                                                  | Излучается, когда                  | Данные |\n| -------------------------------------------------------------------------------------------------------- | ---------------------------------- | ------ |\n| `subagent.selected`                                                                                      | Runtime выбирает агента для задачи |        |\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`                                                                                    | Runtime переключается от субагента | —      |\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. **Оставайтесь доступными** для любого пользовательского субагента, который включает их в свой `tools` массив\n\n### Взаимодействие с другими фильтрами инструментов\n\n`defaultAgent.excludedTools` ортогональна по отношению к уровню `availableTools` сессии и `excludedTools`:\n\n| Filter                       | Объем                 | Эффект                                                             |\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 (Model Context Protocol), что даёт доступ к специализированным источникам данных:\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```"}