Skip to main content

조향 및 대기열 관리

에이전트가 이미 작업 중인 동안에도 사용자가 메시지를 보낼 수 있게 하는 두 가지 상호작용 패턴이 있습니다. 방향 조정은 현재 턴 도중 에이전트의 진행 방향을 바꾸고, 대기열 처리는 현재 턴이 완료된 후 순차적으로 처리할 수 있도록 메시지를 버퍼링합니다.

Overview

세션이 턴을 적극적으로 처리하는 경우 들어오는 메시지는 다음의 필드를 mode통해 MessageOptions 두 가지 모드 중 하나로 배달될 수 있습니다.

모드동작사용 사례
"immediate" (스티어링)
현재 LLM 턴에 삽입"실제로 해당 파일을 만들지 마세요. 다른 방법을 사용하세요."
"enqueue" (대기 중)현재 턴이 완료된 큐에 대기 및 처리됨"이 후에 테스트도 수정합니다."

다이어그램: 설명된 프로세스를 보여 주는 시퀀스 다이어그램

조향(직접 실행 모드)

조향은 에이전트의 현재 턴에 직접 삽입되는 메시지를 보냅니다. 에이전트는 메시지를 실시간으로 보고 그에 따라 응답을 조정합니다. 턴을 중단하지 않고 코스 수정에 유용합니다.

TypeScript
import { CopilotClient } from "@github/copilot-sdk";

const client = new CopilotClient();
await client.start();

const session = await client.createSession({
    model: "gpt-4.1",
    onPermissionRequest: async () => ({ kind: "approve-once" }),
});

// Start a long-running task
const msgId = await session.send({
    prompt: "Refactor the authentication module to use sessions",
});

// While the agent is working, steer it
await session.send({
    prompt: "Actually, use JWT tokens instead of sessions",
    mode: "immediate",
});
Python
from copilot import CopilotClient, PermissionDecisionApproveOnce

async def main():
    client = CopilotClient()
    await client.start()

    session = await client.create_session(
        on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
        model="gpt-4.1",
    )

    # Start a long-running task
    msg_id = await session.send(
        "Refactor the authentication module to use sessions",
    )

    # While the agent is working, steer it
    await session.send(
        "Actually, use JWT tokens instead of sessions",
        mode="immediate",
    )

    await client.stop()
Go
package main

import (
    "context"
    "log"
    copilot "github.com/github/copilot-sdk/go"
    "github.com/github/copilot-sdk/go/rpc"
)

func main() {
    ctx := context.Background()
    client := copilot.NewClient(nil)
    if err := client.Start(ctx); err != nil {
        log.Fatal(err)
    }
    defer client.Stop()

    session, err := client.CreateSession(ctx, &copilot.SessionConfig{
        Model: "gpt-4.1",
        OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {
            return &rpc.PermissionDecisionApproveOnce{}, nil
        },
    })
    if err != nil {
        log.Fatal(err)
    }

    // Start a long-running task
    _, err = session.Send(ctx, copilot.MessageOptions{
        Prompt: "Refactor the authentication module to use sessions",
    })
    if err != nil {
        log.Fatal(err)
    }

    // While the agent is working, steer it
    _, err = session.Send(ctx, copilot.MessageOptions{
        Prompt: "Actually, use JWT tokens instead of sessions",
        Mode:   "immediate",
    })
    if err != nil {
        log.Fatal(err)
    }
}
.NET
using GitHub.Copilot;
using GitHub.Copilot.Rpc;

await using var client = new CopilotClient();
await using var session = await client.CreateSessionAsync(new SessionConfig
{
    Model = "gpt-4.1",
    OnPermissionRequest = (req, inv) =>
        Task.FromResult(PermissionDecision.ApproveOnce()),
});

// Start a long-running task
var msgId = await session.SendAsync(new MessageOptions
{
    Prompt = "Refactor the authentication module to use sessions"
});

// While the agent is working, steer it
await session.SendAsync(new MessageOptions
{
    Prompt = "Actually, use JWT tokens instead of sessions",
    Mode = "immediate"
});
Java
import com.github.copilot.sdk.CopilotClient;
import com.github.copilot.sdk.events.*;
import com.github.copilot.sdk.json.*;

try (var client = new CopilotClient()) {
    client.start().get();

    var session = client.createSession(
        new SessionConfig()
            .setModel("gpt-4.1")
            .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
    ).get();

    // Start a long-running task
    session.send(new MessageOptions()
        .setPrompt("Refactor the authentication module to use sessions")
    ).get();

    // While the agent is working, steer it
    session.send(new MessageOptions()
        .setPrompt("Actually, use JWT tokens instead of sessions")
        .setMode("immediate")
    ).get();
}

조향의 내부 작동 원리

  1. 메시지가 런타임의 ImmediatePromptProcessor 큐에 추가됩니다.
  2. 현재 턴 내의 다음 LLM 요청 전에 프로세서가 메시지를 대화에 삽입합니다.
  3. 에이전트는 조향 메시지를 새 사용자 메시지로 보고 응답을 조정합니다.
  4. 조향 메시지가 처리되기 전에 턴이 완료되면 다음 턴을 위해 자동으로 일반 큐로 이동됩니다.

참고

조향 메시지는 현재 턴 내에서 최선의 노력입니다. 에이전트가 이미 도구 호출에 커밋한 경우 해당 호출이 완료된 후에도 동일한 턴 내에 조향이 적용됩니다.

큐잉(등록 모드)

큐는 현재 턴이 완료된 후 순차적으로 처리할 메시지를 버퍼링합니다. 큐에 대기된 각 메시지는 자체적으로 전체 턴을 시작합니다. 이 모드는 기본 모드입니다. 생략 mode하면 SDK에서 사용합니다 "enqueue".

TypeScript
import { CopilotClient } from "@github/copilot-sdk";

const client = new CopilotClient();
await client.start();

const session = await client.createSession({
    model: "gpt-4.1",
    onPermissionRequest: async () => ({ kind: "approve-once" }),
});

// Send an initial task
await session.send({ prompt: "Set up the project structure" });

// Queue follow-up tasks while the agent is busy
await session.send({
    prompt: "Add unit tests for the auth module",
    mode: "enqueue",
});

await session.send({
    prompt: "Update the README with setup instructions",
    mode: "enqueue",
});

// Messages are processed in FIFO order after each turn completes
Python
from copilot import CopilotClient, PermissionDecisionApproveOnce

async def main():
    client = CopilotClient()
    await client.start()

    session = await client.create_session(
        on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
        model="gpt-4.1",
    )

    # Send an initial task
    await session.send("Set up the project structure")

    # Queue follow-up tasks while the agent is busy
    await session.send(
        "Add unit tests for the auth module",
        mode="enqueue",
    )

    await session.send(
        "Update the README with setup instructions",
        mode="enqueue",
    )

    # Messages are processed in FIFO order after each turn completes
    await client.stop()
Go
package main

import (
    "context"
    copilot "github.com/github/copilot-sdk/go"
    "github.com/github/copilot-sdk/go/rpc"
)

func main() {
    ctx := context.Background()
    client := copilot.NewClient(nil)
    client.Start(ctx)

    session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
        Model: "gpt-4.1",
        OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {
            return &rpc.PermissionDecisionApproveOnce{}, nil
        },
    })

    session.Send(ctx, copilot.MessageOptions{
        Prompt: "Set up the project structure",
    })

    session.Send(ctx, copilot.MessageOptions{
        Prompt: "Add unit tests for the auth module",
        Mode:   "enqueue",
    })

    session.Send(ctx, copilot.MessageOptions{
        Prompt: "Update the README with setup instructions",
        Mode:   "enqueue",
    })
}
// Send an initial task
session.Send(ctx, copilot.MessageOptions{
    Prompt: "Set up the project structure",
})

// Queue follow-up tasks while the agent is busy
session.Send(ctx, copilot.MessageOptions{
    Prompt: "Add unit tests for the auth module",
    Mode:   "enqueue",
})

session.Send(ctx, copilot.MessageOptions{
    Prompt: "Update the README with setup instructions",
    Mode:   "enqueue",
})

// Messages are processed in FIFO order after each turn completes
.NET
using GitHub.Copilot;
using GitHub.Copilot.Rpc;

public static class QueueingExample
{
    public static async Task Main()
    {
        await using var client = new CopilotClient();
        await using var session = await client.CreateSessionAsync(new SessionConfig
        {
            Model = "gpt-4.1",
            OnPermissionRequest = (req, inv) =>
                Task.FromResult(PermissionDecision.ApproveOnce()),
        });

        await session.SendAsync(new MessageOptions
        {
            Prompt = "Set up the project structure"
        });

        await session.SendAsync(new MessageOptions
        {
            Prompt = "Add unit tests for the auth module",
            Mode = "enqueue"
        });

        await session.SendAsync(new MessageOptions
        {
            Prompt = "Update the README with setup instructions",
            Mode = "enqueue"
        });
    }
}
// Send an initial task
await session.SendAsync(new MessageOptions
{
    Prompt = "Set up the project structure"
});

// Queue follow-up tasks while the agent is busy
await session.SendAsync(new MessageOptions
{
    Prompt = "Add unit tests for the auth module",
    Mode = "enqueue"
});

await session.SendAsync(new MessageOptions
{
    Prompt = "Update the README with setup instructions",
    Mode = "enqueue"
});

// Messages are processed in FIFO order after each turn completes
Java
import com.github.copilot.sdk.CopilotClient;
import com.github.copilot.sdk.events.*;
import com.github.copilot.sdk.json.*;

try (var client = new CopilotClient()) {
    client.start().get();

    var session = client.createSession(
        new SessionConfig()
            .setModel("gpt-4.1")
            .setOnPermissionRequest(PermissionHandler.APPROVE_ALL)
    ).get();

    // Send an initial task
    session.send(new MessageOptions().setPrompt("Set up the project structure")).get();

    // Queue follow-up tasks while the agent is busy
    session.send(new MessageOptions()
        .setPrompt("Add unit tests for the auth module")
        .setMode("enqueue")
    ).get();

    session.send(new MessageOptions()
        .setPrompt("Update the README with setup instructions")
        .setMode("enqueue")
    ).get();

    // Messages are processed in FIFO order after each turn completes
}

큐잉이 내부적으로 작동하는 방식

  1. 메시지는 세션의 itemQueue 로 추가됩니다. QueuedItem
  2. 현재 턴이 완료되고 세션이 유휴 상태가 되면 processQueuedItems()이 실행됩니다
  3. 항목은 FIFO 순서로 큐에서 제거됩니다. 각 메시지는 전체 에이전트 턴을 트리거합니다.
  4. 턴이 종료될 때 조향 메시지가 보류 중이면 큐의 앞쪽으로 이동됩니다.
  5. 큐가 비어 있는 때까지 처리가 계속된 다음 세션이 유휴 이벤트를 내보냅니다.

조향 및 큐잉 결합

단일 세션에서 두 패턴을 함께 사용할 수 있습니다. 지연된 메시지가 자신의 차례를 기다리는 동안 조향은 현재 턴에 영향을 줍니다.

TypeScript
const session = await client.createSession({
    model: "gpt-4.1",
    onPermissionRequest: async () => ({ kind: "approve-once" }),
});

// Start a task
await session.send({ prompt: "Refactor the database layer" });

// Steer the current work
await session.send({
    prompt: "Make sure to keep backwards compatibility with the v1 API",
    mode: "immediate",
});

// Queue a follow-up for after this turn
await session.send({
    prompt: "Now add migration scripts for the schema changes",
    mode: "enqueue",
});
Python
session = await client.create_session(
    on_permission_request=lambda req, inv: PermissionDecisionApproveOnce(),
    model="gpt-4.1",
)

# Start a task
await session.send("Refactor the database layer")

# Steer the current work
await session.send(
    "Make sure to keep backwards compatibility with the v1 API",
    mode="immediate",
)

# Queue a follow-up for after this turn
await session.send(
    "Now add migration scripts for the schema changes",
    mode="enqueue",
)

조정 및 대기열 관리 중에서 선택

ScenarioPattern이유
에이전트가 잘못된 길을 가고 있습니다.
스티어링진행률을 잃지 않고 현재 턴을 리디렉션합니다.
에이전트가 해야 할 일을 생각했습니다.
대기열 처리현재 작업을 방해하지 않습니다. 다음을 실행합니다.
에이전트가 실수를 저지르려고 합니다.
스티어링실수를 저지르기 전에 개입하다
여러 작업을 연결하려는 경우
대기열 처리FIFO 순서 지정은 예측 가능한 실행을 보장합니다.
현재 작업에 컨텍스트를 추가하려고 합니다.
스티어링에이전트가 그것을 현재의 추론에 통합합니다.
관련 없는 요청을 일괄 처리하려고 합니다.
대기열 처리각각은 깔끔한 문맥 속에서 자신만의 전체 턴을 갖습니다.

조향 및 대기열을 사용하여 UI 구축

두 모드를 모두 지원하는 대화형 UI를 빌드하는 패턴은 다음과 같습니다.

import { CopilotClient, CopilotSession } from "@github/copilot-sdk";

interface PendingMessage {
    prompt: string;
    mode: "immediate" | "enqueue";
    sentAt: Date;
}

class InteractiveChat {
    private session: CopilotSession;
    private isProcessing = false;
    private pendingMessages: PendingMessage[] = [];

    constructor(session: CopilotSession) {
        this.session = session;

        session.on((event) => {
            if (event.type === "session.idle") {
                this.isProcessing = false;
                this.onIdle();
            }
            if (event.type === "assistant.message") {
                this.renderMessage(event);
            }
        });
    }

    async sendMessage(prompt: string): Promise<void> {
        if (!this.isProcessing) {
            this.isProcessing = true;
            await this.session.send({ prompt });
            return;
        }

        // Session is busy — let the user choose how to deliver
        // Your UI would present this choice (e.g., buttons, keyboard shortcuts)
    }

    async steer(prompt: string): Promise<void> {
        this.pendingMessages.push({
            prompt,
            mode: "immediate",
            sentAt: new Date(),
        });
        await this.session.send({ prompt, mode: "immediate" });
    }

    async enqueue(prompt: string): Promise<void> {
        this.pendingMessages.push({
            prompt,
            mode: "enqueue",
            sentAt: new Date(),
        });
        await this.session.send({ prompt, mode: "enqueue" });
    }

    private onIdle(): void {
        this.pendingMessages = [];
        // Update UI to show session is ready for new input
    }

    private renderMessage(event: unknown): void {
        // Render assistant message in your UI
    }
}

API 참조

메시지 옵션

언어FieldTypeDefaultDescription
Node.jsmode"enqueue" | "immediate""enqueue"메시지 배달 모드
PythonmodeLiteral["enqueue", "immediate"]"enqueue"메시지 배달 모드
GoModestring"enqueue"메시지 배달 모드
.NETModestring?"enqueue"메시지 배달 모드

배송 모드

모드영향활성 턴 중유휴 상태
"enqueue"다음 턴을 위한 대기열FIFO 큐에서 대기새 턴을 즉시 시작합니다.
"immediate"현재 턴에 삽입다음 LLM 호출 전에 주입됨새 턴을 즉시 시작합니다.

참고

세션이 유휴 상태(처리되지 않음)이면 두 모드가 동일하게 작동합니다. 메시지가 즉시 새 턴을 시작합니다.

모범 사례

  1. 기본적으로 대기열에 추가됨—대부분의 메시지에 "enqueue"을 사용하거나 mode을 생략합니다. 예측 가능하며 진행 중인 작업을 방해할 위험이 없습니다.

  2. 조정 작업을 위한 조향 예약 - 에이전트가 잘못된 작업을 적극적으로 하고 있을 때, 그 작업이 더 진행하기 전에 리디렉션해야 할 때 "immediate"을 사용하십시오.

  3. 조향 메시지를 간결하게 유지합니다. 에이전트는 과정 수정 사항을 신속하게 이해해야 합니다. 길고 복잡한 조향 메시지는 현재 컨텍스트를 혼동할 수 있습니다.

  4. 과도하게 조종하지 마세요. 여러 빠른 스티어링 메시지는 턴 품질을 저하시킬 수 있습니다. 방향을 크게 변경해야 하는 경우 턴을 중단하고 새로 시작하는 것이 좋습니다.

  5. UI에 큐 상태 표시 - 사용자가 보류 중인 내용을 알 수 있도록 큐에 대기 중인 메시지 수를 표시합니다. 유휴 상태 이벤트를 수신 대기하여 화면을 초기화합니다.

  6. 조향-대기열로 전환 처리 - 턴이 완료된 후 조향 메시지가 도착하면 자동으로 대기열로 이동합니다. 이 전환을 반영하도록 UI를 디자인합니다.

참고하십시오