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

תיעוד 172

עבודה עם סשנים

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

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

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

[!NOTE]

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

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

#בחירת גישה

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

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

#Continue, resume ו-fork

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

Continue ו-resume שניהם ממשיכים סשן קיים ומוסיפים אליו. ההבדל הוא איך הם מוצאים את הסשן:

  • Continue מוצא את הסשן האחרון בתיקייה הנוכחית. לא עוקבים אחרי כלום. מתאים כשהאפליקציה מריצה שיחה אחת בכל פעם.
  • Resume מקבל מזהה סשן מסוים. עוקבים אחרי המזהה. נדרש כשיש כמה סשנים (לדוגמה, אחד לכל משתמש באפליקציה מרובת משתמשים) או כשרוצים לחזור לסשן שאינו האחרון.

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

#ניהול סשנים אוטומטי

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

#Python: ClaudeSDKClient

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

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

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 לפרטים על מתי להשתמש ב-ClaudeSDKClient לעומת הפונקציה העצמאית query().

#TypeScript: continue: true

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

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

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);
  }
}

[!NOTE]

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

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

#לכידת מזהה הסשן

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

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

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

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

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())
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);
  }
}

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

[!TIP]

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

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

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

  • חיפוש בין ספריות: Claude Code מחפש מעבר לתיקיית הפרויקט הנוכחית כדי למצוא את המזהה. ראו חידוש סשן לסדר החיפוש המדויק ולאופן הטיפול בעותקים כפולים.
  • אותה מכונה בלבד: קובץ הסשן עדיין צריך להתקיים במכונה הנוכחית.

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

כדי לחדש סשנים בין מכונות או בסביבות serverless, משקפים תמלילים לאחסון משותף עם מתאם SessionStore.

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

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

[!NOTE]

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

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

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())
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 שונה ממזהה הסשן המקורי. חידוש הסשן המקורי עדיין ממשיך את שרשור JWT, וזה מאשר שהפיצול לא שינה את ההיסטוריה המקורית.

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

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

  • העברת מאגר סשנים. מצמידים מתאם sessionStore / session_store כדי שה-SDK ישקף תמלילים ל-backend שלכם ומארח אחר יוכל לחדש אותם. מפתח החיפוש של המאגר נגזר מספריית העבודה, ולכן מחדשים מ-cwd שתואם לריצה המקורית.

  • העברת קובץ הסשן. שומרים את ~/.claude/projects/<encoded-cwd>/<session-id>.jsonl מהריצה הראשונה ומשחזרים אותו בתוך כל ספרייה תחת ~/.claude/projects/ במארח החדש לפני הקריאה ל-resume.

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

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

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

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

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