Overview
세션이 턴을 적극적으로 처리하는 경우 들어오는 메시지는 다음의 필드를 mode통해 MessageOptions 두 가지 모드 중 하나로 배달될 수 있습니다.
| 모드 | 동작 | 사용 사례 |
|---|---|---|
"immediate" (스티어링) | ||
| 현재 LLM 턴에 삽입 | "실제로 해당 파일을 만들지 마세요. 다른 방법을 사용하세요." | |
"enqueue" (대기 중) | 현재 턴이 완료된 후 큐에 대기 및 처리됨 | "이 후에 테스트도 수정합니다." |

조향(직접 실행 모드)
조향은 에이전트의 현재 턴에 직접 삽입되는 메시지를 보냅니다. 에이전트는 메시지를 실시간으로 보고 그에 따라 응답을 조정합니다. 턴을 중단하지 않고 코스 수정에 유용합니다.
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",
});
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()
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)
}
}
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"
});
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();
}
조향의 내부 작동 원리
- 메시지가 런타임의
ImmediatePromptProcessor큐에 추가됩니다. - 현재 턴 내의 다음 LLM 요청 전에 프로세서가 메시지를 대화에 삽입합니다.
- 에이전트는 조향 메시지를 새 사용자 메시지로 보고 응답을 조정합니다.
- 조향 메시지가 처리되기 전에 턴이 완료되면 다음 턴을 위해 자동으로 일반 큐로 이동됩니다.
참고
조향 메시지는 현재 턴 내에서 최선의 노력입니다. 에이전트가 이미 도구 호출에 커밋한 경우 해당 호출이 완료된 후에도 동일한 턴 내에 조향이 적용됩니다.
큐잉(등록 모드)
큐는 현재 턴이 완료된 후 순차적으로 처리할 메시지를 버퍼링합니다. 큐에 대기된 각 메시지는 자체적으로 전체 턴을 시작합니다. 이 모드는 기본 모드입니다. 생략 mode하면 SDK에서 사용합니다 "enqueue".
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
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()
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
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
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
}
큐잉이 내부적으로 작동하는 방식
- 메시지는 세션의
itemQueue로 추가됩니다.QueuedItem - 현재 턴이 완료되고 세션이 유휴 상태가 되면
processQueuedItems()이 실행됩니다 - 항목은 FIFO 순서로 큐에서 제거됩니다. 각 메시지는 전체 에이전트 턴을 트리거합니다.
- 턴이 종료될 때 조향 메시지가 보류 중이면 큐의 앞쪽으로 이동됩니다.
- 큐가 비어 있는 때까지 처리가 계속된 다음 세션이 유휴 이벤트를 내보냅니다.
조향 및 큐잉 결합
단일 세션에서 두 패턴을 함께 사용할 수 있습니다. 지연된 메시지가 자신의 차례를 기다리는 동안 조향은 현재 턴에 영향을 줍니다.
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",
});
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",
)
조정 및 대기열 관리 중에서 선택
| Scenario | Pattern | 이유 |
|---|---|---|
| 에이전트가 잘못된 길을 가고 있습니다. | ||
| 스티어링 | 진행률을 잃지 않고 현재 턴을 리디렉션합니다. | |
| 에이전트가 해야 할 일을 생각했습니다. | ||
| 대기열 처리 | 현재 작업을 방해하지 않습니다. 다음을 실행합니다. | |
| 에이전트가 실수를 저지르려고 합니다. | ||
| 스티어링 | 실수를 저지르기 전에 개입하다 | |
| 여러 작업을 연결하려는 경우 | ||
| 대기열 처리 | 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 참조
메시지 옵션
| 언어 | Field | Type | Default | Description |
|---|---|---|---|---|
| Node.js | mode | "enqueue" | "immediate" | "enqueue" | 메시지 배달 모드 |
| Python | mode | Literal["enqueue", "immediate"] | "enqueue" | 메시지 배달 모드 |
| Go | Mode | string | "enqueue" | 메시지 배달 모드 |
| .NET | Mode | string? | "enqueue" | 메시지 배달 모드 |
배송 모드
| 모드 | 영향 | 활성 턴 중 | 유휴 상태 |
|---|---|---|---|
"enqueue" | 다음 턴을 위한 대기열 | FIFO 큐에서 대기 | 새 턴을 즉시 시작합니다. |
"immediate" | 현재 턴에 삽입 | 다음 LLM 호출 전에 주입됨 | 새 턴을 즉시 시작합니다. |
참고
세션이 유휴 상태(처리되지 않음)이면 두 모드가 동일하게 작동합니다. 메시지가 즉시 새 턴을 시작합니다.
모범 사례
-
기본적으로 대기열에 추가됨—대부분의 메시지에
"enqueue"을 사용하거나mode을 생략합니다. 예측 가능하며 진행 중인 작업을 방해할 위험이 없습니다. -
조정 작업을 위한 조향 예약 - 에이전트가 잘못된 작업을 적극적으로 하고 있을 때, 그 작업이 더 진행하기 전에 리디렉션해야 할 때
"immediate"을 사용하십시오. -
조향 메시지를 간결하게 유지합니다. 에이전트는 과정 수정 사항을 신속하게 이해해야 합니다. 길고 복잡한 조향 메시지는 현재 컨텍스트를 혼동할 수 있습니다.
-
과도하게 조종하지 마세요. 여러 빠른 스티어링 메시지는 턴 품질을 저하시킬 수 있습니다. 방향을 크게 변경해야 하는 경우 턴을 중단하고 새로 시작하는 것이 좋습니다.
-
UI에 큐 상태 표시 - 사용자가 보류 중인 내용을 알 수 있도록 큐에 대기 중인 메시지 수를 표시합니다. 유휴 상태 이벤트를 수신 대기하여 화면을 초기화합니다.
-
조향-대기열로 전환 처리 - 턴이 완료된 후 조향 메시지가 도착하면 자동으로 대기열로 이동합니다. 이 전환을 반영하도록 UI를 디자인합니다.
참고하십시오
- 첫 번째 Copilot 기반 앱 빌드: 세션 설정 및 메시지 보내기
- 사용자 정의 에이전트 및 하위 에이전트 오케스트레이션: 범위가 지정된 도구를 사용하여 특수 에이전트 정의
- 세션 후크: 세션 수명 주기 이벤트에 반응
- 세션 다시 시작 및 지속성: 다시 시작 후에도 세션 재개