Skip to main content

セッションの再開と永続化

このガイドでは、SDK のセッション永続化機能について説明します。作業を一時停止し、後で再開し、運用環境でセッションを管理する方法について説明します。

セッションのしくみ

セッションを作成すると、Copilot CLI は会話履歴、ツールの状態、および計画コンテキストを保持します。 既定では、この状態はメモリ内に存在し、セッションが終了すると消えます。 永続化を有効にすると、再起動、コンテナーの移行、または異なるクライアント インスタンス間でセッションを再開できます。

図: 説明されたプロセスを示すフローチャート。

State何が起きるか
Create
session_id が割り当てられた
アクティブプロンプト、ツール呼び出し、応答を送信する
一時停止ディスクに保存された状態
Resumeディスクから読み込まれた状態

クイック スタート: 再開可能なセッションの作成

再開可能なセッションの鍵は、独自の session_idを提供することです。 1 つがないと、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特定のツールを無効にする
providerBYOK 資格情報を再入力する (BYOK セッションに必要)
reasoningEffort推論作業レベルを調整する
streamingストリーミング応答を有効または無効にする
workingDirectory作業ディレクトリを変更する
configDir構成ディレクトリをオーバーライドする
mcpServersMCP サーバーの構成
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 (Bring Your Own Key) の使用

独自の 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
データ永続化しましたか?メモ
会話の履歴
✅ はいメッセージ全体のスレッド
ツール呼び出しの結果
✅ はいコンテキスト用にキャッシュ
エージェントの計画状態
✅ はい
plan.md ファイル
セッション成果物
✅ はい
files/ ディレクトリ内
プロバイダー/API キー
❌ いいえセキュリティ: 再提供する必要があります
メモリ内ツールの状態
❌ いいえツールはステートレスにする必要がある

セッション ID のベスト プラクティス

所有権と目的をエンコードするセッション ID を選択します。 これにより、監査とクリーンアップがはるかに簡単になります。

PatternExampleユースケース(事例)
abc123
ランダム ID監査が難しく、所有権情報がない
user-{userId}-{taskId}
user-alice-pr-review-42マルチユーザー アプリ
tenant-{tenantId}-{workflow}
tenant-acme-onboardingマルチテナント SaaS
{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 には、慣用的な自動クリーンアップ パターンも用意されています。

LanguagePatternExample
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()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 Dynamic Sessions。

図: 説明されたプロセスを示すフローチャート。

**利点:**✅ 完全な分離 | ✅ シンプル なセキュリティ | ✅ 簡単なスケーリング

パターン 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 の互換性 を参照してください。

制限事項と考慮事項

制限事項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/ をマウントする

次のステップ