会话的工作原理
创建会话时,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 | 示例 |
|---|---|---|
| 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() |
注意
destroy() 已弃用,改用 disconnect()。 现有使用destroy()的代码将继续工作,但应迁移。
永久删除会话 (deleteSession)
若要永久删除会话及其所有数据(会话历史记录、规划状态、项目),请使用 deleteSession。 这是不可逆转的 — 删除后 无法 恢复会话:
// Permanently remove session data
await client.deleteSession("user-123-task-456");
disconnect()vsdeleteSession():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`);
});
部署模式
模式 1:每个用户一台 CLI 服务器(建议)
最适合:强隔离、多租户环境、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 兼容性 。
限制和注意事项
| Limitation | Description | 缓解措施 |
|---|---|---|
| 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/ 到永久性存储 |
后续步骤
- 会话挂钩 - 使用挂钩自定义会话行为
- SDK 和 CLI 兼容性 - SDK 与 CLI 功能比较
- 调试指南 - 排查会话问题