SpyBara
Go Premium

agent-sdk/session-storage.md 2026-09-08 20:00 UTC to 2026-09-09 22:58 UTC

This page contains 118 additions and 31 deletions.

2026
Wed 9 22:58 Fri 18 23:58

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 host que se ejecute desde un directorio de trabajo coincidente.

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 las réplicas reanuden las sesiones de las demás.
  • 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 cuatro 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, SessionSummaryEntry.

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 }>>;
listSessionSummaries?(projectKey: string): Promise<SessionSummaryEntry[]>;
delete?(key: SessionKey): Promise<void>;
listSubkeys?(key: {
projectKey: string;
sessionId: string;
}): Promise<string[]>;
};

type SessionSummaryEntry = {
sessionId: string;
mtime: number;
data: Record<string, unknown>;
};

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 subagente o archivo sidecar en lugar de la conversación principal.

Debido a que projectKey codifica el directorio de trabajo, reanude o continúe desde el almacén desde un directorio de trabajo que coincida con la ejecución original. En TypeScript, si establece CLAUDE_CODE_PROJECT_DIR_NAME junto a CLAUDE_CONFIG_DIR en la opción env de una consulta, el SDK codifica las entradas de esa consulta, y sus búsquedas de resume y continue, por ese nombre en su lugar. Debido a que los ayudantes independientes como listSessions y deleteSession no toman env y leen el entorno del proceso, establezca CLAUDE_CONFIG_DIR y el mismo nombre en el entorno del proceso del host también. Requiere Agent SDK v0.3.234 o posterior.

Trate 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í Antes de que se genere el subproceso cuando resume está establecido o continue: true resuelve la sesión de almacén más reciente, y una vez por sesión cuando la enumeración se retrae de listSessionSummaries. Devuelve null si la sesión es desconocida.
listSessions No Por listSessions({ sessionStore }) y por query()/startup() con continue: true. Si no está definido, continue: true lanza una excepción, y listSessions({ sessionStore }) lanza una excepción a menos que listSessionSummaries esté implementado.
listSessionSummaries No Por listSessions({ sessionStore }) para leer metadatos de todas las sesiones en una llamada. Mantenga los resúmenes dentro de append. Si no está definido, la enumeración se retrae a listSessions más una load por sesión.
delete No Por deleteSession({ sessionStore }). Eliminar la clave principal (sin subpath) debe cascada a todas las subclaves para esa sesión y también eliminar la entrada de resumen de la sesión, por lo que una sesión eliminada deja de aparecer en listSessionSummaries. 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.

En una SessionSummaryEntry, mtime es el tiempo de escritura del almacenamiento del sidecar y debe compartir una fuente de reloj con los valores mtime que listSessions devuelve. data es un estado propiedad del SDK opaco; persístalo textualmente sin interpretarlo.

Construya las entradas llamando al ayudante exportado foldSessionSummary, fold_session_summary en Python, en cada lote dentro de append. Omita lotes cuya clave tenga un subpath; las transcripciones de subagente no deben contribuir al resumen de la sesión principal. El fold nunca establece mtime: establézcalo en el momento de la persistencia, a través del argumento options.mtime en TypeScript o sobrescribiendo el campo en la entrada devuelta en Python. Las llamadas concurrentes a append para la misma sesión pueden competir en el sidecar, así que serialice la lectura-fold-escritura con una transacción, una comparación e intercambio, o un bloqueo por sesión; el fold en sí es puro.

Para lo que el SDK hace con la transcripción que load devuelve, consulte Reanudar desde el almacén.

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;
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) {
// A single-shot query() throws after yielding an error result. If the
// failure was an error result, sessionId was already captured by the loop
// above; connection or process failures yield no result message.
console.error(`Session ended with an error: ${error}`);
}

// 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);
}
}

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, listSessionSummaries, delete y listSubkeys si deseas que listSessions(), lecturas de metadatos de una sola llamada, deleteSession() y la reanudación de subagentes 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. Para ejecutarlo con pytest, que no es una dependencia del SDK, instala pytest primero:

pip install pytest

Luego pasa tu adaptador al conjunto en un archivo de prueba como una fábrica sin argumentos, que run_session_store_conformance llama una vez por contrato para construir un almacén nuevo:

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)

Pasar la clase MyRedisStore en sí, como hace este ejemplo, funciona cuando el constructor no toma argumentos. Para un adaptador que toma un cliente preconfigurado, pasa una lambda que construya el almacén en su lugar. Debido a que los contratos reutilizan las mismas claves de sesión, cada almacén que devuelve la fábrica debe comenzar con almacenamiento vacío, así que haz que la lambda aprovisione almacenamiento de respaldo aislado por llamada, como un falso en memoria nuevo, un prefijo de clave único, o una base de datos de prueba nueva.

Notas de comportamiento

Arquitectura de escritura dual

El subproceso de Claude Code siempre escribe cada lote de entradas de transcripción en disco local primero, y luego el SDK reenvía el mismo lote a append() de tu almacén, por lo que el almacén es un espejo de la transcripción local en lugar de un reemplazo. Cuál copia sobrevive a la ejecución depende de cómo se inició la ejecución:

  • Sesión nueva, o una reanudación cuando el almacén no tiene nada para la sesión: la transcripción local bajo tu directorio de configuración sobrevive a la ejecución, y el almacén recibe una copia.
  • Ejecución reanudada desde el almacén: la copia local se elimina al final de la ejecución, por lo que el almacén contiene la única copia duradera.

Si no deseas que una sesión nueva deje una transcripción en disco local, establece CLAUDE_CONFIG_DIR en un directorio temporal en options.env. Una ejecución reanudada desde el almacén ya elimina su copia local, por lo que no necesita tal configuración. En TypeScript, también expande process.env en env, ya que la opción env reemplaza el entorno del subproceso.

Si tu aplicación inicia sesión a través de archivos en el directorio de configuración, como credenciales de OAuth o un apiKeyHelper en tu settings.json de usuario, copia primero esos archivos en el directorio temporal, o establece ANTHROPIC_API_KEY en env en su lugar. De lo contrario, la ejecución falla con Not logged in.

Dos opciones entran en conflicto con el espejo, y el SDK lanza al inicio si combinas cualquiera de ellas con un almacén:

  • persistSession: false en TypeScript: desactiva las escrituras locales en las que se construye el espejo. El SDK de Python no tiene una opción equivalente.
  • Punto de control de archivo, enableFileCheckpointing en TypeScript o enable_file_checkpointing en Python: escribe sus copias de seguridad de archivo directamente en disco local, y el SDK no las refleja en el almacén.

Reanudación desde el almacén

Cuando pasas resume, o continue: true en TypeScript o continue_conversation=True en Python, junto con un almacén, el SDK le pide al almacén una transcripción antes de que genere el subproceso:

  • resume: el SDK solicita la sesión cuyo ID pasaste.
  • continue: true o continue_conversation=True: el SDK solicita la sesión más nueva del almacén.

Cuando el almacén devuelve la transcripción, el SDK la escribe en un directorio de configuración temporal, ejecuta el subproceso con CLAUDE_CONFIG_DIR apuntando allí, y elimina el directorio cuando finaliza la ejecución. La transcripción local que esa ejecución escribe se elimina con él, que es por qué el almacén contiene la única copia duradera en esta ruta.

El SDK también siembra el directorio temporal con archivos de tu directorio de configuración real. Lo que copia difiere según el idioma:

  • TypeScript: credenciales, .claude.json, y tu settings.json de usuario. De settings.json elimina las claves que se comportan mal bajo un directorio de configuración temporal: enabledPlugins, extraKnownMarketplaces, su alias additionalMarketplaces, y cualquier CLAUDE_CONFIG_DIR en el bloque env del archivo. Antes de Agent SDK v0.3.232, el SDK no eliminaba el alias. La autenticación configurada en configuración, como apiKeyHelper, funciona cuando reanudas desde el almacén. Antes de Agent SDK v0.3.222, el SDK de TypeScript copiaba solo credenciales y .claude.json.
  • Python: solo credenciales y .claude.json, por lo que una aplicación que se autentica a través de apiKeyHelper en tu settings.json de usuario falla con Not logged in cuando se reanuda desde un almacén. Un apiKeyHelper en configuración administrada o de proyecto aún funciona, porque Claude Code lee esos archivos desde ubicaciones que CLAUDE_CONFIG_DIR no afecta.

Cuando el almacén no tiene nada para la sesión, el SDK se ejecuta bajo tu directorio de configuración real en su lugar, y el resultado depende de cuál opción pasaste:

  • resume: ambos SDKs pasan el ID al subproceso, que reanuda la transcripción local exactamente como lo hace resume sin un almacén.
  • continue: true en TypeScript: el SDK inicia una sesión nueva.
  • continue_conversation=True en Python: el SDK continúa desde la sesión local más nueva.

Las escrituras de espejo son de mejor esfuerzo

Si append() rechaza, el SDK reintenta el lote hasta dos veces más con un retroceso corto, para un máximo de tres intentos en total. Una llamada que agota el tiempo de espera no se reintenta, ya que la llamada original puede seguir llegando. Si el lote sigue fallando, el SDK registra el error, emite un mensaje { type: "system", subtype: "mirror_error" } en el iterador, descarta el lote y continúa la consulta. Debido a que un lote reintentado puede reentrega entradas que ya llegaron, deduplica por entry.uuid en tu implementación de append().

Una interrupción del almacén no interrumpe al agente, ya que el subproceso escribe localmente primero. Monitorea mirror_error si necesitas detectar pérdida de datos del almacén. En una ejecución reanudada desde el almacén, un lote descartado no tiene copia sobreviviente una vez que finaliza la ejecució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, siguiendo las reglas de barrido de retención. Una ejecución reanudada desde el almacén no deja transcripción local, por lo que para esas ejecuciones la retención de tu almacén es la única retención que existe.

Compatible con

Las siguientes funciones del SDK de TypeScript aceptan una opción sessionStore y operan contra el almacén en lugar del sistema de archivos local cuando se proporciona:

En el SDK de Python, establezca session_store en ClaudeAgentOptions para ejecutar query() contra un almacén. Las operaciones restantes tienen cada una una función respaldada por almacén de Python que toma el almacén como argumento: 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() y fork_session_via_store(). startup() no tiene equivalente en Python. Las funciones independientes documentadas en la referencia del SDK de Python, como list_sessions(), leen archivos de sesión locales.