{"meta":{"title":"Рулевое управление и очередь","intro":"Два паттерна взаимодействия позволяют пользователям отправлять сообщения, пока агент уже работает: управление перенаправляет агента в середине хода, а очередь буферных сообщений для последовательной обработки после завершения текущего хода.","product":"GitHub Copilot","breadcrumbs":[{"href":"/ru/copilot","title":"GitHub Copilot"},{"href":"/ru/copilot/how-tos","title":"Инструкции"},{"href":"/ru/copilot/how-tos/copilot-sdk","title":"Второй пилот SDK"},{"href":"/ru/copilot/how-tos/copilot-sdk/features","title":"Возможности"},{"href":"/ru/copilot/how-tos/copilot-sdk/features/steering-and-queueing","title":"Рулевое управление и очередь"}],"documentType":"article"},"body":"# Рулевое управление и очередь\n\nДва паттерна взаимодействия позволяют пользователям отправлять сообщения, пока агент уже работает: управление перенаправляет агента в середине хода, а очередь буферных сообщений для последовательной обработки после завершения текущего хода.\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\n## Обзор\n\nКогда сессия активно обрабатывает ход, входящие сообщения могут доставляться в одном из двух режимов через `mode` поле на `MessageOptions`:\n\n| Режим                              | Behavior                                                          | Сценарий использования                                           |\n| ---------------------------------- | ----------------------------------------------------------------- | ---------------------------------------------------------------- |\n| `\"immediate\"` (рулевое управление) | Вводится в **текущий** ход LLM                                    | «На самом деле, не создавай этот файл — используй другой подход» |\n| `\"enqueue\"` (очередь)              | Поставлен в очередь и **обработан после** окончания текущего хода | «После этого тоже починьте тесты»                                |\n\n![Диаграмма: диаграмма последовательностей, показывающая описанный процесс.](/assets/images/help/copilot/copilot-sdk/features-steering-and-queueing-diagram-0.png)\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 } from \"@github/copilot-sdk\";\n\nconst client = new CopilotClient();\nawait client.start();\n\nconst session = await client.createSession({\n    model: \"gpt-5.4\",\n    onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n\n// Start a long-running task\nconst msgId = await session.send({\n    prompt: \"Refactor the authentication module to use sessions\",\n});\n\n// While the agent is working, steer it\nawait session.send({\n    prompt: \"Actually, use JWT tokens instead of sessions\",\n    mode: \"immediate\",\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\nasync def main():\n    client = CopilotClient()\n    await client.start()\n\n    session = await client.create_session(\n        on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),\n        model=\"gpt-5.4\",\n    )\n\n    # Start a long-running task\n    msg_id = await session.send(\n        \"Refactor the authentication module to use sessions\",\n    )\n\n    # While the agent is working, steer it\n    await session.send(\n        \"Actually, use JWT tokens instead of sessions\",\n        mode=\"immediate\",\n    )\n\n    await client.stop()\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\npackage main\n\nimport (\n    \"context\"\n    \"log\"\n    copilot \"github.com/github/copilot-sdk/go\"\n    \"github.com/github/copilot-sdk/go/rpc\"\n)\n\nfunc main() {\n    ctx := context.Background()\n    client := copilot.NewClient(nil)\n    if err := client.Start(ctx); err != nil {\n        log.Fatal(err)\n    }\n    defer client.Stop()\n\n    session, err := client.CreateSession(ctx, &copilot.SessionConfig{\n        Model: \"gpt-5.4\",\n        OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {\n            return &rpc.PermissionDecisionApproveOnce{}, nil\n        },\n    })\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    // Start a long-running task\n    _, err = session.Send(ctx, copilot.MessageOptions{\n        Prompt: \"Refactor the authentication module to use sessions\",\n    })\n    if err != nil {\n        log.Fatal(err)\n    }\n\n    // While the agent is working, steer it\n    _, err = session.Send(ctx, copilot.MessageOptions{\n        Prompt: \"Actually, use JWT tokens instead of sessions\",\n        Mode:   \"immediate\",\n    })\n    if err != nil {\n        log.Fatal(err)\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    OnPermissionRequest = (req, inv) =>\n        Task.FromResult(PermissionDecision.ApproveOnce()),\n});\n\n// Start a long-running task\nvar msgId = await session.SendAsync(new MessageOptions\n{\n    Prompt = \"Refactor the authentication module to use sessions\"\n});\n\n// While the agent is working, steer it\nawait session.SendAsync(new MessageOptions\n{\n    Prompt = \"Actually, use JWT tokens instead of sessions\",\n    Mode = \"immediate\"\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.*;\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            .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n    ).get();\n\n    // Start a long-running task\n    session.send(new MessageOptions()\n        .setPrompt(\"Refactor the authentication module to use sessions\")\n    ).get();\n\n    // While the agent is working, steer it\n    session.send(new MessageOptions()\n        .setPrompt(\"Actually, use JWT tokens instead of sessions\")\n        .setMode(\"immediate\")\n    ).get();\n}\n```\n\n</div>\n\n</div>\n\n### Как работает внутреннее управление\n\n1. Сообщение добавляется в очередь `ImmediatePromptProcessor` времени выполнения\n2. Перед следующим запросом LLM в текущем ходу процессор вводит сообщение в разговор\n3. Агент воспринимает сообщение управления как сообщение нового пользователя и корректирует его ответ\n4. Если поворот завершается до обработки сообщения о рулевом управлении, он автоматически переводится в обычную очередь на следующий ход\n\n> \\[!NOTE]\n> Сообщения управления — это лучшее усилие в текущем ходу. Если агент уже выполнил вызов инструмента, управление вступает в силу после завершения этого вызова, но всё ещё в пределах того же хода.\n\n## Очередь (режим очереди)\n\nОчередь буферизирует сообщения для последовательной обработки после завершения текущего хода. Каждое очередное сообщение начинает свой полный ход. Это режим по умолчанию — если пропустить `mode`, SDK использует `\"enqueue\"`.\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    onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n\n// Send an initial task\nawait session.send({ prompt: \"Set up the project structure\" });\n\n// Queue follow-up tasks while the agent is busy\nawait session.send({\n    prompt: \"Add unit tests for the auth module\",\n    mode: \"enqueue\",\n});\n\nawait session.send({\n    prompt: \"Update the README with setup instructions\",\n    mode: \"enqueue\",\n});\n\n// Messages are processed in FIFO order after each turn completes\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\nasync def main():\n    client = CopilotClient()\n    await client.start()\n\n    session = await client.create_session(\n        on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),\n        model=\"gpt-5.4\",\n    )\n\n    # Send an initial task\n    await session.send(\"Set up the project structure\")\n\n    # Queue follow-up tasks while the agent is busy\n    await session.send(\n        \"Add unit tests for the auth module\",\n        mode=\"enqueue\",\n    )\n\n    await session.send(\n        \"Update the README with setup instructions\",\n        mode=\"enqueue\",\n    )\n\n    # Messages are processed in FIFO order after each turn completes\n    await client.stop()\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\n// Send an initial task\nsession.Send(ctx, copilot.MessageOptions{\n    Prompt: \"Set up the project structure\",\n})\n\n// Queue follow-up tasks while the agent is busy\nsession.Send(ctx, copilot.MessageOptions{\n    Prompt: \"Add unit tests for the auth module\",\n    Mode:   \"enqueue\",\n})\n\nsession.Send(ctx, copilot.MessageOptions{\n    Prompt: \"Update the README with setup instructions\",\n    Mode:   \"enqueue\",\n})\n\n// Messages are processed in FIFO order after each turn completes\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\n// Send an initial task\nawait session.SendAsync(new MessageOptions\n{\n    Prompt = \"Set up the project structure\"\n});\n\n// Queue follow-up tasks while the agent is busy\nawait session.SendAsync(new MessageOptions\n{\n    Prompt = \"Add unit tests for the auth module\",\n    Mode = \"enqueue\"\n});\n\nawait session.SendAsync(new MessageOptions\n{\n    Prompt = \"Update the README with setup instructions\",\n    Mode = \"enqueue\"\n});\n\n// Messages are processed in FIFO order after each turn completes\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.*;\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            .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)\n    ).get();\n\n    // Send an initial task\n    session.send(new MessageOptions().setPrompt(\"Set up the project structure\")).get();\n\n    // Queue follow-up tasks while the agent is busy\n    session.send(new MessageOptions()\n        .setPrompt(\"Add unit tests for the auth module\")\n        .setMode(\"enqueue\")\n    ).get();\n\n    session.send(new MessageOptions()\n        .setPrompt(\"Update the README with setup instructions\")\n        .setMode(\"enqueue\")\n    ).get();\n\n    // Messages are processed in FIFO order after each turn completes\n}\n```\n\n</div>\n\n</div>\n\n### Как работает внутренняя очередь\n\n1. Сообщение добавляется в сессии `itemQueue` в виде `QueuedItem`\n2. Когда текущий ход заканчивается и сессия становится бездействующей, `processQueuedItems()` запускается\n3. Элементы выставляются из очереди в порядке FIFO — каждое сообщение запускает полный агентный ход\n4. Если на момент окончания поворота сообщение о рулевом управлении было ожидаемым, оно перемещается в начало очереди\n5. Обработка продолжается до тех пор, пока очередь не опустеет, после чего сессия выпускает событие простоя\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\nconst session = await client.createSession({\n    model: \"gpt-5.4\",\n    onPermissionRequest: async () => ({ kind: \"approve-once\" }),\n});\n\n// Start a task\nawait session.send({ prompt: \"Refactor the database layer\" });\n\n// Steer the current work\nawait session.send({\n    prompt: \"Make sure to keep backwards compatibility with the v1 API\",\n    mode: \"immediate\",\n});\n\n// Queue a follow-up for after this turn\nawait session.send({\n    prompt: \"Now add migration scripts for the schema changes\",\n    mode: \"enqueue\",\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\nsession = await client.create_session(\n    on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),\n    model=\"gpt-5.4\",\n)\n\n# Start a task\nawait session.send(\"Refactor the database layer\")\n\n# Steer the current work\nawait session.send(\n    \"Make sure to keep backwards compatibility with the v1 API\",\n    mode=\"immediate\",\n)\n\n# Queue a follow-up for after this turn\nawait session.send(\n    \"Now add migration scripts for the schema changes\",\n    mode=\"enqueue\",\n)\n```\n\n</div>\n\n</div>\n\n## Выбор между рулём и очередью\n\n| Scenario                                      | Pattern                                                    | Почему |\n| --------------------------------------------- | ---------------------------------------------------------- | ------ |\n| Агент идёт по неправильному пути              |                                                            |        |\n| **Рулевое управление**                        | Перенаправляет текущий ход без потери прогресса            |        |\n| Вы придумали, что агент тоже должен сделать   |                                                            |        |\n| **Очередь**                                   | Не мешает текущей работе; Следующие сезоны                 |        |\n| Агент вот-вот совершит ошибку                 |                                                            |        |\n| **Рулевое управление**                        | Вмешивается до совершения ошибки                           |        |\n| Нужно объединять несколько задач в цепочку    |                                                            |        |\n| **Очередь**                                   | Порядок FIFO обеспечивает предсказуемое выполнение         |        |\n| Вы хотите добавить контекст к текущей задаче  |                                                            |        |\n| **Рулевое управление**                        | Агент включает это в свою текущую логику                   |        |\n| Вы хотите делать пакетные несвязанные запросы |                                                            |        |\n| **Очередь**                                   | Каждый из них получает свой полный ход с чистым контекстом |        |\n\n## Создание интерфейса с управлением и очередями\n\nВот схема создания интерактивного интерфейса, поддерживающего оба режима:\n\n```typescript\nimport { CopilotClient, CopilotSession } from \"@github/copilot-sdk\";\n\ninterface PendingMessage {\n    prompt: string;\n    mode: \"immediate\" | \"enqueue\";\n    sentAt: Date;\n}\n\nclass InteractiveChat {\n    private session: CopilotSession;\n    private isProcessing = false;\n    private pendingMessages: PendingMessage[] = [];\n\n    constructor(session: CopilotSession) {\n        this.session = session;\n\n        session.on((event) => {\n            if (event.type === \"session.idle\") {\n                this.isProcessing = false;\n                this.onIdle();\n            }\n            if (event.type === \"assistant.message\") {\n                this.renderMessage(event);\n            }\n        });\n    }\n\n    async sendMessage(prompt: string): Promise<void> {\n        if (!this.isProcessing) {\n            this.isProcessing = true;\n            await this.session.send({ prompt });\n            return;\n        }\n\n        // Session is busy — let the user choose how to deliver\n        // Your UI would present this choice (e.g., buttons, keyboard shortcuts)\n    }\n\n    async steer(prompt: string): Promise<void> {\n        this.pendingMessages.push({\n            prompt,\n            mode: \"immediate\",\n            sentAt: new Date(),\n        });\n        await this.session.send({ prompt, mode: \"immediate\" });\n    }\n\n    async enqueue(prompt: string): Promise<void> {\n        this.pendingMessages.push({\n            prompt,\n            mode: \"enqueue\",\n            sentAt: new Date(),\n        });\n        await this.session.send({ prompt, mode: \"enqueue\" });\n    }\n\n    private onIdle(): void {\n        this.pendingMessages = [];\n        // Update UI to show session is ready for new input\n    }\n\n    private renderMessage(event: unknown): void {\n        // Render assistant message in your UI\n    }\n}\n```\n\n## Справочник по API\n\n### MessageOptions\n\n| Язык    | Поле   | Тип                               | По умолчанию | Description              |\n| ------- | ------ | --------------------------------- | ------------ | ------------------------ |\n| Node.js | `mode` | `\"enqueue\" \\| \"immediate\"`        | `\"enqueue\"`  | Режим доставки сообщений |\n| Python  | `mode` | `Literal[\"enqueue\", \"immediate\"]` | `\"enqueue\"`  | Режим доставки сообщений |\n| Go      | `Mode` | `string`                          | `\"enqueue\"`  | Режим доставки сообщений |\n| .NET    | `Mode` | `string?`                         | `\"enqueue\"`  | Режим доставки сообщений |\n\n### Режимы доставки\n\n| Режим         | Эффект                   | Во время активного поворота         | Во время простоя         |\n| ------------- | ------------------------ | ----------------------------------- | ------------------------ |\n| `\"enqueue\"`   | Очередь на следующий ход | Ожидания в очереди FIFO             | Сразу начинает новый ход |\n| `\"immediate\"` | Впрыск в ход тока        | Введено перед следующим вызовом LLM | Сразу начинает новый ход |\n\n> \\[!NOTE]\n> Когда сессия находится в режиме простоя (не обрабатывается), оба режима ведут себя одинаково — сообщение сразу начинает новый ход.\n\n## Лучшие практики\n\n1. **По умолчанию очередь** — используйте `\"enqueue\"` (или опускайте `mode`) для большинства сообщений. Это предсказуемо и не мешает работе в процессе.\n\n2. **Резервируйте рулевое управление для исправлений** — используйте `\"immediate\"` тогда, когда агент активно делает что-то не так, и вам нужно перенаправить его, прежде чем он пойдёт дальше.\n\n3. **Держите сообщения управления лаконичными** — агенту нужно быстро понять корректировку курса. Длинные, сложные сообщения управления могут запутать текущий контекст.\n\n4. **Не** переусердствуйте — несколько сообщений о быстром рулевом управлении могут ухудшить качество поворота. Если нужно сильно изменить направление, подумайте о том, чтобы прервать поворот и начать с чистого листа.\n\n5. **Покажите состояние очереди в интерфейсе** — откажите количество поставленных в очередь сообщений, чтобы пользователи знали, что ожидается. Прислушивайтесь к событиям простоя, чтобы очистить дисплей.\n\n6. **Обрабатывайте запасной вариант руления в очередь** — если после завершения поворота приходит сообщение о рулевом управлении, оно автоматически переносится в очередь. Спроектируйте свой интерфейс так, чтобы отражать этот переход.\n\n## См. также\n\n* [Создайте своё первое приложение на базе Copilot](/ru/copilot/how-tos/copilot-sdk/getting-started): Настройте сессию и отправьте сообщения\n* [Пользовательские агенты и оркестровка субагентов](/ru/copilot/how-tos/copilot-sdk/features/custom-agents): Определите специализированные агенты с помощью инструментов с ограниченной областью действия\n* [Сессионные хуки](/ru/copilot/how-tos/copilot-sdk/hooks/hooks-overview): Реагировать на события жизненного цикла сессии\n* [Возобновление сессии и сохранение](/ru/copilot/how-tos/copilot-sdk/features/session-persistence): Возобновить сессии после перезапуска"}