Skip to main content

会话恢复和持久性

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

会话的工作原理

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

图示:显示所述过程的流程图。

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

快速入门:创建可恢复会话

可恢复会话的关键是提供你自己的 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?" });

继续选项

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

选项Description
model更改已恢复的会话的模型
systemMessage覆盖或扩展系统提示
availableTools限制可用的工具
excludedTools禁用特定工具
provider重新提供 BYOK 凭据(这是 BYOK 会话所需的)
reasoningEffort调整推理工作量级别
streaming启用/禁用流式处理响应
workingDirectory更改工作目录
configDir覆盖配置目录
mcpServers配置 MCP 服务器
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
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

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 还提供惯用的自动清理模式:

语言Pattern示例
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 服务器(资源高效)

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

图示:显示所述过程的流程图。

要求

  • ⚠️ 每个用户的唯一会话 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/ 到永久性存储

后续步骤