پایدارسازیِ نشستها در ذخیرهسازیِ بیرونی
بهصورت پیشفرض، SDK رونوشتهای نشست (session transcripts) را در فایلهای JSONL زیرِ ~/.claude/projects/ روی فایلسیستمِ محلی مینویسد. یک آداپتورِ SessionStore به تو اجازه میدهد آن رونوشتها را به backendِ خودت — مثلِ S3، Redis یا یک دیتابیس — آینه (mirror) کنی، تا نشستی که روی یک میزبان ساخته شده روی میزبانی دیگر از سر گرفته شود.
دلایلِ رایج برای استفاده از session store:
- استقرارهای چندمیزبانه. توابعِ serverless، workerهای خودمقیاسشونده و runnerهای CI فایلسیستم را به اشتراک نمیگذارند. یک storeِ مشترک به هر replica اجازه میدهد هر نشستی را از سر بگیرد.
- ماندگاری. کانتینرهای محلی زودگذرند. یک storeِ پشتیبانیشده با S3 یا یک دیتابیس از ریاستارتها و استقرارهای مجدد جانِ سالم به در میبرد.
- انطباق و ممیزی. رونوشتها را در ذخیرهسازیای که از قبل کنترلش میکنی نگه دار، با قواعدِ نگهداریِ خودت، رمزنگاری و کنترلهای دسترسی.
واسطِ SessionStore
Section titled “واسطِ SessionStore”یک SessionStore آبجکتی است با دو متدِ الزامی، append و load، و سه متدِ اختیاری. SDK، append را برای نوشتنِ ورودیهای رونوشت در حینِ یک query و load را برای بازخوانیِ آنها هنگامِ از سرگیری فرا میخواند.
// Exported from @anthropic-ai/claude-agent-sdk as// SessionStore, SessionKey, SessionStoreEntry.
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 }>>; delete?(key: SessionKey): Promise<void>; listSubkeys?(key: { projectKey: string; sessionId: string; }): Promise<string[]>;};# Exported from claude_agent_sdk as# SessionStore, SessionKey, SessionStoreEntry.
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 delete(self, key: SessionKey) -> None: ... async def list_subkeys(self, key: SessionListSubkeysKey) -> list[str]: ...SessionKey به یک رونوشت اشاره میکند. projectKey یک کدگذاریِ پایدار و امن برای فایلسیستم از دایرکتوریِ کاری است، sessionId همان UUID نشست است، و subpath وقتی تنظیم میشود که ورودی به یک رونوشتِ سابایجنت یا فایلِ کناری (sidecar) تعلق داشته باشد، نه به مکالمهی اصلی. با subpath مثلِ یک پسوندِ کلیدِ مبهم رفتار کن؛ از چیدمانِ رویدیسک پیروی میکند، مثلاً subagents/agent-<id>. وقتی subpath تعریفنشده باشد، کلید به رونوشتِ اصلی اشاره میکند.
| متد | الزامی | کِی فرا خوانده میشود |
|---|---|---|
append | بله | پس از هر دسته از ورودیهای رونوشت که محلی نوشته میشود. ورودیها آبجکتهای امن برای JSON هستند، هر کدام یک خط در JSONL محلی. |
load | بله | یکبار پیش از spawn شدنِ زیرفرایند، وقتی resume تنظیم شده باشد. اگر نشست ناشناخته است null برگردان. |
listSessions | خیر | توسطِ listSessions({ sessionStore }) و توسطِ query()/startup() با continue: true. اگر تعریفنشده باشد، آن فراخوانیها خطا میدهند. |
delete | خیر | توسطِ deleteSession({ sessionStore }). حذفِ کلیدِ اصلی (بدونِ subpath) باید بهصورتِ آبشاری همهی subkeyهای آن نشست را حذف کند. اگر تعریفنشده باشد، حذف یک no-op است، که به backendهای فقطافزایشی میخورد. |
listSubkeys | خیر | در حینِ از سرگیری، برای کشفِ رونوشتهای سابایجنت. اگر تعریفنشده باشد، فقط رونوشتِ اصلی بازیابی میشود. |
شروعِ سریع
Section titled “شروعِ سریع”SDK یک InMemorySessionStore برای توسعه و تست همراه دارد. مثالِ زیر یک query را با storeِ متصل اجرا میکند، session ID را از پیامِ نتیجه میگیرد، سپس در یک فراخوانیِ دومِ query() نشست را از store از سر میگیرد. فراخوانیِ دوم همان نمونهی store را بهعلاوهی resume پاس میدهد، پس SDK رونوشت را بهجای فایلسیستمِ محلی از store بارگذاری میکند:
import { query, InMemorySessionStore } from "@anthropic-ai/claude-agent-sdk";
const store = new InMemorySessionStore();
let sessionId: string | undefined;for await (const message of query({ prompt: "List the TypeScript files under src/", options: { sessionStore: store },})) { if (message.type === "result") { sessionId = message.session_id; }}
// 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 asynciofrom claude_agent_sdk import ( ClaudeAgentOptions, InMemorySessionStore, ResultMessage, query,)
store = InMemorySessionStore()
async def main(): session_id = None 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
# 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())query دوم خلاصهای از فایلهای query اول را چاپ میکند، که نشان میدهد ایجنت با کانتکستِ کامل از store از سر گرفته شد.
آداپتورِ خودت را بنویس
Section titled “آداپتورِ خودت را بنویس”append و load را در برابرِ backendت پیادهسازی کن. اگر میخواهی listSessions()، deleteSession() و از سرگیریِ سابایجنت در برابرِ store کار کنند، listSessions، delete و listSubkeys را اضافه کن.
ورودیهایی که به append پاس داده میشوند از نوعِ SessionStoreEntry هستند (یک آبجکتِ { type: string; ... }). با آنها مثلِ مقادیرِ مبهمِ امن برای JSON رفتار کن: آنها را به ترتیب پایدار کن و از load به همان ترتیب برگردان. load باید ورودیهایی برگرداند که با آنچه append شده deep-equal باشند؛ سریالسازیِ byte-equal لازم نیست، پس backendهایی مثلِ jsonb در Postgres که کلیدهای آبجکت را بازچینش میکنند مشکلی ندارند.
پیادهسازیهای مرجع
Section titled “پیادهسازیهای مرجع”مخزنِ SDKِ TypeScript آداپتورهای مرجعِ قابلاجرا برای S3، Redis و Postgres را زیرِ examples/session-stores/ دارد. اینها روی npm منتشر نشدهاند؛ فایلِ src/ موردِ نیازت را در پروژهات کپی کن و کلاینتِ backendِ متناظر را نصب کن.
| آداپتور | کلاینتِ backend | مدلِ ذخیرهسازی |
|---|---|---|
S3SessionStore | @aws-sdk/client-s3 | یک فایلِ JSONL به ازای هر append()؛ load() فهرست، مرتب و الحاق میکند. |
RedisSessionStore | ioredis | یک لیستِ RPUSH/LRANGE به ازای هر رونوشت، بهعلاوهی یک ایندکسِ نشستِ sorted-set. |
PostgresSessionStore | pg | یک ردیف به ازای هر ورودی در یک جدولِ jsonb، مرتبشده با BIGSERIAL. |
هر آداپتور یک نمونهی کلاینتِ از پیشپیکربندیشده میگیرد، پس تو اعتبارنامهها، TLS، region و pooling را کنترل میکنی. مثلاً با 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" },})) { // ...}آداپتورت را اعتبارسنجی کن
Section titled “آداپتورت را اعتبارسنجی کن”هر دو SDK یک مجموعهی conformance همراه دارند که قراردادِ رفتاریای را که append، load و متدهای اختیاری باید برآورده کنند تأیید میکند. تستهای متدهای اختیاری وقتی آن متدها پیادهسازی نشده باشند بهصورت خودکار رد (skip) میشوند.
در TypeScript، shared/conformance.ts را از دایرکتوریِ مثال در مجموعهتستت کپی کن. در Python، این مجموعه درونِ بسته میآید:
import pytestfrom claude_agent_sdk.testing import run_session_store_conformance
@pytest.mark.asyncioasync def test_my_store_conformance(): await run_session_store_conformance(MyRedisStore)نکاتِ رفتاری
Section titled “نکاتِ رفتاری”معماریِ نوشتنِ دوگانه
Section titled “معماریِ نوشتنِ دوگانه”store یک آینه است، نه جایگزین. زیرفرایندِ Claude Code همیشه اول روی دیسکِ محلی مینویسد؛ سپس SDK هر دسته را به append() فوروارد میکند. اگر میخواهی نسخهی محلی زودگذر باشد، CLAUDE_CONFIG_DIR را در options.env به یک دایرکتوریِ موقت اشاره بده. چون آینه به نوشتنهای محلی وابسته است، sessionStore را نمیتوان با persistSession: false ترکیب کرد؛ اگر هر دو را تنظیم کنی SDK خطا میدهد. همچنین اگر با enableFileCheckpointing ترکیب شود خطا میدهد، چون blobهای پشتیبانِ تاریخچهی فایل مستقیماً روی دیسکِ محلی نوشته میشوند و به store آینه نمیشوند.
نوشتنهای آینه best-effort هستند
Section titled “نوشتنهای آینه best-effort هستند”اگر append() رد شود یا تایماوت بخورد، خطا لاگ میشود، یک پیامِ { type: "system", subtype: "mirror_error" } به iterator فرستاده میشود و query ادامه پیدا میکند. رونوشتِ محلی از قبل روی دیسک ماندگار است، پس یک قطعیِ store ایجنت را قطع نمیکند یا داده را محلی از دست نمیدهد. دستههایی که شکست میخورند دوباره تلاش نمیشوند، پس اگر باید از دست رفتنِ دادهی store را تشخیص دهی، mirror_error را پایش کن.
getSessionMessages زنجیرهی پس از فشردهسازی را برمیگرداند
Section titled “getSessionMessages زنجیرهی پس از فشردهسازی را برمیگرداند”getSessionMessages({ sessionStore }) زنجیرهی پیامِ پیوندیای را برمیگرداند که ایجنت هنگامِ از سرگیری میبیند. پس از فشردهسازیِ خودکار (auto-compaction)، نوبتهای قبلی با یک خلاصه جایگزین میشوند، پس نشستی که storeش ۵۰۳ ورودیِ خام دارد ممکن است از getSessionMessages ۱۸ پیام برگرداند. برای تاریخچهی خامِ کامل — از جمله نوبتهای پیش از فشردهسازی و ورودیهای متادیتا — مستقیماً store.load(key) را فرا بخوان.
forkSession یک کپیِ byte به byte نیست
Section titled “forkSession یک کپیِ byte به byte نیست”forkSession({ sessionStore }) ورودیهای منبع را میخواند، هر فیلدِ sessionId را بازنویسی و UUIDهای پیام را remap میکند، سپس ورودیهای تبدیلشده را زیرِ یک کلیدِ جدید append میکند. یک کپی در سطحِ آداپتور یا میانبرِ CopyObject، رونوشتی تولید میکرد که هنوز به session IDِ قدیمی ارجاع میداد، پس SDK از آن استفاده نمیکند.
رونوشتهای سابایجنت
Section titled “رونوشتهای سابایجنت”رونوشتهای سابایجنت زیرِ subpath: "subagents/agent-<id>" آینه میشوند. listSubagents({ sessionStore }) ایجاب میکند که آداپتور listSubkeys را پیادهسازی کند؛ getSubagentMessages({ sessionStore }) در صورتِ موجود بودن از آن استفاده میکند ولی وقتی تعریفنشده باشد به subpathِ مستقیم برمیگردد. از سرگیری هم listSubkeys را برای بازیابیِ فایلهای سابایجنت فرا میخواند؛ بدونِ آن، فقط رونوشتِ اصلی materialize میشود.
نگهداری
Section titled “نگهداری”SDK هرگز خودش از store تو چیزی حذف نمیکند. نگهداری مسئولیتِ آداپتور است: TTLها، سیاستهای lifecycle برای S3، یا پاکسازیِ زمانبندیشده را بر اساسِ الزاماتِ انطباقت پیادهسازی کن. رونوشتهای محلیِ زیرِ CLAUDE_CONFIG_DIR بهصورت مستقل توسطِ تنظیمِ cleanupPeriodDays جارو میشوند.
پشتیبانیشده روی
Section titled “پشتیبانیشده روی”توابعِ SDK زیر گزینهی sessionStore را میپذیرند و وقتی فراهم شده باشد، بهجای فایلسیستمِ محلی در برابرِ store عمل میکنند:
query()startup()listSessions()getSessionInfo()getSessionMessages()renameSession()tagSession()deleteSession()forkSession()listSubagents()getSubagentMessages()
منابعِ مرتبط
Section titled “منابعِ مرتبط”- کار با نشستها: ادامه، از سرگیری و fork بدونِ یک storeِ سفارشی
- Hostٰ کردنِ SDK: الگوهای استقرار برای محیطهای چندمیزبانه
Optionsدر TypeScript: مرجعِ کاملِ گزینههاexamples/session-stores/: آداپتورهای مرجعِ قابلاجرای S3، Redis و Postgres