Skip to main content
By default, the SDK writes session transcripts to JSONL files under ~/.claude/projects/ on the local filesystem. A SessionStore adapter lets you mirror those transcripts to your own backend, such as S3, Redis, or a database, so a session created on one host can be resumed on another. Common reasons to use a session store:
  • Multi-host deployments. Serverless functions, autoscaled workers, and CI runners don’t share a filesystem. A shared store lets any replica resume any session.
  • Durability. Local containers are ephemeral. A store backed by S3 or a database survives restarts and redeploys.
  • Compliance and audit. Keep transcripts in storage you already govern, with your own retention rules, encryption, and access controls.

The SessionStore interface

A SessionStore is an object with two required methods, append and load, and four optional methods. The SDK calls append to write transcript entries during a query and load to read them back for resume.
SessionKey addresses one transcript. projectKey is a stable, filesystem-safe encoding of the working directory, sessionId is the session UUID, and subpath is set when the entry belongs to a subagent transcript or sidecar file rather than the main conversation. Treat subpath as an opaque key suffix; it follows the on-disk layout, for example subagents/agent-<id>. When subpath is undefined the key refers to the main transcript. In a SessionSummaryEntry, mtime is the sidecar’s storage write time and must share a clock source with the mtime values listSessions returns. data is opaque SDK-owned state; persist it verbatim without interpreting it. Build the entries by calling the exported foldSessionSummary helper, fold_session_summary in Python, on each batch inside append. Skip batches whose key has a subpath; subagent transcripts must not contribute to the main session’s summary. The fold never sets mtime: stamp it at persist time, through the options.mtime argument in TypeScript or by overwriting the field on the returned entry in Python. Concurrent append calls for the same session can race on the sidecar, so serialize the read-fold-write with a transaction, a compare-and-swap, or a per-session lock; the fold itself is pure.

Quick start

The SDK ships an InMemorySessionStore for development and testing. The example below runs a query with the store attached, captures the session ID from the result message, then resumes from the store in a second query() call. The second call passes the same store instance plus resume, so the SDK loads the transcript from the store instead of the local filesystem:
The second query prints a summary of the files from the first query, which shows the agent resumed with full context from the store.

Write your own adapter

Implement append and load against your backend. Add listSessions, listSessionSummaries, delete, and listSubkeys if you want listSessions(), one-call metadata reads, deleteSession(), and subagent resume to work against the store. Entries passed to append are typed as SessionStoreEntry (a { type: string; ... } object). Treat them as opaque JSON-safe values: persist them in order and return them from load in the same order. load must return entries that are deep-equal to what was appended; byte-equal serialization is not required, so backends like Postgres jsonb that reorder object keys are fine.

Reference implementations

The TypeScript SDK repository includes runnable reference adapters for S3, Redis, and Postgres under examples/session-stores/. They are not published to npm; copy the src/ file you need into your project and install the corresponding backend client. Each adapter takes a pre-configured client instance, so you control credentials, TLS, region, and pooling. For example, with S3:
TypeScript

Validate your adapter

Both SDKs ship a conformance suite that asserts the behavioral contract append, load, and the optional methods must satisfy. Tests for optional methods skip automatically when those methods are not implemented. In TypeScript, copy shared/conformance.ts from the example directory into your test suite. In Python, the suite ships in the package:
Python

Behavior notes

Dual-write architecture

The store is a mirror, not a replacement. The Claude Code subprocess always writes to local disk first; the SDK then forwards each batch to append(). If you want the local copy to be ephemeral, point CLAUDE_CONFIG_DIR at a temp directory in options.env. Because the mirror depends on local writes, the TypeScript SDK throws if you combine sessionStore with persistSession: false. Both SDKs also throw if you combine the store with file checkpointing, enableFileCheckpointing in TypeScript or enable_file_checkpointing in Python, since file-history backup blobs are written directly to local disk and are not mirrored to the store.

Mirror writes are best-effort

If append() rejects, the SDK retries the batch up to two more times with a short backoff, for at most three attempts in total. A call that times out isn’t retried, since the original call may still land. If the batch still fails, the error is logged, a { type: "system", subtype: "mirror_error" } message is emitted into the iterator, the batch is dropped, and the query continues. The local transcript is already durable on disk, so a store outage doesn’t interrupt the agent or lose data locally. Monitor for mirror_error if you need to detect store data loss. Because a retried batch can re-deliver entries that already landed, deduplicate by entry.uuid in your append() implementation.

getSessionMessages returns the post-compaction chain

getSessionMessages({ sessionStore }) returns the linked message chain the agent would see on resume. After auto-compaction, earlier turns are replaced by a summary, so a session whose store holds 503 raw entries may return 18 messages from getSessionMessages. For the full raw history, including pre-compaction turns and metadata entries, call store.load(key) directly.

forkSession is not a byte copy

forkSession({ sessionStore }) reads the source entries, rewrites every sessionId field and remaps message UUIDs, then appends the transformed entries under a new key. An adapter-level copy or CopyObject shortcut would produce a transcript that still references the old session ID, so the SDK does not use one.

Subagent transcripts

Subagent transcripts are mirrored under subpath: "subagents/agent-<id>". listSubagents({ sessionStore }) requires the adapter to implement listSubkeys; getSubagentMessages({ sessionStore }) uses it when available but falls back to the direct subpath when it is undefined. Resume also calls listSubkeys to restore subagent files; without it, only the main transcript is materialized.

Retention

The SDK never deletes from your store on its own. Retention is the adapter’s responsibility: implement TTLs, S3 lifecycle policies, or scheduled cleanup according to your compliance requirements. Local transcripts under CLAUDE_CONFIG_DIR are swept independently by the cleanupPeriodDays setting.

Supported on

The following TypeScript SDK functions accept a sessionStore option and operate against the store instead of the local filesystem when it is provided: In the Python SDK, set session_store in ClaudeAgentOptions to run query() against a store. The remaining operations each have a store-backed Python function that takes the store as an argument: 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(), and fork_session_via_store(). startup() has no Python equivalent. The standalone functions documented in the Python SDK reference, such as list_sessions(), read local session files.