概要
SDK は既定でセッションのトランスクリプトを、ローカルの ~/.claude/projects/ 配下に JSONL で書く。SessionStore アダプタを与えると、同じトランスクリプトを自前のバックエンド(S3・Redis・データベースなど)へミラーでき、あるホストで作ったセッションを、作業ディレクトリが一致する別のホストで再開できる。
用途として挙げられているもの。
- 複数ホストの配備: サーバーレス関数・オートスケールするワーカー・CI ランナーはファイルシステムを共有しない
- 永続性: ローカルのコンテナは揮発する。S3 やデータベースに置けば再起動・再デプロイをまたぐ
- コンプライアンスと監査: 自分が統制するストレージに、自分の保持規則・暗号化・アクセス制御で置く
仕様
SessionStore の形
必須メソッドは append と load の 2 つ、任意が 4 つ。
| メソッド | 必須 | 呼ばれるとき |
|---|---|---|
append | ○ | トランスクリプトのバッチがローカルへ書かれた後。エントリは JSON-safe なオブジェクトで、ローカル JSONL の 1 行に対応する |
load | ○ | resume 指定時、または continue: true がストア上の最新セッションを解決したときに、サブプロセス起動前。listSessionSummaries からの一覧がフォールバックしたときもセッションごとに 1 回。未知のセッションなら null を返す |
listSessions | – | listSessions({ sessionStore }) と、continue: true の query() / startup() から。未定義だと continue: true は例外になり、listSessionSummaries が無ければ listSessions({ sessionStore }) も例外になる |
listSessionSummaries | – | listSessions({ sessionStore }) が全セッションのメタデータを 1 回で読むため。要約は append の中で維持する。 未定義なら listSessions + セッションごとの load にフォールバックする |
delete | – | deleteSession({ sessionStore }) から。主キー(subpath 無し)の削除は、そのセッションの全サブキーへ波及させ、要約エントリも消す必要がある。未定義なら削除は何もしない(追記専用のバックエンド向け) |
listSubkeys | – | resume 時に subagent のトランスクリプトを見つけるため。未定義だと主トランスクリプトしか復元されない |
SessionKey
projectKey— 作業ディレクトリを、安定した・ファイルシステムで安全な形に符号化したものsessionId— セッションの UUIDsubpath— subagent のトランスクリプトや sidecar ファイルに属するときだけ入る。不透明な鍵の接尾辞として扱う(オンディスクの配置に従い、例:subagents/agent-<id>)。未定義なら主トランスクリプトを指す
projectKey が作業ディレクトリを符号化しているため、ストアからの resume / continue は元の実行と一致する作業ディレクトリから行う。
- TypeScript では、query の
envオプションでCLAUDE_CODE_PROJECT_DIR_NAMEをCLAUDE_CONFIG_DIRと並べて設定すると、その query のエントリとresume/continueの探索がその名前で鍵付けされる。listSessionsやdeleteSessionのような単独のヘルパーはenvを取らずプロセスの環境を読むため、ホストプロセス側にも同じ名前とCLAUDE_CONFIG_DIRを設定する。Agent SDK v0.3.234 以降が必要
SessionSummaryEntry
mtimeは sidecar のストレージ書き込み時刻。listSessionsが返すmtimeと同じ時計を共有する必要があるdataは SDK が所有する不透明な状態。解釈せずそのまま保存する- エントリの組み立ては、
appendの中で各バッチに対しfoldSessionSummary(Python はfold_session_summary)を呼んで行う subpathを持つバッチは飛ばす(subagent のトランスクリプトを主セッションの要約に混ぜない)- fold は
mtimeを設定しない。 永続化時に、TypeScript はoptions.mtime引数、Python は返ったエントリのフィールド上書きで打つ - 同じセッションへの並行
appendは sidecar で競合しうるため、read-fold-write をトランザクション・CAS・セッション単位のロックのいずれかで直列化する(fold 自体は純粋)
アダプタを書く
appendに渡るエントリはSessionStoreEntry({ type: string; ... })。不透明な JSON-safe 値として、順序どおりに保存し、loadで同じ順序で返すloadが返すエントリは append したものと deep-equal であればよい。 バイト単位で同じ直列化は求められないため、キー順を並べ替える Postgres のjsonbのようなバックエンドでも問題ない- TypeScript SDK のリポジトリに S3・Redis・Postgres の参照実装がある(
examples/session-stores/)。npm には公開されていないため、必要なsrc/のファイルを自分のプロジェクトへ複写して、対応するバックエンドのクライアントを入れる - 適合性テスト(conformance suite)が両 SDK に同梱される。任意メソッドのテストは未実装なら自動で飛ばされる。TypeScript は
shared/conformance.tsを自分のテストへ複写、Python はパッケージ同梱でrun_session_store_conformanceに引数なしのファクトリを渡す(契約ごとに新しいストアを作るため、毎回空のストレージから始まる必要がある)
ミラーの構造(dual-write)
- Claude Code のサブプロセスは常にローカルディスクへ先に書き、SDK が同じバッチをストアの
append()へ転送する。 ストアはローカルの置き換えではなくミラー - 新規セッション(またはストアに何も無いときの resume): ローカルのトランスクリプトが実行後も残り、ストアには複製が入る
- ストアから resume した実行: 実行終了時にローカルの複製が削除されるため、ストアが唯一の永続コピーになる
- 新規セッションでローカルに残したくない場合は、
options.envのCLAUDE_CONFIG_DIRを一時ディレクトリにする。TypeScript ではenvがサブプロセスの環境を置き換えるためprocess.envを展開する - 設定ディレクトリ内のファイル(OAuth 認証情報や user
settings.jsonのapiKeyHelper)でサインインしている場合、それらを一時ディレクトリへ複写するか、envにANTHROPIC_API_KEYを設定する。しないとNot logged inで失敗する
ストアからの resume
resume(または TypeScript の continue: true / Python の continue_conversation=True)とストアを併せて渡すと、SDK はサブプロセス起動前にストアへトランスクリプトを要求する。
resume: 渡した ID のセッションを要求するcontinue: ストア上の最新セッションを要求する
ストアが返した場合、SDK は一時的な設定ディレクトリへ書き出し、CLAUDE_CONFIG_DIR をそこへ向けてサブプロセスを実行し、終了時にそのディレクトリを削除する。
一時ディレクトリへ複写されるものは言語で異なる。
- TypeScript: 認証情報・
.claude.json・usersettings.json。settings.jsonからは一時的な設定ディレクトリで不都合になるキー(enabledPlugins、extraKnownMarketplaces、その別名additionalMarketplaces、envブロック内のCLAUDE_CONFIG_DIR)を取り除く。Agent SDK v0.3.232 より前は別名を取り除いていなかった。 v0.3.222 より前は認証情報と.claude.jsonだけを複写していた - Python: 認証情報と
.claude.jsonのみ。usersettings.jsonのapiKeyHelperで認証するアプリは、ストアからの resume でNot logged inになる(managed / project settings のapiKeyHelperはCLAUDE_CONFIG_DIRの影響を受けない場所から読まれるため動く)
ストアに何も無い場合は実際の設定ディレクトリで実行され、結果は渡したオプションによる。
resume: 両 SDK とも ID をサブプロセスへ渡し、ストア無しのresumeと同じくローカルのトランスクリプトを再開するcontinue: true(TypeScript): 新しいセッションを始めるcontinue_conversation=True(Python): ローカルの最新セッションから続ける
制約・注意点
- ミラーへの書き込みは best-effort。
append()が reject した場合、短い待機を挟んで合計 3 回まで再試行する。タイムアウトしたものは再試行しない(元の呼び出しが後から着く可能性があるため)。それでも失敗したらエラーを記録し、{ type: "system", subtype: "mirror_error" }をイテレータへ流し、そのバッチを捨てて処理を続ける - 再試行で同じエントリが再送されうるため、
append()側でentry.uuidによる重複排除を行う - ストアの障害はエージェントを止めない(サブプロセスはローカルへ先に書くため)。ただしストアから resume した実行では、捨てられたバッチは実行終了後どこにも残らない
- ストアと併用できないオプションが 2 つある。 併用すると起動時に例外になる
- TypeScript の
persistSession: false(ミラーの元になるローカル書き込みを止めるため。Python に同等のオプションは無い) - file checkpointing(
enableFileCheckpointing/enable_file_checkpointing。バックアップを直接ローカルへ書き、ストアへミラーされない)
- TypeScript の
getSessionMessagesは compaction 後の連鎖を返す。 自動 compaction 後は以前のターンが要約に置き換わるため、ストアに 503 件の生エントリがあるセッションでも 18 メッセージしか返らないことがある。生の履歴が要るならstore.load(key)を直接呼ぶforkSessionはバイト単位の複製ではない。 元のエントリを読み、すべてのsessionIdを書き換え、メッセージ UUID を対応づけ直してから新しい鍵で append する。アダプタ側の複製やCopyObjectでは古いセッション ID を参照したままになる- SDK は自分からストアを削除しない。 保持(TTL、S3 のライフサイクル、定期削除)はアダプタの責任。ローカルのトランスクリプトは
cleanupPeriodDaysにより独立して掃除される。ストアから resume した実行はローカルに残さないため、ストアの保持設定が唯一の保持になる
sessionStore を受け付ける関数(TypeScript)
query() / startup() / listSessions() / getSessionInfo() / getSessionMessages() / renameSession() / tagSession() / deleteSession() / forkSession() / listSubagents() / getSubagentMessages()
Python では ClaudeAgentOptions の session_store で query() をストアに対して動かす。他の操作はストアを引数に取る個別の関数(list_sessions_from_store()、get_session_info_from_store()、get_session_messages_from_store()、list_subagents_from_store()、get_subagent_messages_from_store()、rename_session_via_store()、tag_session_via_store()、delete_session_via_store()、fork_session_via_store())を使う。startup() に Python の同等物は無い。 Python SDK リファレンスにある単独の関数(list_sessions() など)はローカルのセッションファイルを読む。
関連
facts/claude-code/agent-sdk.mdfacts/claude-code/agent-sdk-typescript.mdfacts/claude-code/agent-sdk-python.mdfacts/claude-code/agent-sdk-subagents.md