Theo mặc định, SDK ghi session transcript vào file JSONL dưới ~/.claude/projects/ trên filesystem local. Một adapter SessionStore cho phép bạn mirror các transcript đó sang backend riêng, như S3, Redis, hoặc một database, để một session tạo trên host này có thể resume trên host khác.
Lý do phổ biến để dùng session store:
- Deployment multi-host. Serverless function, worker autoscale, và CI runner không chia sẻ filesystem. Một store dùng chung cho phép bất kỳ replica nào resume bất kỳ session nào.
- Độ bền. Container local là ephemeral. Store backed bởi S3 hoặc database sống sót qua restart và redeploy.
- Tuân thủ và audit. Giữ transcript trong storage bạn đã có sẵn quyền quản trị, với quy tắc retention, mã hoá, và kiểm soát truy cập riêng.
Interface SessionStore
Phần tiêu đề “Interface SessionStore”Một SessionStore là một object với hai method bắt buộc, append và load, và bốn method tuỳ chọn. SDK gọi append để ghi transcript entry trong lúc query và load để đọc lại chúng cho resume.
// Export từ @anthropic-ai/claude-agent-sdk dưới dạng// SessionStore, SessionKey, SessionStoreEntry, SessionSummaryEntry.
type SessionKey = { projectKey: string; sessionId: string; subpath?: string;};
type SessionStore = { // Bắt buộc append(key: SessionKey, entries: SessionStoreEntry[]): Promise<void>; load(key: SessionKey): Promise<SessionStoreEntry[] | null>;
// Tuỳ chọn 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>;};# Export từ claude_agent_sdk dưới dạng# SessionStore, SessionKey, SessionStoreEntry, SessionSummaryEntry.
class SessionKey(TypedDict): project_key: str session_id: str subpath: NotRequired[str]
class SessionStore(Protocol): # Bắt buộc async def append( self, key: SessionKey, entries: list[SessionStoreEntry] ) -> None: ... async def load(self, key: SessionKey) -> list[SessionStoreEntry] | None: ...
# Tuỳ chọn - bỏ qua hoặc 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 xác định một transcript. projectKey là bản encode ổn định, an toàn cho filesystem của thư mục làm việc, sessionId là UUID session, và subpath được đặt khi entry thuộc về transcript subagent hoặc sidecar file thay vì hội thoại chính. Coi subpath là một hậu tố key mờ (opaque); nó theo layout trên đĩa, ví dụ subagents/agent-<id>. Khi subpath là undefined, key trỏ tới transcript chính.
| Method | Bắt buộc | Được gọi khi |
|---|---|---|
append | Có | Sau mỗi batch transcript entry được ghi local. Entry là object JSON-safe, mỗi dòng một entry trong JSONL local. |
load | Có | Trước khi subprocess spawn khi resume được đặt, và một lần mỗi session khi listing fallback từ listSessionSummaries. Trả về null nếu session không xác định. |
listSessions | Không | Bởi listSessions({ sessionStore }) và bởi query()/startup() với continue: true. Nếu undefined, continue: true throw lỗi, và listSessions({ sessionStore }) throw lỗi trừ khi listSessionSummaries được implement. |
listSessionSummaries | Không | Bởi listSessions({ sessionStore }) để đọc metadata cho mọi session trong một lời gọi. Duy trì summary bên trong append. Nếu undefined, listing fallback về listSessions cộng với load per-session. |
delete | Không | Bởi deleteSession({ sessionStore }). Xoá key chính (không có subpath) phải lan (cascade) tới mọi subkey của session đó và cũng xoá entry summary của session, để một session đã xoá không còn xuất hiện trong listSessionSummaries. Nếu undefined, xoá là no-op, phù hợp với backend append-only. |
listSubkeys | Không | Trong lúc resume, để phát hiện transcript subagent. Nếu undefined, chỉ transcript chính được khôi phục. |
Trong một SessionSummaryEntry, mtime là thời gian ghi storage của sidecar và phải dùng chung clock source với các giá trị mtime mà listSessions trả về. data là trạng thái opaque do SDK sở hữu; lưu nó nguyên vẹn mà không diễn giải.
Xây dựng entry bằng cách gọi helper export foldSessionSummary, fold_session_summary trong Python, trên mỗi batch bên trong append. Bỏ qua batch có key với subpath; transcript subagent không được đóng góp vào summary của session chính. Fold không bao giờ đặt mtime: dán nhãn nó tại thời điểm persist, qua tham số options.mtime trong TypeScript hoặc ghi đè trường trên entry trả về trong Python. Các lời gọi append đồng thời cho cùng session có thể race trên sidecar, nên serialize read-fold-write bằng transaction, compare-and-swap, hoặc lock per-session; bản thân fold là pure.
Bắt đầu nhanh
Phần tiêu đề “Bắt đầu nhanh”SDK đóng gói sẵn InMemorySessionStore cho phát triển và test. Ví dụ dưới chạy một query với store được gắn vào, lấy session ID từ result message, rồi resume từ store trong lời gọi query() thứ hai. Lời gọi thứ hai truyền cùng instance store cộng với resume, nên SDK load transcript từ store thay vì filesystem 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) { // Một lời gọi query() single-shot throw lỗi sau khi yield result lỗi. Nếu // lỗi đến từ result lỗi, sessionId đã được lưu bởi vòng lặp trên; // lỗi kết nối hoặc process không yield result message nào cả. console.error(`Session ended with an error: ${error}`);}
// Resume từ store. Agent có đầy đủ context từ lời gọi đầu.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 asynciofrom 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: # Một lời gọi query() single-shot raise lỗi sau khi yield result lỗi. Nếu # lỗi đến từ result lỗi, session_id đã được lưu bởi vòng lặp trên; # lỗi kết nối hoặc process không yield result message nào cả. print(f"Session ended with an error: {error}")
# Resume từ store. Agent có đầy đủ context từ lời gọi đầu. 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())Query thứ hai in ra bản tóm tắt của các file từ query đầu, cho thấy agent đã resume với đầy đủ context từ store.
Viết adapter của riêng bạn
Phần tiêu đề “Viết adapter của riêng bạn”Implement append và load đối với backend của bạn. Thêm listSessions, listSessionSummaries, delete, và listSubkeys nếu bạn muốn listSessions(), đọc metadata một lời gọi, deleteSession(), và resume subagent hoạt động với store.
Entry truyền vào append được đánh kiểu SessionStoreEntry (một object { type: string; ... }). Coi chúng là giá trị JSON-safe mờ (opaque): lưu chúng theo đúng thứ tự và trả về chúng từ load theo cùng thứ tự đó. load phải trả về entry deep-equal với những gì đã append; serialization byte-equal không bắt buộc, nên các backend như Postgres jsonb sắp xếp lại key object vẫn ổn.
Reference implementation
Phần tiêu đề “Reference implementation”Repository TypeScript SDK có sẵn adapter tham khảo chạy được cho S3, Redis, và Postgres dưới examples/session-stores/. Chúng không được publish lên npm; copy file src/ bạn cần vào project của bạn và cài client backend tương ứng.
| Adapter | Backend client | Mô hình lưu trữ |
|---|---|---|
S3SessionStore | @aws-sdk/client-s3 | Một file part JSONL mỗi append(); load() list, sort, và concat. |
RedisSessionStore | ioredis | List RPUSH/LRANGE mỗi transcript, cộng với một sorted-set session index. |
PostgresSessionStore | pg | Một dòng mỗi entry trong bảng jsonb, sắp theo BIGSERIAL. |
Mỗi adapter nhận một client instance đã cấu hình sẵn, nên bạn kiểm soát credential, TLS, region, và pooling. Ví dụ, với S3:
import { query } from "@anthropic-ai/claude-agent-sdk";import { S3Client } from "@aws-sdk/client-s3";import { S3SessionStore } from "./S3SessionStore"; // copy từ 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); }}
// Sau này, có thể trên một host khác:for await (const message of query({ prompt: "Continue where we left off", options: { sessionStore: store, resume: "previous-session-id" },})) { // ...}Validate adapter của bạn
Phần tiêu đề “Validate adapter của bạn”Cả hai SDK đóng gói một conformance suite khẳng định hợp đồng hành vi mà append, load, và các method tuỳ chọn phải thoả mãn. Test cho method tuỳ chọn tự bỏ qua khi các method đó không được implement.
Trong TypeScript, copy shared/conformance.ts từ thư mục example vào test suite của bạn. Trong Python, suite đóng gói sẵn trong package. Để chạy với pytest, thứ không phải dependency của SDK, cài pytest trước:
pip install pytestRồi truyền adapter của bạn vào suite trong một file test dưới dạng factory không tham số, mà run_session_store_conformance gọi một lần mỗi hợp đồng để xây một store mới:
import pytestfrom claude_agent_sdk.testing import run_session_store_conformance
@pytest.mark.anyioasync def test_my_store_conformance(): await run_session_store_conformance(MyRedisStore)Truyền chính class MyRedisStore, như ví dụ này làm, hoạt động khi constructor không nhận tham số. Với adapter nhận một client đã cấu hình sẵn, truyền một lambda tự xây store thay vào đó. Vì các hợp đồng tái sử dụng cùng session key, mỗi store mà factory trả về phải bắt đầu với storage rỗng, nên hãy để lambda cấp một backing storage cô lập mỗi lần gọi, như một fake in-memory mới, một key prefix riêng, hoặc một test database mới.
Ghi chú hành vi
Phần tiêu đề “Ghi chú hành vi”Kiến trúc dual-write
Phần tiêu đề “Kiến trúc dual-write”Store là một bản mirror, không phải bản thay thế. Subprocess Claude Code luôn ghi vào đĩa local trước; SDK sau đó forward mỗi batch tới append(). Nếu bạn muốn bản copy local là ephemeral, đặt CLAUDE_CONFIG_DIR trỏ tới một thư mục tạm trong options.env.
Vì bản mirror phụ thuộc vào ghi local, TypeScript SDK throw lỗi nếu bạn kết hợp sessionStore với persistSession: false. Cả hai SDK cũng throw lỗi nếu bạn kết hợp store với file checkpointing, enableFileCheckpointing trong TypeScript hoặc enable_file_checkpointing trong Python, vì blob backup file-history được ghi trực tiếp vào đĩa local và không được mirror sang store.
Ghi mirror là best-effort
Phần tiêu đề “Ghi mirror là best-effort”Nếu append() bị reject, SDK retry batch tối đa hai lần nữa với backoff ngắn, tối đa ba lần thử tổng cộng. Một lời gọi timeout thì không được retry, vì lời gọi gốc có thể vẫn thành công. Nếu batch vẫn thất bại, lỗi được log lại, một message { type: "system", subtype: "mirror_error" } được phát vào iterator, batch bị bỏ, và query tiếp tục. Transcript local đã bền vững trên đĩa, nên một store bị outage không làm gián đoạn agent hay mất dữ liệu local. Theo dõi mirror_error nếu bạn cần phát hiện mất dữ liệu store. Vì một batch bị retry có thể gửi lại các entry đã có sẵn, hãy khử trùng lặp theo entry.uuid trong implementation append() của bạn.
getSessionMessages trả về chain sau compaction
Phần tiêu đề “getSessionMessages trả về chain sau compaction”getSessionMessages({ sessionStore }) trả về chain message được liên kết mà agent sẽ thấy khi resume. Sau auto-compaction, các turn trước đó bị thay bằng một summary, nên một session mà store lưu 503 raw entry có thể trả về 18 message từ getSessionMessages. Để lấy lịch sử thô đầy đủ, bao gồm cả turn trước-compaction và metadata entry, gọi store.load(key) trực tiếp.
forkSession không phải copy byte-cho-byte
Phần tiêu đề “forkSession không phải copy byte-cho-byte”forkSession({ sessionStore }) đọc entry nguồn, viết lại mọi trường sessionId và remap message UUID, rồi append các entry đã biến đổi dưới một key mới. Một bản copy cấp adapter hay shortcut CopyObject sẽ tạo ra một transcript vẫn tham chiếu session ID cũ, nên SDK không dùng cách đó.
Transcript subagent
Phần tiêu đề “Transcript subagent”Transcript subagent được mirror dưới subpath: "subagents/agent-<id>". listSubagents({ sessionStore }) cần adapter implement listSubkeys; getSubagentMessages({ sessionStore }) dùng nó khi khả dụng nhưng fallback về subpath trực tiếp khi nó undefined. Resume cũng gọi listSubkeys để khôi phục file subagent; không có nó, chỉ transcript chính được materialize.
Retention
Phần tiêu đề “Retention”SDK không bao giờ tự xoá khỏi store của bạn. Retention là trách nhiệm của adapter: implement TTL, S3 lifecycle policy, hoặc cleanup theo lịch tuỳ theo yêu cầu tuân thủ của bạn. Transcript local dưới CLAUDE_CONFIG_DIR được quét dọn độc lập bởi setting cleanupPeriodDays.
Hỗ trợ trên
Phần tiêu đề “Hỗ trợ trên”Các hàm TypeScript SDK sau nhận option sessionStore và vận hành dựa trên store thay vì filesystem local khi nó được cung cấp:
query()startup()listSessions()getSessionInfo()getSessionMessages()renameSession()tagSession()deleteSession()forkSession()listSubagents()getSubagentMessages()
Trong Python SDK, đặt session_store trong ClaudeAgentOptions để chạy query() dựa trên store. Các thao tác còn lại mỗi cái có một hàm Python backed-bởi-store riêng nhận store làm tham số: 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(), và fork_session_via_store(). startup() không có tương đương trong Python. Các hàm standalone được tài liệu hoá trong Python SDK reference, như list_sessions(), đọc file session local.
Tài nguyên liên quan
Phần tiêu đề “Tài nguyên liên quan”- Làm việc với session: Continue, resume, và fork mà không cần store tuỳ biến
- Host SDK: Mẫu triển khai cho môi trường multi-host
- TypeScript
Options: Tham khảo option đầy đủ examples/session-stores/: Adapter tham khảo S3, Redis, và Postgres chạy được
lượt xem