{"meta":{"title":"セッションの再開と永続化","intro":"このガイドでは、SDK のセッション永続化機能について説明します。作業を一時停止し、後で再開し、運用環境でセッションを管理する方法について説明します。","product":"GitHub Copilot","breadcrumbs":[{"href":"/ja/copilot","title":"GitHub Copilot"},{"href":"/ja/copilot/how-tos","title":"方法"},{"href":"/ja/copilot/how-tos/copilot-sdk","title":"Copilot SDK"},{"href":"/ja/copilot/how-tos/copilot-sdk/features","title":"機能"},{"href":"/ja/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| State                 | 何が起きるか                |\n| --------------------- | --------------------- |\n| **Create**            |                       |\n| `session_id` が割り当てられた |                       |\n| **アクティブ**             | プロンプト、ツール呼び出し、応答を送信する |\n| **一時停止**              | ディスクに保存された状態          |\n| **Resume**            | ディスクから読み込まれた状態        |\n\n## クイック スタート: 再開可能なセッションの作成\n\n再開可能なセッションの鍵は、独自の `session_id`を提供することです。 1 つがないと、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| オプション              | 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 (Bring Your Own Key) の使用\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| Language                  | 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()` と `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 がランタイム プロセスを生成する場合にのみ適用されます。\n> `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: ユーザーごとに 1 つの CLI サーバー (推奨)\n\n最適な用途: 強力な分離、マルチテナント環境、Azure Dynamic Sessions。\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**要件：**\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 の互換性](/ja/copilot/how-tos/copilot-sdk/troubleshooting/compatibility) を参照してください。\n\n## 制限事項と考慮事項\n\n| 制限事項                                         | 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* [セッション フック](/ja/copilot/how-tos/copilot-sdk/hooks/hooks-overview) - フックを使用してセッションの動作をカスタマイズする\n* [SDK と CLI の互換性](/ja/copilot/how-tos/copilot-sdk/troubleshooting/compatibility) - SDK と CLI の機能の比較\n* [デバッグ ガイド](/ja/copilot/how-tos/copilot-sdk/troubleshooting/debugging) - セッションの問題のトラブルシューティング"}