{"meta":{"title":"会话恢复和持久性","intro":"本指南将指导你完成 SDK 的会话持久性功能 -- 如何暂停工作、稍后恢复工作和管理生产环境中的会话。","product":"GitHub Copilot","breadcrumbs":[{"href":"/zh/copilot","title":"GitHub Copilot"},{"href":"/zh/copilot/how-tos","title":"操作方法"},{"href":"/zh/copilot/how-tos/copilot-sdk","title":"Copilot SDK"},{"href":"/zh/copilot/how-tos/copilot-sdk/features","title":"功能"},{"href":"/zh/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| **激活**          | 发送提示、工具调用、响应 |\n| **已暂停**         | 保存到磁盘的状态     |\n| **Resume**      | 状态从磁盘加载      |\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| 选项                 | 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| Data         | 持久化？           | 笔记 |\n| ------------ | -------------- | -- |\n| 对话历史记录       |                |    |\n| ✅ 是的         | 完整消息线程         |    |\n| 工具调用结果       |                |    |\n| ✅ 是的         | 为提供上下文而缓存      |    |\n| 代理规划状态       |                |    |\n| ✅ 是的         |                |    |\n| `plan.md` 文件 |                |    |\n| 会话项目         |                |    |\n| ✅ 是的         | 在 `files/` 目录中 |    |\n| 提供者/API 密钥   |                |    |\n| ❌ 否          | 安全性：必须重新提供     |    |\n| 内存中工具状态      |                |    |\n| ❌ 否          | 工具应为无状态        |    |\n\n## 会话 ID 最佳实践\n\n选择能编码拥有者和用途的会话 ID。 这使得审核和清理更加容易。\n\n| Pattern                         | 示例           | 用例 |\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`        | 多租户软件即服务     |    |\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                                                                             | 示例                                                                   |\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**要求**：\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 兼容性](/zh/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 简历**                                            | 重新提供 `provider` 配置                    |\n| **列表会话**                                               | `client.listSessions(filter?)`        |\n| **断开与活动会话的连接**                                         |                                       |\n| `session.disconnect()`- 释放内存中资源;保留磁盘上的会话数据以恢复          |                                       |\n| **永久删除会话**                                             |                                       |\n| `client.deleteSession(sessionId)`— 永久删除磁盘中的所有会话数据;无法恢复 |                                       |\n| **容器化部署**                                              | 装载 `~/.copilot/session-state/` 到永久性存储 |\n\n## 后续步骤\n\n* [会话挂钩](/zh/copilot/how-tos/copilot-sdk/hooks/hooks-overview) - 使用挂钩自定义会话行为\n* [SDK 和 CLI 兼容性](/zh/copilot/how-tos/copilot-sdk/troubleshooting/compatibility) - SDK 与 CLI 功能比较\n* [调试指南](/zh/copilot/how-tos/copilot-sdk/troubleshooting/debugging) - 排查会话问题"}