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

תיעוד 131

עבודה עם sessions

כיצד sessions שומרים את היסטוריית השיחה של הסוכן, ומתי להשתמש ב-continue, ב-resume וב-fork כדי לחזור להרצה קודמת.

session הוא היסטוריית השיחה שה-SDK צובר בזמן שהסוכן שלכם עובד. הוא מכיל את ההנחיה (prompt) שלכם, כל קריאה לכלי שהסוכן ביצע, כל תוצאת כלי וכל תגובה. ה-SDK כותב אותו לדיסק באופן אוטומטי כדי שתוכלו לחזור אליו מאוחר יותר.

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

הערה: sessions שומרים את השיחה, לא את מערכת הקבצים. כדי ליצור תמונת מצב (snapshot) ולבטל שינויים בקבצים שהסוכן ביצע, השתמשו ב-file checkpointing.

מדריך זה מסביר כיצד לבחור את הגישה הנכונה ליישום שלכם, את ממשקי ה-SDK שעוקבים אחרי sessions באופן אוטומטי, כיצד ללכוד מזהי session ולהשתמש ב-resume וב-fork באופן ידני, ומה כדאי לדעת על חידוש sessions בין מארחים (hosts).

#בחירת גישה

היקף הטיפול ב-session הדרוש לכם תלוי במבנה היישום שלכם. ניהול session נכנס לתמונה כאשר אתם שולחים מספר הנחיות שאמורות לחלוק הקשר משותף. בתוך קריאת query() בודדת, הסוכן כבר לוקח כמה תורות (turns) שהוא צריך, ובקשות הרשאה ו-AskUserQuestion מטופלות בתוך הלולאה (הן אינן מסיימות את הקריאה).

מה אתם בוניםבמה להשתמש
משימה חד-פעמית: הנחיה בודדת, ללא המשךשום דבר נוסף. קריאת query() אחת מטפלת בזה.
שיחה מרובת תורות בתהליך יחידClaudeSDKClient (ב-Python) או continue: true (ב-TypeScript). ה-SDK עוקב אחר ה-session עבורכם ללא ניהול מזהים.
המשך מהמקום שבו עצרתם לאחר הפעלה מחדש של תהליךcontinue_conversation=True (ב-Python) / continue: true (ב-TypeScript). מחדש את ה-session העדכני ביותר בספריה, ללא צורך במזהה.
חידוש session מסוים מהעבר (לא העדכני ביותר)לכדו את ה-session ID והעבירו אותו אל resume.
ניסיון גישה חלופית מבלי לאבד את המקורבצעו fork ל-session.
משימה ללא מצב (Stateless), כשאינכם רוצים שדבר ייכתב לדיסקהגדירו persistSession: false (ב-TypeScript בלבד). ה-session קיים בזיכרון בלבד למשך הקריאה. ב-Python, הגדירו את CLAUDE_CODE_SKIP_PROMPT_HISTORY באפשרות ה-env כדי למנוע כתיבת תמלילים (transcripts) במקום זאת.

#Continue, resume ו-fork

Continue, resume ו-fork הם שדות אפשרויות שאתם מגדירים ב-query() (ClaudeAgentOptions ב-Python, Options ב-TypeScript).

גם continue וגם resume ממשיכים session קיים ומוסיפים אליו. ההבדל הוא באופן שבו הם מוצאים את אותו session:

  • Continue מוצא את ה-session העדכני ביותר בספריה הנוכחית. אינכם עוקבים אחר שום דבר. פועל היטב כאשר היישום שלכם מריץ שיחה אחת בכל פעם.
  • Resume מקבל מזהה session ID ספציפי. אתם עוקבים אחר המזהה. נדרש כאשר יש לכם מספר sessions (למשל, אחד לכל משתמש ביישום מרובה משתמשים) או כאשר אתם רוצים לחזור לאחד שאינו העדכני ביותר.

Fork שונה: הוא יוצר session חדש שמתחיל עם עותק של היסטוריית המקור. המקור נשאר ללא שינוי. השתמשו ב-fork כדי לנסות כיוון אחר תוך שמירה על האפשרות לחזור לאחור.

#ניהול sessions אוטומטי

שני ה-SDKs מציעים ממשק שעוקב אחר מצב ה-session עבורכם בין קריאות, כך שאינכם צריכים להעביר מזהים ידנית. השתמשו בהם עבור שיחות מרובות תורות בתוך תהליך יחיד.

#Python: ClaudeSDKClient

ClaudeSDKClient מטפל במזהי session ID באופן פנימי. כל קריאה ל-client.query() ממשיכה אוטומטית את אותו session. קראו ל-client.receive_response() כדי לבצע איטרציה על ההודעות עבור השאילתה הנוכחית. השתמשו בלקוח כ-async context manager כך שהקמת החיבור וסגירתו יטופלו עבורכם, או קראו ל-connect() ול-disconnect() ידנית.

דוגמה זו מריצה שתי שאילתות מול אותו client. הראשונה מבקשת מהסוכן לנתח מודול, והשנייה מבקשת ממנו לבצע refactoring לאותו מודול. מכיוון ששתי הקריאות עוברות דרך אותו מופע לקוח, לשאילתה השנייה יש הקשר מלא מהראשונה ללא resume מפורש או session ID:

import asyncio
from claude_agent_sdk import (
    ClaudeSDKClient,
    ClaudeAgentOptions,
    AssistantMessage,
    ResultMessage,
    TextBlock,
)


def print_response(message):
    """Print only the human-readable parts of a message."""
    if isinstance(message, AssistantMessage):
        for block in message.content:
            if isinstance(block, TextBlock):
                print(block.text)
    elif isinstance(message, ResultMessage):
        cost = (
            f"${message.total_cost_usd:.4f}"
            if message.total_cost_usd is not None
            else "N/A"
        )
        print(f"[done: {message.subtype}, cost: {cost}]")


async def main():
    options = ClaudeAgentOptions(
        allowed_tools=["Read", "Edit", "Glob", "Grep"],
    )

    async with ClaudeSDKClient(options=options) as client:
        
# First query: client captures the session ID internally
        await client.query("Analyze the auth module")
        async for message in client.receive_response():
            print_response(message)

        
# Second query: automatically continues the same session
        await client.query("Now refactor it to use JWT")
        async for message in client.receive_response():
            print_response(message)


asyncio.run(main())

כל שאילתה מדפיסה את תגובת הטקסט של הסוכן ואחריה שורת סטטוס מתוך הודעת התוצאה, כגון [done: success, cost: $0.0042].

עיינו ב-Python SDK reference לפרטים על מתי להשתמש ב-ClaudeSDKClient לעומת פונקציית query() העצמאית.

#TypeScript: continue: true

ל-TypeScript SDK אין אובייקט לקוח שמחזיק session כמו ClaudeSDKClient של Python. במקום זאת, העבירו continue: true בכל קריאת query() עוקבת וה-SDK ימשיך את ה-session העדכני ביותר בספריה הנוכחית. אין צורך במעקב אחר מזהה.

דוגמה זו מבצעת שתי קריאות query() נפרדות. הראשונה יוצרת session חדש, והשנייה מגדירה continue: true, מה שמורה ל-SDK למצוא ולחדש את ה-session העדכני ביותר בדיסק. לסוכן יש הקשר מלא מהקריאה הראשונה:

import { query } from "@anthropic-ai/claude-agent-sdk";

// First query: creates a new session
try {
  for await (const message of query({
    prompt: "Analyze the auth module",
    options: { allowedTools: ["Read", "Glob", "Grep"] }
  })) {
    if (message.type === "result" && message.subtype === "success") {
      console.log(message.result);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result,
  // so the follow-up query below still runs.
  console.error(`Session ended with an error: ${error}`);
}

// Second query: continue: true resumes the most recent session
for await (const message of query({
  prompt: "Now refactor it to use JWT",
  options: {
    continue: true,
    allowedTools: ["Read", "Edit", "Write", "Glob", "Grep"]
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

הערה: ממשק ה-API הניסיוני V2 session API, שסיפק את createSession() עם תבנית של send / stream, הוסר ב-TypeScript Agent SDK גרסה 0.3.142. השתמשו בפונקציה query() ובאפשרויות ה-session המתוארות בדף זה במקום זאת.

#שימוש באפשרויות session עם query()

#לכידת ה-session ID

פעולות resume ו-fork דורשות session ID. קראו אותו מתוך השדה session_id בהודעת התוצאה (ResultMessage ב-Python, SDKResultMessage ב-TypeScript), אשר קיים בכל תוצאה ללא תלות בהצלחה או בשגיאה. ב-TypeScript המזהה זמין גם מוקדם יותר כשדה ישיר בהודעת SystemMessage של ה-init, וב-Python הוא מקונן בתוך SystemMessage.data.

#Python

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage


async def main():
    session_id = None

    try:
        async for message in query(
            prompt="Analyze the auth module and suggest improvements",
            options=ClaudeAgentOptions(
                allowed_tools=["Read", "Glob", "Grep"],
            ),
        ):
            if isinstance(message, ResultMessage):
                session_id = message.session_id
                if message.subtype == "success":
                    print(message.result)
    except Exception as error:
        
# A single-shot query() raises after yielding an error result. If the
        
# failure was an error result, the loop above already captured session_id;
        
# connection or process failures yield no result message, so session_id stays None.
        print(f"Session ended with an error: {error}")

    print(f"Session ID: {session_id}")
    return session_id


session_id = asyncio.run(main())

#TypeScript

import { query } from "@anthropic-ai/claude-agent-sdk";

let sessionId: string | undefined;

try {
  for await (const message of query({
    prompt: "Analyze the auth module and suggest improvements",
    options: { allowedTools: ["Read", "Glob", "Grep"] }
  })) {
    if (message.type === "result") {
      sessionId = message.session_id;
      if (message.subtype === "success") {
        console.log(message.result);
      }
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, the loop above already captured sessionId;
  // connection or process failures yield no result message, so sessionId stays undefined.
  console.error(`Session ended with an error: ${error}`);
}

console.log(`Session ID: ${sessionId}`);

כאשר השאילתה מסתיימת, הסקריפט מדפיס את תגובת הסוכן ואחריה שורה כגון Session ID: 5b3f2c1a-8d4e-4f6b-9a7c-2e1d0f9b8a6c. בסעיפים הבאים תעבירו מזהה זה ל-resume.

#חידוש לפי מזהה (Resume by ID)

העבירו session ID אל resume כדי לחזור לאותו session ספציפי. הסוכן ממשיך עם הקשר מלא מהמקום שבו ה-session עצר. סיבות נפוצות לחידוש:

  • מעקב אחר משימה שהושלמה. הסוכן כבר ניתח משהו, וכעת אתם רוצים שהוא יפעל על סמך אותו ניתוח מבלי לקרוא מחדש קבצים.
  • התאוששות ממגבלה. ההרצה הראשונה הסתיימה עם error_max_turns או error_max_budget_usd (ראו Handle the result), חדשו עם מגבלה גבוהה יותר. בקריאת query() חד-פעמית ה-SDK זורק שגיאה לאחר הפקת תוצאת שגיאה זו, לכן תפסו את השגיאה לפני החידוש.
  • הפעלה מחדש של התהליך שלכם. לכדתם את המזהה לפני הכיבוי ואתם רוצים לשחזר את השיחה.

דוגמה זו מחדשת את ה-session מ-לכידת ה-session ID באמצעות הנחיית המשך. מכיוון שאתם מחדשים, לסוכן כבר יש את הניתוח הקודם בהקשר:

#Python

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

session_id = "..."  
# The ID you captured in the previous example


async def main():
    
# Earlier session analyzed the code; now build on that analysis
    async for message in query(
        prompt="Now implement the refactoring you suggested",
        options=ClaudeAgentOptions(
            resume=session_id,
            allowed_tools=["Read", "Edit", "Write", "Glob", "Grep"],
        ),
    ):
        if isinstance(message, ResultMessage) and message.subtype == "success":
            print(message.result)


asyncio.run(main())

#TypeScript

import { query } from "@anthropic-ai/claude-agent-sdk";

const sessionId = "..."; // The ID you captured in the previous example

// Earlier session analyzed the code; now build on that analysis
for await (const message of query({
  prompt: "Now implement the refactoring you suggested",
  options: {
    resume: sessionId,
    allowedTools: ["Read", "Edit", "Write", "Glob", "Grep"]
  }
})) {
  if (message.type === "result" && message.subtype === "success") {
    console.log(message.result);
  }
}

אתם אמורים לראות תגובה שמתבססת על הניתוח המוקדם יותר במקום להתחיל מחדש. זה מאשר שהסוכן חידש את ה-session כאשר ההקשר הקודם שלו נותר שלם.

טיפ: Claude Code שומר sessions תחת ~/.claude/projects/<encoded-cwd>/*.jsonl. אם הגדרתם את משתנה הסביבה CLAUDE_CONFIG_DIR, חפשו תחת $CLAUDE_CONFIG_DIR/projects/ במקום זאת.

כדי למצוא את ספריית ה-session שלכם, החליפו כל תו שאינו אלפאנומרי בנתיב המוחלט של ספריית העבודה בתו -: הנתיב /Users/me/proj הופך ל--Users-me-proj. עבור ספריית עבודה שהשם המומר שלה עולה על 200 תווים, Claude Code מקצר את השם ומוסיף גיבוב (hash), לכן התאימו את 200 התווים הראשונים של השם המומר בעת רישום תכולת projects/.

אם הגדרתם את CLAUDE_CODE_PROJECT_DIR_NAME לצד CLAUDE_CONFIG_DIR, חפשו תחת אותו שם ב-projects/ במקום זאת. דורש את TypeScript Agent SDK בגרסה v0.3.234 ומעלה, או את Python Agent SDK בגרסה v0.2.140 ומעלה.

אתם יכולים לחדש מכל ספריית עבודה:

  • חיפוש בין ספריות (Cross-directory lookup): Claude Code מחפש מעבר לספריית הפרויקט הנוכחית כדי למצוא את המזהה, ראו Resume a session עבור סדר החיפוש המדויק וכיצד מטופלים עותקים כפולים.
  • אותה מכונה בלבד: קובץ ה-session עדיין חייב להתקיים במכונה הנוכחית.

לפני גרסה v2.1.223, החיפוש הוגבל לספריית הפרויקט הנוכחית ול-git worktrees שלה, וגרסאות SDK שכוללות CLI ישן יותר עדיין מתנהגות כך.

כדי לחדש sessions בין מכונות או בסביבות serverless, בצעו שיקוף (mirror) של תמלילים לאחסון משותף באמצעות מתאם SessionStore.

#ביצוע Fork כדי לבחון חלופות

ביצוע fork יוצר session חדש שמתחיל עם עותק של היסטוריית המקור אך מתפצל מאותה נקודה. ה-fork מקבל session ID משלו, בעוד שהמזהה וההיסטוריה של המקור נשארים ללא שינוי. בסופו של דבר יש לכם שני sessions עצמאיים שתוכלו לחדש בנפרד.

הערה: ביצוע fork מפצל את היסטוריית השיחה, לא את מערכת הקבצים. אם סוכן מפוצל עורך קבצים, השינויים הללו אמיתיים וגלויים לכל session שעובד באותה ספריה. כדי לפצל ולבטל שינויים בקבצים, השתמשו ב-file checkpointing.

דוגמה זו מתבססת על לכידת ה-session ID: כבר ניתחתם מודול אימות ב-session_id ואתם רוצים לבחון את OAuth2 מבלי לאבד את השרשור שהתמקד ב-JWT. הבלוק הראשון מבצע fork ל-session ולוכד את מזהה ה-fork (בשם forked_id), והבלוק השני מחדש את ה-session_id המקורי כדי להמשיך במסלול ה-JWT. כעת יש לכם שני מזהי session שמצביעים על שתי היסטוריות נפרדות:

#Python

import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions, ResultMessage

session_id = "..."  
# The ID you captured in the previous example


async def main():
    
# Fork: branch from session_id into a new session
    forked_id = None
    try:
        async for message in query(
            prompt="Instead of JWT, outline how OAuth2 would work for the auth module",
            options=ClaudeAgentOptions(
                resume=session_id,
                fork_session=True,
                max_turns=5,
            ),
        ):
            if isinstance(message, ResultMessage):
                forked_id = message.session_id  
# The fork's ID, distinct from session_id
                if message.subtype == "success":
                    print(message.result)
    except Exception as error:
        
# A single-shot query() raises after yielding an error result. If the
        
# failure was an error result, forked_id was already captured by the
        
# loop above; connection or process failures yield no result message.
        print(f"Session ended with an error: {error}")

    print(f"Forked session: {forked_id}")

    
# Original session is untouched; resuming it continues the JWT thread
    try:
        async for message in query(
            prompt="Continue with the JWT approach",
            options=ClaudeAgentOptions(resume=session_id),
        ):
            if isinstance(message, ResultMessage) and message.subtype == "success":
                print(message.result)
    except Exception as error:
        
# A single-shot query() raises after yielding an error result.
        print(f"Session ended with an error: {error}")


asyncio.run(main())

#TypeScript

import { query } from "@anthropic-ai/claude-agent-sdk";

const sessionId = "..."; // The ID you captured in the previous example

// Fork: branch from sessionId into a new session
let forkedId: string | undefined;

try {
  for await (const message of query({
    prompt: "Instead of JWT, outline how OAuth2 would work for the auth module",
    options: {
      resume: sessionId,
      forkSession: true,
      maxTurns: 5
    }
  })) {
    if (message.type === "system" && message.subtype === "init") {
      forkedId = message.session_id; // The fork's ID, distinct from sessionId
    }
    if (message.type === "result" && message.subtype === "success") {
      console.log(message.result);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result. If the
  // failure was an error result, forkedId was already captured by the loop
  // above; connection or process failures yield no result message.
  console.error(`Session ended with an error: ${error}`);
}

console.log(`Forked session: ${forkedId}`);

// Original session is untouched; resuming it continues the JWT thread
try {
  for await (const message of query({
    prompt: "Continue with the JWT approach",
    options: { resume: sessionId }
  })) {
    if (message.type === "result" && message.subtype === "success") {
      console.log(message.result);
    }
  }
} catch (error) {
  // A single-shot query() throws after yielding an error result.
  console.error(`Session ended with an error: ${error}`);
}

אתם אמורים לראות ש-forkedId שונה ממזהה ה-session המקורי. חידוש ה-session המקורי עדיין ממשיך את שרשור ה-JWT, מה שמאשר שה-fork לא שינה את היסטוריית המקור.

#חידוש בין מארחים (hosts)

קובצי session הם מקומיים למכונה שיצרה אותם. כדי לחדש session במארח שונה (סביבות CI, מכולות ארעיות, סביבות serverless), בחרו בגישה המתאימה:

  • העברת מאגר sessions (Session store): חברו מתאם sessionStore / session_store כך שה-SDK ישקף תמלילים למערכת הקצה (backend) שלכם ומארח אחר יוכל לחדש אותם. מפתח החיפוש במאגר נגזר מספריית העבודה, לכן חדשו מתוך cwd התואם לזה של ההרצה המקורית.
  • העברת קובץ ה-session: שמרו את ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl מההרצה הראשונה ושחזרו אותו בתוך ספרייה כלשהי תחת ~/.claude/projects/ במארח החדש לפני קריאה ל-resume. Claude Code מחפש מעבר לספריית הפרויקט הנוכחית כדי למצוא את המזהה, ראו Resume a session עבור סדר החיפוש המדויק וכיצד מטופלים עותקים כפולים. לפני גרסה v2.1.223, החיפוש הוגבל לספריית הפרויקט הנוכחית ול-git worktrees שלה, וגרסאות SDK שכוללות CLI ישן יותר עדיין מתנהגות כך.
  • אל תסתמכו על חידוש session: לכדו את התוצאות הדרושות לכם (פלט ניתוח, החלטות, קובצי diff) כמצב יישום (application state) והעבירו אותן לתוך ההנחיה של session חדש. גישה זו לעיתים קרובות יציבה יותר מאשר העברת קובצי תמליל ממקום למקום.

שני ה-SDKs חושפים פונקציות למניית sessions בדיסק ולקריאת ההודעות שלהם: listSessions() ו-getSessionMessages() ב-TypeScript, ו-list_sessions() ו-get_session_messages() ב-Python. השתמשו בהן כדי לבנות רכיבי בחירת session מותאמים אישית, לוגיקת ניקוי, או מציגי תמלילים.

שני ה-SDKs חושפים גם פונקציות לאיתור ושינוי של sessions בודדים: get_session_info(), rename_session() ו-tag_session() ב-Python, ו-getSessionInfo(), renameSession() ו-tagSession() ב-TypeScript. השתמשו בהן כדי לארגן sessions לפי תגיות או לתת להם כותרות קריאות לבני אדם.

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