Skip to main content

세션 다시 시작 및 지속성

이 가이드에서는 SDK의 세션 지속성 기능(작업을 일시 중지하고, 나중에 다시 시작하고, 프로덕션 환경에서 세션을 관리하는 방법)을 안내합니다.

세션 작동 방식

세션을 만들 때 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?" });

다시 시작 옵션

세션을 다시 시작하면 필요에 따라 여러 설정을 다시 구성할 수 있습니다. 이 기능은 모델을 변경하거나, 도구 구성을 업데이트하거나, 동작을 수정해야 하는 경우에 유용합니다.

OptionDescription
model다시 시작된 세션의 모델 변경
systemMessage시스템 프롬프트 재정의 또는 확장
availableTools사용할 수 있는 도구 제한
excludedTools특정 도구 사용 안 함
providerBYOK 자격 증명 다시 제공(BYOK 세션에 필요)
reasoningEffort추론 노력 수준 조정
streaming스트리밍 응답 사용/사용 안 함
workingDirectory작업 디렉터리 변경
configDir구성 디렉터리를 재정의
mcpServersMCP 서버 구성
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를 선택합니다. 이렇게 하면 감사 및 정리가 훨씬 쉬워집니다.

PatternExample사용 사례
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는 해당 언어에 맞는 자동 정리 패턴도 제공합니다:

언어PatternExample
TypeScriptSymbol.asyncDisposeawait using session = await client.createSession(config);
Python
async with 컨텍스트 관리자async with await client.create_session(on_permission_request=handler) as session:
C#IAsyncDisposableawait using var session = await client.CreateSessionAsync(config);
Godeferdefer session.Disconnect()

참고

destroy()은 더 이상 사용되지 않으며, 대신 disconnect() 사용이 권장됩니다. 사용하는 destroy() 기존 코드는 계속 작동하지만 마이그레이션해야 합니다.

세션 영구 삭제(deleteSession)

디스크에서 세션과 그 모든 데이터(대화 기록, 계획 상태, 아티팩트)를 영구적으로 제거하려면 deleteSession를 사용하십시오. 이는 되돌릴 수 없습니다. 삭제 후 세션을 다시 시작 하지 못할 수 있습니다 .

// Permanently remove session data
await client.deleteSession("user-123-task-456");

disconnect() vs deleteSession():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`);
});

배포 패턴

적합 대상: 강력한 격리, 다중 테넌트 환경, 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 호환성 을 참조하세요.

제한 사항 및 고려 사항

LimitationDescription완화 방법
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/

다음 단계