# 会话恢复和持久性

本指南将指导你完成 SDK 的会话持久性功能 -- 如何暂停工作、稍后恢复工作和管理生产环境中的会话。

<!-- markdownlint-disable GHD046 GHD005 -->

<!-- Suppressed: GHD046 (outdated release terminology), GHD005 (hardcoded data variable) -->

## 会话的工作原理

创建会话时，Copilot CLI 会维护会话历史记录、工具状态和规划上下文。 默认情况下，此状态位于内存中，并在会话结束时消失。 启用持久性后，可以在重启、容器迁移甚至不同的客户端实例之间恢复会话。

![图示：显示所述过程的流程图。](/assets/images/help/copilot/copilot-sdk/features-session-persistence-diagram-0.png)

| 州               | 发生的情况        |
| --------------- | ------------ |
| **创建**          |              |
| `session_id` 分配 |              |
| **激活**          | 发送提示、工具调用、响应 |
| **已暂停**         | 保存到磁盘的状态     |
| **Resume**      | 状态从磁盘加载      |

## 快速入门：创建可恢复会话

可恢复会话的关键在于提供你自己的 `session_id`。 如果没有 ID，SDK 将生成随机 ID，会话以后无法恢复。

### TypeScript

```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

```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

```golang
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）

```csharp
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
```

## 恢复会话

稍后（分钟、小时甚至几天）可以从离开位置恢复会话。

![图示：显示所述过程的流程图。](/assets/images/help/copilot/copilot-sdk/features-session-persistence-diagram-1.png)

### TypeScript

```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

```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

```golang
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）

```csharp
// 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?" });
```

## 恢复选项

恢复会话时，可以选择重新配置许多设置。 如果需要更改模型、更新工具配置或修改行为，这非常有用。

| 选项                 | Description                 |
| ------------------ | --------------------------- |
| `model`            | 更改已恢复的会话的模型                 |
| `systemMessage`    | 覆盖或扩展系统提示                   |
| `availableTools`   | 限制可用的工具                     |
| `excludedTools`    | 禁用特定工具                      |
| `provider`         | 重新提供 BYOK 凭据（这是 BYOK 会话所需的） |
| `reasoningEffort`  | 调整推理工作量级别                   |
| `streaming`        | 启用/禁用流式处理响应                 |
| `workingDirectory` | 更改工作目录                      |
| `configDir`        | 覆盖配置目录                      |
| `mcpServers`       | 配置 MCP 服务器                  |
| `customAgents`     | 配置自定义代理                     |
| `agent`            | 按名称预先选择自定义代理                |
| `skillDirectories` | 要从中加载技能的目录                  |
| `disabledSkills`   | 禁用技能                        |
| `infiniteSessions` | 配置无限会话模式                    |

### 示例：恢复时更改模型。

```typescript
// 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 密钥永远不会保存到磁盘。

```typescript
// 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}/`：

```text
~/.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
```

| Data         | 持久化？           | 笔记 |
| ------------ | -------------- | -- |
| 对话历史记录       |                |    |
| ✅ 是的         | 完整消息线程         |    |
| 工具调用结果       |                |    |
| ✅ 是的         | 为提供上下文而缓存      |    |
| 代理规划状态       |                |    |
| ✅ 是的         |                |    |
| `plan.md` 文件 |                |    |
| 会话项目         |                |    |
| ✅ 是的         | 在 `files/` 目录中 |    |
| 提供者/API 密钥   |                |    |
| ❌ 否          | 安全性：必须重新提供     |    |
| 内存中工具状态      |                |    |
| ❌ 否          | 工具应为无状态        |    |

## 会话 ID 最佳实践

选择能编码拥有者和用途的会话 ID。 这使得审核和清理更加容易。

| Pattern                         | 示例           | 用例 |
| ------------------------------- | ------------ | -- |
| ❌                               |              |    |
| `abc123`                        |              |    |
| 随机 ID                           | 难以审核，没有所有权信息 |    |
| ✅                               |              |    |
| `user-{userId}-{taskId}`        |              |    |
| `user-alice-pr-review-42`       | 多用户应用        |    |
| ✅                               |              |    |
| `tenant-{tenantId}-{workflow}`  |              |    |
| `tenant-acme-onboarding`        | 多租户软件即服务     |    |
| ✅                               |              |    |
| `{userId}-{taskId}-{timestamp}` |              |    |
| `alice-deploy-1706932800`       | 基于时间的清理      |    |

**结构化 ID 的优点：**

* 易于审核：“显示用户 alice 的所有会话”
* 便于清理：“删除所有早于 X 的会话”
* 自主访问控制：从会话 ID 解析用户 ID

### 示例：生成会话 ID

```typescript
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"
```

```python
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"
```

## 管理会话生命周期

### 列出活动会话

```typescript
// 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" });
```

### 清理旧会话

```typescript
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`）

任务完成后，显式断开会话连接，而非等待超时。 这会释放内存中资源，但 **会保留磁盘上的会话数据**，以便以后仍可以恢复会话：

```typescript
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                                                                             | 示例                                                                   |
| ------------------- | ----------------------------------------------------------------------------------- | -------------------------------------------------------------------- |
| **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()`                                         |

> \[!NOTE]
> `destroy()` 已弃用，改用 `disconnect()`。 现有使用`destroy()`的代码将继续工作，但应迁移。

### 永久删除会话 （`deleteSession`）

若要永久删除会话及其所有数据（会话历史记录、规划状态、项目），请使用 `deleteSession`。 这是不可逆转的 — 删除后 **无法** 恢复会话：

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

> **`disconnect()` vs `deleteSession()`：**`disconnect()` 释放内存中资源，但保留磁盘上的会话数据以供以后恢复。 `deleteSession()` 永久删除所有内容，包括磁盘上的文件。

## 自动清理：空闲超时

默认情况下，会话**没有空闲超时限制**，并且会无限期持续存在，直到被显式断开连接或删除。 您可以选择通过 `CopilotClientOptions.sessionIdleTimeoutSeconds` 配置服务器级空闲超时：

```typescript
const client = new CopilotClient({
  sessionIdleTimeoutSeconds: 30 * 60, // 30 minutes
});
```

配置了超时时间后，在该时长内无活动的会话会被自动清理。 设置为 `0` 或省略以禁用。

> \[!NOTE]
> 此选项仅适用于 SDK 生成运行时进程时。 通过 `cliUrl` 连接到现有服务器时，将采用服务器自身的超时配置。

![图示：显示所述过程的流程图。](/assets/images/help/copilot/copilot-sdk/features-session-persistence-diagram-2.png)

具有活动工作（正在运行的命令、后台智能体）的会话始终受到空闲清理保护，无论超时设置如何。

监听空闲事件以响应会话不活动：

```typescript
session.on("session.idle", (event) => {
  console.log(`Session idle for ${event.idleDurationMs}ms`);
});
```

## 部署模式

### 模式 1：每个用户一台 CLI 服务器（建议）

最适合：强隔离、多租户环境、Azure动态会话。

![图示：显示所述过程的流程图。](/assets/images/help/copilot/copilot-sdk/features-session-persistence-diagram-3.png)

\*\*好处：\*\*✅ 完全隔离 | ✅ 简单安全性 | ✅ 轻松缩放

### 模式 2：共享 CLI 服务器（资源高效）

最适用于：内部工具、可信环境、资源受限的部署环境。

![图示：显示所述过程的流程图。](/assets/images/help/copilot/copilot-sdk/features-session-persistence-diagram-4.png)

**要求**：

* ⚠️ 每个用户的唯一会话 ID
* ⚠️ 应用程序级访问控制
* ⚠️ 操作前的会话 ID 验证

```typescript
// 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动态会话

对于容器可以重启或迁移的无服务器/容器部署：

### 装载永久性存储

会话状态目录必须装载到永久性存储：

```yaml
# 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
```

![图示：显示所述过程的流程图。](/assets/images/help/copilot/copilot-sdk/features-session-persistence-diagram-5.png)

**会话在容器重启后仍然存在！**

## 长时间运行的工作流的无限会话

如果工作流可能超出上下文限制，请启用自动压缩以支持无限会话：

```typescript
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
  },
});
```

> \[!NOTE]
> 阈值是上下文利用率（0.0-1.0），而不是绝对令牌计数。 有关详细信息，请参阅 [SDK 和 CLI 兼容性](/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/troubleshooting/compatibility) 。

## 限制和注意事项

| Limitation                       | Description   | 缓解措施                |
| -------------------------------- | ------------- | ------------------- |
| **BYOK 重新身份验证**                  | API 密钥不会持久化   | 将密钥存储在机密管理器中；在恢复时提供 |
| **可写存储**                         |               |                     |
| `~/.copilot/session-state/` 必须可写 | 在容器中装载永久性卷    |                     |
| **无会话锁定**                        | 对同一会话的并发访问未定义 | 实现应用程序级锁定或队列        |
| **工具状态未持久化**                     | 内存中的工具状态已丢失   | 将工具设计为无状态或保留其自己的状态  |

### 处理并发访问

SDK 不提供内置会话锁定。 如果多个客户端可能访问同一会话：

```typescript
// 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/` 到永久性存储 |

## 后续步骤

* [会话挂钩](/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/hooks/hooks-overview) - 使用挂钩自定义会话行为
* [SDK 和 CLI 兼容性](/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/troubleshooting/compatibility) - SDK 与 CLI 功能比较
* [调试指南](/zh/enterprise-cloud@latest/copilot/how-tos/copilot-sdk/troubleshooting/debugging) - 排查会话问题