Persistir sesiones en almacenamiento externo
Refleja transcripciones de sesiones en S3, Redis o tu propio backend para que cualquier host pueda reanudarlas.
De forma predeterminada, el SDK escribe transcripciones de sesiones en archivos JSONL bajo ~/.claude/projects/ en el sistema de archivos local. Un adaptador SessionStore te permite reflejar esas transcripciones en tu propio backend, como S3, Redis o una base de datos, para que una sesión creada en un host pueda reanudarse en otro.
Razones comunes para usar un almacén de sesiones:
- Implementaciones multi-host. Las funciones sin servidor, los trabajadores con escalado automático y los ejecutores de CI no comparten un sistema de archivos. Un almacén compartido permite que cualquier réplica reanude cualquier sesión.
- Durabilidad. Los contenedores locales son efímeros. Un almacén respaldado por S3 o una base de datos sobrevive a reinicios y redeploys.
- Cumplimiento y auditoría. Mantén transcripciones en almacenamiento que ya gobiernas, con tus propias reglas de retención, cifrado y controles de acceso.
La interfaz `SessionStore`
Un SessionStore es un objeto con dos métodos requeridos, append y load, y tres métodos opcionales. El SDK llama a append para escribir entradas de transcripción durante una consulta y a load para leerlas de nuevo para reanudar.
// Exported from @anthropic-ai/claude-agent-sdk as
// SessionStore, SessionKey, SessionStoreEntry.
type SessionKey = {
projectKey: string;
sessionId: string;
subpath?: string;
};
type SessionStore = {
// Required
append(key: SessionKey, entries: SessionStoreEntry[]): Promise<void>;
load(key: SessionKey): Promise<SessionStoreEntry[] | null>;
// Optional
listSessions?(
projectKey: string,
): Promise<Array<{ sessionId: string; mtime: number }>>;
delete?(key: SessionKey): Promise<void>;
listSubkeys?(key: {
projectKey: string;
sessionId: string;
}): Promise<string[]>;
};
# Exported from claude_agent_sdk as
// SessionStore, SessionKey, SessionStoreEntry.
class SessionKey(TypedDict):
project_key: str
session_id: str
subpath: NotRequired[str]
class SessionStore(Protocol):
# Required
async def append(
self, key: SessionKey, entries: list[SessionStoreEntry]
) -> None: ...
async def load(self, key: SessionKey) -> list[SessionStoreEntry] | None: ...
# Optional — omit or raise NotImplementedError
async def list_sessions(
self, project_key: str
) -> list[SessionStoreListEntry]: ...
async def delete(self, key: SessionKey) -> None: ...
async def list_subkeys(self, key: SessionListSubkeysKey) -> list[str]: ...
SessionKey direcciona una transcripción. projectKey es una codificación estable y segura para el sistema de archivos del directorio de trabajo, sessionId es el UUID de la sesión, y subpath se establece cuando la entrada pertenece a una transcripción de subagenteor archivo sidecar en lugar de la conversación principal. Trata subpath como una clave de sufijo opaca; sigue el diseño en disco, por ejemplo subagents/agent-<id>. Cuando subpath no está definido, la clave se refiere a la transcripción principal.
| Método | Requerido | Se llama cuando |
|---|---|---|
append |
Sí | Después de que cada lote de entradas de transcripción se escriba localmente. Las entradas son objetos seguros para JSON, uno por línea en el JSONL local. |
load |
Sí | Una vez antes de que se genere el subproceso, cuando resume está establecido. Devuelve null si la sesión es desconocida. |
listSessions |
No | Por listSessions({ sessionStore }) y por query()/startup() con continue: true. Si no está definido, esas llamadas lanzan una excepción. |
delete |
No | Por deleteSession({ sessionStore }). Eliminar la clave principal (sin subpath) debe cascada a todas las subclaves para esa sesión. Si no está definido, la eliminación es una no-op, que se adapta a backends de solo anexión. |
listSubkeys |
No | Durante la reanudación, para descubrir transcripciones de subagenteor. Si no está definido, solo se restaura la transcripción principal. |
Inicio rápido
El SDK incluye un InMemorySessionStore para desarrollo y pruebas. El ejemplo a continuación ejecuta una consulta con el almacén adjunto, captura el ID de sesión del mensaje de resultado, luego reanuda desde el almacén en una segunda llamada query(). La segunda llamada pasa la misma instancia de almacén más resume, por lo que el SDK carga la transcripción del almacén en lugar del sistema de archivos local:
import { query, InMemorySessionStore } from "@anthropic-ai/claude-agent-sdk";
const store = new InMemorySessionStore();
let sessionId: string | undefined;
for await (const message of query({
prompt: "List the TypeScript files under src/",
options: { sessionStore: store },
})) {
if (message.type === "result") {
sessionId = message.session_id;
}
}
// Resume from the store. The agent has full context from the first call.
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);
}
}
import asyncio
from claude_agent_sdk import (
ClaudeAgentOptions,
InMemorySessionStore,
ResultMessage,
query,
)
store = InMemorySessionStore()
async def main():
session_id = None
async for message in query(
prompt="List the Python files under src/",
options=ClaudeAgentOptions(session_store=store),
):
if isinstance(message, ResultMessage):
session_id = message.session_id
# Resume from the store. The agent has full context from the first call.
async for message in query(
prompt="Summarize what those files do",
options=ClaudeAgentOptions(session_store=store, resume=session_id),
):
if isinstance(message, ResultMessage) and message.subtype == "success":
print(message.result)
asyncio.run(main())
La segunda consulta imprime un resumen de los archivos de la primera consulta, lo que muestra que el agente reanudó con contexto completo desde el almacén.
Escribe tu propio adaptador
Implementa append y load contra tu backend. Añade listSessions, delete y listSubkeys si deseas que listSessions(), deleteSession() y la reanudación de subagenteor funcionen contra el almacén.
Las entradas pasadas a append se escriben como SessionStoreEntry (un objeto { type: string; ... }). Trátalas como valores opacos seguros para JSON: persístalas en orden y devuélvelas desde load en el mismo orden. load debe devolver entradas que sean profundamente iguales a lo que se anexó; la serialización byte-igual no es requerida, por lo que backends como Postgres jsonb que reordenan claves de objeto están bien.
Implementaciones de referencia
El repositorio del SDK de TypeScript incluye adaptadores de referencia ejecutables para S3, Redis y Postgres bajo examples/session-stores/. No se publican en npm; copia el archivo src/ que necesites en tu proyecto e instala el cliente backend correspondiente.
| Adaptador | Cliente backend | Modelo de almacenamiento |
|---|---|---|
S3SessionStore |
@aws-sdk/client-s3 |
Un archivo de parte JSONL por append(); load() lista, ordena y concatena. |
RedisSessionStore |
ioredis |
Lista RPUSH/LRANGE por transcripción, más un índice de conjunto ordenado de sesión. |
PostgresSessionStore |
pg |
Una fila por entrada en una tabla jsonb, ordenada por BIGSERIAL. |
Cada adaptador toma una instancia de cliente preconfigurada, por lo que controlas credenciales, TLS, región y agrupación. Por ejemplo, con S3:
import { query } from "@anthropic-ai/claude-agent-sdk";
import { S3Client } from "@aws-sdk/client-s3";
import { S3SessionStore } from "./S3SessionStore"; // copied from 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);
}
}
// Later, possibly on a different host:
for await (const message of query({
prompt: "Continue where we left off",
options: { sessionStore: store, resume: "previous-session-id" },
})) {
// ...
}
Valida tu adaptador
Ambos SDKs incluyen un conjunto de conformidad que afirma el contrato de comportamiento que append, load y los métodos opcionales deben satisfacer. Las pruebas para métodos opcionales se omiten automáticamente cuando esos métodos no se implementan.
En TypeScript, copia shared/conformance.ts del directorio de ejemplos en tu suite de pruebas. En Python, el conjunto se incluye en el paquete:
import pytest
from claude_agent_sdk.testing import run_session_store_conformance
@pytest.mark.asyncio
async def test_my_store_conformance():
await run_session_store_conformance(MyRedisStore)
Notas de comportamiento
Arquitectura de escritura dual
El almacén es un espejo, no un reemplazo. El subproceso de Claude Code siempre escribe primero en disco local; el SDK luego reenvía cada lote a append(). Si deseas que la copia local sea efímera, apunta CLAUDE_CONFIG_DIR a un directorio temporal en options.env. Debido a que el espejo depende de escrituras locales, sessionStore no se puede combinar con persistSession: false; el SDK lanza si estableces ambos. También lanza si se combina con enableFileCheckpointing, ya que los blobs de copia de seguridad del historial de archivos se escriben directamente en disco local y no se reflejan en el almacén.
Las escrituras de espejo son de mejor esfuerzo
Si append() rechaza o agota el tiempo de espera, el error se registra, se emite un mensaje { type: "system", subtype: "mirror_error" } en el iterador, y la consulta continúa. La transcripción local ya es duradera en disco, por lo que una interrupción del almacén no interrumpe al agente ni pierde datos localmente. Los lotes que fallan no se reintentan, por lo que monitorea mirror_error si necesitas detectar pérdida de datos del almacén.
`getSessionMessages` devuelve la cadena posterior a la compactación
getSessionMessages({ sessionStore }) devuelve la cadena de mensajes vinculada que el agente vería al reanudar. Después de la compactación automática, los turnos anteriores se reemplazan por un resumen, por lo que una sesión cuyo almacén contiene 503 entradas sin procesar puede devolver 18 mensajes desde getSessionMessages. Para el historial sin procesar completo, incluidos los turnos previos a la compactación y las entradas de metadatos, llama a store.load(key) directamente.
`forkSession` no es una copia byte
forkSession({ sessionStore }) lee las entradas de origen, reescribe cada campo sessionId y remapea UUIDs de mensajes, luego anexa las entradas transformadas bajo una clave nueva. Una copia a nivel de adaptador o un atajo CopyObject produciría una transcripción que aún hace referencia al ID de sesión anterior, por lo que el SDK no usa uno.
Transcripciones de subagente
Las transcripciones de subagente se reflejan bajo subpath: "subagents/agent-<id>". listSubagents({ sessionStore }) requiere que el adaptador implemente listSubkeys; getSubagentMessages({ sessionStore }) lo usa cuando está disponible pero vuelve al subpath directo cuando no está definido. La reanudación también llama a listSubkeys para restaurar archivos de subagente; sin él, solo se materializa la transcripción principal.
Retención
El SDK nunca elimina de tu almacén por su cuenta. La retención es responsabilidad del adaptador: implementa TTLs, políticas de ciclo de vida de S3 o limpieza programada según tus requisitos de cumplimiento. Las transcripciones locales bajo CLAUDE_CONFIG_DIR se barren independientemente por la configuración cleanupPeriodDays.
Compatible con
Las siguientes funciones del SDK aceptan una opción sessionStore y operan contra el almacén en lugar del sistema de archivos local cuando se proporciona:
query()startup()listSessions()getSessionInfo()getSessionMessages()renameSession()tagSession()deleteSession()forkSession()listSubagents()getSubagentMessages()
Recursos relacionados
- Trabajar con sesiones: Continúa, reanuda y bifurca sin un almacén personalizado
- Alojar el SDK: Patrones de implementación para entornos multi-host
- TypeScript
Options: Referencia de opción completa examples/session-stores/: Adaptadores de referencia ejecutables de S3, Redis y Postgres