מדריך קלוד קוד בעברית

תיעוד 173

שמירת סשנים באחסון חיצוני

שקף תמלילי סשן של Agent SDK לחנות אובייקטים, לחנות מפתח-ערך או למסד נתונים משלך, כדי שמארחים אחרים יוכלו לחדש את הסשנים שלך.

כברירת מחדל, ה-SDK כותב תמלילי סשן לקבצי JSONL תחת ~/.claude/projects/ במערכת הקבצים המקומית. מתאם SessionStore מאפשר לשקף את התמלילים האלה ל-backend משלך, כגון חנות אובייקטים, חנות מפתח-ערך או מסד נתונים, כדי שסשן שנוצר על מארח אחד יוכל להתחדש על מארח אחר שרץ מתיקיית עבודה תואמת.

סיבות נפוצות להשתמש בחנות סשנים:

  • פריסות מרובות מארחים. פונקציות serverless, workers בהתרחבות אוטומטית ורצי CI אינם חולקים מערכת קבצים. חנות משותפת מאפשרת לרפליקות לחדש זו את הסשנים של זו.
  • עמידות. קונטיינרים מקומיים הם ארעיים. חנות חיצונית שורדת הפעלות מחדש ופריסות מחדש.
  • ציות וביקורת. שמור תמלילים באחסון שכבר בשליטתך, עם כללי שמירה, הצפנה ובקרות גישה משלך.

#ממשק SessionStore

SessionStore הוא אובייקט עם שתי מתודות חובה, append ו-load, וארבע מתודות אופציונליות. ה-SDK קורא ל-append כדי לכתוב רשומות תמליל במהלך query, ול-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 מוגדר כשהרשומה שייכת לתמליל של subagent או לקובץ sidecar ולא לשיחה הראשית.

מכיוון ש-projectKey מקודד את תיקיית העבודה, חדש או המשך מהחנות מתיקיית עבודה שתואמת לזו של הריצה המקורית. ב-TypeScript, אם מגדירים את CLAUDE_CODE_PROJECT_DIR_NAME לצד CLAUDE_CONFIG_DIR באפשרות env של query, ה-SDK ממפתח את רשומות ה-query הזה, ואת חיפושי resume ו-continue שלו, לפי השם הזה במקום. מכיוון שעוזרים עצמאיים כמו listSessions ו-deleteSession אינם מקבלים env וקוראים את סביבת התהליך, הגדר גם את CLAUDE_CONFIG_DIR ואת אותו שם בסביבת תהליך המארח. נדרש Agent SDK גרסה v0.3.234 או חדשה יותר.

התייחס ל-subpath כסיומת מפתח אטומה. הוא עוקב אחרי הפריסה על הדיסק, לדוגמה subagents/agent-<id>. כש-subpath הוא undefined המפתח מתייחס לתמליל הראשי.

מתודהחובהמתי נקראת
appendכןאחרי שכל אצווה של רשומות תמליל נכתבת מקומית. הרשומות הן אובייקטים בטוחים ל-JSON, אחת לכל שורה ב-JSONL המקומי.
loadכןלפני שה-subprocess נוצר כש-resume מוגדר או כש-continue: true מאתר את סשן החנות החדש ביותר, ופעם אחת לכל סשן כשהרישום נופל חזרה מ-listSessionSummaries. החזר null אם הסשן אינו מוכר.
listSessionsלאעל ידי listSessions({ sessionStore }) ועל ידי query()/startup() עם continue: true. אם המתודה undefined, continue: true זורק חריגה, ו-listSessions({ sessionStore }) זורק חריגה אלא אם listSessionSummaries ממומש.
listSessionSummariesלאעל ידי listSessions({ sessionStore }) כדי לקרוא מטא-דאטה של כל הסשנים בקריאה אחת. תחזק את הסיכומים בתוך append. אם המתודה undefined, הרישום נופל חזרה אל listSessions ובנוסף load לכל סשן.
deleteלאעל ידי deleteSession({ sessionStore }). מחיקת המפתח הראשי (בלי subpath) חייבת לבצע cascade לכל ה-subkeys של הסשן, וגם להסיר את רשומת הסיכום של הסשן, כדי שסשן שנמחק יפסיק להופיע ב-listSessionSummaries. אם המתודה undefined, המחיקה היא no-op, מה שמתאים ל-backends שהם append-only.
listSubkeysלאבמהלך חידוש, כדי לגלות תמלילי subagent. אם המתודה undefined, רק התמליל הראשי משוחזר.

ב-SessionSummaryEntry, mtime הוא זמן הכתיבה של ה-sidecar לאחסון, וחייב לחלוק מקור שעון עם ערכי mtime ש-listSessions מחזיר. data הוא מצב אטום בבעלות ה-SDK. שמור אותו כלשונו בלי לפרש אותו.

בנה את הרשומות על ידי קריאה לעוזר המיוצא foldSessionSummary, וב-Python fold_session_summary, על כל אצווה בתוך append. דלג על אצוות שלמפתח שלהן יש subpath. אסור שתמלילי subagent יתרמו לסיכום של הסשן הראשי. ה-fold אף פעם לא מגדיר mtime: הטבע אותו בזמן השמירה, דרך הארגומנט options.mtime ב-TypeScript או על ידי דריסת השדה ברשומה המוחזרת ב-Python. קריאות append מקבילות לאותו סשן יכולות להיכנס למרוץ על ה-sidecar, אז סדר את הרצף read-fold-write עם טרנזקציה, compare-and-swap, או נעילה לכל סשן. ה-fold עצמו טהור.

למה שה-SDK עושה עם התמליל ש-load מחזיר, ראה חידוש מהחנות.

#התחלה מהירה

ה-SDK מגיע עם InMemorySessionStore לפיתוח ולבדיקות. הדוגמה למטה מריצה query עם החנות מצורפת, לוכדת את מזהה הסשן מהודעת התוצאה, ואז מחדשת מהחנות בקריאת 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())

ה-query השני מדפיס סיכום של הקבצים מה-query הראשון, מה שמראה שהסוכן חודש עם הקשר מלא מהחנות.

#כתוב מתאם משלך

ממש את append ואת load מול ה-backend שלך. הוסף את listSessions, listSessionSummaries, delete ו-listSubkeys אם אתה רוצה ש-listSessions(), קריאות מטא-דאטה בקריאה אחת, deleteSession() וחידוש subagent יעבדו מול החנות.

רשומות שמועברות ל-append מטיפוס SessionStoreEntry (אובייקט { type: string; ... }). התייחס אליהן כערכים אטומים שבטוחים ל-JSON: שמור אותן לפי הסדר והחזר אותן מ-load באותו סדר. load חייב להחזיר רשומות ששוות בעומק (deep-equal) למה שצורף. סריאליזציה שווה בבתים אינה נדרשת, כך ש-backend שמסדר מחדש מפתחות אובייקט, כגון סוג עמודה של JSON בינארי, מתאים.

#מימושי ייחוס

שני מאגרי ה-SDK כוללים מתאמי ייחוס שניתן להריץ תחת examples/session-stores/ ב-TypeScript ו-examples/session_stores/ ב-Python. יש מתאם אחד לכל סוג אחסון, וכל אחד מראה איך append ו-load ממופים לסוג ה-backend הזה. הם אינם מתפרסמים כחבילות. העתק את המתאם של הסוג הקרוב ביותר ל-backend שלך אל הפרויקט, התקן את הלקוח של ה-backend, והתאם אותו.

סוג אחסוןמודל אחסוןמתאם לדוגמה
חנות אובייקטיםקובץ חלק אחד לכל append(). load() מציג את החלקים ברשימה, ממיין אותם ומשרשר.S3 (TypeScript, Python)
חנות מפתח-ערךרשימה אחת לכל תמליל, ש-append() דוחף אליה ו-load() קורא בטווח, ועוד אינדקס ממוין של סשנים.Redis (TypeScript, Python)
מסד נתונים יחסי או חנות מסמכיםשורה או מסמך אחד לכל רשומה, שמורים כ-JSON ומסודרים לפי מפתח שמוקצה בהכנסה.Postgres (TypeScript, Python)

כל מתאם מקבל מופע לקוח שהוגדר מראש, כך שאתה שולט בפרטי הזדהות, ב-TLS, באזור וב-pooling. הדוגמה הבאה מחברת את מתאם חנות האובייקטים אל query() ואז מחדשת ממנו על מארח אחר:

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 מגיעים עם חבילת conformance שמוודאת את החוזה ההתנהגותי ש-append, load והמתודות האופציונליות חייבים לקיים. בדיקות למתודות אופציונליות מדלגות אוטומטית כשהמתודות האלה אינן ממומשות.

ב-TypeScript, העתק את shared/conformance.ts מתיקיית הדוגמה אל חבילת הבדיקות שלך. ב-Python, החבילה מגיעה בתוך החבילה. כדי להריץ אותה עם pytest, שאינו תלות של ה-SDK, התקן תחילה את pytest:

pip install pytest

אחר כך העבר את המתאם שלך לחבילה בקובץ בדיקה כ-factory ללא ארגומנטים, ש-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 עצמה, כפי שהדוגמה הזו עושה, עובדת כשהבנאי אינו מקבל ארגומנטים. למתאם שמקבל לקוח שהוגדר מראש, העבר lambda שבונה את החנות. מכיוון שהחוזים עושים שימוש חוזר באותם מפתחות סשן, כל חנות שה-factory מחזיר חייבת להתחיל באחסון ריק, ולכן ה-lambda צריך להקצות אחסון גיבוי מבודד בכל קריאה, כגון fake חדש בזיכרון, קידומת מפתח ייחודית, או מסד נתונים חדש לבדיקות.

#הערות התנהגות

#ארכיטקטורת כתיבה כפולה

תת-התהליך של Claude Code תמיד כותב כל אצווה של רשומות תמליל לדיסק המקומי קודם, ואז ה-SDK מעביר את אותה אצווה אל append() של המאגר שלך, כך שהמאגר הוא מראה של התמליל המקומי ולא תחליף לו. איזה עותק שורד אחרי הריצה תלוי באופן שבו הריצה התחילה:

  • הפעלה חדשה, או חידוש כשלמאגר אין דבר עבור ההפעלה: התמליל המקומי תחת ספריית התצורה שלך שורד אחרי הריצה, והמאגר מקבל עותק.
  • ריצה שחודשה מהמאגר: העותק המקומי נמחק בסוף הריצה, כך שהמאגר מחזיק את העותק העמיד היחיד.

אם אינך רוצה שהפעלה חדשה תשאיר תמליל על הדיסק המקומי, הגדר את CLAUDE_CONFIG_DIR לספרייה זמנית חדשה ופרטית ב-options.env. ריצה שחודשה מהמאגר כבר מוחקת את העותק המקומי שלה, ולכן אינה זקוקה להגדרה כזו. ב-TypeScript, פרוס גם את process.env אל תוך env, מפני שאפשרות env מחליפה את סביבת תת-התהליך.

אם תגדיר את CLAUDE_CONFIG_DIR לספרייה זמנית ותתחבר באמצעות התחברות claude.ai או apiKeyHelper ב-settings.json של המשתמש שלך, הריצה נכשלת עם Not logged in. כדי לאמת את הריצה, הגדר אסימון ארוך טווח, הגדר מפתח API, או העתק את קובצי התצורה שלך:

  • אסימון ארוך טווח: אם אתה מריץ את הסוכן בעצמך תחת מנוי Claude שלך, הגדר את CLAUDE_CODE_OAUTH_TOKEN ב-env לאסימון מ-claude setup-token. אפשר לבצע עם האסימון הזה רק בקשות מודל, כך שהמחברים של claude.ai אינם זמינים בריצה הזו.
  • מפתח API: הגדר את ANTHROPIC_API_KEY ב-env.
  • קובצי תצורה: אם אתה מתחבר דרך קבצים בספריית התצורה הרגילה שלך, כגון אישורי OAuth או apiKeyHelper ב-settings.json של המשתמש שלך, העתק אותם אל הספרייה הזמנית.

שתי אפשרויות מתנגשות עם המראה, וה-SDK זורק בשלב ההפעלה אם משלבים אחת מהן עם מאגר:

  • persistSession: false ב-TypeScript: מכבה את הכתיבות המקומיות שהמראה נבנית מהן. ל-SDK של Python אין אפשרות מקבילה.
  • נקודות ביקורת של קבצים, enableFileCheckpointing ב-TypeScript או enable_file_checkpointing ב-Python: כותב את גיבויי הקבצים שלו ישירות לדיסק המקומי, וה-SDK אינו משקף אותם למאגר.

#חידוש מהמאגר

כשאתה מעביר resume, או continue: true ב-TypeScript או continue_conversation=True ב-Python, יחד עם מאגר, ה-SDK מבקש מהמאגר תמליל לפני שהוא מפעיל את תת-התהליך:

  • resume: ה-SDK מבקש את ההפעלה שהמזהה שלה הועבר.
  • continue: true או continue_conversation=True: ה-SDK מבקש את ההפעלה החדשה ביותר במאגר.

כשהמאגר מחזיר את התמליל, ה-SDK כותב אותו לספריית תצורה זמנית, מריץ את תת-התהליך עם CLAUDE_CONFIG_DIR שמצביע לשם, ומוחק את הספרייה כשהריצה מסתיימת. התמליל המקומי שהריצה הזו כותבת נמחק יחד איתה, ולכן המאגר מחזיק את העותק העמיד היחיד במסלול הזה.

ה-SDK גם ממלא את הספרייה הזמנית בקבצים מספריית התצורה האמיתית שלך. מה שהוא מעתיק משתנה לפי שפה:

  • TypeScript: אישורים, .claude.json, ו-settings.json של המשתמש שלך. מתוך settings.json הוא מסיר את המפתחות שמתנהגים בצורה שגויה תחת ספריית תצורה זמנית: enabledPlugins, extraKnownMarketplaces, הכינוי additionalMarketplaces שלו, וכל CLAUDE_CONFIG_DIR בבלוק env של הקובץ. לפני Agent SDK v0.3.232, ה-SDK לא הסיר את הכינוי. אימות שמוגדר בהגדרות, כגון apiKeyHelper, עובד כשמחדשים מהמאגר. לפני Agent SDK v0.3.222, ה-SDK של TypeScript העתיק רק אישורים ו-.claude.json.
  • Python: רק אישורים ו-.claude.json, ולכן אפליקציה שמאמתת דרך apiKeyHelper ב-settings.json של המשתמש שלך נכשלת עם Not logged in כשמחדשים ממאגר. apiKeyHelper בהגדרות מנוהלות או בהגדרות פרויקט עדיין עובד, מפני ש-Claude Code קורא את הקבצים האלה ממיקומים ש-CLAUDE_CONFIG_DIR אינו משפיע עליהם.

כשלמאגר אין דבר עבור ההפעלה, ה-SDK רץ תחת ספריית התצורה האמיתית שלך, והתוצאה תלויה באיזו אפשרות העברת:

  • resume: שני ה-SDK מעבירים את המזהה לתת-התהליך, שמחדש את התמליל המקומי בדיוק כפי ש-resume עושה בלי מאגר.
  • continue: true ב-TypeScript: ה-SDK מתחיל הפעלה חדשה.
  • continue_conversation=True ב-Python: ה-SDK ממשיך מההפעלה המקומית החדשה ביותר.

#כתיבות המראה הן best-effort

אם 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 היו מייצרים תמליל שעדיין מפנה למזהה ההפעלה הישן, ולכן ה-SDK אינו משתמש בדרך כזו.

#תמלילי תת-סוכנים

תמלילי תת-סוכנים משוקפים תחת subpath: "subagents/agent-<id>". listSubagents({ sessionStore }) דורש שהמתאם יממש את listSubkeys. getSubagentMessages({ sessionStore }) משתמש בו כשהוא זמין, אך חוזר לנתיב המשנה הישיר כשהוא undefined. גם החידוש קורא ל-listSubkeys כדי לשחזר קבצי תת-סוכן. בלעדיו, רק התמליל הראשי ממומש.

#שימור

ה-SDK לעולם אינו מוחק מהמאגר שלך בעצמו. השימור הוא באחריות המתאם: השתמש במנגנון התפוגה או מחזור החיים של ה-backend שלך, או הרץ ניקוי מתוזמן, בהתאם לדרישות הציות שלך.

תמלילים מקומיים תחת CLAUDE_CONFIG_DIR נסרקים באופן עצמאי על ידי ההגדרה cleanupPeriodDays, לפי כללי סריקת השימור. ריצה שחודשה מהמאגר אינה משאירה תמליל מקומי, כך שעבור הריצות האלה שימור המאגר שלך הוא השימור היחיד שקיים.

#נתמך ב

הפונקציות הבאות של ה-SDK של TypeScript מקבלות אפשרות sessionStore ופועלות מול המאגר במקום מול מערכת הקבצים המקומית כשהיא מסופקת:

ב-SDK של Python, הגדר את 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(), קוראות קובצי הפעלה מקומיים.

#משאבים קשורים