Agent SDK のセッションを外部ストレージに保存する SessionStore

Claude Agent SDK エンジニアリング 7分で読めます

Claude Agent SDK は、セッションの会話履歴(トランスクリプト)を既定でローカルの ~/.claude/projects/ 配下に JSONL ファイルとして書きます。SessionStore アダプタを渡すと、その履歴を S3 のようなオブジェクトストレージ、Redis のようなキーバリューストア、Postgres のようなデータベースへ複製でき、あるホストで作ったセッションを別のホストから再開(resume)できるようになります。このページでは公式の「Persist sessions to external storage」に沿って、インタフェース・試し方・参照実装・注意点を整理します。

SessionStore が必要な場面

セッションファイルは作ったマシンのローカルにしか無いため、通常の resume は同じマシンの中でしか使えません(セッションの基本は Agent SDK のセッション管理を参照)。公式は外部ストアを使う理由として次の3つを挙げています。

別ホストでの再開には、ストアを使う以外に「JSONL ファイル自体を運ぶ」「再開に頼らず、必要な結果をアプリ側の状態として次のセッションのプロンプトに渡す」という方法も公式に挙げられています。ストアの検索キーは作業ディレクトリから作られるので、再開するときは元の実行と同じ作業ディレクトリ(cwd)から呼ぶ点に注意します。

SessionStore インタフェース

SessionStore は、必須メソッド append と load、任意メソッド4つを持つオブジェクトです。SDK はクエリ中に append でトランスクリプトのエントリを書き、再開時に load で読み戻します。型は TypeScript では @anthropic-ai/claude-agent-sdk、Python では claude_agent_sdk から SessionStore・SessionKey・SessionStoreEntry・SessionSummaryEntry として公開されています。

type SessionKey = {
  projectKey: string;
  sessionId: string;
  subpath?: string;
};

type SessionStore = {
  // 必須
  append(key: SessionKey, entries: SessionStoreEntry[]): Promise<void>;
  load(key: SessionKey): Promise<SessionStoreEntry[] | null>;

  // 任意
  listSessions?(projectKey: string): Promise<Array<{ sessionId: string; mtime: number }>>;
  listSessionSummaries?(projectKey: string): Promise<SessionSummaryEntry[]>;
  delete?(key: SessionKey): Promise<void>;
  listSubkeys?(key: { projectKey: string; sessionId: string }): Promise<string[]>;
};

Python では Protocol として定義され、メソッド名は append・load・list_sessions・list_session_summaries・delete・list_subkeys、キーは project_key・session_id・subpath です。任意メソッドは省略するか NotImplementedError を送出します。

SessionKey は1本のトランスクリプトを指します。projectKey は作業ディレクトリをファイル名として安全な形に符号化した値、sessionId はセッションの UUID、subpath はサブエージェントのトランスクリプトなど本体以外のファイルのときだけ付きます(例 subagents/agent-<id>。中身は解釈せず不透明なキーとして扱います)。

メソッド必須呼ばれるとき
appendはいエントリの一括がローカルに書かれた後ごと。エントリはローカル JSONL の1行に当たる JSON 安全なオブジェクト
loadはいresume 指定時や continue: true でストアの最新セッションを解決したとき、サブプロセス起動前に呼ばれる。未知のセッションなら null を返す
listSessionsいいえlistSessions({ sessionStore }) と continue: true。未実装だと continue: true は例外になる
listSessionSummariesいいえ全セッションのメタデータを1回で読むとき。未実装なら listSessions とセッションごとの load にフォールバック
deleteいいえdeleteSession({ sessionStore })。本体キーの削除はサブキーとサマリーにも連鎖させる。未実装なら何もしない(追記専用のバックエンド向け)
listSubkeysいいえ再開時にサブエージェントのトランスクリプトを探すとき。未実装なら本体だけが復元される

listSessionSummaries を実装する場合は、append の中で各一括に対してエクスポートされた foldSessionSummary(Python では fold_session_summary)を呼んでサマリーを更新します。subpath 付きの一括は本体のサマリーに混ぜないこと、mtime は保存時に付けること、同じセッションへの同時 append が競合しないようトランザクションやロックで読み・畳み込み・書きを直列化することが求められています。data は SDK 所有の不透明な値なので、そのまま保存します。

InMemorySessionStore で試す

開発とテスト用に、SDK には InMemorySessionStore が同梱されています。次の例は、ストアを付けて1回目のクエリを実行し、結果メッセージからセッション ID を取り出してから、同じストアと resume を渡して2回目のクエリを実行します。2回目はローカルのファイルではなくストアからトランスクリプトを読み込みます。

import { query, InMemorySessionStore } from "@anthropic-ai/claude-agent-sdk";

const store = new InMemorySessionStore();

let sessionId: string | undefined;
try {
  for await (const message of query({
    prompt: "List the TypeScript files under src/",
    options: { sessionStore: store },
  })) {
    if (message.type === "result") {
      sessionId = message.session_id;
    }
  }
} catch (error) {
  console.error(`Session ended with an error: ${error}`);
}

// ストアから再開する。1回目の文脈をすべて引き継ぐ
for await (const message of query({
  prompt: "Summarize what those files do",
  options: { sessionStore: store, resume: sessionId },
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

Python では ClaudeAgentOptions(session_store=store) と ClaudeAgentOptions(session_store=store, resume=session_id) で同じことをします。2回目のクエリが1回目に列挙したファイルの要約を返せば、ストア経由で文脈ごと再開できています。

TypeScript では query() のほか、startup()・listSessions()・getSessionInfo()・getSessionMessages()・renameSession()・tagSession()・deleteSession()・forkSession()・listSubagents()・getSubagentMessages() が sessionStore オプションを受け取ります。Python では query() 以外は list_sessions_from_store() や fork_session_via_store() のようなストア用の別関数を使います(startup() に相当するものはありません)。

自前アダプタと参照実装

自分のバックエンド向けには、最低限 append と load を実装します。エントリは { type: string; ... } 形の不透明な JSON 値として扱い、受け取った順に保存し、load では同じ順で返します。返す値は追記したものと深い等価であればよく、バイト単位で同じである必要はありません(キーの順序を並べ替える JSON 型のカラムでも問題ありません)。

両 SDK のリポジトリには、そのまま動く参照アダプタが置かれています。パッケージとしては公開されていないので、近いものを自分のプロジェクトにコピーし、バックエンドのクライアントを入れて手直しします。

ストレージの種類保存のしかた参照アダプタ
オブジェクトストアappend() ごとに1つのパートファイル。load() はパートを列挙・整列して連結S3
キーバリューストアトランスクリプトごとに1本のリストへ追加し範囲で読む。セッションの索引はソート済みセットRedis
RDB / ドキュメントストアエントリ1件を1行(JSON)として保存し、挿入時に振るキーで並べるPostgres

各アダプタは設定済みのクライアントを受け取るので、認証情報・TLS・リージョン・コネクションプールは呼び出し側で管理できます。S3 の例では次のように渡し、後で別ホストから同じストアと resume で再開します。

import { query } from "@anthropic-ai/claude-agent-sdk";
import { S3Client } from "@aws-sdk/client-s3";
import { S3SessionStore } from "./S3SessionStore"; // examples/session-stores/s3 からコピー

const store = new S3SessionStore({
  bucket: "my-claude-sessions",
  prefix: "transcripts",
  client: new S3Client({ region: "us-east-1" }),
});

for await (const message of query({
  prompt: "Hello!",
  options: { sessionStore: store },
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

アダプタが契約を満たしているかは、SDK 付属の適合テストで確かめます。TypeScript は例のディレクトリにある shared/conformance.ts をテストにコピーし、Python はパッケージに入っている run_session_store_conformance を pytest から呼びます(pytest は SDK の依存ではないので別途 pip install pytest)。任意メソッドのテストは、未実装なら自動でスキップされます。

import pytest
from claude_agent_sdk.testing import run_session_store_conformance


@pytest.mark.anyio
async def test_my_store_conformance():
    await run_session_store_conformance(MyRedisStore)

引数にはストアを作る引数なしのファクトリを渡します。クライアントを受け取るアダプタならラムダで包み、呼ばれるたびに空のストレージ(新しいキー接頭辞やテスト用 DB など)を用意します。

動作上の注意点

ストアはローカルの「複製」

Claude Code のサブプロセスは常に先にローカルディスクへ書き、SDK が同じ一括をストアの append() に転送します。新規セッション(またはストアに何も無い状態での再開)ではローカルのトランスクリプトが残り、ストアはそのコピーを持ちます。ストアから再開した実行では、実行終了時にローカル側が削除されるので、ストアだけが唯一の永続コピーになります。新規セッションでもローカルに残したくなければ、options.env で CLAUDE_CONFIG_DIR を新しい一時ディレクトリに向けます(TypeScript では env がサブプロセスの環境を置き換えるので process.env も展開して渡します)。その場合、claude.ai ログインやユーザー settings.json の apiKeyHelper で認証していると Not logged in になるため、CLAUDE_CODE_OAUTH_TOKEN や ANTHROPIC_API_KEY を env に設定するなどの対応が要ります。

ストアと併用できないオプション

TypeScript の persistSession: false(複製の元になるローカル書き込みを止めてしまう)と、ファイルチェックポイント(enableFileCheckpointing / enable_file_checkpointing。バックアップはローカルに直接書かれストアには複製されない)は、ストアと組み合わせると起動時に例外になります。チェックポイントについては Agent SDK のファイルチェックポイントを参照してください。

書き込みはベストエフォート

append() が失敗すると、SDK は短い間隔を置いて最大3回まで試します(タイムアウトした呼び出しは、元の呼び出しが届いている可能性があるので再試行しません)。それでも失敗した一括は破棄され、{ type: "system", subtype: "mirror_error" } のメッセージが流れてクエリは続行します。再試行で同じエントリが重複して届くことがあるので、append() 側で entry.uuid による重複排除をします。データ欠落を検知したいなら mirror_error を監視します。

再開時の挙動と保持期間

複数ホストへのデプロイ構成そのものは Agent SDK のホスティング、安全な運用は Agent SDK の安全なデプロイも合わせて確認してください。

本ページは Persist sessions to external storage を中心に、Work with sessions(ローカルの保存先と「Resume across hosts」)と claude-agent-sdk-python の参照アダプタ(S3・Redis・Postgres と適合テスト)を照合した非公式の日本語解説です(確認日 2026-11-05)。最新の仕様は公式ドキュメントを確認してください。