~/.claude/projects/ 下的 JSONL 文件。SessionStore 适配器让您可以将这些记录镜像到您自己的后端,例如对象存储、键值存储或数据库,这样在一个主机上创建的会话可以在另一个主机上恢复,只要工作目录相同。
使用会话存储的常见原因:
- 多主机部署。 无服务器函数、自动扩展的工作进程和 CI 运行器不共享文件系统。共享存储让副本可以恢复彼此的会话。
- 持久性。 本地容器是临时的。外部存储可以在重启和重新部署后继续存在。
- 合规性和审计。 将记录保存在您已经管理的存储中,使用您自己的保留规则、加密和访问控制。
SessionStore 接口
SessionStore 是一个对象,具有两个必需的方法 append 和 load,以及四个可选方法。SDK 调用 append 在查询期间写入记录条目,调用 load 读取它们以便恢复。
SessionKey 寻址一个记录。projectKey 是工作目录的稳定的、文件系统安全的编码,sessionId 是会话 UUID,subpath 在条目属于子代理记录或边车文件而不是主对话时设置。
因为 projectKey 编码了工作目录,请从与原始运行的工作目录匹配的工作目录中从存储恢复或继续。在 TypeScript 中,如果在查询的 env 选项中在 CLAUDE_CONFIG_DIR 旁边设置 CLAUDE_CODE_PROJECT_DIR_NAME,SDK 会按该名称对该查询的条目以及其 resume 和 continue 查找进行键控。因为 listSessions 和 deleteSession 等独立帮助程序不接受 env 并读取进程环境,也要在主机进程环境中设置 CLAUDE_CONFIG_DIR 和相同的名称。需要 Agent SDK v0.3.234 或更高版本。
将 subpath 视为不透明的密钥后缀;它遵循磁盘上的布局,例如 subagents/agent-<id>。当 subpath 未定义时,密钥指的是主记录。
在
SessionSummaryEntry 中,mtime 是边车的存储写入时间,必须与 listSessions 返回的 mtime 值共享一个时钟源。data 是不透明的 SDK 拥有的状态;按原样持久化它,不要解释它。
通过在 append 中的每个批次上调用导出的 foldSessionSummary 帮助程序(Python 中为 fold_session_summary)来构建条目。跳过其密钥具有 subpath 的批次;子代理记录不得对主会话的摘要做出贡献。折叠永远不会设置 mtime:在持久化时通过 TypeScript 中的 options.mtime 参数或通过在 Python 中覆盖返回条目上的字段来标记它。同一会话的并发 append 调用可能在边车上竞争,因此使用事务、比较交换或每个会话的锁来序列化读取-折叠-写入;折叠本身是纯的。
有关 SDK 对记录 load 返回的处理,请参阅从存储恢复。
快速开始
SDK 附带一个InMemorySessionStore 用于开发和测试。下面的示例使用附加的存储运行查询,从结果消息中捕获会话 ID,然后在第二个 query() 调用中从存储恢复。第二个调用传递相同的存储实例加上 resume,因此 SDK 从存储而不是本地文件系统加载记录:
编写您自己的适配器
针对您的后端实现append 和 load。如果您希望 listSessions()、一次调用元数据读取、deleteSession() 和子代理恢复针对存储工作,请添加 listSessions、listSessionSummaries、delete 和 listSubkeys。
传递给 append 的条目类型为 SessionStoreEntry(一个 { type: string; ... } 对象)。将它们视为不透明的 JSON 安全值:按顺序持久化它们,并从 load 以相同的顺序返回它们。load 必须返回与追加的条目深度相等的条目;不需要字节相等的序列化,因此像重新排序对象键的二进制 JSON 列类型这样的后端是可以的。
参考实现
两个 SDK 存储库在 TypeScript 的examples/session-stores/ 和 Python 的 examples/session_stores/ 下都包含可运行的参考适配器。每种存储类型都有一个适配器,每个都展示了 append 和 load 如何映射到该类型的后端。它们不作为包发布;将最接近您后端的类型的适配器复制到您的项目中,安装您后端的客户端,并进行调整。
每个适配器都采用预配置的客户端实例,因此您可以控制凭证、TLS、区域和池。以下示例将对象存储适配器连接到
query() 中,然后在另一台主机上从中恢复:
TypeScript
验证您的适配器
两个 SDK 都附带一个一致性套件,该套件断言append、load 和可选方法必须满足的行为契约。当未实现这些方法时,可选方法的测试会自动跳过。
在 TypeScript 中,从示例目录将 shared/conformance.ts 复制到您的测试套件中。在 Python 中,该套件在包中提供。要使用 pytest 运行它(pytest 不是 SDK 依赖项),请先安装 pytest:
run_session_store_conformance 会为每个契约调用一次以构建一个新的存储:
Python
MyRedisStore 类本身,当构造函数不带参数时有效。对于采用预配置客户端的适配器,改为传递构造存储的 lambda。由于契约重用相同的会话密钥,工厂返回的每个存储必须以空存储开始,因此让 lambda 为每次调用配置隔离的后端存储,例如新的内存中伪造、唯一的密钥前缀或新的测试数据库。
行为说明
双写架构
Claude Code 子进程始终首先将每批转录条目写入本地磁盘,然后 SDK 将同一批转发到您的存储的append(),因此存储是本地转录的镜像而不是替代品。哪个副本在运行后保留取决于运行的启动方式:
- 新会话,或当存储对该会话没有任何内容时的恢复:您配置目录下的本地转录在运行后保留,存储接收一个副本。
- 从存储恢复的运行:本地副本在运行结束时被删除,因此存储持有唯一的持久副本。
options.env 中将 CLAUDE_CONFIG_DIR 设置为临时目录。从存储恢复的运行已经删除其本地副本,因此不需要此类设置。在 TypeScript 中,也将 process.env 展开到 env 中,因为 env 选项 替换子进程环境。
如果您的应用通过配置目录中的文件进行登录,例如 OAuth 凭证或用户 settings.json 中的 apiKeyHelper,请先将这些文件复制到临时目录中,或改为在 env 中设置 ANTHROPIC_API_KEY。否则运行失败并显示 Not logged in。
两个选项与镜像冲突,如果您将其中任何一个与存储结合,SDK 会在启动时抛出异常:
persistSession: false在 TypeScript 中:关闭镜像所基于的本地写入。Python SDK 没有等效选项。- 文件检查点,TypeScript 中的
enableFileCheckpointing或 Python 中的enable_file_checkpointing:将其文件备份直接写入本地磁盘,SDK 不会将其镜像到存储。
从存储恢复
当您传递resume,或 TypeScript 中的 continue: true 或 Python 中的 continue_conversation=True,以及存储时,SDK 在生成子进程之前向存储请求转录:
resume:SDK 请求您传递的会话 ID 的会话。continue: true或continue_conversation=True:SDK 请求存储的最新会话。
CLAUDE_CONFIG_DIR 指向该目录,并在运行结束时删除该目录。该运行写入的本地转录随之被删除,这就是为什么存储在此路径上持有唯一的持久副本的原因。
SDK 还使用来自您真实配置目录的文件为临时目录提供种子。它复制的内容因语言而异:
- TypeScript:凭证、
.claude.json和您的用户settings.json。从settings.json中,它删除在临时配置目录下表现不佳的密钥:enabledPlugins、extraKnownMarketplaces、其additionalMarketplaces别名,以及文件env块中的任何CLAUDE_CONFIG_DIR。在 Agent SDK v0.3.232 之前,SDK 没有删除别名。在设置中配置的身份验证,例如apiKeyHelper,在从存储恢复时有效。在 Agent SDK v0.3.222 之前,TypeScript SDK 仅复制凭证和.claude.json。 - Python:仅凭证和
.claude.json,因此通过用户settings.json中的apiKeyHelper进行身份验证的应用在从存储恢复时失败并显示Not logged in。托管或项目设置中的apiKeyHelper仍然有效,因为 Claude Code 从CLAUDE_CONFIG_DIR不影响的位置读取这些文件。
resume:两个 SDK 都将 ID 传递给子进程,子进程恢复本地转录,完全如同没有存储的resume一样。- TypeScript 中的
continue: true:SDK 启动新会话。 - Python 中的
continue_conversation=True:SDK 从最新的本地会话继续。
镜像写入是尽力而为的
如果append() 拒绝,SDK 会以短退避重试该批次最多两次,总共最多三次尝试。超时的调用不会重试,因为原始调用可能仍然会成功。如果批次仍然失败,SDK 记录错误,向迭代器发出 { type: "system", subtype: "mirror_error" } 消息,丢弃批次,并继续查询。因为重试的批次可以重新传递已经成功的条目,请在您的 append() 实现中按 entry.uuid 进行去重。
存储中断不会中断代理,因为子进程首先在本地写入。如果您需要检测存储数据丢失,请监视 mirror_error。在从存储恢复的运行上,丢弃的批次在运行结束后没有幸存的副本。
getSessionMessages 返回后压缩链
getSessionMessages({ sessionStore }) 返回代理在恢复时会看到的链接消息链。自动压缩后,早期的轮次被摘要替换,因此存储中包含 503 个原始条目的会话可能从 getSessionMessages 返回 18 条消息。对于完整的原始历史记录,包括压缩前的轮次和元数据条目,直接调用 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()
ClaudeAgentOptions 中设置 session_store 以针对存储运行 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:完整的选项参考 - 参考实现:对象存储、键值存储和数据库的可运行示例适配器,在两个 SDK 存储库中