{"meta":{"title":"스티어링 및 대기열 지정","intro":"에이전트가 이미 작업 중인 동안에도 사용자가 메시지를 보낼 수 있게 하는 두 가지 상호작용 패턴이 있습니다. 방향 조정은 현재 턴 도중 에이전트의 진행 방향을 바꾸고, 대기열 처리는 현재 턴이 완료된 후 순차적으로 처리할 수 있도록 메시지를 버퍼링합니다.","product":"GitHub Copilot","breadcrumbs":[{"href":"/ko/enterprise-cloud@latest/copilot","title":"GitHub Copilot"},{"href":"/ko/enterprise-cloud@latest/copilot/how-tos","title":"방법"},{"href":"/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk","title":"코필로트 SDK"},{"href":"/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/features","title":"기능"},{"href":"/ko/enterprise-cloud@latest/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## Overview\n\n세션이 턴을 적극적으로 처리하는 경우 들어오는 메시지는 다음의 필드를 `mode`통해 `MessageOptions` 두 가지 모드 중 하나로 배달될 수 있습니다.\n\n| 모드                   | 동작                                  | 사용 사례              |\n| -------------------- | ----------------------------------- | ------------------ |\n| `\"immediate\"` (스티어링) |                                     |                    |\n| **현재** 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## 스티어링 및 대기열 지정을 사용하여 UI 구축\n\n두 모드를 모두 지원하는 대화형 UI를 빌드하는 패턴은 다음과 같습니다.\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### 메시지 옵션\n\n| 언어      | Field  | Type                              | Default     | 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. **UI에 큐 상태 표시** - 사용자가 보류 중인 내용을 알 수 있도록 큐에 대기 중인 메시지 수를 표시합니다. 유휴 상태 이벤트를 수신 대기하여 화면을 초기화합니다.\n\n6. **조향-대기열로 전환 처리** - 턴이 완료된 후 조향 메시지가 도착하면 자동으로 대기열로 이동합니다. 이 전환을 반영하도록 UI를 디자인합니다.\n\n## 참고하십시오\n\n* [첫 번째 Copilot 기반 앱 빌드](/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/getting-started): 세션 설정 및 메시지 보내기\n* [사용자 정의 에이전트 및 하위 에이전트 오케스트레이션](/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/features/custom-agents): 범위가 지정된 도구를 사용하여 특수 에이전트 정의\n* [세션 후크](/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks/hooks-overview): 세션 수명 주기 이벤트에 반응\n* [세션 다시 시작 및 지속성](/ko/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/features/session-persistence): 다시 시작 후에도 세션 재개"}