세션 작동 방식
세션을 만들 때 Copilot CLI는 대화 기록, 도구 상태 및 계획 컨텍스트를 유지 관리합니다. 기본적으로 이 상태는 메모리에 있으며 세션이 종료되면 사라집니다. 지속성을 사용하도록 설정하면 다시 시작, 컨테이너 마이그레이션 또는 다른 클라이언트 인스턴스 간에 세션을 다시 시작할 수 있습니다.

| 상태 | 어떻게 되나요? |
|---|---|
| 창조하다 | |
session_id 할당 | |
| Active | 프롬프트 보내기, 도구 호출, 응답 |
| 일시 중지된 | 디스크에 저장된 상태 |
| 다시 시작 | 디스크에서 로드된 상태 |
빠른 시작: 다시 시작 가능한 세션 만들기
재개 가능한 세션의 핵심은 자체 session_id를 제공하는 것입니다. 이 ID가 없으면 SDK는 임의 ID를 생성하고 세션은 나중에 다시 시작될 수 없습니다.
TypeScript
import { CopilotClient } from "@github/copilot-sdk";
const client = new CopilotClient();
// Create a session with a meaningful ID
const session = await client.createSession({
sessionId: "user-123-task-456",
model: "gpt-5.2-codex",
});
// Do some work...
await session.sendAndWait({ prompt: "Analyze my codebase" });
// Session state is automatically persisted
// You can safely close the client
Python
from copilot import CopilotClient
from copilot.session import PermissionHandler
client = CopilotClient()
await client.start()
# Create a session with a meaningful ID
session = await client.create_session(on_permission_request=PermissionHandler.approve_all, model="gpt-5.2-codex", session_id="user-123-task-456")
# Do some work...
await session.send_and_wait("Analyze my codebase")
# Session state is automatically persisted
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)
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
SessionID: "user-123-task-456",
Model: "gpt-5.2-codex",
OnPermissionRequest: func(req copilot.PermissionRequest, inv copilot.PermissionInvocation) (rpc.PermissionDecision, error) {
return &rpc.PermissionDecisionApproveOnce{}, nil
},
})
session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Analyze my codebase"})
_ = session
}
ctx := context.Background()
client := copilot.NewClient(nil)
// Create a session with a meaningful ID
session, _ := client.CreateSession(ctx, &copilot.SessionConfig{
SessionID: "user-123-task-456",
Model: "gpt-5.2-codex",
})
// Do some work...
session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "Analyze my codebase"})
// Session state is automatically persisted
C#(.NET)
using GitHub.Copilot;
var client = new CopilotClient();
// Create a session with a meaningful ID
var session = await client.CreateSessionAsync(new SessionConfig
{
SessionId = "user-123-task-456",
Model = "gpt-5.2-codex",
});
// Do some work...
await session.SendAndWaitAsync(new MessageOptions { Prompt = "Analyze my codebase" });
// Session state is automatically persisted
세션 다시 열기
나중에(분, 시간 또는 며칠) 중단된 위치에서 세션을 다시 시작할 수 있습니다.

TypeScript
// Resume from a different client instance (or after restart)
const session = await client.resumeSession("user-123-task-456");
// Continue where you left off
await session.sendAndWait({ prompt: "What did we discuss earlier?" });
Python
# Resume from a different client instance (or after restart)
session = await client.resume_session("user-123-task-456", on_permission_request=PermissionHandler.approve_all)
# Continue where you left off
await session.send_and_wait("What did we discuss earlier?")
Go
package main
import (
"context"
copilot "github.com/github/copilot-sdk/go"
)
func main() {
ctx := context.Background()
client := copilot.NewClient(nil)
session, _ := client.ResumeSession(ctx, "user-123-task-456", nil)
session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "What did we discuss earlier?"})
_ = session
}
ctx := context.Background()
// Resume from a different client instance (or after restart)
session, _ := client.ResumeSession(ctx, "user-123-task-456", nil)
// Continue where you left off
session.SendAndWait(ctx, copilot.MessageOptions{Prompt: "What did we discuss earlier?"})
C#(.NET)
using GitHub.Copilot;
using GitHub.Copilot.Rpc;
public static class ResumeSessionExample
{
public static async Task Main()
{
await using var client = new CopilotClient();
var session = await client.ResumeSessionAsync("user-123-task-456", new ResumeSessionConfig
{
OnPermissionRequest = (req, inv) =>
Task.FromResult(PermissionDecision.ApproveOnce()),
});
await session.SendAndWaitAsync(new MessageOptions { Prompt = "What did we discuss earlier?" });
}
}
// Resume from a different client instance (or after restart)
var session = await client.ResumeSessionAsync("user-123-task-456");
// Continue where you left off
await session.SendAndWaitAsync(new MessageOptions { Prompt = "What did we discuss earlier?" });
다시 시작 옵션
세션을 다시 시작하면 필요에 따라 여러 설정을 다시 구성할 수 있습니다. 이 기능은 모델을 변경하거나, 도구 구성을 업데이트하거나, 동작을 수정해야 하는 경우에 유용합니다.
| Option | Description |
|---|---|
model | 다시 시작된 세션의 모델 변경 |
systemMessage | 시스템 프롬프트 재정의 또는 확장 |
availableTools | 사용할 수 있는 도구 제한 |
excludedTools | 특정 도구 사용 안 함 |
provider | BYOK 자격 증명 다시 제공(BYOK 세션에 필요) |
reasoningEffort | 추론 노력 수준 조정 |
streaming | 스트리밍 응답 사용/사용 안 함 |
workingDirectory | 작업 디렉터리 변경 |
configDir | 구성 디렉터리를 재정의 |
mcpServers | MCP 서버 구성 |
customAgents | 사용자 지정 에이전트 구성 |
agent | 이름으로 사용자 지정 에이전트 사전 선택 |
skillDirectories | 기술을 로드하기 위한 디렉터리 |
disabledSkills | 사용하지 않도록 설정하는 기술 |
infiniteSessions | 무한 세션 동작 구성 |
예: 다시 시작할 때 모델 변경
// Resume with a different model
const session = await client.resumeSession("user-123-task-456", {
model: "claude-sonnet-4", // Switch to a different model
reasoningEffort: "high", // Increase reasoning effort
});
다시 시작된 세션과 함께 BYOK(사용자 고유의 키 가져오기) 사용
사용자 고유의 API 키를 사용하는 경우 다시 시작 시 공급자 구성을 다시 제공해야 합니다. API 키는 보안상의 이유로 디스크에 유지되지 않습니다.
// Original session with BYOK
const session = await client.createSession({
sessionId: "user-123-task-456",
model: "gpt-5.2-codex",
provider: {
type: "azure",
endpoint: "https://my-resource.openai.azure.com",
apiKey: process.env.AZURE_OPENAI_KEY,
deploymentId: "my-gpt-deployment",
},
});
// When resuming, you MUST re-provide the provider config
const resumed = await client.resumeSession("user-123-task-456", {
provider: {
type: "azure",
endpoint: "https://my-resource.openai.azure.com",
apiKey: process.env.AZURE_OPENAI_KEY, // Required again
deploymentId: "my-gpt-deployment",
},
});
무엇이 지속됩니까?
세션 상태는 다음으로 저장됩니다 ~/.copilot/session-state/{sessionId}/.
~/.copilot/session-state/
└── user-123-task-456/
├── checkpoints/ # Conversation history snapshots
│ ├── 001.json # Initial state
│ ├── 002.json # After first interaction
│ └── ... # Incremental checkpoints
├── plan.md # Agent's planning state (if any)
└── files/ # Session artifacts
├── analysis.md # Files the agent created
└── notes.txt # Working documents
| 데이터 | 보존되었습니까? | 비고 |
|---|---|---|
| 대화 기록 | ||
| ✅ 예 | 전체 메시지 스레드 | |
| 도구 호출 결과 | ||
| ✅ 예 | 컨텍스트용으로 캐시됨 | |
| 에이전트 계획 상태 | ||
| ✅ 예 | ||
plan.md 파일 | ||
| 세션 아티팩트 | ||
| ✅ 예 | ||
files/ 디렉터리 안에 | ||
| 공급자/API 키 | ||
| ❌ 아니요 | 보안: 다시 제공해야 합니다. | |
| 메모리 내 도구 상태 | ||
| ❌ 아니요 | 도구는 무상태여야 합니다. |
세션 ID 모범 사례
소유권 및 용도를 인코딩하는 세션 ID를 선택합니다. 이렇게 하면 감사 및 정리가 훨씬 쉬워집니다.
| Pattern | Example | 사용 사례 |
|---|---|---|
| ❌ | ||
abc123 | ||
| 임의 ID | 감사하기 어렵고 소유권 정보 없음 | |
| ✅ | ||
user-{userId}-{taskId} | ||
user-alice-pr-review-42 | 다중 사용자 앱 | |
| ✅ | ||
tenant-{tenantId}-{workflow} | ||
tenant-acme-onboarding | 다중 사용자 SaaS | |
| ✅ | ||
{userId}-{taskId}-{timestamp} | ||
alice-deploy-1706932800 | 시간 기반 정리 작업 |
구조적 ID의 이점:
- 감사가 용이합니다: "사용자 alice의 모든 세션 표시"
- 정리하기 쉽습니다. "X보다 오래된 모든 세션 삭제"
- 자연 액세스 제어: 세션 ID에서 사용자 ID 추출
예: 세션 ID 생성
function createSessionId(userId: string, taskType: string): string {
const timestamp = Date.now();
return `${userId}-${taskType}-${timestamp}`;
}
const sessionId = createSessionId("alice", "code-review");
// → "alice-code-review-1706932800000"
import time
def create_session_id(user_id: str, task_type: str) -> str:
timestamp = int(time.time())
return f"{user_id}-{task_type}-{timestamp}"
session_id = create_session_id("alice", "code-review")
# → "alice-code-review-1706932800"
세션 수명 주기 관리
활성 세션 나열
// List all sessions
const sessions = await client.listSessions();
console.log(`Found ${sessions.length} sessions`);
for (const session of sessions) {
console.log(`- ${session.sessionId} (created: ${session.createdAt})`);
}
// Filter sessions by repository
const repoSessions = await client.listSessions({ repository: "owner/repo" });
이전 세션 정리
async function cleanupExpiredSessions(maxAgeMs: number) {
const sessions = await client.listSessions();
const now = Date.now();
for (const session of sessions) {
const age = now - new Date(session.createdAt).getTime();
if (age > maxAgeMs) {
await client.deleteSession(session.sessionId);
console.log(`Deleted expired session: ${session.sessionId}`);
}
}
}
// Clean up sessions older than 24 hours
await cleanupExpiredSessions(24 * 60 * 60 * 1000);
세션에서 연결 끊기(disconnect)
작업이 완료되면 시간 제한을 기다리지 않고 세션에서 명시적으로 연결을 끊습니다. 이렇게 하면 메모리 내 리소스가 해제되지만 디스크에 세션 데이터가 유지되므로 나중에 세션을 다시 시작해도 됩니다.
try {
// Do work...
await session.sendAndWait({ prompt: "Complete the task" });
// Task complete — release in-memory resources (session can be resumed later)
await session.disconnect();
} catch (error) {
// Clean up even on error
await session.disconnect();
throw error;
}
또한 각 SDK는 해당 언어에 맞는 자동 정리 패턴도 제공합니다:
| 언어 | Pattern | Example |
|---|---|---|
| TypeScript | Symbol.asyncDispose | await using session = await client.createSession(config); |
| Python | ||
async with 컨텍스트 관리자 | async with await client.create_session(on_permission_request=handler) as session: | |
| C# | IAsyncDisposable | await using var session = await client.CreateSessionAsync(config); |
| Go | defer | defer session.Disconnect() |
참고
destroy()은 더 이상 사용되지 않으며, 대신 disconnect() 사용이 권장됩니다. 사용하는 destroy() 기존 코드는 계속 작동하지만 마이그레이션해야 합니다.
세션 영구 삭제(deleteSession)
디스크에서 세션과 그 모든 데이터(대화 기록, 계획 상태, 아티팩트)를 영구적으로 제거하려면 deleteSession를 사용하십시오. 이는 되돌릴 수 없습니다. 삭제 후 세션을 다시 시작 하지 못할 수 있습니다 .
// Permanently remove session data
await client.deleteSession("user-123-task-456");
disconnect()vsdeleteSession():disconnect()메모리 내 리소스를 해제하지만 나중에 다시 시작하려면 세션 데이터를 디스크에 유지합니다.deleteSession()는 디스크의 파일을 포함하여 모든 항목을 영구적으로 제거합니다.
자동 정리: 유휴 시간 제한
기본적으로 세션은 유휴 시간 제한이 없으며 명시적으로 연결이 끊어지거나 삭제될 때까지 무기한으로 라이브됩니다. 필요에 따라 다음을 통해 CopilotClientOptions.sessionIdleTimeoutSeconds서버 전체 유휴 시간 제한을 구성할 수 있습니다.
const client = new CopilotClient({
sessionIdleTimeoutSeconds: 30 * 60, // 30 minutes
});
시간 제한이 구성되면 해당 기간 동안 활동이 없는 세션이 자동으로 정리됩니다. 비활성화하려면 0로 설정하거나 생략합니다.
참고
이 옵션은 SDK가 런타임 프로세스를 생성하는 경우에만 적용됩니다. 를 통해 cliUrl기존 서버에 연결할 때 서버의 자체 시간 제한 구성이 적용됩니다.

활성 작업(실행 중인 명령, 백그라운드 에이전트)이 있는 세션은 제한 시간 설정에 관계없이 항상 유휴 정리로부터 보호됩니다.
유휴 이벤트를 수신 대기하여 세션 비활성 상태에 반응합니다.
session.on("session.idle", (event) => {
console.log(`Session idle for ${event.idleDurationMs}ms`);
});
배포 패턴
패턴 1: 사용자당 하나의 CLI 서버(권장)
적합 대상: 강력한 격리, 다중 테넌트 환경, Azure 동적 세션.

**혜택:**✅ 완전한 격리 | ✅ 단순 보안 | ✅ 간편한 크기 조정
패턴 2: 공유 CLI 서버(리소스 효율적)
적합 대상: 내부 도구, 신뢰할 수 있는 환경, 리소스 제한 설정.

Requirements:
- ⚠️ 사용자당 고유한 세션 ID
- ⚠️ 애플리케이션 수준 액세스 제어
- ⚠️ 작업 전 세션 ID 유효성 검사
// Application-level access control for shared CLI
async function resumeSessionWithAuth(
client: CopilotClient,
sessionId: string,
currentUserId: string
): Promise<Session> {
// Parse user from session ID
const [sessionUserId] = sessionId.split("-");
if (sessionUserId !== currentUserId) {
throw new Error("Access denied: session belongs to another user");
}
return client.resumeSession(sessionId);
}
Azure 동적 세션
컨테이너를 다시 시작하거나 마이그레이션할 수 있는 서버리스/컨테이너 배포의 경우:
영구 스토리지 탑재
세션 상태 디렉터리를 영구 스토리지에 탑재해야 합니다.
# Azure Container Instance example
containers:
- name: copilot-agent
image: my-agent:latest
volumeMounts:
- name: session-storage
mountPath: /home/app/.copilot/session-state
volumes:
- name: session-storage
azureFile:
shareName: copilot-sessions
storageAccountName: myaccount

컨테이너를 다시 시작해도 세션이 유지됩니다!
장기 실행 워크플로에 대한 무한 세션
컨텍스트 제한을 초과할 수 있는 워크플로의 경우 자동 압축을 사용하여 무한 세션을 사용하도록 설정합니다.
const session = await client.createSession({
sessionId: "long-workflow-123",
infiniteSessions: {
enabled: true,
backgroundCompactionThreshold: 0.80, // Start compaction at 80% context
bufferExhaustionThreshold: 0.95, // Block at 95% if needed
},
});
참고
임계값은 절대 토큰 수가 아닌 컨텍스트 사용률(0.0-1.0)입니다. 자세한 내용은 SDK 및 CLI 호환성 을 참조하세요.
제한 사항 및 고려 사항
| Limitation | Description | 완화 방법 |
|---|---|---|
| BYOK 다시 인증 | API 키는 유지되지 않습니다. | 비밀 관리자에 키를 저장합니다. 이력서에 제공 |
| 쓰기 가능한 스토리지 | ||
~/.copilot/session-state/ 쓰기 가능해야 합니다. | 컨테이너에 영구 볼륨 탑재 | |
| 세션 잠금 없음 | 동일한 세션에 대한 동시 액세스가 정의되지 않았습니다. | 애플리케이션 레벨 잠금 또는 큐 시스템 구현 |
| 도구 상태가 유지되지 않음 | 메모리 내 도구 상태가 손실됨 | 상태비저장이 되거나 자체 상태를 유지하도록 도구를 설계하십시오. |
동시 액세스 처리
SDK는 기본 제공 세션 잠금을 제공하지 않습니다. 여러 클라이언트가 동일한 세션에 액세스할 수 있는 경우:
// Option 1: Application-level locking with Redis
import Redis from "ioredis";
const redis = new Redis();
async function withSessionLock<T>(
sessionId: string,
fn: () => Promise<T>
): Promise<T> {
const lockKey = `session-lock:${sessionId}`;
const acquired = await redis.set(lockKey, "locked", "NX", "EX", 300);
if (!acquired) {
throw new Error("Session is in use by another client");
}
try {
return await fn();
} finally {
await redis.del(lockKey);
}
}
// Usage
await withSessionLock("user-123-task-456", async () => {
const session = await client.resumeSession("user-123-task-456");
await session.sendAndWait({ prompt: "Continue the task" });
});
요약
| 특징 | 사용 방법 |
|---|---|
| 다시 열 수 있는 세션 만들기 | 사용자 고유의 것을 제공하십시오 sessionId |
| 세션 다시 시작 | client.resumeSession(sessionId) |
| BYOK 다시 시작 | |
provider 구성 다시 제공 | |
| 세션 목록 | client.listSessions(filter?) |
| 활성 세션에서 연결 끊기 | |
session.disconnect()- 메모리 내 리소스를 해제합니다. 디스크의 세션 데이터는 재개를 위해 유지됩니다. | |
| 세션을 영구적으로 삭제 | |
client.deleteSession(sessionId)- 디스크에서 모든 세션 데이터를 영구적으로 제거합니다. 을(를) 다시 시작하지 못 함 | |
| 컨테이너화된 배포 | 영구 스토리지에 탑재 ~/.copilot/session-state/ |
다음 단계
- 세션 후크 - 후크를 사용하여 세션 동작 사용자 지정
- SDK 및 CLI 호환성 - SDK 및 CLI 기능 비교
- 디버깅 가이드 - 세션 문제 해결