Сохранение сеансов во внешнее хранилище
Зеркалируйте стенограммы сеансов в S3, Redis или собственный бэкенд, чтобы другие хосты могли возобновить ваши сеансы.
По умолчанию SDK записывает стенограммы сеансов в файлы JSONL в папке ~/.claude/projects/ на локальной файловой системе. Адаптер SessionStore позволяет зеркалировать эти стенограммы в собственный бэкенд, такой как S3, Redis или база данных, чтобы сеанс, созданный на одном хосте, можно было возобновить на другом хосте, работающем из одного и того же рабочего каталога.
Основные причины использования хранилища сеансов:
- Развертывания на нескольких хостах. Бессерверные функции, автомасштабируемые рабочие процессы и CI-раннеры не используют общую файловую систему. Общее хранилище позволяет репликам возобновлять сеансы друг друга.
- Надежность. Локальные контейнеры являются временными. Хранилище, поддерживаемое S3 или базой данных, сохраняется при перезагрузках и переразвертываниях.
- Соответствие и аудит. Сохраняйте стенограммы в хранилище, которым вы уже управляете, с собственными правилами хранения, шифрованием и контролем доступа.
Интерфейс `SessionStore`
SessionStore — это объект с двумя обязательными методами, append и load, и четырьмя необязательными методами. SDK вызывает append для записи записей стенограммы во время запроса и load для их чтения при возобновлении.
// 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>;
};
# Exported from claude_agent_sdk as
# SessionStore, SessionKey, SessionStoreEntry, SessionSummaryEntry.
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 list_session_summaries(
self, project_key: str
) -> list[SessionSummaryEntry]: ...
async def delete(self, key: SessionKey) -> None: ...
async def list_subkeys(self, key: SessionListSubkeysKey) -> list[str]: ...
class SessionSummaryEntry(TypedDict):
session_id: str
mtime: int
data: dict[str, Any]
SessionKey адресует одну стенограмму. projectKey — это стабильное, безопасное для файловой системы кодирование рабочей директории, sessionId — это UUID сеанса, а subpath устанавливается, когда запись принадлежит стенограмме подагента или файлу сайдкара, а не основному разговору.
Поскольку projectKey кодирует рабочую директорию, возобновляйте или продолжайте из хранилища из рабочей директории, соответствующей исходному запуску. В TypeScript, если вы установите CLAUDE_CODE_PROJECT_DIR_NAME рядом с CLAUDE_CONFIG_DIR в опции env запроса, SDK будет ключировать записи этого запроса и его поиски resume и continue по этому имени вместо этого. Поскольку автономные вспомогательные функции, такие как listSessions и deleteSession, не принимают env и читают переменные окружения процесса, установите CLAUDE_CONFIG_DIR и то же имя в переменных окружения хост-процесса. Требуется Agent SDK v0.3.234 или позже.
Рассматривайте subpath как непрозрачный суффикс ключа; он следует макету на диске, например subagents/agent-<id>. Когда subpath не определен, ключ ссылается на основную стенограмму.
| Метод | Обязательный | Вызывается когда |
|---|---|---|
append |
Да | После записи каждого пакета записей стенограммы локально. Записи — это объекты, безопасные для JSON, по одному на строку в локальном JSONL. |
load |
Да | Перед порождением подпроцесса, когда установлен resume, или continue: true разрешает самый новый сеанс хранилища, и один раз за сеанс при перечислении, если происходит откат от listSessionSummaries. Возвращайте null, если сеанс неизвестен. |
listSessions |
Нет | По listSessions({ sessionStore }) и по query()/startup() с continue: true. Если не определено, continue: true выбрасывает исключение, и listSessions({ sessionStore }) выбрасывает исключение, если не реализован listSessionSummaries. |
listSessionSummaries |
Нет | По listSessions({ sessionStore }) для чтения метаданных всех сеансов в одном вызове. Поддерживайте сводки внутри append. Если не определено, перечисление откатывается к listSessions плюс load для каждого сеанса. |
delete |
Нет | По deleteSession({ sessionStore }). Удаление основного ключа (без subpath) должно каскадировать на все подключи для этого сеанса и также удалить запись сводки сеанса, чтобы удаленный сеанс перестал появляться в listSessionSummaries. Если не определено, удаление — это холостой ход, что подходит для добавляемых только бэкендов. |
listSubkeys |
Нет | Во время возобновления для обнаружения стенограмм подагентов. Если не определено, восстанавливается только основная стенограмма. |
В SessionSummaryEntry mtime — это время записи хранилища сайдкара и должно использовать один источник часов со значениями mtime, которые возвращает listSessions. data — это непрозрачное состояние, принадлежащее SDK; сохраняйте его дословно без интерпретации.
Создавайте записи, вызывая экспортированный вспомогательный метод foldSessionSummary, fold_session_summary в Python, для каждого пакета внутри append. Пропускайте пакеты, чей ключ имеет subpath; стенограммы подагентов не должны вносить вклад в сводку основного сеанса. Fold никогда не устанавливает mtime: отметьте его во время сохранения через аргумент options.mtime в TypeScript или перезаписав поле на возвращаемой записи в Python. Одновременные вызовы append для одного сеанса могут конкурировать на сайдкаре, поэтому сериализуйте чтение-fold-запись с помощью транзакции, compare-and-swap или блокировки для каждого сеанса; сам fold является чистым.
Для информации о том, что SDK делает со стенограммой, которую возвращает load, см. Возобновление из хранилища.
Быстрый старт
SDK поставляется с InMemorySessionStore для разработки и тестирования. Пример ниже запускает запрос с подключенным хранилищем, захватывает ID сеанса из результирующего сообщения, а затем возобновляет из хранилища во втором вызове query(). Второй вызов передает тот же экземпляр хранилища плюс resume, поэтому SDK загружает стенограмму из хранилища вместо локальной файловой системы:
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);
}
}
import asyncio
from claude_agent_sdk import (
ClaudeAgentOptions,
InMemorySessionStore,
ResultMessage,
query,
)
store = InMemorySessionStore()
async def main():
session_id = None
try:
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
except Exception as error:
# A single-shot query() raises after yielding an error result. If the
# failure was an error result, session_id was already captured by the
# loop above; connection or process failures yield no result message.
print(f"Session ended with an error: {error}")
# 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())
Второй запрос выводит сводку файлов из первого запроса, что показывает, что агент возобновил работу с полным контекстом из хранилища.
Напишите собственный адаптер
Реализуйте append и load для вашего бэкенда. Добавьте listSessions, listSessionSummaries, delete и listSubkeys, если вы хотите, чтобы listSessions(), одноразовое чтение метаданных, deleteSession() и возобновление подагента работали с хранилищем.
Записи, переданные в append, типизированы как SessionStoreEntry (объект { type: string; ... }). Рассматривайте их как непрозрачные значения, безопасные для JSON: сохраняйте их по порядку и возвращайте из load в том же порядке. load должен возвращать записи, которые глубоко равны тому, что было добавлено; сериализация, равная по байтам, не требуется, поэтому бэкенды, такие как Postgres jsonb, которые переупорядочивают ключи объектов, подходят.
Эталонные реализации
Репозиторий TypeScript SDK включает запускаемые эталонные адаптеры для S3, Redis и Postgres в examples/session-stores/. Они не опубликованы в npm; скопируйте нужный файл src/ в ваш проект и установите соответствующий клиент бэкенда.
| Адаптер | Клиент бэкенда | Модель хранения |
|---|---|---|
S3SessionStore |
@aws-sdk/client-s3 |
Один файл части JSONL на append(); load() перечисляет, сортирует и объединяет. |
RedisSessionStore |
ioredis |
Список RPUSH/LRANGE на стенограмму плюс индекс отсортированного набора сеансов. |
PostgresSessionStore |
pg |
Одна строка на запись в таблице jsonb, упорядоченная по BIGSERIAL. |
Каждый адаптер принимает предварительно настроенный экземпляр клиента, поэтому вы контролируете учетные данные, TLS, регион и пулинг. Например, с 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" },
})) {
// ...
}
Проверьте ваш адаптер
Оба SDK поставляются с набором соответствия, который утверждает поведенческий контракт, который должны удовлетворять append, load и необязательные методы. Тесты для необязательных методов автоматически пропускаются, когда эти методы не реализованы.
В TypeScript скопируйте shared/conformance.ts из директории примеров в ваш набор тестов. В Python набор поставляется в пакете. Чтобы запустить его с pytest, который не является зависимостью SDK, сначала установите pytest:
pip install pytest
Затем передайте ваш адаптер в набор в файле теста как фабрику без аргументов, которую run_session_store_conformance вызывает один раз для каждого контракта, чтобы построить свежее хранилище:
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)
Передача самого класса MyRedisStore, как в этом примере, работает, когда конструктор не принимает аргументы. Для адаптера, который принимает предварительно настроенный клиент, передайте вместо этого лямбду, которая конструирует хранилище. Поскольку контракты повторно используют одни и те же ключи сеансов, каждое хранилище, которое возвращает фабрика, должно начинаться с пустого хранилища, поэтому пусть лямбда предоставляет изолированное резервное хранилище для каждого вызова, такое как свежий поддельный объект в памяти, уникальный префикс ключа или новую тестовую базу данных.
Примечания о поведении
Архитектура двойной записи
Подпроцесс Claude Code всегда сначала записывает каждый пакет записей стенограммы на локальный диск, а затем SDK пересылает тот же пакет в append() вашего хранилища, поэтому хранилище является зеркалом локальной стенограммы, а не её заменой. Какая копия пережит выполнение, зависит от того, как было запущено выполнение:
- Новый сеанс или возобновление, когда хранилище не содержит ничего для сеанса: локальная стенограмма в вашей директории конфигурации пережит выполнение, и хранилище получит копию.
- Выполнение возобновлено из хранилища: локальная копия удаляется в конце выполнения, поэтому хранилище содержит единственную надёжную копию.
Если вы не хотите, чтобы новый сеанс оставлял стенограмму на локальном диске, установите CLAUDE_CONFIG_DIR на временную директорию в options.env. Выполнение, возобновленное из хранилища, уже удаляет свою локальную копию, поэтому ему не требуется такая настройка. В TypeScript распространите process.env в env также, поскольку опция env заменяет окружение подпроцесса.
Если ваше приложение входит через файлы в директории конфигурации, такие как учётные данные OAuth или apiKeyHelper в вашем пользовательском settings.json, сначала скопируйте эти файлы во временную директорию, или установите ANTHROPIC_API_KEY в env вместо этого. В противном случае выполнение завершится с ошибкой Not logged in.
Две опции конфликтуют с зеркалом, и SDK выбрасывает исключение при запуске, если вы объедините любую из них с хранилищем:
persistSession: falseв TypeScript: отключает локальные записи, на которых построено зеркало. Python SDK не имеет эквивалентной опции.- Контрольные точки файлов,
enableFileCheckpointingв TypeScript илиenable_file_checkpointingв Python: записывает резервные копии файлов прямо на локальный диск, и SDK не зеркалирует их в хранилище.
Возобновление из хранилища
Когда вы передаёте resume, или continue: true в TypeScript или continue_conversation=True в Python, вместе с хранилищем, SDK запрашивает у хранилища стенограмму перед тем, как порождает подпроцесс:
resume: SDK запрашивает сеанс, чей ID вы передали.continue: trueилиcontinue_conversation=True: SDK запрашивает самый новый сеанс хранилища.
Когда хранилище возвращает стенограмму, SDK записывает её во временную директорию конфигурации, запускает подпроцесс с CLAUDE_CONFIG_DIR, указывающим туда, и удаляет директорию по окончании выполнения. Локальная стенограмма, которую записывает это выполнение, удаляется вместе с ней, поэтому хранилище содержит единственную надёжную копию на этом пути.
SDK также заполняет временную директорию файлами из вашей реальной директории конфигурации. Что копируется, отличается по языкам:
- TypeScript: учётные данные,
.claude.jsonи ваш пользовательскийsettings.json. Изsettings.jsonон удаляет ключи, которые ведут себя неправильно во временной директории конфигурации:enabledPlugins,extraKnownMarketplaces, его aliasadditionalMarketplacesи любойCLAUDE_CONFIG_DIRв блокеenvфайла. До Agent SDK v0.3.232 SDK не удалял alias. Аутентификация, настроенная в settings, такая какapiKeyHelper, работает, когда вы возобновляете из хранилища. До Agent SDK v0.3.222 TypeScript SDK копировал только учётные данные и.claude.json. - Python: только учётные данные и
.claude.json, поэтому приложение, которое аутентифицируется черезapiKeyHelperв вашем пользовательскомsettings.json, завершается с ошибкойNot logged inпри возобновлении из хранилища.apiKeyHelperв управляемых или проектных settings всё ещё работает, потому что Claude Code читает эти файлы из местоположений, на которые не влияетCLAUDE_CONFIG_DIR.
Когда хранилище не содержит ничего для сеанса, SDK запускается в вашей реальной директории конфигурации вместо этого, и результат зависит от того, какую опцию вы передали:
resume: оба SDK передают ID через подпроцесс, который возобновляет локальную стенограмму точно так же, какresumeбез хранилища.continue: trueв TypeScript: SDK запускает новый сеанс.continue_conversation=Trueв Python: SDK продолжает с самого нового локального сеанса.
Зеркальные записи — это лучшие усилия
Если append() отклоняет, SDK повторяет попытку пакета ещё два раза с коротким отступом, всего максимум три попытки. Вызов, который истекает по времени, не повторяется, поскольку исходный вызов может всё ещё приземлиться. Если пакет всё ещё не удаётся, SDK регистрирует ошибку, выдаёт сообщение { type: "system", subtype: "mirror_error" } в итератор, отбрасывает пакет и продолжает запрос. Поскольку повторный пакет может повторно доставить записи, которые уже приземлились, дедублируйте по entry.uuid в вашей реализации append().
Сбой хранилища не прерывает агента, поскольку подпроцесс записывает локально в первую очередь. Отслеживайте mirror_error, если вам нужно обнаружить потерю данных хранилища. При выполнении возобновленном из хранилища, отброшенный пакет не имеет выжившей копии по окончании выполнения.
`getSessionMessages` возвращает цепь после компактирования
getSessionMessages({ sessionStore }) возвращает связанную цепь сообщений, которую агент видел бы при возобновлении. После автоматического компактирования более ранние ходы заменяются резюме, поэтому сеанс, чье хранилище содержит 503 необработанные записи, может возвращать 18 сообщений из getSessionMessages. Для полной необработанной истории, включая ходы до компактирования и записи метаданных, вызовите store.load(key) напрямую.
`forkSession` — это не побайтовая копия
forkSession({ sessionStore }) читает исходные записи, переписывает каждое поле sessionId и переназначает UUID сообщений, затем добавляет преобразованные записи под новым ключом. Копия на уровне адаптера или ярлык CopyObject создали бы стенограмму, которая все еще ссылается на старый ID сеанса, поэтому SDK не использует один.
Стенограммы подагентов
Стенограммы подагентов зеркалируются под subpath: "subagents/agent-<id>". listSubagents({ sessionStore }) требует, чтобы адаптер реализовал listSubkeys; getSubagentMessages({ sessionStore }) использует его, когда доступно, но возвращается к прямому подпути, когда он не определен. Возобновление также вызывает listSubkeys для восстановления файлов подагентов; без него материализуется только основная стенограмма.
Хранение
SDK никогда не удаляет из вашего хранилища самостоятельно. Хранение — это ответственность адаптера: реализуйте TTL, политики жизненного цикла S3 или запланированную очистку в соответствии с вашими требованиями соответствия.
Локальные стенограммы в CLAUDE_CONFIG_DIR очищаются независимо параметром cleanupPeriodDays, следуя правилам очистки при сохранении. Выполнение возобновленное из хранилища не оставляет локальную стенограмму, поэтому для этих выполнений хранение вашего хранилища — это единственное хранение, которое существует.
Поддерживается на
Следующие функции TypeScript SDK принимают опцию sessionStore и работают с хранилищем вместо локальной файловой системы, когда она предоставляется:
query()startup()listSessions()getSessionInfo()getSessionMessages()renameSession()tagSession()deleteSession()forkSession()listSubagents()getSubagentMessages()
В Python SDK установите session_store в ClaudeAgentOptions для запуска query() против хранилища. Остальные операции имеют каждая функцию с поддержкой хранилища на Python, которая принимает хранилище в качестве аргумента: 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(), читают локальные файлы сеансов.
Связанные ресурсы
- Работа с сеансами: Продолжение, возобновление и разветвление без пользовательского хранилища
- Размещение SDK: Шаблоны развертывания для сред с несколькими хостами
- TypeScript
Options: Полная справка по опциям examples/session-stores/: Запускаемые эталонные адаптеры S3, Redis и Postgres