SpyBara
Go Premium

agent-sdk/session-storage.md 2026-09-17 05:00 UTC to 2026-09-18 23:58 UTC

This page contains 12 additions and 12 deletions.

2026
Wed 9 22:58 Fri 18 23:58

Persistere le sessioni nell'archiviazione esterna

Eseguire il mirroring dei trascritti di sessione Agent SDK nel vostro object store, key-value store o database in modo che altri host possano riprendere le vostre sessioni.

Per impostazione predefinita, l'SDK scrive i trascritti di sessione in file JSONL in ~/.claude/projects/ nel filesystem locale. Un adattatore SessionStore consente di eseguire il mirroring di questi trascritti nel vostro backend, come un object store, un key-value store o un database, in modo che una sessione creata su un host possa essere ripresa su un altro host in esecuzione da una directory di lavoro corrispondente.

Motivi comuni per utilizzare un session store:

  • Distribuzioni multi-host. Le funzioni serverless, i worker con scalabilità automatica e i runner CI non condividono un filesystem. Un store condiviso consente alle repliche di riprendere le sessioni reciproche.
  • Durabilità. I container locali sono effimeri. Uno store esterno sopravvive ai riavvii e ai ridistribuzioni.
  • Conformità e audit. Mantenete i trascritti nell'archiviazione che già controllate, con le vostre regole di conservazione, crittografia e controlli di accesso.

L'interfaccia `SessionStore`

Un SessionStore è un oggetto con due metodi obbligatori, append e load, e quattro metodi facoltativi. L'SDK chiama append per scrivere le voci di trascritto durante una query e load per leggerle di nuovo per la ripresa.

// 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 indirizza un trascritto. projectKey è una codifica stabile e sicura per il filesystem della directory di lavoro, sessionId è l'UUID della sessione, e subpath è impostato quando la voce appartiene a un trascritto di subagent o a un file sidecar piuttosto che alla conversazione principale.

Poiché projectKey codifica la directory di lavoro, riprendere o continuare dal negozio da una directory di lavoro che corrisponda all'esecuzione originale. In TypeScript, se impostate CLAUDE_CODE_PROJECT_DIR_NAME accanto a CLAUDE_CONFIG_DIR nell'opzione env di una query, l'SDK codifica le voci di quella query e le sue ricerche resume e continue per quel nome. Poiché helper autonomi come listSessions e deleteSession non accettano env e leggono l'ambiente del processo, impostate CLAUDE_CONFIG_DIR e lo stesso nome nell'ambiente del processo host. Richiede Agent SDK v0.3.234 o successivo.

Trattate subpath come una chiave suffisso opaca; segue il layout su disco, ad esempio subagents/agent-<id>. Quando subpath non è definito, la chiave si riferisce al trascritto principale.

Metodo Obbligatorio Chiamato quando
append Sì Dopo che ogni batch di voci di trascritto viene scritto localmente. Le voci sono oggetti JSON-safe, uno per riga nel JSONL locale.
load Sì Prima che il subprocess venga generato quando resume è impostato o continue: true risolve la sessione del negozio più recente, e una volta per sessione quando l'elenco ricade da listSessionSummaries. Restituire null se la sessione è sconosciuta.
listSessions No Da listSessions({ sessionStore }) e da query()/startup() con continue: true. Se non definito, continue: true genera un'eccezione, e listSessions({ sessionStore }) genera un'eccezione a meno che listSessionSummaries non sia implementato.
listSessionSummaries No Da listSessions({ sessionStore }) per leggere i metadati per tutte le sessioni in una sola chiamata. Mantenete i riepiloghi all'interno di append. Se non definito, l'elenco ricade a listSessions più un load per sessione.
delete No Da deleteSession({ sessionStore }). L'eliminazione della chiave principale (nessun subpath) deve propagarsi a tutte le subchiavi per quella sessione e anche rimuovere la voce di riepilogo della sessione, in modo che una sessione eliminata smetta di apparire in listSessionSummaries. Se non definito, l'eliminazione è un'operazione nulla, che si adatta ai backend append-only.
listSubkeys No Durante la ripresa, per scoprire i trascritti dei subagent. Se non definito, viene ripristinato solo il trascritto principale.

In una SessionSummaryEntry, mtime è il tempo di scrittura dell'archiviazione del sidecar e deve condividere una fonte di clock con i valori mtime che listSessions restituisce. data è uno stato di proprietà dell'SDK opaco; persistetelo verbatim senza interpretarlo.

Costruite le voci chiamando l'helper esportato foldSessionSummary, fold_session_summary in Python, su ogni batch all'interno di append. Saltate i batch la cui chiave ha un subpath; i trascritti dei subagent non devono contribuire al riepilogo della sessione principale. Il fold non imposta mai mtime: contrassegnatelo al momento della persistenza, tramite l'argomento options.mtime in TypeScript o sovrascrivendo il campo sulla voce restituita in Python. Le chiamate append simultanee per la stessa sessione possono correre sul sidecar, quindi serializzate la lettura-fold-scrittura con una transazione, un compare-and-swap, o un blocco per sessione; il fold stesso è puro.

Per ciò che l'SDK fa con il trascritto che load restituisce, vedere Riprendere dal negozio.

Avvio rapido

L'SDK fornisce un InMemorySessionStore per lo sviluppo e i test. L'esempio seguente esegue una query con lo store allegato, acquisisce l'ID della sessione dal messaggio di risultato, quindi riprende dallo store in una seconda chiamata query(). La seconda chiamata passa la stessa istanza dello store più resume, in modo che l'SDK carichi il trascritto dallo store invece dal filesystem locale:

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 seconda query stampa un riepilogo dei file dalla prima query, il che mostra che l'agente ha ripreso con il contesto completo dallo store.

Scrivere il vostro adattatore

Implementate append e load rispetto al vostro backend. Aggiungete listSessions, listSessionSummaries, delete e listSubkeys se desiderate che listSessions(), letture di metadati in una sola chiamata, deleteSession() e la ripresa dei subagent funzionino rispetto allo store.

Le voci passate a append sono digitate come SessionStoreEntry (un oggetto { type: string; ... }). Trattate le come valori JSON-safe opachi: persistetele in ordine e restituitetele da load nello stesso ordine. load deve restituire voci che siano deep-equal a quelle aggiunte; la serializzazione byte-equal non è richiesta, quindi un backend che riordina le chiavi degli oggetti, come un tipo di colonna JSON binaria, va bene.

Implementazioni di riferimento

Entrambi i repository SDK includono adattatori di riferimento eseguibili in examples/session-stores/ in TypeScript e examples/session_stores/ in Python. C'è un adattatore per tipo di archiviazione, e ognuno mostra come append e load si mappano su quel tipo di backend. Non sono pubblicati come pacchetti; copiate l'adattatore per il tipo più vicino al vostro backend nel vostro progetto, installate il client del vostro backend, e adattatelo.

Tipo di archiviazione Modello di archiviazione Adattatore di esempio
Object store Un file di parte per append(); load() elenca le parti, le ordina e le concatena. S3 (TypeScript, Python)
Key-value store Una lista per trascritto che append() inserisce e load() legge in intervallo, più un indice ordinato di sessioni. Redis (TypeScript, Python)
Database relazionale o document store Una riga o documento per voce, archiviato come JSON e ordinato per una chiave assegnata all'inserimento. Postgres (TypeScript, Python)

Ogni adattatore accetta un'istanza client preconfigurata, in modo che controlliate le credenziali, TLS, la regione e il pooling. L'esempio seguente collega l'adattatore object-store in query() e quindi riprende da esso su un altro host:

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" },
})) {
  // ...
}

Convalidare il vostro adattatore

Entrambi gli SDK forniscono una suite di conformità che asserisce il contratto comportamentale che append, load e i metodi facoltativi devono soddisfare. I test per i metodi facoltativi vengono saltati automaticamente quando questi metodi non sono implementati.

In TypeScript, copiate shared/conformance.ts dalla directory di esempio nella vostra suite di test. In Python, la suite è fornita nel pacchetto. Per eseguirla con pytest, che non è una dipendenza dell'SDK, installate prima pytest:

pip install pytest

Quindi passate il vostro adattatore alla suite in un file di test come factory a zero argomenti, che run_session_store_conformance chiama una volta per contratto per costruire un nuovo store:

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)

Passare la classe MyRedisStore stessa, come fa questo esempio, funziona quando il costruttore non accetta argomenti. Per un adattatore che accetta un client preconfigurato, passate invece una lambda che costruisce lo store. Poiché i contratti riutilizzano le stesse chiavi di sessione, ogni store che la factory restituisce deve iniziare con archiviazione vuota, quindi fate in modo che la lambda fornisca archiviazione di supporto isolata per ogni chiamata, come un nuovo fake in memoria, un prefisso di chiave univoco o un nuovo database di test.

Note sul comportamento

Architettura a doppia scrittura

Il subprocess Claude Code scrive sempre ogni batch di voci di trascritto su disco locale per primo, e l'SDK quindi invia lo stesso batch a append() del vostro store, quindi lo store è un mirror del trascritto locale piuttosto che una sostituzione per esso. Quale copia sopravvive all'esecuzione dipende da come è stata avviata l'esecuzione:

  • Sessione nuova, o una ripresa quando lo store non ha nulla per la sessione: il trascritto locale nella vostra directory di configurazione sopravvive all'esecuzione, e lo store riceve una copia.
  • Esecuzione ripresa dallo store: la copia locale viene eliminata alla fine dell'esecuzione, quindi lo store contiene l'unica copia durevole.

Se non desiderate che una sessione nuova lasci un trascritto su disco locale, impostate CLAUDE_CONFIG_DIR a una directory temporanea in options.env. Un'esecuzione ripresa dallo store elimina già la sua copia locale, quindi non ha bisogno di tale impostazione. In TypeScript, diffondete anche process.env in env, poiché l'opzione env sostituisce l'ambiente del subprocess.

Se la vostra app accede tramite file nella directory di configurazione, come credenziali OAuth o un apiKeyHelper nel vostro settings.json utente, copiate prima quei file nella directory temporanea, oppure impostate ANTHROPIC_API_KEY in env invece. Altrimenti l'esecuzione fallisce con Not logged in.

Due opzioni entrano in conflitto con il mirror, e l'SDK genera un'eccezione all'avvio se combinate una di esse con uno store:

  • persistSession: false in TypeScript: disattiva le scritture locali su cui è costruito il mirror. L'SDK Python non ha un'opzione equivalente.
  • File checkpointing, enableFileCheckpointing in TypeScript o enable_file_checkpointing in Python: scrive i suoi backup di file direttamente su disco locale, e l'SDK non li esegue il mirroring nello store.

Ripresa dallo store

Quando passate resume, o continue: true in TypeScript o continue_conversation=True in Python, insieme a uno store, l'SDK chiede allo store un trascritto prima di generare il subprocess:

  • resume: l'SDK chiede la sessione il cui ID avete passato.
  • continue: true o continue_conversation=True: l'SDK chiede la sessione più recente dello store.

Quando lo store restituisce il trascritto, l'SDK lo scrive in una directory di configurazione temporanea, esegue il subprocess con CLAUDE_CONFIG_DIR che punta lì, ed elimina la directory quando l'esecuzione termina. Il trascritto locale che quell'esecuzione scrive viene eliminato con essa, motivo per cui lo store contiene l'unica copia durevole su questo percorso.

L'SDK semina anche la directory temporanea con file dalla vostra directory di configurazione reale. Ciò che copia differisce per linguaggio:

  • TypeScript: credenziali, .claude.json, e il vostro settings.json utente. Da settings.json rimuove le chiavi che si comportano male in una directory di configurazione temporanea: enabledPlugins, extraKnownMarketplaces, il suo alias additionalMarketplaces, e qualsiasi CLAUDE_CONFIG_DIR nel blocco env del file. Prima di Agent SDK v0.3.232, l'SDK non rimuoveva l'alias. L'autenticazione configurata nelle impostazioni, come apiKeyHelper, funziona quando riprendete dallo store. Prima di Agent SDK v0.3.222, l'SDK TypeScript copiava solo credenziali e .claude.json.
  • Python: solo credenziali e .claude.json, quindi un'app che si autentica tramite apiKeyHelper nel vostro settings.json utente fallisce con Not logged in quando riprende da uno store. Un apiKeyHelper nelle impostazioni gestite o di progetto funziona ancora, perché Claude Code legge quei file da posizioni che CLAUDE_CONFIG_DIR non influenza.

Quando lo store non ha nulla per la sessione, l'SDK viene eseguito nella vostra directory di configurazione reale, e il risultato dipende da quale opzione avete passato:

  • resume: entrambi gli SDK passano l'ID al subprocess, che riprende il trascritto locale esattamente come resume fa senza uno store.
  • continue: true in TypeScript: l'SDK avvia una sessione nuova.
  • continue_conversation=True in Python: l'SDK continua dalla sessione locale più recente.

Le scritture mirror sono best-effort

Se append() rifiuta, l'SDK ritenta il batch fino a due volte in più con un breve backoff, per un massimo di tre tentativi in totale. Una chiamata che scade non viene ritentata, poiché la chiamata originale potrebbe comunque arrivare. Se il batch continua a fallire, l'SDK registra l'errore, emette un messaggio { type: "system", subtype: "mirror_error" } nell'iteratore, scarta il batch e continua la query. Poiché un batch ritentato può ri-consegnare voci che hanno già raggiunto la destinazione, deduplicare per entry.uuid nella vostra implementazione di append().

Un'interruzione dello store non interrompe l'agente, poiché il subprocess scrive localmente per primo. Monitorate mirror_error se dovete rilevare la perdita di dati dello store. Su un'esecuzione ripresa dallo store, un batch scartato non ha una copia sopravvivente una volta che l'esecuzione termina.

`getSessionMessages` restituisce la catena post-compattazione

getSessionMessages({ sessionStore }) restituisce la catena di messaggi collegati che l'agente vedrebbe al momento della ripresa. Dopo la compattazione automatica, i turni precedenti vengono sostituiti da un riepilogo, quindi una sessione il cui store contiene 503 voci grezze può restituire 18 messaggi da getSessionMessages. Per la cronologia grezza completa, inclusi i turni pre-compattazione e le voci di metadati, chiamate store.load(key) direttamente.

`forkSession` non è una copia byte

forkSession({ sessionStore }) legge le voci di origine, riscrive ogni campo sessionId e rimappa gli UUID dei messaggi, quindi aggiunge le voci trasformate sotto una nuova chiave. Una copia a livello di adattatore o un collegamento CopyObject produrrebbe un trascritto che fa ancora riferimento all'ID della sessione precedente, quindi l'SDK non ne utilizza uno.

Trascritti dei subagent

I trascritti dei subagent vengono sottoposti a mirroring in subpath: "subagents/agent-<id>". listSubagents({ sessionStore }) richiede che l'adattatore implementi listSubkeys; getSubagentMessages({ sessionStore }) lo utilizza quando disponibile ma ricade al subpath diretto quando non è definito. La ripresa chiama anche listSubkeys per ripristinare i file dei subagent; senza di esso, viene materializzato solo il trascritto principale.

Conservazione

L'SDK non elimina mai dal vostro store di sua iniziativa. La conservazione è responsabilità dell'adattatore: implementate TTL, politiche del ciclo di vita S3 o pulizia pianificata secondo i vostri requisiti di conformità.

I trascritti locali in CLAUDE_CONFIG_DIR vengono puliti indipendentemente dall'impostazione cleanupPeriodDays, seguendo le regole di pulizia della conservazione. Un'esecuzione ripresa dallo store non lascia alcun trascritto locale, quindi per quelle esecuzioni la conservazione del vostro store è l'unica conservazione che esiste.

Supportato su

Le seguenti funzioni SDK TypeScript accettano un'opzione sessionStore e operano rispetto allo store invece del filesystem locale quando viene fornita:

In Python SDK, impostare session_store in ClaudeAgentOptions per eseguire query() rispetto a uno store. Le operazioni rimanenti hanno ciascuna una funzione Python supportata da store che accetta lo store come argomento: 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() e fork_session_via_store(). startup() non ha un equivalente Python. Le funzioni standalone documentate nel riferimento Python SDK, come list_sessions(), leggono i file di sessione locali.