🧵 חוט הפרויקט
מאיפה באנו (פרק 5 — Observability ו-Failure Recovery): חיברת ל-harness עיניים — tracing מלא ב-Langfuse עם span לכל turn ו-tool call, circuit-breaker שעוצר לולאות, ו-failure capture מובנה. עכשיו אתה רואה מה הסוכן עושה וכמה זה עולה.
איפה אנחנו עכשיו: ה-observability הזה הוא בדיוק התנאי שמאפשר לעבור מסוכן יחיד לצוות. בלי trace לכל subagent, צוות סוכנים הוא קופסה שחורה כפול N. בפרק הזה תבנה supervisor loop שמתזמר 2–3 subagents עם max-turn limits ו-budget caps, תחליט בין ephemeral ל-durable, תריץ Claude Agent Teams מקבילים — ותלמד לחשב מראש מתי subagent מקבילי שורף תקציב אקספוננציאלית.
לאן ממשיכים (פרק 7 — זיכרון ו-Dreaming): ברגע שיש לך צוות שעובד, השאלה הבאה היא איך הוא לומד מהטעויות שלו במקום לחזור עליהן. ה-failure capture מפרק 5 וה-handoff שתבנה כאן הם חומר הגלם ל-memory store ול-Claude Dreaming של הפרק הבא.
🎯 מה תדע/י לעשות בסוף הפרק
- לבנות supervisor loop שמפצל 2–3 subagents, מריץ ב-
asyncio.gather, ומדפיסtotal_tokensפר-subagent — תוך שמירה עלbudget_tokens=40,000פר-subagent בלי חריגה. - לבחור בין ephemeral ל-durable orchestration לפי קריטריון מדיד: אם הריצה צריכה resume אחרי pause-for-approval — durable; אחרת — ephemeral.
- להריץ Claude Agent Teams מקבילים (≥2 workers שמדברים worker↔worker בזמן אמת) ולהפעיל V2 Session API —
createSession/resumeSession/forkSession— עם span לכל סוכן ב-Langfuse. - לחשב מראש (לפני spawn) את
total_tokensו-est_cost_usdשל ריצה רב-סוכנית, ולסרב להריץ אם החיזוי עובר 500K tokens.
✅ מה צריך לפני שמתחילים
- פרק 1 — אנטומיית ה-harness הדק (loop, state, tools, context, governance).
- פרק 2 — לולאת agent מפורשת עם
max-turnsו-message state. - פרק 3 — context management מול ה-compaction buffer (קריטי כאן: כל subagent הוא חלון context נפרד).
- פרק 4 — structured outputs ו-policy gates דטרמיניסטיים (כל subagent חייב לעבור דרך אותם gates).
- פרק 5 — observability ו-failure recovery (בלי tracing אי אפשר לדבג צוות).
- התקנה חד-פעמית (לפני תרגיל 1):
pip install claude-agent-sdk langfuse— שני המודולים נדרשים בפרק:claude-agent-sdkל-Exercise 1, ו-langfuseל-Exercise 4 ול-tracing מפרק 5. - משתני סביבה לפני כל ריצה:
export ANTHROPIC_API_KEY=sk-ant-...ו-export LANGFUSE_PUBLIC_KEY=pk-lf-... LANGFUSE_SECRET_KEY=sk-lf-...(את הזוג השני מקבלים בחינם ב-cloud.langfuse.com). בלי הראשון — תרגיל 1 ייכשל ב-AuthenticationError; בלי השני — תרגיל 4 ייכשל ביצירת trace.
📦 מה תייצר/י בפרק הזה (Deliverables)
- supervisor loop שמתזמר 2–3 subagents על תת-משימות, עם
max-turn limitו-budget capנפרד לכל subagent. - השוואה רצה: אותה משימה ב-ephemeral (Agent Teams) מול durable (state persisted) — עם token cost ו-tracing לכל גישה.
- מחשבון cost רב-סוכני: בהינתן N subagents ו-turns/subagent, אומדן ה-tokens הכולל ונקודת ההתפוצצות.
- handoff module: פונקציה שמעבירה ל-subagent רק את ה-context הממוקד למשימה שלו — לא את כל ההיסטוריה של ה-supervisor.
- טבלת החלטה ephemeral↔durable שתחזיק לאורך כל harness עתידי שתבנה.
מ-Single Agent ל-Team — מתי בכלל צריך כמה סוכנים
הפיתוי הכי גדול אחרי שבנית harness שעובד הוא להכפיל אותו. "אם סוכן אחד טוב, חמישה יהיו פי חמש." זו טעות שמדגדגת לכל vibe coder, והיא יקרה — מילולית. לפני שנכתוב שורת קוד אחת של orchestration, צריך לענות על שאלה אחת: מה הבעיה שצוות פותר שסוכן יחיד לא פותר?
יש בדיוק שלוש תשובות לגיטימיות, ושאר המקרים הם over-engineering:
- Parallelism אמיתי — יש 8 קבצים שצריך לסקור, והסקירות לא תלויות זו בזו. סוכן יחיד יעשה אותן בטור (8 × זמן); צוות יעשה אותן במקביל (1 × זמן, 8 × עלות). אתה קונה זמן בכסף.
- הפרדת אחריות (separation of concerns) — משימה שדורשת שני "מצבי חשיבה" סותרים. למשל: סוכן שכותב קוד וסוכן שמבקר אותו. אם תבקש מאותו סוכן גם לכתוב וגם לבקר, הוא יאשר את עצמו. הפרדה לשני subagents עם system prompts שונים יוצרת מתח בריא.
- Context isolation — תת-משימה שדורשת המון context משלה (לדוגמה, קריאת 30 קבצי לוג) ושהיית מעדיף שלא תזהם את ה-context של ה-supervisor. אתה שולח subagent, הוא "שורף" את ה-context שלו על הלוגים, ומחזיר רק את המסקנה. ה-supervisor נשאר רזה.
שים לב שכל שלושת המקרים הם החלטות הנדסיות, לא קסם. אם המשימה שלך היא רצף ליניארי של צעדים תלויים — "קרא קובץ, אחר כך ערוך אותו, אחר כך הרץ טסט" — סוכן יחיד עם לולאה טובה (זה מה שבנית בפרק 2) הוא תמיד הבחירה הזולה והפשוטה יותר. צוות נכנס לתמונה רק כשיש מקביליות, מתח-תפקידים, או בידוד context.
יש דרך מהירה לבדוק לאיזה מקרה אתה שייך — שאלה אחת על תלויות: "אם אריץ את תת-המשימות בסדר אקראי, האם התוצאה תשתנה?" אם כן (יש תלות סדר) → ליניארי, סוכן יחיד. אם לא (הסדר לא משנה) → יש מקביליות, מועמד לצוות. זו לא שאלה פילוסופית — היא בדיוק מה שקובע אם asyncio.gather חוקי כאן או לא. ב-orchestration, גרף התלויות הוא הארכיטקטורה; כל השאר זה אינסטרומנטציה סביבו.
נקודה שקל לפספס: גם כשיש מקביליות, היא לא תמיד שווה את העלות. parallelism קונה זמן ומשלם ב-כסף — תמיד. אם 8 הסקירות לוקחות 4 שניות כל אחת בטור (32 שניות) ואין לך לחץ זמן, אולי 32 שניות סדרתיות עם סוכן יחיד עדיפות על פני 8 sessions מקבילים ב-8× עלות. החלטת ה-fan-out היא תמיד trade-off בין latency לעלות, ולא ברירת מחדל. ככלל אצבע: fan-out משתלם כשהמקביליות גם חוסכת זמן וגם כשמדובר בעבודה שבכל מקרה היית עושה (כלומר ה-8× עלות לא נוסף — הוא פשוט במקביל במקום בטור).
תרחישי גבול שכדאי להכיר מראש
מעבר לשלוש התשובות הקלאסיות, יש כמה תרחישי גבול שנראים "מתאימים" לצוות אבל בפועל לא. שווה לדעת אותם לפני שמתחילים, כי הם המקום שבו מפתחים שורפים הכי הרבה tokens:
- "אני רוצה תשובה מהירה יותר, אז אני מפצל." זה לא parallelism, זה פנטזיה. אם המשימה היא מונוליטית (למשל "נתח את הבאג הזה"), פיצול לא יקצר — רק יוסיף overhead של handoff ו-spawn. רק עבודה שאפשר לחלק ל-N חתיכות בלי תלות סדר מתאימה ל-fan-out.
- "אני רוצה תשובה 'ממספר זוויות'." זה fork, לא team. אם אתה רוצה 3 גישות לאותה בעיה, השתמש ב-
forkSession(נכיר בהמשך) — לא ב-supervisor שמפצל 3 subagents לאותה משימה. fork חולק context, team מכפיל context. - "אני רוצה בקרת איכות." סוכן יחיד עם
self-critique stepבלולאה (פרק 2) עושה את זה ב-30% מהעלות של reviewer נפרד. הפרדה ל-author+reviewer הגיונית רק אם הביקורת דורשת system prompt שונה מהותית (למשל: תפקיד, סמכויות, סט כלים), לא סתם "עוד prompt שמבקר".
⚠️ טעות נפוצה: "צוות סוכנים תמיד טוב יותר"
הרבה מפתחים מפצלים ל-subagents בגלל שזה נשמע מתוחכם, ואז משלמים פי 5 על עבודה שסוכן יחיד היה עושה זול יותר ומהר יותר. ברירת המחדל היא סוכן יחיד. צוות הוא חריג שצריך להצדיק במספרים — parallelism, separation of concerns, או context isolation. אם אתה לא יכול להצביע על אחד מהשלושה, אתה מבזבז כסף.
⚠️ טעות נפוצה: להפוך סוכן יחיד מוצלח ל"מיקרו-שירות" של subagents
כשיש לך סוכן יחיד שעובד טוב, הפיתוי הוא "לפצל אותו לכמה חלקים קטנים יותר" כדי שיהיה "גמיש". זה הפוך מ-micro-services של תוכנה רגילה — שם פיצול עוזר. בסוכנים, פיצול מוסיף עלות handoff, מקטין את היכולת של כל חלק לראות את התמונה המלאה, ומקשה על debugging. אל תפצל סוכן שעובד. פצל רק משימה שמחייבת זאת.
⚡ Do Now
קח משימה אמיתית שאתה עובד עליה עכשיו. כתוב בשורה אחת: האם היא ליניארית (צעד-אחרי-צעד תלוי), מקבילית (תת-משימות עצמאיות), או דורשת-מתח (כתיבה מול ביקורת)? רק אם ענית "מקבילית" או "דורשת-מתח" — היא מועמדת לצוות.
ה-Supervisor Loop — ה-Harness שמעל ה-Harnesses
ה-supervisor (נקרא גם orchestrator) הוא סוכן-על שלא עושה את העבודה בעצמו — הוא מפרק משימה לתת-משימות, מפצל subagent לכל אחת, ומאחד את התוצאות. אם הלולאה מפרק 2 הייתה goal → call model → run tools → repeat, הלולאה של ה-supervisor היא goal → decompose → spawn subagents → collect → synthesize.
החדשות הטובות: subagent הוא בדיוק ה-harness שכבר בנית. אתה לא לומד טכנולוגיה חדשה — אתה עוטף את ה-harness הקיים בלולאה אחת מעליו. הנה השלד המינימלי ב-Python מעל ה-Claude Agent SDK:
import asyncio
from claude_agent_sdk import query, ClaudeAgentOptions
# subagent = ה-harness שכבר בנית, עכשיו עם תקרות משלו
async def run_subagent(task: str, max_turns: int, budget_tokens: int):
used_tokens = 0
options = ClaudeAgentOptions(
system_prompt="אתה worker ממוקד. בצע רק את המשימה שקיבלת.",
max_turns=max_turns, # הבלם מפרק 2 — לכל subagent בנפרד
)
result_text = ""
async for message in query(prompt=task, options=options):
# token accounting מפרק 5 — סופרים בזמן אמת
if hasattr(message, "usage"):
used_tokens += message.usage.input_tokens + message.usage.output_tokens
if used_tokens > budget_tokens: # budget cap לכל subagent
return {"task": task, "status": "budget_exceeded",
"partial": result_text, "tokens": used_tokens}
if hasattr(message, "result"):
result_text = message.result
return {"task": task, "status": "done",
"result": result_text, "tokens": used_tokens}
# ה-supervisor: מפרק, מפצל, מאחד
async def supervisor(goal: str):
# 1. decompose — מפרקים את ה-goal לתת-משימות עצמאיות
subtasks = [
"סקור את קבצי src/auth/ ודווח על בעיות אבטחה",
"סקור את קבצי src/api/ ודווח על בעיות אבטחה",
"סקור את קבצי src/db/ ודווח על בעיות אבטחה",
]
# 2. spawn — כל subagent עם תקרות משלו, במקביל
results = await asyncio.gather(*[
run_subagent(t, max_turns=8, budget_tokens=40_000) for t in subtasks
])
# 2.5 token accounting — מדפיסים per-subagent + total לפני synthesize
for r in results:
print(f"{r['task'][:30]:30s} → status={r['status']:15s} tokens={r['tokens']:,}")
total_tokens = sum(r["tokens"] for r in results)
print(f"{'TOTAL':30s} → {total_tokens:,} tokens")
# 3. synthesize — ה-supervisor מאחד רק את המסקנות
return synthesize(goal, results)
שלוש נקודות שמבדילות supervisor אמיתי מ-"לולאה שמריצה לולאות":
- כל subagent מקבל max_turns ו-budget_tokens משלו. זו לא תוספת — זו הליבה. נחזור לזה בסעיף העלות, אבל תזכור: ה-budget cap הוא בתוך ה-subagent, לא רק ב-supervisor.
- ה-supervisor לא מעביר את כל ההיסטוריה ל-subagent. כל subagent מקבל רק את ה-task string שלו. את ה-handoff הממוקד נבנה בסעיף נפרד — זו אחת הטעויות הכי יקרות אם מפספסים אותה.
- ה-synthesize מקבל רק מסקנות, לא transcripts. אם תזרוק את כל ה-output של שלושת ה-subagents ל-context של ה-supervisor, תאיץ compaction (פרק 3) ותשרוף tokens. כל subagent מחזיר תקציר מובנה, וה-supervisor מאחד תקצירים.
נקודה עדינה שכדאי להבין לפני שמתקדמים: ה-supervisor עצמו צריך max-turns ו-budget. זה לא רק subagents. תאר לך שכל ה-subagents חזרו תקינים, אבל שלב ה-synthesize (האיחוד) נתקע בלולאה כי הוא מנסה לאחד סיכומים סותרים. בלי תקרה על ה-supervisor, הוא יכול לרוץ עד שה-token counter של ה-API יחנוק אותו — או, גרוע יותר, עד שתקבל חשבון בלתי צפוי. הכלל: לכל סוכן בעץ, גם השורש, יש תקרה משלו. ה-supervisor מקבל תקרה גדולה יותר (כי הוא מאחד), אבל לא בלתי-מוגבלת.
🧭 Framework: מתי לפצל subagent
אם תת-המשימה עצמאית מאחרות (אין תלות סדר), וגם היא דורשת context משלה שעדיף לבודד, אז פצל ל-subagent עם max_turns ו-budget_cap משלו.
אם תת-המשימה תלויה בתוצאת קודמתה (צעד-אחרי-צעד), אז השאר אותה בלולאה הליניארית של סוכן יחיד — פיצול כאן רק מוסיף עלות handoff בלי לחסוך זמן.
אם אתה לא בטוח אם יש תלות — אז התחל ליניארי. קל לפצל אחר כך; קשה לאחד צוות שכבר התפזר.
שלושת דפוסי ה-Orchestration שתפגוש
בפועל, כמעט כל harness רב-סוכני שתבנה נופל לאחד משלושה דפוסים. שווה להכיר אותם בשמם כדי שתדע מתי אתה משתמש בכל אחד:
- Fan-out / fan-in — ה-supervisor מפצל N subagents עצמאיים בו-זמנית (fan-out), מחכה לכולם, ומאחד (fan-in). זה הדפוס בשלד למעלה. מתאים לסקירות מקבילות, חיפוש רחב, או כל עבודה ש-"חלק אותה ל-N ואחד". זה גם הדפוס הכי קל לתמחר מראש.
- Pipeline / chain — subagents בשרשרת: הפלט של אחד הוא הקלט של הבא. כאן אין מקביליות אמיתית, אז שאל את עצמך אם בכלל צריך subagents — לעיתים קרובות זו לולאה ליניארית מחופשת. הצדקה אמיתית לשרשרת היא context isolation: כל שלב צריך context שונה לגמרי (למשל: extractor → summarizer → formatter, כל אחד עם system prompt אחר).
- Hierarchical / recursive — supervisor שמפצל subagents שהם בעצמם supervisors שמפצלים עוד subagents. עץ. כאן ה-cost explosion הכי מסוכן, כי כל רמה מכפילה. אם אתה לא ה-Anthropic שבונה compiler עם 16 סוכנים — כמעט תמיד עדיף שתי רמות לכל היותר.
הדפוס שתשתמש בו 90% מהזמן הוא fan-out/fan-in. הוא הכי פשוט לתמחר (N × עלות subagent), הכי קל לעשות לו tracing (span לכל ענף), והכי בטוח מבחינת cost — אין רקורסיה שמתפוצצת. אם אתה מתחיל ב-orchestration, התחל שם.
והנה הקוד ל-pipeline pattern לשם השוואה — שים לב שהוא כמעט זהה לסוכן יחיד עם context חיצוני, ולכן הצדקה צריכה להיות חזקה:
# pipeline: extractor → summarizer → formatter, כל אחד subagent נפרד
async def pipeline_documentation(notes: str) -> str:
extract = await run_subagent(
f"חלץ עובדות מובנות מתוך: {notes}", max_turns=5, budget_tokens=15_000)
if extract["status"] != "done":
return {"status": "failed_at_extract", "partial": extract}
summary = await run_subagent(
f"סכם את העובדות הבאות לפסקה אחת: {extract['result']}",
max_turns=3, budget_tokens=10_000)
if summary["status"] != "done":
return {"status": "failed_at_summary", "partial": summary}
return await run_subagent(
f"עצב את הפסקה הבאה כ-Markdown: {summary['result']}",
max_turns=3, budget_tokens=10_000)
⚡ Do Now
סווג את שלוש המשימות הבאות לדפוס: (א) "סכם 10 מאמרים" (ב) "חלץ נתונים מ-PDF, אחר כך נסח אותם, אחר כך עצב כ-HTML" (ג) "תכנן מוצר, ולכל feature שלח צוות-משנה שמתכנן אותו לעומק". כתוב ליד כל אחת: fan-out, pipeline, או hierarchical — והאם בכלל מצדיק subagents.
🛠️ תרגיל 1: בנה supervisor שמסכם שלושה קבצים במקביל
המטרה: harness עם supervisor שמפצל 3 subagents לסכם 3 קבצים שונים, כל אחד עם max_turns=5 ו-budget_tokens=20_000, ומאחד לדוח אחד.
- צור 3 קבצי טקסט קצרים (לדוגמה
a.txt,b.txt,c.txt) עם פסקה בכל אחד. - השתמש בשלד למעלה. כל subagent מקבל task: "סכם את הקובץ X במשפט אחד".
- הרץ עם
asyncio.gatherכך ששלושתם רצים במקביל. - ה-supervisor מאחד את שלושת המשפטים לדוח אחד ומדפיס את סך ה-tokens שכל subagent צרך.
subagent_a: 4,210 | subagent_b: 3,980 | subagent_c: 4,505 | total: 12,695. אם הרצת בטור היה לוקח ~3× בזמן הקיר (wall-clock) — צלם את ההפרש.
Anti-Patterns של Multi-Agent — מה לא לעשות
לפני שממשיכים, שווה לעצור ולהגדיר את הצורות הנפוצות של כישלון ב-multi-agent. רוב הקוד שתראה ב-Stack Overflow ובפוסטים על "Agentic AI" נופל לפחות אחד מה-patterns האלה. ההגנה היא להכיר אותם בשם:
- Shared-state pollution — subagents שונים שמשתפים את אותו dict/list ודורסים זה את זה. זה קורה כשמנסים "לחסוך" על ידי כתיבת state משותף במקום כל subagent עם state משלו. התוצאה: תוצאות לא-דטרמיניסטיות שבלתי-אפשרי לדבג. הכלל: state פר-subagent, תמיד.
- Circular handoff — agent A קורא ל-agent B, שקורא ל-agent C, שקורא בחזרה ל-A. בלי timeout גלובלי, הלולאה רצה עד שמישהו נחנק על tokens. תיקון: graph של תלויות שאסור לו להכיל מעגלים, או hop counter פר-handoff שמסרב לעבור את N קפיצות.
- Subagent שעושה רק routing — supervisor שמפצל "router" agent שרק מחליט לאן להעביר, בלי ערך מוסף. זה over-engineering קלאסי: אתה משלם על session מלא כדי לקבל תשובה של משפט אחד. routing הוא תפקיד של ה-supervisor עצמו (if/elif), לא סוכן.
- Unbounded depth — hierarchical של 4+ רמות. גם אם ההגיון "מתאים" לעץ עמוק, העלות מתפוצצת: 3 רמות של 3 subagents = 27 sessions, ו-27 sessions × 20 turns = בלתי-אפשרי. תקרה קשיחה: 2 רמות בלבד, אלא אם כן אתה Anthropic עם תקציב מחקר.
- Untyped handoff — subagent שמחזיר free-form text וה-supervisor מנסה לפרסר אותו בכוח. זה הסיכון ש-flagged במפורש ב-course.research.json: "Fragility of parsing unstructured free-form text outputs from agents instead of enforcing structured JSON schemas." תיקון: JSON schema חובה על כל פלט subagent (פרק 4).
🔬 Case Study — Refactoring של Shared-State Pollution
קוד שראיתי בפועל ב-2 פרויקטונים שונים בשבוע שעבר: "למה כל subagent צריך state משלו? בואו נחסוך dict אחד". הנה האב-טיפוס, מצומצם:
# ❌ Anti-pattern: shared mutable state בין subagents
blackboard = {"findings": []} # dict משותף לכל הצוות
async def review_module(module: str):
findings = await scan(module) # 4-8 findings אופייני
blackboard["findings"].extend(findings) # race בכתיבה מקבילית
return blackboard # מחזירים את כל ה-state, לא רק findings שלנו
async def supervisor_bad():
return await asyncio.gather(*[review_module(m) for m in MODULES])
# תסמין: findings חסרים, סדר לא-דטרמיניסטי, דיבוג בלתי-אפשרי
מה השתבש: שלושת ה-subagents רצים במקביל דרך asyncio.gather, ו-extend() על list משותף בלי Lock מייצר race: לפעמים finding נדרס, לפעמים כפול. התיקון הוא לא להוסיף asyncio.Lock — אלא לבטל את ה-state המשותף. כל subagent מחזיר את שלו, וה-supervisor מאחד:
# ✅ Refactored: per-subagent state, merge ב-supervisor
async def review_module(module: str) -> list[dict]:
findings = await scan(module) # רק שלי, אין כתיבה ל-blackboard
return findings # מחזירים רק findings שלנו, לא את כל ה-dict
async def supervisor_good():
per_module = await asyncio.gather(*[review_module(m) for m in MODULES])
merged = [f for module_findings in per_module for f in module_findings]
return merged # איחוד דטרמיניסטי, בלי race
מה הרווחנו: אפס race condition (כל subagent רץ על state משותף לקריאה בלבד), איחוד דטרמיניסטי ב-supervisor, וכל subagent ניתן לדיבוג בנפרד — span הוא הגבול. העיקרון שעומד מאחורי: subagent = פונקציה טהורה על input, ה-state הוא רק ה-input שקיבלנו + ה-output שהחזרנו.
⚠️ טעות נפוצה: "אם subagent אחד טוב, הוסף עוד אחד"
קל להיסחף ולחשוב שעוד subagents = יותר ערך. המציאות: כל subagent נוסף מוסיף גם עלות חישובית וגם עלות תיאום. אם הוא לא פותר צוואר-בקבוק אמיתי, הוא רק מאט. תמיד שאל: "איזו בעיה הוא פותר שאין לי כרגע פתרון זול יותר עבורה?" אם אין תשובה ברורה — אל תוסיף.
Ephemeral מול Durable — ההחלטה המרכזית
זו ההחלטה הארכיטקטונית הכי חשובה ב-orchestration, והיא חוזרת גם בפרק 8 כשתחליט in-process מול hosted. course.research.json מסמן אותה במפורש כ-key_decision: "Ephemeral vs. Durable State: Deciding whether to use ephemeral, session-bound agents (like Claude Agent Teams) or durable, code-defined agents with persistent state databases (like LangGraph or Aden Hive)." שני סוגי orchestration:
- Ephemeral orchestration (session-bound) — הסוכנים חיים רק לאורך session אחד. כשהמשימה נגמרת, הם נעלמים, וה-state שלהם נעלם איתם. זה המודל של Claude Agent Teams (לפי course.research.json, GA פברואר 2026 לצד Claude Sonnet 4.6): מופעי Claude מקבילים שמתקשרים בזמן אמת כ-teammates ארעיים. מצוין למשימות חד-פעמיות, מהיר להקים, אפס תשתית state.
- Durable orchestration (persistent state) — הסוכנים מוגדרים בקוד, ויש להם state database מתמשך. ריצה יכולה להיעצר ולהמשיך מאוחר יותר, ו-state שורד בין הרצות. זה המודל של frameworks כמו LangGraph (הבסיס ל-Deep Agents של LangChain) ו-Aden Hive (לפי course.research.json: outcome-driven multi-agent harness שמייצר את ה-graph מתיאור בשפה טבעית, ~10.5k כוכבים ב-GitHub). מתאים ל-pipelines שרצים שוב ושוב, ל-human-in-the-loop שמחכה שעות לאישור, ולתהליכים שחייבים לשרוד crash.
| שיקול | Ephemeral (Agent Teams) | Durable (LangGraph / Aden Hive) |
|---|---|---|
| אורך חיים | session אחד | חוצה הרצות, שורד crash |
| תשתית state | אין — הכל בזיכרון | state DB נדרש |
| זמן הקמה | דקות | שעות (סכמת state, persistence) |
| human-in-the-loop ארוך | קשה (session פג) | טבעי (pause/resume) |
| תרחיש אופייני | "סרוק את ה-repo עכשיו" | "lead pipeline שרץ כל בוקר" |
| עלות תשתית | אפס | תחזוקת DB + scheduler |
עוד נקודה שלא תמיד ברורה: durable לא אומר שהסוכן עצמו רץ ברקע. הוא אומר שה-state שלו שרד. הסוכן עצמו עדיין קם, רץ, ויורד — רק שכשהוא קם שוב, הוא טוען את ה-state מה-DB. זה הבדל חשוב: durable זה persistence של מידע, לא availability של חישוב. אם אתה צריך גם availability (סוכן שרץ 24/7 ומגיב מיידית לאירועים), אתה צריך managed/hosted — וזה כבר פרק 8.
🧭 Framework: ephemeral או durable?
אם המשימה חד-פעמית, מסתיימת בתוך דקות, ואין צורך לשרוד crash או להמתין שעות לאישור — אז ephemeral (Agent Teams). אפס תשתית, מהיר.
אם ה-pipeline רץ שוב ושוב, צריך human-approval שעלול לקחת שעות, או חייב לשרוד נפילה ולהמשיך — אז durable (state DB, LangGraph/Aden Hive).
אם אתה בונה capstone (פרק 8) של lead-enrichment שעוצר לאישור אנושי לפני שליחת email — אז כמעט תמיד durable, כי ה-pause עלול להיות ארוך.
⚠️ טעות נפוצה: durable כברירת מחדל "ליתר ביטחון"
durable מרגיש "רציני" יותר, אז מפתחים מקימים state DB גם למשימה חד-פעמית של 90 שניות. התוצאה: שעות תשתית, סכמה לתחזק, ו-scheduler — כדי לשרת משהו ש-ephemeral היה פותר בקובץ אחד. התחל ephemeral. שדרג ל-durable רק כשיש דרישה אמיתית של persistence (pause ארוך, survival ל-crash, הרצה חוזרת).
⚡ Do Now
קח את ה-capstone שאתה מכוון אליו (lead-enrichment או documentation agent). שאל: כמה זמן הוא עלול להיתקע ב-human-approval? אם התשובה היא "דקות" → ephemeral מספיק. אם "שעות/ימים" → durable. כתוב את ההחלטה ואת הנימוק במשפט אחד.
ההיברידי שכמעט תמיד עובד
בפועל, רוב ה-harnesses הטובים אינם טהורים. הם ephemeral בתוך, durable מסביב: ה-supervisor וה-subagents רצים ephemeral (מהיר, אפס תשתית) למשך ריצה אחת — אבל ה-תוצאות וה-state הקריטי (decisions, deliverables, failures) נכתבים ל-store מתמשך בין הריצות. ככה אתה מקבל את המהירות של ephemeral ואת השרידות של durable בלי לשלם את מלוא מחיר ה-state DB על כל turn.
זה בדיוק ה-threshold-based backup מפרק 3, רק במעלה רמה: שם שמרת state קריטי לפני compaction; כאן אתה שומר את תוצאת הריצה לפני שה-session הארעי נעלם. וזה גם הגשר לפרק 7 — ה-store המתמשך הזה הוא בדיוק ה-memory store שעליו ירוץ Dreaming. שים לב לתבנית החוזרת: ריצה ארעית, זיכרון מתמשך. היא תחזור בכל harness production שתבנה.
דוגמה מעשית להיברידי: דוח סקירת קוד שרץ בכל commit (ephemeral — 30 שניות), אבל הממצאים נכתבים לטבלת code_reviews ב-DB (durable — שורד). בלי DB, אין היסטוריה; בלי ephemeral, כל commit מחכה ל-DB write שלא נגמר.
⚠️ טעות נפוצה: לבלבל "ephemeral agent" עם "אין state בכלל"
ephemeral מתאר את ה-סוכן, לא את ה-נתונים. הרבה מפתחים שומעים "ephemeral" ומסיקים שאסור לשמור כלום — ואז כל ריצה מתחילה מאפס ושוכחת מה למדה. ההפך הוא הנכון: דווקא כי הסוכן ארעי, אתה חייב להוציא את ה-state הקריטי החוצה לפני שהוא נעלם. ephemeral agent + persistent state = הדפוס הנכון.
Parallel Subagent Cost Explosion — הסכנה שלא רואים עד החשבון
זו הסכנה המספר-אחת של orchestration רב-סוכני, והיא מופיעה במפורש ב-topic_risk_flags וב-gotchas של course.research.json. הציטוט המדויק: "Parallel Subagent Cost Explosion: Each spawned subagent consumes a completely separate session, leading to exponential token cost spikes."
הסיבה פשוטה אבל קלה לפספס: כל subagent הוא session נפרד לחלוטין. יש לו ה-system prompt שלו, ה-context שלו, ההיסטוריה שלו. כשאתה מפצל 5 subagents, אתה לא משלם פי 5 על "עוד קצת עבודה" — אתה משלם על 5 חלונות context מלאים, כל אחד גדל בכל turn.
בוא נראה את המתמטיקה. נניח subagent ממוצע: ~3,000 input tokens ב-context ההתחלתי, גדל ב-~2,000 tokens בכל turn (history מצטבר + tool results), ו-~500 output tokens ל-turn:
| תרחיש | subagents | turns/subagent | אומדן tokens כולל | הערה |
|---|---|---|---|---|
| סוכן יחיד | 1 | 10 | ~75,000 | baseline |
| צוות קטן | 3 | 10 | ~225,000 | פי 3 — צפוי |
| צוות בלי תקרה | 5 | 20 | ~1,150,000 | turns כפול → לא ליניארי |
| subagent בורח | 5 (אחד נתקע) | 4×20 + 1×60 | ~1,900,000 | אחד בלי cap שורף הכל |
שורת המפתח היא האחרונה: subagent אחד בלי budget cap שנתקע בלולאה (בדיוק התרחיש שה-circuit-breaker מפרק 5 נועד לתפוס) יכול לשרוף יותר tokens מכל שאר הצוות יחד. הסכנה היא לא "צוות גדול", אלא צוות בלי תקרות פר-subagent.
שים לב למה זה לא ליניארי, כי זו הנקודה שמפילה אנשים. כל subagent הוא session נפרד, ובכל turn ה-context שלו גדל — ההיסטוריה מצטברת, tool results נערמים. כלומר ה-turn העשירי של subagent יקר משמעותית מה-turn הראשון שלו, כי הוא נושא את כל מה שקדם לו. עכשיו הכפל את זה ב-N subagents שכל אחד עושה את אותו דבר. התוצאה: כשאתה מכפיל את ה-turns, אתה לא מכפיל את העלות — אתה מרבע אותה בערך (כי גם מספר ה-turns וגם הגודל של כל turn גדלים). זה ההבדל בין "פי 3 כי 3 subagents" (לינארי, צפוי) ל-"פי 15 כי כל אחד עשה 20 turns ארוכים" (ריבועי, מפתיע).
תקרת ה-Parallelism — מתי עוד סוכנים מחמירים את המצב
יש נקודה שבה הוספת subagent לא רק עולה יותר — היא מאטה את הריצה הכוללת ומעלה את הסיכון לכשל. זה קורה בשלושה תרחישים שכדאי להכיר:
- API rate limits — Anthropic (וכל ספק) מגבילים requests-per-minute. אם 10 subagents קוראים במקביל, אתה פוגע ב-rate limit וחלקם נחנקים ב-429. תיקון: semaphore מודע ל-rate limit, או backoff.
- Supervisor context bloat — ה-supervisor שמחכה ל-N תוצאות צריך להחזיק את כולן בזיכרון עד ה-synthesize. אם 10 תוצאות × 5K tokens כל אחת = 50K רק של תוצאות, וזה נכנס ל-compaction buffer. תיקון: stream תוצאות בחזרה, או summarize לפני שמחזירים.
- Failure multiplication — אם הסיכוי להצלחה של subagent הוא 90%, הסיכוי שכל 10 יצליחו במקביל הוא רק 0.9¹⁰ ≈ 35%. כל subagent נוסף מכפיל את הסיכוי שמשהו ייכשל. תיקון: תכנן retry או partial-success mode ("עבור אם 7/10 הצליחו").
🧭 Framework: כמה subagents זה "יותר מדי"?
אם N קטן או שווה ל-3 — בדרך כלל בטוח, גם ב-API רגיל.
אם N בין 4 ל-8 — צריך semaphore + rate limit awareness, ולבדוק שה-supervisor מסוגל להחזיק את כל התוצאות בלי compaction.
אם N מעל 8 — כמעט תמיד צריך batch (8 עכשיו, 8 בעוד דקה) במקום fan-out מלא. ולשאול את עצמך אם המשימה בכלל מצדיקה את הסקייל.
שלוש בלמים עובדים יחד נגד ההתפוצצות, וכל אחד תופס שכבה אחרת:
| בלם | מאיפה | מה הוא תופס |
|---|---|---|
max_turns פר-subagent | פרק 2 | חוסם subagent שלא מסיים — תקרה קשיחה על מספר הסבבים |
budget_tokens פר-subagent | הפרק הזה | חוסם subagent שעושה turns "חוקיים" אבל ארוכים מדי |
| circuit-breaker | פרק 5 | תופס לולאת tool (אותה קריאה 6+ פעמים) בתוך ה-turns, הרבה לפני שהתקרות נורות |
שים לב ש-max_turns לבדו לא מספיק: subagent יכול לעשות 8 turns "חוקיים" שכל אחד מהם מחזיר tool result של 40KB ולשרוף תקציב עצום בלי לחצות את תקרת ה-turns. לכן budget_tokens הוא בלם נפרד — הוא סופר את מה ש-max_turns לא רואה. ובלם שלישי, ה-circuit-breaker, תופס את המקרה הספציפי של לולאה לפני ששני האחרים בכלל מתקרבים. שלושתם יחד, פר-subagent — זו רשת הביטחון.
המספרים בטבלה הם אומדן הדגמתי כדי להבהיר את הצורה האקספוננציאלית — לא מדידה מתועדת. צריכת ה-tokens האמיתית תלויה בגודל ה-context, ב-tool results, ובמודל. השתמש בטבלה כדי להבין את המבנה (turns × subagents הוא לא ליניארי), ומדוד את המספרים האמיתיים שלך עם ה-token accounting מפרק 5.
הנה מחשבון ה-cost שתבנה כ-deliverable — פונקציה דטרמיניסטית, בלי LLM, שמעריכה מראש:
def estimate_run_cost(n_subagents: int, turns_per_subagent: int,
base_input: int = 3_000, growth_per_turn: int = 2_000,
output_per_turn: int = 500,
price_in_per_mtok: float = 3.0,
price_out_per_mtok: float = 15.0):
"""אומדן tokens ועלות USD לריצה רב-סוכנית. דטרמיניסטי, לרוץ לפני spawn."""
input_per_agent = 0
for turn in range(turns_per_subagent):
# ה-context גדל בכל turn — זה הלא-ליניארי
input_per_agent += base_input + growth_per_turn * turn
output_per_agent = output_per_turn * turns_per_subagent
total_input = input_per_agent * n_subagents
total_output = output_per_agent * n_subagents
cost_usd = (total_input / 1_000_000) * price_in_per_mtok \
+ (total_output / 1_000_000) * price_out_per_mtok
return {
"total_input_tokens": total_input,
"total_output_tokens": total_output,
"total_tokens": total_input + total_output,
"est_cost_usd": round(cost_usd, 4),
}
# לפני spawn — בודקים אם זה שפוי
print(estimate_run_cost(n_subagents=5, turns_per_subagent=20))
# {'total_tokens': ~1.1M, 'est_cost_usd': ~1.05} → לישראלי: + מע"מ + שער ILS/USD
⚠️ טעות נפוצה: לפצל subagents בלי max-turn limit ו-budget cap לכל אחד
כל subagent הוא session נפרד, והעלות מתפוצצת אקספוננציאלית בלי שתראה את זה עד שמגיע החשבון. max-turns על ה-supervisor לבדו לא מספיק — הוא לא מגביל subagent שנתקע בלולאה פנימית. כל subagent חייב תקרה משלו: max_turns וגם budget_tokens. זה לא "best practice" — זה הקו שמפריד בין צוות לחשבון מפתיע.
כל שירותי Anthropic ו-LangChain מחויבים ב-USD. מפתח ישראלי שמריץ צוות רב-סוכני חייב לתמחר מע"מ ושער ILS/USD בתוך אומדן ה-cost-per-run. ריצת צוות של ~1M tokens שעולה ~$1 ב-USD היא ~3.7₪ + מע"מ — וזה לריצה אחת. אם ה-pipeline רץ 200 פעם ביום, המספר הזה הוא מה שקובע אם הפרויקט כלכלי.
⚡ Do Now
פתח את ה-Langfuse מפרק 5 וקח ריצה אמיתית אחת של סוכן יחיד שכבר הרצת. רשום את ה-input ו-output tokens שלה. עכשיו שאל: אם הייתי מפצל את המשימה הזו ל-3 subagents במקום סוכן יחיד, כמה זה היה עולה? הכפל ב-3 כקירוב ראשון, והוסף את ה-overhead של ה-handoff. המספר הזה הוא ה"מחיר הכניסה" שלך ל-orchestration — לפני שתחליט אם הוא שווה את הזמן שנחסך.
🛠️ תרגיל 2: בנה את מחשבון ה-cost ומצא את נקודת ההתפוצצות
המטרה: להפעיל את estimate_run_cost על רשת תרחישים ולזהות איפה העלות "מתפוצצת".
- הרץ את הפונקציה על כל הצירופים:
n_subagents ∈ {1,3,5}×turns ∈ {5,10,20,40}. - הדפס טבלה של total_tokens ו-est_cost_usd לכל צירוף.
- סמן את הצירוף הראשון שעובר 500K tokens — זו "נקודת ההתפוצצות" שלך, מעליה צריך אישור מודע.
- הוסף שורה שמתרגמת ל-₪ (כפול ~3.7 + 17% מע"מ).
5 subagents × 20 turns → 1.15M tok → $1.05 → 4.55₪.
Claude Agent Teams — מקביליות עם תקשורת בזמן אמת
עד עכשיו ה-supervisor שלנו היה היררכי: supervisor מדבר ל-workers, workers לא מדברים ביניהם. זה דפוס supervisor→worker. Claude Agent Teams מוסיף משהו חדש: worker↔worker — סוכנים מקבילים שמתקשרים ביניהם בזמן אמת, לא רק דרך ה-supervisor.
לפי course.research.json, Claude Agent Teams הוא "a design primitive in Claude Code that allows dedicated, parallel Claude instances to act as ephemeral teammates communicating directly in real-time", שיצא ל-GA בפברואר 2026 לצד Claude Sonnet 4.6. ה-key update המלווה: Anthropic הדגימה את היכולת על ידי בניית C compiler פונקציונלי עם 16 סוכני Claude מקבילים על פני 2,000 sessions.
סוכני Claude מקבילים בנו C compiler פונקציונלי על פני 2,000 sessions — ה-case study של Anthropic שמדגים את ה-scale של Agent Teams (course.research.json).
מה ה-case study הזה מלמד, ומה הוא לא מלמד:
- מה כן: בעיות שניתנות לחלוקה ברורה (compiler = lexer + parser + codegen + optimizer + tests) מתאימות במיוחד ל-Agent Teams. כל סוכן בעל אחריות מוגדרת, והם מתאמים בזמן אמת על הממשקים ביניהם.
- מה לא: 16 סוכנים × 2,000 sessions זה הרבה tokens. זה הדגמה של יכולת, לא של חיסכון. אל תסיק מזה ש"יותר סוכנים = יותר טוב". הסק שכש-הבעיה מתחלקת באמת, scale אפשרי — אבל מי שמשלם הוא אתה, ולכן ה-budget caps מהסעיף הקודם הם לא אופציה.
פירוט קצר על ה-compiler case study, כי הוא מלמד על הארכיטקטורה: ה-16 סוכנים חולקו לתפקידים — כל אחד אחראי על "שכבה" של ה-compiler (lexing, parsing, type-checking, code generation לכל target, optimization, tests). הם תיאמו בזמן אמת על ה-AST format, על ה-symbol table API, ועל ה-IR (intermediate representation). זה בדיוק המקרה שבו peer-to-peer קריטי: parser ו-type-checker חייבים להסכים על ה-AST node structure תוך כדי, לא דרך supervisor שמתווך. עם תיווך, כל שינוי ב-AST דורש round-trip דרך ה-supervisor, שמאט את הצוות בסדר גודל. ה-case study הזה מוכיח שבבעיות מצומדות, ה-peer channel הוא must, לא nice-to-have.
ההבדל המעשי בין supervisor heirarchy ל-Agent Teams:
| תכונה | Supervisor→Worker (בנינו ידנית) | Claude Agent Teams |
|---|---|---|
| תקשורת | רק דרך ה-supervisor | worker↔worker בזמן אמת |
| תיאום ממשקים | ה-supervisor מתווך | הסוכנים מתאמים ישירות |
| שליטה | מלאה — אתה כתבת את הלולאה | primitive מנוהל של Claude Code |
| מתאים ל- | fan-out פשוט, תת-משימות עצמאיות | בעיות שדורשות תיאום הדוק בין חלקים |
ב-Agent Teams, סוכן ה-parser יכול לשלוח לסוכן ה-codegen הודעה "סיימתי את ה-AST node הזה, החתימה היא X" בלי שה-supervisor יתווך. זה מקצר את לולאת התיאום דרמטית בבעיות מצומדות. (התיאור הוא דוגמה מייצגת של ה-pattern; ה-API המדויק לתקשורת peer מתפתח — אמת מול code.claude.com/docs/en/agent-teams.)
למה ה-peer communication חשוב מבחינה הנדסית, ולא רק "נחמד"? בדפוס supervisor→worker, כל פיסת מידע שעוברת בין שני workers חייבת לעשות מסלול של שתי קפיצות: worker A → supervisor → worker B. זה אומר שה-supervisor חייב להחזיק בראש את כל מצב התיאום של כל הזוגות, וה-context שלו תופח. בבעיה צמודה עם 5 חלקים, יש 10 זוגות פוטנציאליים של תיאום — וכולם עוברים דרך צוואר בקבוק אחד. עם peer communication, A מדבר ל-B ישירות, ה-supervisor משוחרר לתפקיד שלו (פירוק ואיחוד), וה-context שלו נשאר רזה. זה בדיוק למה Anthropic בחרה ב-Agent Teams ל-compiler: lexer, parser, ו-codegen חייבים להסכים על ממשקים תוך כדי, ולתעל את כל זה דרך supervisor אחד היה הופך אותו לצוואר בקבוק.
אבל שים לב למה זה לא מלמד. Agent Teams הוא primitive מנוהל של Claude Code — אתה לא כותב את לולאת התקשורת, Claude Code מנהל אותה. זה נוח, אבל זה גם אומר שאתה מאבד חלק מהשליטה הגרעינית שבנית בקורס (הלולאה המפורשת, ה-state management הידני). זו אותה דילמת control מול convenience שתחזור בפרק 8 כשתחליט in-process מול hosted. ל-fan-out פשוט של תת-משימות עצמאיות, ה-supervisor→worker הידני שכתבת נותן לך יותר שליטה בזול. ל-תיאום הדוק בין חלקים מצומדים, Agent Teams חוסך לך לכתוב מנגנון תקשורת שלם — בתמורה לשליטה.
⚠️ טעות נפוצה: להשתמש ב-Agent Teams גם כשאין צורך אמיתי ב-peer channel
ה-convenience של Agent Teams מפתה: "למה לא להריץ 4 סוכנים מקבילים שמדברים ישירות?" התשובה: כי ה-peer channel הוא לא בחינם — הוא מנוהל על ידי Claude Code (לא אתה), מוסיף latency לכל הודעה, ומקשה על observability. אם התת-משימות שלך עצמאיות (3 סקירות קבצים, 10 סיכומי מאמרים), supervisor→worker פשוט עדיף. ה-peer channel שווה את המחיר רק כשיש ממשק משותף שדורש תיאום תוך-כדי.
⚡ Do Now
שאל את עצמך על המשימה שלך: האם החלקים צריכים לדבר תוך כדי עבודה (כמו parser↔codegen), או שכל אחד עובד בנפרד ומחזיר תוצאה (כמו 3 סקירות קבצים)? אם הם צריכים לדבר תוך כדי → Agent Teams. אם לא → supervisor→worker ידני זול ופשוט יותר. כתוב בשורה אחת לאיזה צד המשימה שלך נוטה.
V2 Session API — send/stream מנותקים וניהול sessions
כדי לתזמר sessions מרובים, אתה צריך שליטה גרעינית יותר ממה ש-query() המונוליטי נותן. כאן נכנס ה-V2 Session API. לפי course.research.json זוהי "a modernized preview API within the Claude Agent SDK that shifts away from the single, monolithic query() generator to decoupled send() and stream() methods", ומאפשרת "create, resume, list, and fork" של sessions.
למה זה משנה ל-orchestration:
send()/stream()מנותקים — במקום generator יחיד שמחזיק את כל הריצה, אתה שולח הודעה ומקבל stream בנפרד. זה מאפשר ל-supervisor להחזיק כמה sessions פתוחים בו-זמנית ולנהל אותם בלי שכל אחד יחסום את האחר.createSession/resumeSession/listSessions— ניהול מחזור החיים של sessions.resumeSessionהוא הבסיס ל-durable: אתה יכול לעצור subagent, לשמור את ה-session id, ולהמשיך אותו מאוחר יותר.forkSession— היכולת המעניינת ביותר ל-orchestration. אתה לוקח session בנקודה מסוימת ו"מפצל" אותו לכמה מסלולים שונים מאותה נקודת התחלה. שימוש קלאסי: לנסות 3 גישות שונות לאותה בעיה במקביל, מאותו context משותף, ולבחור את הטובה ביותר.
# דפוס fork — לנסות כמה מסלולים מאותה נקודה (סכמטי; אמת מול ה-docs)
from claude_agent_sdk import unstable_v2_createSession, unstable_v2_forkSession
async def explore_approaches(base_goal: str, approaches: list[str]):
# 1. session בסיס משותף — מבצע את ה-setup פעם אחת
base = await unstable_v2_createSession(system_prompt="...")
await base.send(base_goal) # context משותף שכל ה-forks יורשים
# 2. fork לכל גישה — כל fork יורש את ה-base אבל מתפצל ממנו
forks = [await unstable_v2_forkSession(base.id) for _ in approaches]
# 3. כל fork מנסה מסלול אחר, במקביל — עם budget cap לכל אחד
results = await asyncio.gather(*[
run_fork(fork, approach, budget_tokens=30_000)
for fork, approach in zip(forks, approaches)
])
return pick_best(results)
בנוסף ל-fork, resumeSession פותח את דפוס ה-durable orchestration. הנה דוגמה מעשית — pipeline שעוצר ל-human-approval:
# resume pattern — subagent שמחכה לאישור אנושי
from claude_agent_sdk import unstable_v2_createSession, unstable_v2_resumeSession
async def approval_gated_subagent(plan: str, approver_id: str):
# 1. צור session, שלח תוכנית, בקש אישור
session = await unstable_v2_createSession(
system_prompt="אתה agent שמבצע רק אחרי אישור אנושי")
await session.send(f"הכן תוכנית עבור: {plan}")
plan_text = await session.stream_until_text()
# 2. שמור session_id לפני שה-process יורד — כאן durable מתחיל
session_id = session.id
save_to_db("pending_approvals", session_id=session_id,
plan=plan_text, approver=approver_id, status="waiting")
# 3. חזור מאוחר יותר (אולי שעות) והמשך
# resume_session = await unstable_v2_resumeSession(session_id)
# await resume_session.send(f"האישור התקבל. בצע: {plan_text}")
return session_id
זה בדיוק ה-pattern שמאפשר lead-enrichment של ה-capstone: הסוכן מכין טיוטת email, נשמר ב-DB עם status "pending", וכשהאדם מאשר ב-UI, ה-process חוזר וממשיך אותו session — בלי לאבד את הקונטקסט של ההכנה.
⚠️ טעות נפוצה: לקבע קוד לחתימות של V2 Session API כאילו הן יציבות
שם ה-API הוא unstable_v2_* מסיבה. course.research.json מסמן זאת מפורשות ב-freshness_sensitive_areas: "The API signatures are subject to change as they transition out of preview." אם תפזר קריאות unstable_v2_createSession ישירות בכל ה-harness, השדרוג הבא יישבר אצלך בעשרות מקומות. עטוף את ה-V2 API בשכבת abstraction דקה משלך (מודול sessions.py עם create / resume / fork שלך), כך ששינוי חתימה ייגע במקום אחד בלבד.
🧭 Framework: query() מול V2 Session API
אם אתה מריץ סוכן יחיד עם משימה ליניארית פשוטה — אז query() המונוליטי מספיק ויציב (לא preview).
אם אתה מתזמר כמה sessions במקביל, צריך resume לאחר pause, או fork למסלולים מרובים — אז V2 Session API, אבל מאחורי abstraction דקה כי הוא preview.
אם אתה בונה production שחייב יציבות עכשיו — אז שקול אם ה-fork/resume שווה את סיכון ה-freshness, או אם query() + ניהול state ידני מספיק לבינתיים.
Handoff בין סוכנים — להעביר context ממוקד, לא את הכל
זו הנקודה שמחברת את הפרק הזה ישירות ל-context management מפרק 3, והיא אחת הטעויות הכי יקרות ב-orchestration. כשאתה מפצל subagent, מה אתה מעביר לו?
הפיתוי: "אעביר לו את כל ההיסטוריה של ה-supervisor, ליתר ביטחון, שיהיה לו את כל ההקשר." זו טעות שעולה משולש:
- מנפח את ה-context של ה-subagent — הוא מתחיל עם 30K tokens של היסטוריה לא רלוונטית במקום 2K של המשימה שלו.
- מאיץ compaction (פרק 3) — ה-subagent מגיע לסף ה-83.5% מהר יותר, וההיסטוריה שלו נדחסת — אולי דווקא החלק החשוב.
- שורף tokens — אתה משלם על העברת אותו context שוב ושוב, לכל subagent בנפרד.
ה-handoff הנכון מעביר רק את ה-context הממוקד למשימה: ה-task string, ה-constraints הרלוונטיים, וה-references (לא התוכן המלא) ל-state חיצוני אם צריך. הנה מודול handoff מינימלי:
def build_handoff(subtask: str, shared_constraints: list[str],
refs: dict | None = None) -> str:
"""בונה context ממוקד ל-subagent — לא את כל ההיסטוריה."""
parts = [f"המשימה שלך (וזו בלבד): {subtask}", ""]
if shared_constraints:
parts.append("אילוצים שחלים על כל הצוות:")
parts += [f" - {c}" for c in shared_constraints]
if refs:
# references ל-store חיצוני, לא התוכן עצמו (פרק 3 — scratchpad)
parts.append("\nמקורות זמינים (טען לפי צורך):")
parts += [f" - {k}: {v}" for k, v in refs.items()]
parts.append("\nאל תניח context שלא נמסר לך כאן. שאל אם חסר.")
return "\n".join(parts)
# בשימוש מתוך ה-supervisor:
handoff = build_handoff(
subtask="סקור את src/auth/ לבעיות אבטחה",
shared_constraints=["דווח רק high/critical", "JSON output בלבד"],
refs={"security_policy": "file://policies/sec.md"}, # reference, לא תוכן
)
שים לב: ה-refs מעבירים הצבעה ל-store חיצוני (קובץ, KV) — בדיוק ה-scratchpad / working memory מפרק 3. ה-subagent טוען את התוכן רק אם הוא צריך אותו, במקום לקבל 50KB של policy ב-context ההתחלתי.
שלושה דפוסי handoff — בחר לפי המשימה
לא כל ה-handoffs זהים. יש שלושה דפוסים מובחנים, ובחירה בדפוס הלא-נכון היא מקור ההפתעות:
- Minimal handoff (task + constraints) — הקצר ביותר. מתאים ל-subagents שעובדים על קובץ או פיסת קוד עצמאית, בלי צורך ב-context חיצוני. הסיכון: ה-subagent עלול להחליט בלי context קריטי (למשל, "למחוק" קובץ בלי לדעת שהוא שייך לפיצ'ר פעיל).
- Structured handoff (task + constraints + structured payload) — הוספת payload מובנה (JSON) עם הקשר ספציפי. למשל, ה-supervisor מעביר רשימת 5 קבצים רלוונטיים ב-JSON, ולא את כל ה-repo. זה הדפוס המומלץ לרוב המקרים — מאזן בין בהירות לעלות.
- Resumable handoff (task + constraints + handoff_id) — ה-subagent מקבל
handoff_idשמצביע ל-state ב-DB. הוא יכול לקרוא, לעבוד, ולכתוב בחזרה ל-DB. זה מה שמאפשר resume אחרי crash (durable) או המשך על ידי subagent אחר (handoff בין sessions). הסיכון: coupling לסכמת ה-DB.
כלל אצבע מעשי: אם ה-subagent שלך יכול לסיים את המשימה בלי לדבר עם אף אחד אחר, minimal מספיק. אם הוא צריך רק "את הנתונים האלה", structured. אם הוא צריך "לדבר" עם העולם (לכתוב, לקרוא, לחכות לאישור) — resumable.
⚠️ טעות נפוצה: להעביר ל-subagent את כל ההיסטוריה של ה-supervisor
"ליתר ביטחון" זה הנימוק, וזה תמיד יקר. מנפח את ה-context של ה-subagent, מאיץ compaction (פרק 3), ושורף tokens מיותרים בכל פיצול. מעבירים רק את ה-context הממוקד למשימה — task + constraints + references. אם ה-subagent צריך עוד, שיבקש; זול יותר מלהציף אותו מראש.
⚡ Do Now
תרגם את ה-handoff הנוכחי שלך (אם יש) לשלושת הדפוסים: minimal / structured / resumable. עבור כל אחד, רשום את ה-token overhead המוערך. אם ה-structured גדול ב-10× מה-minimal אבל חוסך שאלות הבהרה מאוחר יותר — ה-structured משתלם. אם הוא גדול ב-50× בלי חיסכון — חזור ל-minimal.
🛠️ תרגיל 3: השווה handoff מלא מול handoff ממוקד
המטרה: למדוד במספרים את ההפרש בין "העבר הכל" ל-"העבר ממוקד".
- קח את ה-supervisor מתרגיל 1 והוסף לו היסטוריה מלאכותית (~5,000 tokens של "שיחה קודמת").
- גרסה A: העבר ל-subagent את כל ההיסטוריה + המשימה. מדוד input tokens בקריאה הראשונה.
- גרסה B: השתמש ב-
build_handoffוהעבר רק task + constraints. מדוד input tokens. - חשב את ההפרש, וכפול במספר ה-subagents כדי לראות את החיסכון בצוות.
handoff_full: 5,400 input | handoff_focused: 380 input | חיסכון: 5,020 × 3 subagents = 15,060 tokens. תצוגה ויזואלית של הפער.
Observability לצוות — Trace לכל Subagent
זוכר את ה-Langfuse מפרק 5? כאן הוא הופך מ"נחמד שיש" ל"בלעדיו אי אפשר". כשסוכן יחיד נתקע, אתה יכול לקרוא את ה-print-ים. כשאחד מתוך 5 subagents מקבילים נתקע, בלי tracing אין לך מושג איזה, איפה, או כמה זה עלה. צוות בלי observability הוא קופסה שחורה כפול N.
הדפוס: trace אחד לכל הריצה, span נפרד לכל subagent, ו-sub-span לכל turn ו-tool call בתוכו. ככה אתה רואה את כל הצוות במבט אחד, יכול לפתוח כל subagent בנפרד, ולייחס token cost לכל ענף.
from langfuse import Langfuse
lf = Langfuse()
async def supervisor_traced(goal: str, subtasks: list[str]):
trace = lf.trace(name="multi-agent-run", input={"goal": goal})
async def run_one(task, idx):
# span לכל subagent — נראה בנפרד ב-Langfuse
span = trace.span(name=f"subagent-{idx}", input={"task": task})
result = await run_subagent(task, max_turns=8, budget_tokens=40_000)
span.update(output=result, metadata={"tokens": result["tokens"],
"status": result["status"]})
span.end()
return result
results = await asyncio.gather(*[run_one(t, i) for i, t in enumerate(subtasks)])
trace.update(output={"n_agents": len(results),
"total_tokens": sum(r["tokens"] for r in results)})
return results
עכשיו כשתפתח את ה-trace ב-Langfuse, תראה את ה-supervisor למעלה ושלושה spans מתחתיו — אחד לכל subagent, כל אחד עם ה-tokens, ה-status (done / budget_exceeded), וה-latency שלו. ה-subagent שנתקע בולט מיד באדום.
איך לקרוא trace: ה-rendering ב-Langfuse הוא עץ — trace למעלה, span לכל subagent מתחתיו, ובתוך כל span sub-spans לכל turn ו-tool call. צבע span = status: ירוק = done, צהוב = budget_exceeded (הקאפ עצר אותו — זה הצלחה, לא כישלון), אדום = error. המספר latency ליד כל span הוא זמן הקיר של אותו סוכן — subagents מקבילים חולקים את ציר הזמן, אז latency-הם לא מסתכמים אלא שווים ל-max ביניהם.
Token Attribution — לחלק את החשבון בצורה הוגנת
Tracing נותן לך נתונים גולמיים, אבל השאלה הניהולית היא: למי מיוחס ה-cost? בעולם production עם ריצות רבות, אתה צריך attribution ברור. שלוש שיטות נפוצות, ולכל אחת trade-off:
- By-subagent (הכי קל) — כל span עם ה-tokens שלו, הסיכום ב-trace. מתאים ל-debugging ולשיפור ביצועים ("איזה subagent צורך הכי הרבה?").
- By-supervisor (הכי הוגן ל-billing) — כל ה-tokens של הריצה (supervisor + כל ה-subagents) מיוחסים למי שהפעיל את הריצה. זה מה שתרצה לחייב לקוח או למדוד ROI.
- By-tenant (ל-multi-tenant) — חלוקה לפי לקוח/פרויקט. דורש metadata על ה-tenant בכל span. הכי מורכב, אבל הכי חשוב אם יש לך כמה לקוחות על אותו harness.
🔁 שגרת עבודה: Debug של צוות סוכנים
- פתח את ה-trace, לא את ה-logs. מבט-על על כל ה-spans לפני שאתה צולל לאחד.
- מיין לפי tokens. ה-subagent שצרך הכי הרבה הוא החשוד הראשון — בדרך כלל זה שנתקע בלולאה.
- בדוק status לפני content.
budget_exceededאומר שה-cap עצר אותו — טוב, המערכת עבדה. אם הואdoneאבל יקר, בעיה אחרת. - צלול ל-tool calls החוזרים. אם אותו tool call מופיע 6+ פעמים ב-span אחד — זו לולאה שה-circuit-breaker (פרק 5) היה אמור לתפוס; בדוק למה לא.
- השווה לריצה תקינה. trace קודם שעבד הוא ה-baseline שלך. מה שונה?
🛠️ תרגיל 4: הרץ ephemeral מול durable עם trace מלא
המטרה: ה-deliverable המרכזי — אותה משימה בשתי הגישות, עם token cost ו-tracing להשוואה.
- קח את ה-supervisor המ-traced מלמעלה. הרץ משימה של 3 תת-סקירות פעם אחת כ-ephemeral (sessions בזיכרון, נעלמים בסוף).
- הרץ אותה משימה כ-durable: שמור כל session id לקובץ, והדגם
resumeשל אחד מהם אחרי "עצירה". - פתח את שני ה-traces ב-Langfuse זה לצד זה. רשום: total tokens, latency, ו-overhead ה-state של durable.
- כתוב 3 שורות מסקנה: מתי ה-overhead של durable מוצדק ומתי לא.
ephemeral: 225K tok, 4.2s, אפס overhead | durable: 248K tok, 5.1s, +state DB + 3 שורות מסקנה.
מילון מונחים
- Supervisor / Orchestrator Loop
- סוכן-על שלא מבצע את העבודה אלא מפרק משימה לתת-משימות, מפצל subagents, ומאחד תוצאות. ה-harness שמעל ה-harnesses.
- Subagent
- סוכן עצמאי שה-supervisor מפצל למשימה ממוקדת. כל subagent הוא session נפרד לחלוטין — context, history ו-tools משלו. הבידוד הוא יתרון (context isolation) אך גם מקור העלות.
- Subagent Spawning
- פעולת יצירת subagent חדש מתוך ה-supervisor. כל spawn = session מלא נוסף, ולכן חייב
max_turnsו-budget_capמשלו. - Fan-out / Fan-in
- הדפוס הבסיסי ביותר: supervisor מפצל N subagents עצמאיים במקביל (fan-out), מחכה לכולם, ומאחד (fan-in). הכי קל לתמחר, הכי קל ל-trace.
- Pipeline / Chain Orchestration
- subagents בשרשרת שבה הפלט של אחד הוא הקלט של הבא. אין מקביליות אמיתית, אז הצדקה חייבת להיות context isolation (system prompts שונים מהותית).
- Hierarchical Orchestration
- supervisor שמפצל subagents שהם בעצמם supervisors. עץ. עלות מתפוצצת אקספוננציאלית — תקרה מעשית: 2 רמות, למעט מקרים חריגים (Anthropic compiler).
- Ephemeral Orchestration
- סוכנים שחיים רק לאורך session אחד ונעלמים עם ה-state שלהם. המודל של Claude Agent Teams. אפס תשתית state, מהיר להקים.
- Durable Orchestration
- סוכנים מוגדרי-קוד עם state database מתמשך ששורד crash והרצות חוזרות. המודל של LangGraph / Aden Hive. מתאים ל-pipelines ול-human-in-the-loop ארוך.
- Parallel Subagent Cost Explosion
- זינוק אקספוננציאלי בעלות ה-tokens כשמפצלים subagents מקבילים בלי תקרות — כל אחד session מלא, ו-turns × subagents הוא לא ליניארי. סיכון מסומן ב-course.research.json.
- Budget Cap
- תקרת tokens פר-subagent שעוצרת אותו כשהוא חורג — מעבר ל-
max_turns. הקו שמפריד בין צוות לחשבון מפתיע. - Claude Agent Teams
- primitive ב-Claude Code (GA פברואר 2026, עם Sonnet 4.6) של מופעי Claude מקבילים שמתקשרים בזמן אמת כ-teammates ארעיים — worker↔worker, לא רק supervisor→worker.
- Peer Communication
- תקשורת ישירה בין subagents מקבילים בלי תיווך של supervisor. קריטית לבעיות מצומדות (compiler: parser↔codegen). זמין ב-Claude Agent Teams, לא ב-supervisor→worker ידני.
- V2 Session API (unstable_v2_*)
- API בתצוגה מקדימה ב-Claude Agent SDK שמחליף את
query()המונוליטי ב-send()/stream()מנותקים, עםcreate / resume / list / forksessions. החתימות עלולות להשתנות. - forkSession
- פיצול session מנקודה מסוימת לכמה מסלולים שיורשים את אותו context בסיס — לנסות כמה גישות במקביל ולבחור את הטובה.
- resumeSession
- חידוש session שנעצר (pause, crash, או human-approval) מאותו state. הבסיס הטכני ל-durable orchestration.
- Handoff
- העברת context ממוקד מ-supervisor ל-subagent — task + constraints + references בלבד, לא כל ההיסטוריה. מונע ניפוח context והאצת compaction.
- Minimal / Structured / Resumable Handoff
- שלושה דפוסי handoff מובחנים: minimal (task+constraints בלבד), structured (עם payload JSON), resumable (עם handoff_id ל-DB). בחירה לפי מורכבות המשימה.
- Shared-State Pollution
- anti-pattern שבו subagents שונים משתפים dict/list ודורסים זה את זה. תוצאה לא-דטרמיניסטית, בלתי-ניתנת לדיבוג. state פר-subagent, תמיד.
- Circular Handoff
- anti-pattern שבו A→B→C→A בלי timeout. צריך hop-counter או graph חסר-מעגלים.
- Token Attribution
- הקצאת עלות ה-tokens לגורם אחראי — by-subagent (debug), by-supervisor (billing), by-tenant (multi-tenant). מודל metadata בכל span.
בדוק/י את עצמך
5 שאלות לפני שממשיכים לפרק 7
- מתי משימה לא צריכה צוות? תן את שלושת התנאים שמצדיקים צוות, ומה ברירת המחדל אם אף אחד מהם לא מתקיים.
- למה max-turns על ה-supervisor לבדו לא מספיק? הסבר מה יכול לקרות ל-subagent בודד בלי
budget_capמשלו. - ephemeral או durable ל-pipeline של lead-enrichment שעוצר לאישור אנושי שעלול לקחת שעות — ולמה?
- מה הבעיה בלהעביר ל-subagent את כל ההיסטוריה של ה-supervisor? מנה את שלוש העלויות.
- למה לעטוף את ה-V2 Session API ב-abstraction דקה במקום לקרוא ל-
unstable_v2_*ישירות בכל מקום?
💡 Just One Thing
אם תיקח מהפרק הזה דבר אחד: כל subagent הוא session נפרד לחלוטין — ולכן כל subagent חייב max-turns ו-budget cap משלו. לא ה-supervisor. כל אחד. זה הקו היחיד שמפריד בין צוות שעובד לחשבון שמתפוצץ בשקט. אם אתה מפצל subagent בלי שתי התקרות האלה, אתה לא בונה orchestration — אתה כותב צ'ק פתוח.
סיכום הפרק וגשר לפרק הבא
מה בנינו בפרק 6
- קריטריון הצוות — צוות מוצדק רק ב-parallelism, separation of concerns, או context isolation. אחרת: סוכן יחיד, תמיד.
- Supervisor loop — סוכן-על שמפרק → מפצל → מאחד, כש-subagent הוא ה-harness שכבר בנית, עכשיו עם תקרות משלו. גם ל-supervisor עצמו תקרה (גדולה יותר, אבל לא בלתי-מוגבלת).
- Anti-patterns — shared-state pollution, circular handoff, unbounded depth, untyped handoff, subagent-as-router. הכר אותם בשם לפני שתיתקע בהם.
- Ephemeral מול durable — session-bound (Agent Teams) מול persistent state (LangGraph / Aden Hive), עם טבלת החלטה שתחזיק לכל harness עתידי. ההיברידי (ephemeral בתוך, durable מסביב) הוא הדפוס הנפוץ ביותר.
- Cost explosion ו-budget caps — כל subagent הוא session נפרד; turns × subagents לא ליניארי; מחשבון cost דטרמיניסטי שרץ לפני spawn. תקרת parallelism מעשית: 3 subagents ללא הגבלה, 4-8 עם semaphore, 8+ בצורת batch.
- Claude Agent Teams — worker↔worker בזמן אמת, ה-case study של 16 סוכנים שבנו C compiler על פני 2,000 sessions — יכולת, לא חיסכון. שווה את המחיר רק כשיש ממשק משותף שדורש תיאום תוך-כדי.
- V2 Session API —
send/streamמנותקים,create/resume/list/fork, מאחורי abstraction דקה כי הוא preview. resume פותח את דפוס ה-durable. - Handoff ממוקד — task + constraints + references בלבד, לא כל ההיסטוריה — חיבור ישיר ל-context management מפרק 3. שלושה דפוסים (minimal / structured / resumable) לפי מורכבות המשימה.
- Observability לצוות — trace אחד, span לכל subagent, sub-span לכל turn ו-tool call. token attribution (by-subagent / by-supervisor / by-tenant) למי שצריך לחייב או למדוד ROI. בלעדיו צוות הוא קופסה שחורה כפול N.
🌉 הגשר לפרק 7 — זיכרון ו-Dreaming: עכשיו יש לך צוות שעובד. אבל בכל ריצה הוא מתחיל מאפס וחוזר על אותן טעויות. ה-failure capture מפרק 5 וה-handoff הממוקד מהפרק הזה הם בדיוק חומר הגלם ל-memory store מתמשך. בפרק הבא נחבר ל-harness זיכרון מובנה (decisions / failures / patterns), ונפעיל את Claude Dreaming — תהליך רקע אסינכרוני שסוקר transcripts, מחלץ patterns, ומאחד insights בחזרה לזיכרון. ונלמד לרסן את ה-Dreaming Token Burn: curation על עד 100 transcripts יכול לשרוף מיליוני tokens אם מתזמנים אותו תכף מדי. הצוות שלך עומד ללמוד מהטעויות שלו — בלי לפשוט את הרגל.
צ'קליסט סיום פרק
סמן/י כל סעיף לפני המעבר לפרק 7. אם משהו לא מסומן — חזור אליו, הוא יחזור להכאיב בפרק 8.
- בניתי supervisor loop שמפצל 2–3 subagents במקביל עם
asyncio.gather. - לכל subagent יש
max_turnsוגםbudget_tokensמשלו — לא רק ל-supervisor. - גם ל-supervisor עצמו יש
max_turnsו-budget_tokens(גדולים יותר, אבל לא בלתי-מוגבלים). - אני יכול/ה לנמק במשפט אחד מתי משימה צריכה צוות ומתי סוכן יחיד עדיף.
- אני מכיר/ה את 5 ה-anti-patterns (shared-state, circular, unbounded-depth, untyped, subagent-as-router) ובודק/ת את הקוד שלי מולם.
- יש לי טבלת החלטה ephemeral↔durable, ואני יודע/ת לאיזה צד ה-capstone שלי נוטה ולמה.
- החלטתי האם ה-capstone שלי היא "ephemeral בתוך, durable מסביב" או דפוס אחר.
- בניתי מחשבון cost דטרמיניסטי שרץ לפני spawn ומזהה את נקודת ההתפוצצות (500K+).
- תרגמתי את ה-cost-per-run ל-₪ כולל מע"מ ושער USD (הערת שוק מקומי).
- אני מבין/ה את ההבדל בין supervisor→worker ל-worker↔worker (Agent Teams), ומתי כל אחד.
- עטפתי כל קריאת
unstable_v2_*בשכבת abstraction דקה (sessions.py) — לא פיזרתי אותן. - אני יודע/ת מתי להשתמש ב-
resumeSession(durable orchestration, pause-for-approval) ומתי ב-forkSession(כמה גישות לאותה בעיה). - מודול ה-handoff שלי מעביר task + constraints + references בלבד — לא את כל ההיסטוריה.
- בחרתי בין minimal / structured / resumable handoff לפי מורכבות המשימה.
- מדדתי במספרים את החיסכון של handoff ממוקד מול handoff מלא (תרגיל 3).
- כל subagent מקבל span נפרד ב-Langfuse, ואני יכול/ה לפתוח כל אחד ולראות tokens + status.
- החלטתי על מודל token attribution (by-subagent / by-supervisor / by-tenant) לפי הצורך שלי.
- הרצתי את אותה משימה ephemeral מול durable והשוויתי traces (תרגיל 4 — deliverable מרכזי).
- בדקתי שה-circuit-breaker מפרק 5 תופס לולאת tool גם בתוך subagent, לא רק בסוכן יחיד.
- ענה/תי על כל 5 שאלות ה-check-yourself בלי לחזור לטקסט.
- סימנתי את אזורי ה-freshness (V2 Session API ב-preview) שצריך לאמת מול ה-docs לפני production.