{"meta":{"title":"세션 다시 시작 및 지속성","intro":"이 가이드에서는 SDK의 세션 지속성 기능(작업을 일시 중지하고, 나중에 다시 시작하고, 프로덕션 환경에서 세션을 관리하는 방법)을 안내합니다.","product":"GitHub Copilot","breadcrumbs":[{"href":"/ko/copilot","title":"GitHub Copilot"},{"href":"/ko/copilot/how-tos","title":"방법"},{"href":"/ko/copilot/how-tos/copilot-sdk","title":"코필로트 SDK"},{"href":"/ko/copilot/how-tos/copilot-sdk/features","title":"기능"},{"href":"/ko/copilot/how-tos/copilot-sdk/features/session-persistence","title":"세션 지속성"}],"documentType":"article"},"body":"# 세션 다시 시작 및 지속성\n\n이 가이드에서는 SDK의 세션 지속성 기능(작업을 일시 중지하고, 나중에 다시 시작하고, 프로덕션 환경에서 세션을 관리하는 방법)을 안내합니다.\n\n<!-- markdownlint-disable GHD046 GHD005 -->\n\n<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->\n\n## 세션 작동 방식\n\n세션을 만들 때 Copilot CLI는 대화 기록, 도구 상태 및 계획 컨텍스트를 유지 관리합니다. 기본적으로 이 상태는 메모리에 있으며 세션이 종료되면 사라집니다. 지속성을 사용하도록 설정하면 다시 시작, 컨테이너 마이그레이션 또는 다른 클라이언트 인스턴스 간에 세션을 다시 시작할 수 있습니다.\n\n![다이어그램: 설명된 프로세스를 보여 주는 순서도입니다.](/assets/images/help/copilot/copilot-sdk/features-session-persistence-diagram-0.png)\n\n| 상태              | 어떻게 되나요?            |\n| --------------- | ------------------- |\n| **창조하다**        |                     |\n| `session_id` 할당 |                     |\n| **Active**      | 프롬프트 보내기, 도구 호출, 응답 |\n| **일시 중지된**      | 디스크에 저장된 상태         |\n| **다시 시작**       | 디스크에서 로드된 상태        |\n\n## 빠른 시작: 다시 시작 가능한 세션 만들기\n\n재개 가능한 세션의 핵심은 자체 `session_id`를 제공하는 것입니다. 이 ID가 없으면 SDK는 임의 ID를 생성하고 세션은 나중에 다시 시작될 수 없습니다.\n\n### TypeScript\n\n```typescript\nimport { CopilotClient } from \"@github/copilot-sdk\";\n\nconst client = new CopilotClient();\n\n// Create a session with a meaningful ID\nconst session = await client.createSession({\n  sessionId: \"user-123-task-456\",\n  model: \"gpt-5.2-codex\",\n});\n\n// Do some work...\nawait session.sendAndWait({ prompt: \"Analyze my codebase\" });\n\n// Session state is automatically persisted\n// You can safely close the client\n```\n\n### Python\n\n```python\nfrom copilot import CopilotClient\nfrom copilot.session import PermissionHandler\n\nclient = CopilotClient()\nawait client.start()\n\n# Create a session with a meaningful ID\nsession = await client.create_session(on_permission_request=PermissionHandler.approve_all, model=\"gpt-5.2-codex\", session_id=\"user-123-task-456\")\n\n# Do some work...\nawait session.send_and_wait(\"Analyze my codebase\")\n\n# Session state is automatically persisted\n```\n\n### Go\n\n```golang\nctx := context.Background()\nclient := copilot.NewClient(nil)\n\n// Create a session with a meaningful ID\nsession, _ := client.CreateSession(ctx, &copilot.SessionConfig{\n    SessionID: \"user-123-task-456\",\n    Model:     \"gpt-5.2-codex\",\n})\n\n// Do some work...\nsession.SendAndWait(ctx, copilot.MessageOptions{Prompt: \"Analyze my codebase\"})\n\n// Session state is automatically persisted\n```\n\n### C#(.NET)\n\n```csharp\nusing GitHub.Copilot;\n\nvar client = new CopilotClient();\n\n// Create a session with a meaningful ID\nvar session = await client.CreateSessionAsync(new SessionConfig\n{\n    SessionId = \"user-123-task-456\",\n    Model = \"gpt-5.2-codex\",\n});\n\n// Do some work...\nawait session.SendAndWaitAsync(new MessageOptions { Prompt = \"Analyze my codebase\" });\n\n// Session state is automatically persisted\n```\n\n## 세션 다시 열기\n\n나중에(분, 시간 또는 며칠) 중단된 위치에서 세션을 다시 시작할 수 있습니다.\n\n![다이어그램: 설명된 프로세스를 보여 주는 순서도입니다.](/assets/images/help/copilot/copilot-sdk/features-session-persistence-diagram-1.png)\n\n### TypeScript\n\n```typescript\n// Resume from a different client instance (or after restart)\nconst session = await client.resumeSession(\"user-123-task-456\");\n\n// Continue where you left off\nawait session.sendAndWait({ prompt: \"What did we discuss earlier?\" });\n```\n\n### Python\n\n```python\n# Resume from a different client instance (or after restart)\nsession = await client.resume_session(\"user-123-task-456\", on_permission_request=PermissionHandler.approve_all)\n\n# Continue where you left off\nawait session.send_and_wait(\"What did we discuss earlier?\")\n```\n\n### Go\n\n```golang\nctx := context.Background()\n\n// Resume from a different client instance (or after restart)\nsession, _ := client.ResumeSession(ctx, \"user-123-task-456\", nil)\n\n// Continue where you left off\nsession.SendAndWait(ctx, copilot.MessageOptions{Prompt: \"What did we discuss earlier?\"})\n```\n\n### C#(.NET)\n\n```csharp\n// Resume from a different client instance (or after restart)\nvar session = await client.ResumeSessionAsync(\"user-123-task-456\");\n\n// Continue where you left off\nawait session.SendAndWaitAsync(new MessageOptions { Prompt = \"What did we discuss earlier?\" });\n```\n\n## 다시 시작 옵션\n\n세션을 다시 시작하면 필요에 따라 여러 설정을 다시 구성할 수 있습니다. 이 기능은 모델을 변경하거나, 도구 구성을 업데이트하거나, 동작을 수정해야 하는 경우에 유용합니다.\n\n| Option             | Description                   |\n| ------------------ | ----------------------------- |\n| `model`            | 다시 시작된 세션의 모델 변경              |\n| `systemMessage`    | 시스템 프롬프트 재정의 또는 확장            |\n| `availableTools`   | 사용할 수 있는 도구 제한                |\n| `excludedTools`    | 특정 도구 사용 안 함                  |\n| `provider`         | BYOK 자격 증명 다시 제공(BYOK 세션에 필요) |\n| `reasoningEffort`  | 추론 노력 수준 조정                   |\n| `streaming`        | 스트리밍 응답 사용/사용 안 함             |\n| `workingDirectory` | 작업 디렉터리 변경                    |\n| `configDir`        | 구성 디렉터리를 재정의                  |\n| `mcpServers`       | MCP 서버 구성                     |\n| `customAgents`     | 사용자 지정 에이전트 구성                |\n| `agent`            | 이름으로 사용자 지정 에이전트 사전 선택        |\n| `skillDirectories` | 기술을 로드하기 위한 디렉터리              |\n| `disabledSkills`   | 사용하지 않도록 설정하는 기술              |\n| `infiniteSessions` | 무한 세션 동작 구성                   |\n\n### 예: 다시 시작할 때 모델 변경\n\n```typescript\n// Resume with a different model\nconst session = await client.resumeSession(\"user-123-task-456\", {\n  model: \"claude-sonnet-4\",  // Switch to a different model\n  reasoningEffort: \"high\",   // Increase reasoning effort\n});\n```\n\n## 다시 시작된 세션과 함께 BYOK(사용자 고유의 키 가져오기) 사용\n\n사용자 고유의 API 키를 사용하는 경우 다시 시작 시 공급자 구성을 다시 제공해야 합니다. API 키는 보안상의 이유로 디스크에 유지되지 않습니다.\n\n```typescript\n// Original session with BYOK\nconst session = await client.createSession({\n  sessionId: \"user-123-task-456\",\n  model: \"gpt-5.2-codex\",\n  provider: {\n    type: \"azure\",\n    endpoint: \"https://my-resource.openai.azure.com\",\n    apiKey: process.env.AZURE_OPENAI_KEY,\n    deploymentId: \"my-gpt-deployment\",\n  },\n});\n\n// When resuming, you MUST re-provide the provider config\nconst resumed = await client.resumeSession(\"user-123-task-456\", {\n  provider: {\n    type: \"azure\",\n    endpoint: \"https://my-resource.openai.azure.com\",\n    apiKey: process.env.AZURE_OPENAI_KEY,  // Required again\n    deploymentId: \"my-gpt-deployment\",\n  },\n});\n```\n\n## 지속되는 항목은?\n\n세션 상태는 다음으로 저장됩니다 `~/.copilot/session-state/{sessionId}/`.\n\n```text\n~/.copilot/session-state/\n└── user-123-task-456/\n    ├── checkpoints/           # Conversation history snapshots\n    │   ├── 001.json          # Initial state\n    │   ├── 002.json          # After first interaction\n    │   └── ...               # Incremental checkpoints\n    ├── plan.md               # Agent's planning state (if any)\n    └── files/                # Session artifacts\n        ├── analysis.md       # Files the agent created\n        └── notes.txt         # Working documents\n```\n\n| 데이터              | 보존되었습니까?         | 비고 |\n| ---------------- | ---------------- | -- |\n| 대화 기록            |                  |    |\n| ✅ 예              | 전체 메시지 스레드       |    |\n| 도구 호출 결과         |                  |    |\n| ✅ 예              | 컨텍스트용으로 캐시됨      |    |\n| 에이전트 계획 상태       |                  |    |\n| ✅ 예              |                  |    |\n| `plan.md` 파일     |                  |    |\n| 세션 아티팩트          |                  |    |\n| ✅ 예              |                  |    |\n| `files/` 디렉터리 안에 |                  |    |\n| 공급자/API 키        |                  |    |\n| ❌ 아니요            | 보안: 다시 제공해야 합니다. |    |\n| 메모리 내 도구 상태      |                  |    |\n| ❌ 아니요            | 도구는 무상태여야 합니다.   |    |\n\n## 세션 ID 모범 사례\n\n소유권 및 용도를 인코딩하는 세션 ID를 선택합니다. 이렇게 하면 감사 및 정리가 훨씬 쉬워집니다.\n\n| Pattern                         | Example            | 사용 사례 |\n| ------------------------------- | ------------------ | ----- |\n| ❌                               |                    |       |\n| `abc123`                        |                    |       |\n| 임의 ID                           | 감사하기 어렵고 소유권 정보 없음 |       |\n| ✅                               |                    |       |\n| `user-{userId}-{taskId}`        |                    |       |\n| `user-alice-pr-review-42`       | 다중 사용자 앱           |       |\n| ✅                               |                    |       |\n| `tenant-{tenantId}-{workflow}`  |                    |       |\n| `tenant-acme-onboarding`        | 다중 사용자 SaaS        |       |\n| ✅                               |                    |       |\n| `{userId}-{taskId}-{timestamp}` |                    |       |\n| `alice-deploy-1706932800`       | 시간 기반 정리 작업        |       |\n\n**구조적 ID의 이점:**\n\n* 감사가 용이합니다: \"사용자 alice의 모든 세션 표시\"\n* 정리하기 쉽습니다. \"X보다 오래된 모든 세션 삭제\"\n* 자연 액세스 제어: 세션 ID에서 사용자 ID 추출\n\n### 예: 세션 ID 생성\n\n```typescript\nfunction createSessionId(userId: string, taskType: string): string {\n  const timestamp = Date.now();\n  return `${userId}-${taskType}-${timestamp}`;\n}\n\nconst sessionId = createSessionId(\"alice\", \"code-review\");\n// → \"alice-code-review-1706932800000\"\n```\n\n```python\nimport time\n\ndef create_session_id(user_id: str, task_type: str) -> str:\n    timestamp = int(time.time())\n    return f\"{user_id}-{task_type}-{timestamp}\"\n\nsession_id = create_session_id(\"alice\", \"code-review\")\n# → \"alice-code-review-1706932800\"\n```\n\n## 세션 수명 주기 관리\n\n### 활성 세션 나열\n\n```typescript\n// List all sessions\nconst sessions = await client.listSessions();\nconsole.log(`Found ${sessions.length} sessions`);\n\nfor (const session of sessions) {\n  console.log(`- ${session.sessionId} (created: ${session.createdAt})`);\n}\n\n// Filter sessions by repository\nconst repoSessions = await client.listSessions({ repository: \"owner/repo\" });\n```\n\n### 이전 세션 정리\n\n```typescript\nasync function cleanupExpiredSessions(maxAgeMs: number) {\n  const sessions = await client.listSessions();\n  const now = Date.now();\n  \n  for (const session of sessions) {\n    const age = now - new Date(session.createdAt).getTime();\n    if (age > maxAgeMs) {\n      await client.deleteSession(session.sessionId);\n      console.log(`Deleted expired session: ${session.sessionId}`);\n    }\n  }\n}\n\n// Clean up sessions older than 24 hours\nawait cleanupExpiredSessions(24 * 60 * 60 * 1000);\n```\n\n### 세션에서 연결 끊기(`disconnect`)\n\n작업이 완료되면 시간 제한을 기다리지 않고 세션에서 명시적으로 연결을 끊습니다. 이렇게 하면 메모리 내 리소스가 해제되지만 **디스크에 세션 데이터가 유지**되므로 나중에 세션을 다시 시작해도 됩니다.\n\n```typescript\ntry {\n  // Do work...\n  await session.sendAndWait({ prompt: \"Complete the task\" });\n  \n  // Task complete — release in-memory resources (session can be resumed later)\n  await session.disconnect();\n} catch (error) {\n  // Clean up even on error\n  await session.disconnect();\n  throw error;\n}\n```\n\n또한 각 SDK는 해당 언어에 맞는 자동 정리 패턴도 제공합니다:\n\n| 언어                    | Pattern                                                                             | Example                                                              |\n| --------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------- |\n| **TypeScript**        | `Symbol.asyncDispose`                                                               | `await using session = await client.createSession(config);`          |\n| **Python**            |                                                                                     |                                                                      |\n| `async with` 컨텍스트 관리자 | `async with await client.create_session(on_permission_request=handler) as session:` |                                                                      |\n| **C#**                | `IAsyncDisposable`                                                                  | `await using var session = await client.CreateSessionAsync(config);` |\n| **Go**                | `defer`                                                                             | `defer session.Disconnect()`                                         |\n\n> \\[!NOTE]\n> `destroy()`은 더 이상 사용되지 않으며, 대신 `disconnect()` 사용이 권장됩니다. 사용하는 `destroy()` 기존 코드는 계속 작동하지만 마이그레이션해야 합니다.\n\n### 세션 영구 삭제(`deleteSession`)\n\n디스크에서 세션과 그 모든 데이터(대화 기록, 계획 상태, 아티팩트)를 영구적으로 제거하려면 `deleteSession`를 사용하십시오. 이는 되돌릴 수 없습니다. 삭제 후 세션을 다시 시작 **하지 못할 수 있습니다** .\n\n```typescript\n// Permanently remove session data\nawait client.deleteSession(\"user-123-task-456\");\n```\n\n> **`disconnect()` vs `deleteSession()`:**`disconnect()` 메모리 내 리소스를 해제하지만 나중에 다시 시작하려면 세션 데이터를 디스크에 유지합니다. `deleteSession()` 는 디스크의 파일을 포함하여 모든 항목을 영구적으로 제거합니다.\n\n## 자동 정리: 유휴 시간 제한\n\n기본적으로 세션은 **유휴 시간 제한이 없으며** 명시적으로 연결이 끊어지거나 삭제될 때까지 무기한으로 라이브됩니다. 필요에 따라 다음을 통해 `CopilotClientOptions.sessionIdleTimeoutSeconds`서버 전체 유휴 시간 제한을 구성할 수 있습니다.\n\n```typescript\nconst client = new CopilotClient({\n  sessionIdleTimeoutSeconds: 30 * 60, // 30 minutes\n});\n```\n\n시간 제한이 구성되면 해당 기간 동안 활동이 없는 세션이 자동으로 정리됩니다. 비활성화하려면 `0`로 설정하거나 생략합니다.\n\n> \\[!NOTE]\n> 이 옵션은 SDK가 런타임 프로세스를 생성하는 경우에만 적용됩니다. 를 통해 `cliUrl`기존 서버에 연결할 때 서버의 자체 시간 제한 구성이 적용됩니다.\n\n![다이어그램: 설명된 프로세스를 보여 주는 순서도입니다.](/assets/images/help/copilot/copilot-sdk/features-session-persistence-diagram-2.png)\n\n활성 작업(실행 중인 명령, 백그라운드 에이전트)이 있는 세션은 제한 시간 설정에 관계없이 항상 유휴 정리로부터 보호됩니다.\n\n유휴 이벤트를 수신 대기하여 세션 비활성 상태에 반응합니다.\n\n```typescript\nsession.on(\"session.idle\", (event) => {\n  console.log(`Session idle for ${event.idleDurationMs}ms`);\n});\n```\n\n## 배포 패턴\n\n### 패턴 1: 사용자당 하나의 CLI 서버(권장)\n\n적합 대상: 강력한 격리, 다중 테넌트 환경, Azure 동적 세션.\n\n![다이어그램: 설명된 프로세스를 보여 주는 순서도입니다.](/assets/images/help/copilot/copilot-sdk/features-session-persistence-diagram-3.png)\n\n\\*\\*혜택:\\*\\*✅ 완전한 격리 | ✅ 단순 보안 | ✅ 간편한 크기 조정\n\n### 패턴 2: 공유 CLI 서버(리소스 효율적)\n\n적합 대상: 내부 도구, 신뢰할 수 있는 환경, 리소스 제한 설정.\n\n![다이어그램: 설명된 프로세스를 보여 주는 순서도입니다.](/assets/images/help/copilot/copilot-sdk/features-session-persistence-diagram-4.png)\n\n**Requirements:**\n\n* ⚠️ 사용자당 고유한 세션 ID\n* ⚠️ 애플리케이션 수준 액세스 제어\n* ⚠️ 작업 전 세션 ID 유효성 검사\n\n```typescript\n// Application-level access control for shared CLI\nasync function resumeSessionWithAuth(\n  client: CopilotClient,\n  sessionId: string,\n  currentUserId: string\n): Promise<Session> {\n  // Parse user from session ID\n  const [sessionUserId] = sessionId.split(\"-\");\n  \n  if (sessionUserId !== currentUserId) {\n    throw new Error(\"Access denied: session belongs to another user\");\n  }\n  \n  return client.resumeSession(sessionId);\n}\n```\n\n## Azure 동적 세션\n\n컨테이너를 다시 시작하거나 마이그레이션할 수 있는 서버리스/컨테이너 배포의 경우:\n\n### 영구 스토리지 탑재\n\n세션 상태 디렉터리를 영구 스토리지에 탑재해야 합니다.\n\n```yaml\n# Azure Container Instance example\ncontainers:\n  - name: copilot-agent\n    image: my-agent:latest\n    volumeMounts:\n      - name: session-storage\n        mountPath: /home/app/.copilot/session-state\n\nvolumes:\n  - name: session-storage\n    azureFile:\n      shareName: copilot-sessions\n      storageAccountName: myaccount\n```\n\n![다이어그램: 설명된 프로세스를 보여 주는 순서도입니다.](/assets/images/help/copilot/copilot-sdk/features-session-persistence-diagram-5.png)\n\n**컨테이너를 다시 시작해도 세션이 유지됩니다!**\n\n## 장기 실행 워크플로를 위한 무한 세션\n\n컨텍스트 제한을 초과할 수 있는 워크플로의 경우 자동 압축을 사용하여 무한 세션을 사용하도록 설정합니다.\n\n```typescript\nconst session = await client.createSession({\n  sessionId: \"long-workflow-123\",\n  infiniteSessions: {\n    enabled: true,\n    backgroundCompactionThreshold: 0.80,  // Start compaction at 80% context\n    bufferExhaustionThreshold: 0.95,      // Block at 95% if needed\n  },\n});\n```\n\n> \\[!NOTE]\n> 임계값은 절대 토큰 수가 아닌 컨텍스트 사용률(0.0-1.0)입니다. 자세한 내용은 [SDK 및 CLI 호환성](/ko/copilot/how-tos/copilot-sdk/troubleshooting/compatibility) 을 참조하세요.\n\n## 제한 사항 및 고려 사항\n\n| Limitation                               | Description                    | 완화 방법                                |\n| ---------------------------------------- | ------------------------------ | ------------------------------------ |\n| **BYOK 다시 인증**                           | API 키는 유지되지 않습니다.              | 비밀 관리자에 키를 저장합니다. 이력서에 제공            |\n| **쓰기 가능한 스토리지**                          |                                |                                      |\n| `~/.copilot/session-state/` 쓰기 가능해야 합니다. | 컨테이너에 영구 볼륨 탑재                 |                                      |\n| **세션 잠금 없음**                             | 동일한 세션에 대한 동시 액세스가 정의되지 않았습니다. | 애플리케이션 레벨 잠금 또는 큐 시스템 구현             |\n| **도구 상태가 유지되지 않음**                       | 메모리 내 도구 상태가 손실됨               | 상태 비저장이 되거나, 자체 상태를 유지하도록 도구를 설계하세요. |\n\n### 동시 액세스 처리\n\nSDK는 기본 제공 세션 잠금을 제공하지 않습니다. 여러 클라이언트가 동일한 세션에 액세스할 수 있는 경우:\n\n```typescript\n// Option 1: Application-level locking with Redis\nimport Redis from \"ioredis\";\n\nconst redis = new Redis();\n\nasync function withSessionLock<T>(\n  sessionId: string,\n  fn: () => Promise<T>\n): Promise<T> {\n  const lockKey = `session-lock:${sessionId}`;\n  const acquired = await redis.set(lockKey, \"locked\", \"NX\", \"EX\", 300);\n  \n  if (!acquired) {\n    throw new Error(\"Session is in use by another client\");\n  }\n  \n  try {\n    return await fn();\n  } finally {\n    await redis.del(lockKey);\n  }\n}\n\n// Usage\nawait withSessionLock(\"user-123-task-456\", async () => {\n  const session = await client.resumeSession(\"user-123-task-456\");\n  await session.sendAndWait({ prompt: \"Continue the task\" });\n});\n```\n\n## 요약\n\n| 특징                                                                                | 사용 방법                                   |\n| --------------------------------------------------------------------------------- | --------------------------------------- |\n| **다시 열 수 있는 세션 만들기**                                                              | 사용자 고유의 것을 제공하십시오 `sessionId`           |\n| **세션 다시 시작**                                                                      | `client.resumeSession(sessionId)`       |\n| **BYOK 다시 시작**                                                                    |                                         |\n| `provider` 구성 다시 제공                                                               |                                         |\n| **세션 목록**                                                                         | `client.listSessions(filter?)`          |\n| **활성 세션에서 연결 끊기**                                                                 |                                         |\n| `session.disconnect()`- 메모리 내 리소스를 해제합니다. 디스크의 세션 데이터는 재개를 위해 유지됩니다.              |                                         |\n| **세션을 영구적으로 삭제**                                                                  |                                         |\n| `client.deleteSession(sessionId)`- 디스크에서 모든 세션 데이터를 영구적으로 제거합니다. 을(를) 다시 시작하지 못 함 |                                         |\n| **컨테이너화된 배포**                                                                     | 영구 스토리지에 탑재 `~/.copilot/session-state/` |\n\n## 다음 단계\n\n* [세션 후크](/ko/copilot/how-tos/copilot-sdk/hooks/hooks-overview) - 후크를 사용하여 세션 동작 사용자 지정\n* [SDK 및 CLI 호환성](/ko/copilot/how-tos/copilot-sdk/troubleshooting/compatibility) - SDK 및 CLI 기능 비교\n* [디버깅 가이드](/ko/copilot/how-tos/copilot-sdk/troubleshooting/debugging) - 세션 문제 해결"}