4 Skill-Building · פרק 4 מתוך 8

ה-Loop שעובד בזמן שאתה ישן: Cron + Eval + Reflexion

עד עכשיו כתבת לסוכן תפקיד (פרק 2) ו-SOP מדויק (פרק 3). אבל סוכן שמחכה שתפעיל אותו הוא עדיין עובד שמחכה להוראה. בפרק הזה נבנה את מנוע השיפור של הצי: סוכן שרץ לבד בתזמון (cron), מנקד את עצמו מול דוגמאות-זהב (eval gate), וכותב לעצמו לקח כדי שמחר הוא יהיה טוב יותר מהיום (reflexion) — בלי שתיגע בו.

🧵 חוט הפרויקט (Project Thread)

מאיפה באנו: בפרק 3 כתבת SOP אגנטי מלא לתפקיד אחד (Support או SDR), בפורמט Markdown עם RFC 2119, אחסנת אותו ב-.context/ ב-git, ועשית לו dry-run על 3–5 מקרים אמיתיים מהחודש שעבר. שמור את תוצרי ה-dry-run האלה ליד — הם הופכים לדלק של הפרק הזה.

איפה אנחנו עכשיו: ניקח את ה-SOP הזה ונהפוך אותו ל-loop שרץ לבד. הסוכן יקבל שעון (cron), מבחן (eval gate שבנוי מתוצרי ה-dry-run), ויומן לקחים (reflexion). התוצר: סוכן morning brief חי שמתחיל לך את היום כ-operator במקום כמכבה-אש.

לאן ממשיכים: בפרק 5 ("Owner-as-Operator") ניקח את ה-pass-rate של ה-eval gate שנבנה כאן ונשתמש בו כסף שמטפסים בו בסולם האוטונומיה — מתי מותר לתת לסוכן לפעול לבד בלי שתאשר כל פעולה. ה-loop הזה הוא התנאי המקדים לכל ניהול ה-autonomy שאחריו.

🎯 מטרות למידה

בסוף הפרק הזה תוכל/י:

  1. להגדיר תזמון לסוכן — cron (זמן), heartbeat, event-driven או adaptive — ולבחור בין self-host (Hermes Agent) ל-managed (Claude Code Routines), כולל קובץ crontab עובד.
  2. לבנות eval gate: golden dataset של 10+ מקרים מתוצרי ה-dry-run של פרק 3, ו-scorer אוטומטי שמחזיר pass/fail וחוסם regression לפני שהפלט מגיע ללקוח.
  3. להטמיע loop של reflexion (Actor → Evaluator → Self-Reflection) שכותב "לקח" טקסטואלי לזיכרון אפיזודי, כך שהריצה ה-10 רצה טוב יותר מהראשונה — בלי אימון מחדש של המודל.
  4. להבטיח idempotency, dead-agent alerting ו-review של reflections — שלושת המנגנונים שמונעים את שלושת כשלי-הליבה: עבודה כפולה, daemon שמת בשקט, ולקחים מורעלים.

📋 דרישות קדם

📦 תוצרים (Deliverables)

בסוף הפרק יהיו לך שלושה תוצרים ממשיים, שמורים ב-git:

  1. סוכן morning_brief חי — cron שרץ ב-06:00 ומושך הכנסות, תור-תמיכה, מהלכי-מתחרים ו-inbox ל-digest אחד בעברית (Telegram או email), עם נתוני ₪.
  2. Eval gate עובד — golden dataset של 10+ מקרים (מתוצרי ה-dry-run של פרק 3) + scorer אוטומטי שמנקד כל פלט ומחזיר pass/fail עם סיבה.
  3. loop reflexion מתועד — אחרי כל ריצה הסוכן כותב "לקח" ל-.context/lessons.md, עם צעד review שמונע לקח מורעל. כולל הדגמת "לפני/אחרי" שמראה שיפור מדיד.

למה "סוכן שמחכה לך" הוא עדיין צוואר בקבוק

בוא נחזור רגע לבעיה שפתחנו בה את הקורס. בפרק 1 אמרנו: אם אתה פותח ChatGPT, מבקש משימה, מקבל תשובה, וחוזר חלילה — אתה לא operator, אתה עדיין צוואר הבקבוק היחיד. בפרק 2 נתת לסוכן תפקיד. בפרק 3 נתת לו נוהל. אבל יש משהו ששני הפרקים האלה לא פתרו: מי לוחץ על "הפעל"?

כל עוד אתה זה שמפעיל את הסוכן, הצי לא באמת רץ — הוא מחכה לך. וזה אומר שהוא לא עובד בלילה, לא עובד בשבת, ולא עובד כשאתה אצל לקוח. סוכן שדורש שתתחיל אותו הוא לא עובד עם תפקיד — הוא כלי עם ממשק יפה. ההבדל בין "כלי" ל"צוות" הוא בדיוק הדבר שנבנה בפרק הזה: ה-loop.

ה-loop הוא שלושה רכיבים שמחוברים למעגל אחד:

שים לב לסדר ההגיוני: cron בלי eval = סוכן שרץ לבד אבל אף אחד לא בודק אותו (מתכון לאסון). cron + eval בלי reflexion = סוכן שלא שובר את אותו ראש פעמיים אבל גם לא לומד — הוא נופל באותה מהמורה כל בוקר. שלושתם ביחד הם מנוע שיפור עצמי: רץ, נבדק, לומד, חוזר. זה ה-loop ש"עובד בזמן שאתה ישן" — וגם משתפר בזמן שאתה ישן.

⚠️ טעות נפוצה: לבלבל "רץ לבד" עם "משתפר לבד"

הרבה מדריכים מלמדים רק את החלק של ה-cron — "הנה איך מתזמנים סוכן". אבל סוכן מתוזמן בלי eval הוא סוכן שעושה את אותה טעות 30 פעם בחודש, אוטומטית, בלי שתדע. בלי reflexion הוא לעולם לא משתפר. שלושת הרכיבים הם חבילה אחת. אם אתה מתזמן בלי לנקד ובלי ללמוד — בנית מכונה שמייצרת את אותו פלט בינוני בקנה מידה.

⚡ עשה עכשיו (2 דקות)

פתח את ה-SOP שכתבת בפרק 3 וענה בכתב על שלוש שאלות:

  1. מתי התפקיד הזה צריך לרוץ? (כל בוקר? כשמגיע מייל? פעם בשעה?) — זה ה-cron שלך.
  2. איך תדע אם הפלט טוב? (מה צריך להופיע בו? מה אסור שיופיע?) — זה ה-eval שלך.
  3. מה הוא יכול ללמוד מטעות? (איזה סוג פספוס חוזר על עצמו?) — זה ה-reflexion שלך.

שמור את שלוש התשובות. אנחנו נהפוך אותן לקוד עד סוף הפרק.

רכיב 1 — Cron: חמשת דפוסי התזמון של 2026

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

דפוסמתי מפעילמתאים ל...
Cron (זמן)בשעה קבועה — "כל יום ב-06:00"morning brief, דוח שבועי, גיבוי
Event-drivenכשקורה משהו — מייל נכנס, טופס נשלחSDR שמגיב ל-lead חדש, תמיכה שמגיבה לפנייה
Heartbeatבקצב קבוע + מדווח "אני חי" — כל 5 דקותmonitor שבודק שהמערכת למעלה
Queue / backlog drainכשיש עבודה בתור, עד שהתור ריקעיבוד אצווה של לידים שהצטברו
Adaptiveהסוכן בוחר בעצמו מתי לרוץ שובresearch agent שמחליט אם יש סיבה לבדוק שוב

לרוב המקרים של operator סולו, cron הוא ברירת המחדל הנכונה. הוא הכי פשוט, הכי צפוי, והכי קל לנפות. נתחיל ממנו, ונשאיר event-driven ו-adaptive לתפקידים שבאמת צריכים אותם (פרק 7 חוזר לזה כשנרכיב את הצי המלא).

קריאת crontab — התחביר ב-5 שדות

cron הוא תוכנת תזמון ותיקה שקיימת בכל מערכת לינוקס/Mac. אתה כותב שורה אחת בקובץ שנקרא crontab, והמערכת מריצה את הפקודה שלך בזמן שביקשת. התחביר הוא חמישה שדות + הפקודה:

# ┌───────── דקה (0-59)
# │ ┌─────── שעה (0-23)
# │ │ ┌───── יום בחודש (1-31)
# │ │ │ ┌─── חודש (1-12)
# │ │ │ │ ┌─ יום בשבוע (0-6, 0=ראשון)
# │ │ │ │ │
# * * * * *  command-to-run

# כל יום ב-06:00 בבוקר — זה ה-morning brief שלנו:
0 6 * * *  cd /home/you/fleet && /usr/bin/python3 morning_brief.py >> logs/brief.log 2>&1

שלוש מילים על השורה האחרונה, כי בה מסתתרות שתי טעויות-זהב:

💡 דוגמה: כמה ביטויי cron נפוצים
0 6 * * *      → כל יום ב-06:00
*/15 * * * *   → כל 15 דקות (heartbeat)
0 9 * * 1      → כל יום שני ב-09:00 (דוח שבועי)
0 */2 * * *    → כל שעתיים
30 8 1 * *     → ה-1 בכל חודש ב-08:30 (P&L חודשי)

אם אתה לא בטוח, האתר crontab.guru (חינמי) מתרגם כל ביטוי לאנגלית פשוטה. דוגמה מייצגת — אמת מול האתר.

Self-host מול Managed: איפה רץ השעון

יש לך שתי דרכים להריץ סוכן מתוזמן, ולכל אחת trade-off ברור:

Self-host (Hermes Agent)Managed (Claude Code Routines)
איפה רץdaemon על שרת שלך (או Raspberry Pi בבית)על תשתית Anthropic, בלי תהליך לוקלי
עלות infra$0 (אתה משלם רק טוקנים)טוקנים + שימוש בפלטפורמה
זיכרון cross-sessionכן — נשמר ב-daemonתלוי-פלטפורמה
הסיכון העיקריאם השרת/daemon נופל — הסוכן מת בשקטתלות בספק; פחות שליטה
מתאים ל...מי שנוח לו עם שרת ורוצה $0 ובעלות מלאהמי שרוצה "להדליק ולשכוח" בלי לתחזק שרת

Hermes Agent (של Nous Research, שוחרר ב-25 בפברואר 2026) הוא daemon פתוח (open-source) שרץ באופן רציף, מתזמן משימות cron, ושומר זיכרון בין סשנים. "daemon" הוא תהליך-רקע שרץ כל הזמן בלי חלון — כמו עובד שיושב בשרת ולא הולך הביתה. היתרון: $0 על infra, אתה משלם רק את הטוקנים של ה-LLM מתחת. החיסרון, וזה קריטי: daemon ש"רץ בזמן שאתה ישן" גם נכשל בזמן שאתה ישן — נחזור לזה בחלק ה-gotchas.

Claude Code Routines הוא הצד השני: תזמון מרחוק על תשתית Anthropic, בלי שתשאיר מחשב דלוק. זה מוריד את הרף ל-operator שלא רוצה לתחזק שרת. ה-trade-off הוא פחות שליטה ותלות בספק.

🧭 Framework: איזה דפוס תזמון לבחור

השתמש בכלל ההחלטה הבא לכל תפקיד בצי:

🧭 Framework: cron מול event-driven — ארבע שאלות שמכריעות

הבלבול בין cron ל-event-driven הוא הטעות הכי יקרה בתזמון. השאל ארבע שאלות על כל תפקיד:

עלות-תועלת מהירה: cron כל שעה על תפקיד שבדרך כלל לא עושה כלום = 720 הרצות חסרות-ערך בחודש. אם כל הרצה עולה ₪0.05 בטוקנים — זה ₪36/חודש על שקט. event-driven היה עולה ₪0 עבור הרצות שאין בהן אירוע.

⚡ עשה עכשיו (5 דקות) — שורת ה-cron הראשונה שלך

ב-Terminal (Mac/Linux), הרץ crontab -e. הדבק את השורה הבאה, ושנה את הנתיב לתיקייה האמיתית שלך:

*/5 * * * * cd /YOUR/PATH/fleet && echo "alive: $(date)" >> heartbeat.log

זה רץ כל 5 דקות וכותב חותמת-זמן לקובץ. חכה 10 דקות, ואז cat heartbeat.log. אם רואים שתי שורות — cron עובד אצלך. מחק את השורה אחר כך (crontab -e ולמחוק). זה ה-Hello World של תזמון.

⚡ עשה עכשיו (3 דקות) — בדוק idempotency לפני שזה כואב

לפני שתתזמן כל סוכן, ענה על שאלה אחת: "אם הוא ירוץ פעמיים ברצף על אותו קלט — מה יקרה?"

כלל האגודל: כל פעולה שמגיעה ל"בחוץ" (שולחת, כותבת, מחייבת) חייבת idempotency. כל דבר שנשאר "פנימה" (דוח לוג, קובץ מקומי) — אפשר לסבול כפילות זמנית.

רכיב 2 — Eval Gate: לשמור איכות בלי לקרוא כל פלט

עכשיו הסוכן רץ לבד. אבל אם הוא רץ לבד — מי בודק שהפלט שלו טוב? אתה לא יכול לקרוא כל מייל שה-SDR שולח וכל סיכום שה-research agent מייצר. אם תקרא הכל, חזרת להיות צוואר הבקבוק. אם לא תקרא כלום, אתה משגר פלט בינוני (או שגוי) ללקוחות, אוטומטית, בקנה מידה.

הפתרון הוא eval gate: scorer אוטומטי שמנקד את הפלט מול golden dataset — אוסף של דוגמאות-ייחוס שאתה כבר יודע שהן טובות — לפני שהפלט מגיע ללקוח. הוא יושב כמו שער בין "הסוכן ייצר" ל"הפלט יוצא". פלט שעובר את הסף — ממשיך. פלט שנכשל — נחסם, מסומן, או מנותב אליך.

📊 הרווחים האמיתיים מ-eval-driven loops (מאומת)

במאמר Reflexion (Shinn et al., arXiv 2303.11366) — שאליו נגיע מיד — eval שמשמש כ-feedback signal הביא לשיפורים מדידים: קוד ב-HumanEval מ-80% ל-91%, AlfWorld +22% על פני 12 צעדים, HotPotQA +20%. ה-eval הוא לא בירוקרטיה — הוא ה-signal שבלעדיו אין שיפור.

בלי golden set, ה-eval שלך הוא סתם LLM שמנחש

זאת הנקודה הכי חשובה בכל החלק הזה, אז בוא נאט. הרבה אנשים "מנקדים" פלט של סוכן על-ידי לשאול LLM אחר "האם זה טוב?". זה לא eval — זה ניחוש. ה-LLM השני לא יודע מה אתה מחשיב כטוב, אין לו נקודת ייחוס, והוא ייתן ציון שונה בכל פעם.

eval אמיתי מנקד מול דוגמאות-זהב. וכאן מתחבר פרק 3: תוצרי ה-dry-run שעשית — 3–5 מקרים אמיתיים מהחודש שעבר שעליהם הרצת את ה-SOP וחידדת אותו — הם הזרע של ה-golden set שלך. הרחב אותם ל-10+ מקרים, ויש לך נקודת ייחוס אמיתית. בלי golden set, כמו שאומר המחקר במפורש: ה-"eval" הוא רק עוד LLM שמנחש, והוא לא יתפוס regression אמיתי.

⚠️ טעות נפוצה: מדדים שגויים ב-golden set — "must_contain" שתמיד עובר

הטעות הנפוצה ביותר בבניית golden set: כותבים must_contain שקל מדי לעבור. למשל, לבדוק שה-brief מכיל "הכנסות" — אבל כל brief שפוי יכיל את המילה הזאת, גם ה-brief הגרוע ביותר. ה-scorer מחזיר 100%, ואתה חושב שהכל עובד מעולה.

הכלל: must_contain חייב להכיל ערכים ספציפיים מה-input — המספר האמיתי (1,240), השם האמיתי (מתחרה X), הנושא האמיתי (חידוש). מילה כללית ("הכנסות", "תמיכה") היא לא מדד — היא רעש. golden case שמת בדיקה שתמיד עוברת הוא לא golden case; הוא אשליה של איכות. בדוק: אם תיתן input מזויף לחלוטין, האם עדיין יעבור? אם כן — המדד שבור.

איך נראה golden case

כל פריט ב-golden dataset הוא קלט + מה אתה מצפה לראות בפלט. דוגמה ל-morning brief:

// golden/brief_cases.json — golden dataset ל-morning brief
[
  {
    "id": "case_01_normal_day",
    "input": {
      "revenue_today_ils": 1240,
      "support_open": 3,
      "competitor_signals": ["מתחרה X הוריד מחיר ל-99₪"],
      "inbox_highlights": ["לקוח Y שואל על חידוש"]
    },
    "must_contain": ["1240", "3 פניות", "חידוש"],
    "must_not_contain": ["undefined", "null", "[object Object]"],
    "max_words": 180
  },
  {
    "id": "case_02_empty_day",
    "input": { "revenue_today_ils": 0, "support_open": 0,
               "competitor_signals": [], "inbox_highlights": [] },
    "must_contain": ["אין", "שקט"],
    "must_not_contain": ["שגיאה"],
    "max_words": 80
  }
]

שים לב לרכיבים: must_contain (מה חייב להופיע — מספרים אמיתיים, לא placeholder), must_not_contain (סימני באג — undefined, null, מחרוזות ריקות), ו-max_words (brief שעובר 180 מילים הוא לא brief — הוא דוח). זה ה-rubric. הוא לא צריך להיות מתוחכם בהתחלה; הוא צריך לתפוס את הכשלים שכבר ראית ב-dry-run.

ה-scorer — 40 שורות Python שעובדות

עכשיו ה-scorer עצמו. הוא קורא את ה-golden cases, מריץ את הסוכן על כל input, ובודק את הפלט מול הכללים. זה קוד עובד — העתק, שנה נתיבים:

# eval_gate.py — scorer אוטומטי מול golden dataset
import json, re

def score_output(output: str, case: dict) -> dict:
    """מנקד פלט בודד מול golden case. מחזיר pass/fail + סיבות."""
    fails = []

    # 1. חייב להכיל את כל ה-must_contain
    for needle in case.get("must_contain", []):
        if needle not in output:
            fails.append(f"חסר: '{needle}'")

    # 2. אסור שיכיל אף must_not_contain (סימני באג)
    for bad in case.get("must_not_contain", []):
        if bad in output:
            fails.append(f"מופיע באג: '{bad}'")

    # 3. אורך — brief ארוך מדי נכשל
    words = len(re.findall(r"\S+", output))
    if words > case.get("max_words", 9999):
        fails.append(f"ארוך מדי: {words} מילים")

    return {"id": case["id"], "passed": len(fails) == 0, "fails": fails}

def run_gate(cases_path: str, agent_fn) -> dict:
    """מריץ את הסוכן על כל golden case ומחזיר pass-rate כולל."""
    cases = json.load(open(cases_path, encoding="utf-8"))
    results = [score_output(agent_fn(c["input"]), c) for c in cases]
    passed = sum(r["passed"] for r in results)
    rate = passed / len(results)
    return {"pass_rate": rate, "passed": passed,
            "total": len(results), "results": results}

if __name__ == "__main__":
    # agent_fn = הפונקציה שמייצרת את ה-brief (ב-morning_brief.py)
    from morning_brief import generate_brief
    report = run_gate("golden/brief_cases.json", generate_brief)
    print(f"pass-rate: {report['pass_rate']:.0%} "
          f"({report['passed']}/{report['total']})")
    for r in report["results"]:
        if not r["passed"]:
            print(f"  ✗ {r['id']}: {', '.join(r['fails'])}")

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

# פלט אמיתי לדוגמה מ-eval_gate.py:

pass-rate: 80% (8/10)
  ✗ case_03_missing_data: חסר: '847', ארוך מדי: 213 מילים
  ✗ case_07_competitor_alert: מופיע באג: 'undefined'

# מה לקרוא מהפלט הזה:
# 1. 80% — case 01, 02, 04, 05, 06, 08, 09, 10 עברו בסדר.
# 2. case_03 — הסוכן לא הזכיר את סכום ה-847₪ (השמיט נתון!) ו-brief ארוך מ-180 מילה.
# 3. case_07 — באג: יש "undefined" בפלט, כנראה שדה שלא מולא בנתונים.
🛠️ כלים מוכנים (לא חובה לבנות מאפס)

אם אתה מעדיף פלטפורמה: Braintrust היא eval-first עם workflow של CI/CD eval-gated deployment ו-free tier נדיב (לפי המחקר: 1M spans/חודש, 10K eval runs). Langfuse היא OSS חינמית (MIT) ל-self-host (נרכשה ע"י ClickHouse בינואר 2026 אך נשארה חינמית להרצה-עצמית). אלו דוגמאות, לא המלצות — ה-scorer ב-40 שורות למעלה מספיק לתפקיד ראשון, ומגבלות ה-free-tier משתנות, אז אמת אותן לפני שתסתמך.

⚡ עשה עכשיו (10 דקות) — golden set ראשון

פתח את תוצרי ה-dry-run מפרק 3. בחר 3 מקרים. לכל אחד, כתוב ב-JSON: ה-input, מה חייב להופיע בפלט טוב (must_contain), ומה אסור (must_not_contain). שמור ל-golden/cases.json. זה הזרע. נרחיב ל-10+ בתרגיל המסכם.

רכיב 3 — Reflexion: איך הסוכן לומד מטעויות בלי אימון מחדש

יש לנו סוכן שרץ לבד (cron) ונבדק לבד (eval). אבל מה קורה כשהוא נכשל? בלי הרכיב השלישי, התשובה היא: כלום. הוא ייכשל באותו אופן שוב מחר. Reflexion הוא הרכיב שהופך כישלון ללקח, ולקח לשיפור.

Reflexion היא טכניקה מתועדת ממאמר אקדמי (Shinn et al., 2023, arXiv 2303.11366) שבו סוכן מנסח במילים מה הוא למד מכישלון, ושומר את הניסוח הזה בזיכרון אפיזודי (episodic memory — זיכרון של "מה קרה בריצות קודמות"), כדי לעשות טוב יותר בניסיון הבא. הקסם: השיפור קורה בלי לעדכן את משקלי המודל — אין אימון, אין fine-tuning. הסוכן פשוט קורא את הלקחים שלו לפני שהוא פועל בפעם הבאה.

שלושת המודולים: Actor → Evaluator → Self-Reflection

המאמר מגדיר את ה-loop בשלושה מודולים, ושים לב כמה זה מתאים בדיוק למה שכבר בנינו:

מודולמה עושהאצלנו זה...
Actorה-LLM פועל ומייצר פלטהסוכן שמריץ את ה-SOP מפרק 3
Evaluatorמנקד את הפלטה-eval gate שבנינו בחלק הקודם
Self-Reflectionמנתח את הכישלון וכותב לקח טקסטואליהרכיב החדש — כותב ל-.context/lessons.md

ה-loop הוא: ה-Actor מייצר → ה-Evaluator מנקד → אם נכשל, ה-Self-Reflection כותב "מה הייתי צריך לעשות אחרת" → הלקח נשמר → בריצה הבאה ה-Actor קורא את הלקחים לפני שהוא פועל. ככה, כפי שמנסח template ה-OSS gru-ai, "ההנחיה ה-10 רצה טוב יותר מהראשונה" — כי היא נכתבת על גבי 9 לקחים.

⚡ עשה עכשיו (5 דקות) — סמולציית reflexion ידנית

לפני שכותבים שורת קוד, חווים את ה-loop בראש. קח מקרה שה-SOP שלך פספס ב-dry-run של פרק 3 — מקרה שבו הפלט לא היה טוב. ענה בכתב על שלוש שאלות ב-2-3 משפטים כל אחת:

  1. Actor: מה הסוכן עשה? (מה היה הפלט שנוצר?)
  2. Evaluator: מה נכשל? (מה חסר, מה עודף, מה שגוי?) — בשפה ספציפית, לא "לא טוב".
  3. Self-Reflection: כתוב לקח אחד בגוף ציווי: "בעתיד, כש... — עשה/אל תעשה...". עשה את זה קצר וספציפי.

שמור את שלושת התשובות בקובץ זמני. בתרגיל 3, הקוד יעשה בדיוק את מה שעשית כאן — אוטומטית, על כל כישלון. אתה לא כותב קוד חדש; אתה מכניס ל-code מה שהמוח שלך כבר עשה.

הקוד: צעד ה-reflexion

הצעד הזה רץ רק כשה-eval נכשל. הוא לוקח את הקלט, הפלט שנכשל, וסיבת הכישלון, ומבקש מה-LLM לכתוב לקח קצר ופעולתי. זה קוד עובד:

# reflexion.py — צעד ה-Self-Reflection
import datetime
from anthropic import Anthropic   # ה-SDK של Claude

client = Anthropic()  # קורא ANTHROPIC_API_KEY מהסביבה

REFLECT_PROMPT = """אתה מנתח כישלון של סוכן כדי לכתוב לקח לעתיד.

הקלט שהסוכן קיבל:
{input}

הפלט שייצר (שנכשל ב-eval):
{output}

סיבת הכישלון:
{fails}

כתוב לקח אחד, קצר (משפט-שניים), פעולתי, בגוף ציווי
("בעתיד, כש... — עשה/אל תעשה..."). אל תתנצל, אל תסביר.
רק הלקח."""

def reflect(input_data, output, fails) -> str:
    msg = client.messages.create(
        model="claude-haiku-4-5",   # מודל זול — reflexion הוא משימה קלה
        max_tokens=120,
        messages=[{"role": "user", "content": REFLECT_PROMPT.format(
            input=input_data, output=output, fails="; ".join(fails))}],
    )
    return msg.content[0].text.strip()

def save_lesson(lesson: str, case_id: str, path=".context/lessons.md"):
    stamp = datetime.date.today().isoformat()
    line = f"- [{stamp}] (מתוך {case_id}, **לבדיקה**) {lesson}\n"
    with open(path, "a", encoding="utf-8") as f:
        f.write(line)
    return line

⚠️ הערה על המודל

שים לב שצעד ה-reflexion משתמש ב-claude-haiku-4-5 — מודל קטן וזול — ולא ב-frontier. reflexion הוא ניתוח קצר של כישלון בודד; אין סיבה לשלם מחיר מלא עליו. זה preview ל-tiered model routing שנלמד בפרק 6 (ה-P&L): המשימה הקלה הולכת למודל הזול, ה-frontier שמור ל-10% הקשים. שמות וגרסאות מודלים משתנים — אמת את ה-model ID הנוכחי מול תיעוד הספק לפני הרצה.

איך הלקחים מוזרקים חזרה

לקח שנשמר ולא נקרא הוא חסר ערך. הקטע האחרון: בתחילת כל ריצה, הסוכן טוען את .context/lessons.md ומדביק אותו ל-prompt שלו, אחרי ה-SOP. ככה:

# בתוך morning_brief.py, לפני שהסוכן פועל:
def load_lessons(path=".context/lessons.md") -> str:
    try:
        lessons = open(path, encoding="utf-8").read()
    except FileNotFoundError:
        return ""
    # רק לקחים שאושרו (לא מסומנים **לבדיקה**) נכנסים ל-prompt:
    approved = [l for l in lessons.splitlines()
                if l.strip() and "**לבדיקה**" not in l]
    if not approved:
        return ""
    return "לקחים מריצות קודמות (קח בחשבון):\n" + "\n".join(approved)

# ואז ה-prompt נבנה כך:
prompt = f"{SOP_TEXT}\n\n{load_lessons()}\n\nהקלט להיום:\n{todays_data}"

שים לב לשורה הקריטית: רק לקחים שלא מסומנים **לבדיקה** נכנסים ל-prompt. כל לקח חדש נכתב עם תג "לבדיקה" (ראית את זה ב-save_lesson), והוא לא משפיע על הסוכן עד שאתה מאשר אותו ידנית (מוחק את התג). זה לא קישוט — זה ההגנה מפני "לקח מורעל", והנושא של ה-gotcha הכי חשוב בפרק.

MAR — הרחבת 2026 (לידיעה)

אם תרצה להעמיק: הרחבה מ-2026 בשם MAR (Multi-Agent Reflexion, arXiv 2512.20845) מפרידה את ה-act / evaluate / critique לפרסונות שונות — סוכן שפועל, סוכן שמנקד, וסוכן שמבקר. זה רלוונטי לציים גדולים; ל-operator סולו שמתחיל, ה-loop של שלושה מודולים בקובץ אחד מספיק לחלוטין. אל תסבך לפני שיש לך loop בסיסי שעובד.

🧭 Framework: מתי לשדרג מ-Single-Agent Reflexion ל-MAR

MAR מוסיף מורכבות — שלושה סוכנים, שלוש הרצות, שלושה עלויות-טוקן. השתמש בכלל ההחלטה הזה:

עקרון ברזל: Reflexion בסיסי עם review שבועי ידני (15 דקות) שווה יותר מ-MAR מורכב שאתה לא מספיק לתחזק. פשטות שמתחזקת > מורכבות שנרקבת.

⚡ עשה עכשיו (5 דקות)

צור קובץ .context/lessons.md וכתוב בו ידנית לקח אחד מתוך הראש שלך — משהו שהיית רוצה שהסוכן "ידע" (למשל: "בעתיד, כשאין הכנסות היום — אל תכתוב '0₪', כתוב 'יום שקט, אין עסקאות'"). הוסף git add .context/ && git commit -m "first lesson". עכשיו ה'מוח' של החברה התחיל לצבור ידע, והוא ב-git.

שלושת כשלי-הליבה (ואיך מונעים כל אחד)

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

כשל 1 — Idempotency: עבודה כפולה ושליחה כפולה ללקוח

idempotency (אִי-דֶמְפּוֹטֶנְטִיוּת) פירושה: להריץ את אותה פעולה פעמיים נותן את אותה תוצאה כמו להריץ אותה פעם אחת. cron בלי idempotency הוא אסון: סוכן SDR שרץ כל שעה ו"מעבד את ה-inbox" יעבד שוב את אותם לידים בכל ריצה — ישלח לאותו לקוח את אותו מייל 24 פעם ביום. זה לא רק בזבוז טוקנים; זה נזק למוניטין מול לקוח אמיתי.

הפתרון: already-done marker — סימון שאומר "כבר טיפלתי בזה". לפני שהסוכן מטפל בפריט, הוא בודק אם הוא כבר עיבד אותו:

# idempotency.py — מונע עיבוד כפול
import json, os

DONE_FILE = ".context/processed.json"

def already_done(item_id: str) -> bool:
    if not os.path.exists(DONE_FILE):
        return False
    return item_id in json.load(open(DONE_FILE))

def mark_done(item_id: str):
    done = json.load(open(DONE_FILE)) if os.path.exists(DONE_FILE) else []
    done.append(item_id)
    json.dump(done, open(DONE_FILE, "w"))

# בשימוש:
for lead in incoming_leads:
    if already_done(lead["id"]):
        continue          # דלג — כבר טיפלנו
    process(lead)
    mark_done(lead["id"]) # סמן רק אחרי הצלחה

⚠️ טעות נפוצה: cron בלי idempotency

זו הטעות שעולה הכי הרבה כסף ומוניטין ביחד. סוכן שמעבד שוב את אותם לידים/הודעות בכל ריצה שורף טוקנים ושולח כפול ללקוחות. הכלל: כל סוכן שעובד על פריטים (מיילים, לידים, פניות) חייב already-done marker, וה-marker מסומן רק אחרי שהפעולה הצליחה — לא לפניה (אחרת כישלון באמצע מסמן "בוצע" משהו שלא בוצע).

📊 תרחיש אמיתי: SDR שרץ כל שעה בלי already-done marker

נניח שסוכן ה-SDR שלך רץ כל שעה ו"מעבד לידים חדשים" מה-CRM. בלי marker — הנה מה שקורה בסוף יום עבודה שמיני:

# lead_id: "roi-cohen-2026-06-15" הוכנס ל-CRM ב-09:00
# ריצה 09:00 → עיבד → שלח מייל היכרות → ✓
# ריצה 10:00 → "lead חדש!" → שלח מייל היכרות שני → ✗
# ריצה 11:00 → "lead חדש!" → שלח מייל היכרות שלישי → ✗
# ...
# ריצה 17:00 → "lead חדש!" → שלח מייל היכרות תשיעי → ✗

# רועי כהן קיבל 9 מיילים זהים ביום אחד.
# עלות טוקנים: 8 הרצות מיותרות × 2,000 טוקן = ~16,000 טוקן = ~₪3.20
# עלות מוניטין: רועי סימן את הדומיין כ-spam.

עם already-done marker (3 שורות קוד): הריצה הראשונה שולחת + מסמנת "roi-cohen-2026-06-15" כ-done. כל ריצה אחרת רואה already_done(...) == True ומדלגת. עלות: 1 הרצה. מוניטין: שלם.

כשל 2 — Dead-agent alerting: ה-daemon שמת בשקט

זה הכשל הכי ערמומי, כי הוא בלתי-נראה. סוכן ש"רץ בזמן שאתה ישן" גם נכשל בזמן שאתה ישן. אם השרת קרס, ה-API key פג, או הסקריפט זרק חריגה ב-3 בלילה — הסוכן פשוט מפסיק. אין הודעת שגיאה שתראה, כי אתה ישן. אתה תגלה את זה רק כשלקוח יתלונן ש"לא קיבלתי תשובה" — וזה כבר מאוחר.

הפתרון הוא heartbeat monitoring: הסוכן כותב חותמת-זמן בכל ריצה מוצלחת ("אני חי"), ו-watchdog נפרד בודק שהחותמת טרייה. אם הסוכן לא דיווח "חי" בחלון הצפוי — ה-watchdog מתריע אליך.

# watchdog.py — רץ ב-cron נפרד, מתריע אם הסוכן מת
import time, os, requests

HEARTBEAT = ".context/last_run.txt"
MAX_AGE_SECONDS = 26 * 3600   # brief יומי — מותר עד 26 שעות שקט

def alert(msg: str):
    # שלח ל-Telegram (או email/SMS) — זה ה-tripwire שלך
    requests.post(
        f"https://api.telegram.org/bot{os.environ['TG_TOKEN']}/sendMessage",
        json={"chat_id": os.environ["TG_CHAT"], "text": f"🚨 {msg}"})

if not os.path.exists(HEARTBEAT):
    alert("הסוכן morning_brief מעולם לא דיווח שהוא חי!")
else:
    age = time.time() - os.path.getmtime(HEARTBEAT)
    if age > MAX_AGE_SECONDS:
        alert(f"morning_brief שותק {age/3600:.0f} שעות — כנראה מת.")

# והסוכן עצמו, בסוף ריצה מוצלחת, כותב:
#   open(".context/last_run.txt", "w").write(str(time.time()))

שים לב: ה-watchdog רץ ב-cron נפרד מהסוכן. אם הם רצים באותו תהליך והתהליך מת — גם ה-watchdog מת, ושוב אתה עיוור. ה-watchdog חייב להיות תהליך עצמאי שבודק את הסוכן מבחוץ.

⚠️ טעות נפוצה: "רץ בזמן שאתה ישן" בלי alerting

סוכן בלי heartbeat monitoring מת בשקט עד שלקוח מתלונן. הכלל: כל סוכן מתוזמן חייב watchdog שמתריע אליך כשהסוכן שותק מעבר לחלון הצפוי. ה-"שקט" של סוכן הוא לא "הכל בסדר" — הוא "אין לי מושג". התרעה על שקט היא לא מותרות; היא תנאי להפעיל סוכן שאתה לא צופה בו.

⚡ עשה עכשיו (8 דקות) — חבר את ה-watchdog לפני שהסוכן עולה לאוויר

אל תחכה לתרגיל 4 לשלב את ה-watchdog. ברגע שה-morning brief רץ בפעם הראשונה בצינור, הוסף את שורת ה-watchdog לcrontab:

# ה-watchdog רץ פעם בשעה, בדקה ה-30 — לא באותה דקה כמו הסוכן
30 * * * * cd /YOUR/PATH && /usr/bin/python3 watchdog.py >> logs/watchdog.log 2>&1

שלוש נקודות לשים לב אליהן:

  1. דקה 30, לא דקה 00 — הסוכן רץ ב-06:00 (דקה 00). Watchdog שרץ גם ב-06:00 יבדוק לפני שהסוכן הספיק לסיים ולכתוב heartbeat. דקה שונה = בדיקה אחרי שהסוכן כתב.
  2. לוג נפרדwatchdog.logbrief.log. אם שניהם הולכים לאותו קובץ קשה להפריד בין "הסוכן כתב" ל-"ה-watchdog בדק".
  3. תבדוק שה-watchdog מתריע — שלח ל-Telegram מסר ניסוי אחד ידנית: python watchdog.py בפעם הראשונה, לפני שלסוכן יש heartbeat, וודא שמגיעה התרעה. אל תגלה ביום שהסוכן מת שה-watchdog לא עבד.

כשל 3 — Poisoned lesson: ה-reflexion שמרעיל את הצי

זה הכשל שהכי קל לפספס כי הוא נראה כמו תכונה. reflexion אמור לשפר את הסוכן — אבל מה אם הוא כותב לקח שגוי? נניח שביום אחד היו נתונים חריגים, והסוכן "למד" לקח שגוי: "כשהכנסות גבוהות מ-10,000₪ — כנראה טעות, התעלם". הלקח הזה נשמר בזיכרון האפיזודי, ומעכשיו כל ריצה עתידית קוראת אותו ומתעלמת מימים טובים. לקח אחד שגוי מרעיל את כל הצי, בשקט, לנצח.

הפתרון כבר בנוי בקוד שלנו, וזו הסיבה שבנינו אותו ככה: reflections דורשים review, לא אמון עיוור. כל לקח חדש נכתב עם תג **לבדיקה**, ו-load_lessons לא מזריק לקחים מסומנים ל-prompt. הלקח משפיע רק אחרי שאתה קראת אותו ומחקת את התג ידנית. זה צעד שלוקח 10 שניות בשבוע, והוא ההבדל בין "הסוכן לומד" ל"הסוכן צובר אמונות שגויות".

🧭 Framework: review של reflections — מה לאשר, מה לדחות

כשאתה עובר על .context/lessons.md פעם בשבוע, לכל לקח מסומן **לבדיקה**:

⚠️ טעות נפוצה: לבטוח ב-reflexion עיוור

לקח שגוי שנשמר בזיכרון האפיזודי מרעיל כל ריצה עתידית בלי שתשים לב. הכלל: שום reflection לא משפיע על הסוכן עד שאדם (אתה) אישר אותו. ה-loop כותב לקחים אוטומטית; ה-operator מאשר אותם ידנית. זו חלוקת העבודה הנכונה.

⚠️ טעות נפוצה: לקח שחוזר שוב ושוב — הסימן לבעיה עמוקה יותר

אם אתה מוצא שאותו לקח נכתב שלוש פעמים בשלושה שבועות, למשל: "בעתיד, כשאין נתוני הכנסות — אל תכתוב 0₪" — זה לא סימן שה-reflexion עובד טוב. זה סימן שה-SOP עצמו חסר. הלקח חייב להיות מוזרק ל-SOP (פרק 3), לא רק ל-lessons.md. הכלל: לקח שחוזר יותר מפעמיים הוא לא לקח — הוא פגם ב-SOP. פתח את .context/sops/, מצא את הסעיף הרלוונטי, ותקן את הנוהל במקור. אחר כך מחק את הלקח החוזר מ-lessons.md — כי עכשיו הוא כבר בנוי לתוך ה-SOP, ולא צריך עוד להיות reminder. מי שמצטבר רק ב-lessons.md ולא עובר ל-SOP — הוא ספריה של patch-im, לא חברה שלמדה.

⚠️ טעות נפוצה: זיכרון append-forever

בונוס — כשל רביעי קטן: אם lessons.md רק גדל לנצח, יום אחד ה-prompt יתפח, העלות תזנק, והאיכות תרד (context truncation — חיתוך הקשר). הכלל: סכם ו-prune מדי פעם. שמור 15–20 הלקחים הכי שימושיים, ארכב את השאר. זיכרון הוא לא "append-forever" — הוא ספר נהלים שמתעדכן, לא יומן שתופח.

הרכבה: ה-loop השלם של ה-morning brief

עכשיו מחברים הכל לקובץ אחד שרץ מ-cron. זה השלד של morning_brief.py שמחבר את ארבעת החלקים — load → act → evaluate → reflect — עם כל ההגנות:

# morning_brief.py — ה-loop השלם, רץ מ-cron ב-06:00
import time, os
from anthropic import Anthropic
from eval_gate import score_output
from reflexion import reflect, save_lesson

client = Anthropic()
SOP_TEXT = open(".context/sops/morning_brief.md", encoding="utf-8").read()

def load_lessons(path=".context/lessons.md") -> str:
    try: lines = open(path, encoding="utf-8").read().splitlines()
    except FileNotFoundError: return ""
    ok = [l for l in lines if l.strip() and "**לבדיקה**" not in l]
    return ("לקחים מאושרים:\n" + "\n".join(ok)) if ok else ""

def generate_brief(data: dict) -> str:
    prompt = (f"{SOP_TEXT}\n\n{load_lessons()}\n\n"
              f"נתוני היום (₪):\n{data}\n\nכתוב brief בעברית, עד 180 מילים.")
    msg = client.messages.create(model="claude-sonnet-4-5",
        max_tokens=600, messages=[{"role":"user","content":prompt}])
    return msg.content[0].text

def run():
    data = collect_metrics()                 # הכנסות/תמיכה/מתחרים/inbox
    output = generate_brief(data)            # 1. Actor
    golden_case = {"must_contain": [str(data["revenue_today_ils"])],
                   "must_not_contain": ["undefined","null"], "max_words": 180}
    verdict = score_output(output, {**golden_case, "id":"live_run"})  # 2. Evaluator

    if verdict["passed"]:
        send_to_telegram(output)             # רק פלט שעבר יוצא
    else:                                    # 3. Self-Reflection
        lesson = reflect(data, output, verdict["fails"])
        save_lesson(lesson, "live_run")      # נשמר עם **לבדיקה**
        send_to_telegram(f"⚠️ ה-brief נכשל ב-eval ({verdict['fails']}). "
                         f"נכתב לקח לבדיקה. לא נשלח digest היום.")

    open(".context/last_run.txt","w").write(str(time.time()))  # heartbeat

if __name__ == "__main__":
    run()

קרא את ה-run() שורה-שורה ותראה את כל הפרק: Actor מייצר, Evaluator מנקד, ורק אם עבר — הפלט יוצא. אם נכשל — reflexion כותב לקח (עם תג לבדיקה), ואתה מקבל התרעה במקום digest שגוי. ובסוף, תמיד, חותמת ה-heartbeat. זה ה-loop. שמור הכל ב-git, ויש לך חברה שמתחילה לזכור.

✅ בדוק את עצמך — 5 שאלות

  1. סדר ה-loop: מה הסדר הנכון של שלושת המודולים, ומה תפקיד כל אחד? (Actor / Evaluator / Self-Reflection)
  2. למה golden set: מדוע "eval" בלי golden dataset הוא "סתם LLM שמנחש"? מאיפה בא ה-golden set שלך (רמז: פרק 3)?
  3. idempotency: סוכן SDR שלך רץ כל שעה ושולח לכל לקוח את אותו מייל 24 פעם ביום. איזה מנגנון חסר, ואיפה בקוד מסמנים את ה-marker (לפני או אחרי הצלחת הפעולה)?
  4. לקח מורעל: הסוכן כתב לקח "התעלם מהכנסות מעל 10,000₪". מדוע התג **לבדיקה** ב-load_lessons מציל אותך כאן, ומה אתה עושה עם הלקח הזה ב-review?
  5. daemon מת: מדוע ה-watchdog חייב לרוץ ב-cron נפרד מהסוכן, ולא באותו תהליך?

🔄 שגרת עבודה: ה-loop השבועי של ה-operator

אחרי שה-loop רץ, התפקיד שלך הוא לא לצפות בו — אלא לתחזק אותו פעם בשבוע, ב-15 דקות:

  1. יום ראשון, 15 דק': פתח את .context/lessons.md. עבור על כל לקח מסומן **לבדיקה**. אשר (מחק תג) או דחה (מחק שורה) לפי ה-Framework למעלה.
  2. בדוק את ה-pass-rate: הרץ python eval_gate.py. אם ה-pass-rate ירד מהשבוע שעבר — משהו ב-SOP או בנתונים השתנה. חקור.
  3. הרחב golden set: אם השבוע היה מקרה חדש שהסוכן פספס — הוסף אותו כ-golden case. ה-set גדל עם הזמן ותופס יותר.
  4. בדוק heartbeat: ודא ש-last_run.txt טרי. אם ה-watchdog לא התריע השבוע — יופי, הסוכן חי.
  5. commit: git add .context/ && git commit -m "weekly review week N". החברה שלך מתעדת את עצמה.

תרגילים — בנה את ה-loop שלך

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

תרגיל 1 — שורת ה-cron של ה-morning brief

מטרה: סוכן שרץ לבד ב-06:00.

  1. צור קובץ morning_brief.py זמני שכל מה שהוא עושה הוא לכתוב חותמת-זמן ל-.context/last_run.txt ולהדפיס "brief ran".
  2. הוסף שורת crontab עם הנתיב המוחלט של תיקיית הפרויקט שלך (לא /YOUR/PATH): 0 6 * * * cd /home/you/fleet && /usr/bin/python3 morning_brief.py >> logs/brief.log 2>&1 — החלף את /home/you/fleet בנתיב האמיתי במכונה שלך, ואמת את /usr/bin/python3 מול which python3. לפני הרצה ראשונה הרץ mkdir -p logs .context — בלי שתי התיקיות האלה logs/brief.log ו-last_run.txt לא ייווצרו וה-cron ייכשל בשקט בריצה הראשונה.
  3. כדי לבדוק בלי לחכות ל-06:00, שנה זמנית ל-*/2 * * * * (כל שנתי-דקות), חכה, ובדוק ש-last_run.txt ו-logs/brief.log מתעדכנים.

פלט נראה-לעין: קובץ logs/brief.log עם 2+ שורות "brief ran", וחותמת-זמן טרייה ב-last_run.txt — הוכחה ש-cron מפעיל את הסוכן שלך לבד.

תרגיל 2 — golden dataset של 10+ מקרים

מטרה: eval gate עם נקודת-ייחוס אמיתית.

  1. פתח את תוצרי ה-dry-run מפרק 3. הפוך כל מקרה ל-golden case ב-golden/cases.json (פורמט כמו בדוגמה בפרק) — לפחות 10.
  2. ודא שכיסית מקרי-קצה: יום ריק, נתון חסר, ערך חריג. אלו המקרים שבהם סוכנים נשברים.
  3. הרץ python eval_gate.py מול הסוכן שלך וקרא את ה-pass-rate.

פלט נראה-לעין: פלט Terminal שמראה pass-rate: X% (n/10) ורשימת הכשלונות עם סיבות — ה-eval gate הראשון שלך מנקד פלט אמיתי.

תרגיל 3 — loop reflexion עם review

מטרה: סוכן שלומד מטעות — בבטחה.

  1. חבר את reflexion.py: כש-case נכשל ב-eval, הסוכן כותב לקח ל-.context/lessons.md עם תג **לבדיקה**.
  2. גרום לכישלון מכוון (תן input חריג). ודא שנכתב לקח עם התג.
  3. עבור review: אשר את הלקח (מחק תג). הרץ שוב את אותו case וודא שעכשיו load_lessons מזריק אותו ל-prompt — ושהפלט השתפר.

פלט נראה-לעין: "לפני/אחרי" — צילום של lessons.md עם הלקח שנכתב, ו-2 פלטים של אותו case: אחד שנכשל (לפני הלקח) ואחד שעבר (אחרי שאישרת את הלקח). זו ההוכחה שהסוכן השתפר.

תרגיל 4 (מסכם) — ה-morning brief המלא + watchdog

מטרה: ה-loop השלם, חי, עם כל ההגנות.

  1. חבר את morning_brief.py המלא (load → act → eval → reflect → heartbeat) שמחבר את כל החלקים מהתרגילים 1–3.
  2. הוסף idempotency.py: אם מקור הנתונים שלך כולל פריטים (לידים/מיילים), ודא שלא מעובדים פעמיים.
  3. הוסף watchdog.py בשורת cron נפרדת שמתריע ל-Telegram/email אם הסוכן שותק מעבר ל-26 שעות. לפני הרצה — checklist סביבה: (1) pip install requests (או pip install yagmail אם אתה שולח ב-email במקום Telegram); (2) צור bot דרך @BotFather, קבל token ושמור ב-export TG_TOKEN=...; (3) שלח הודעה אחת לבוט שלך, ואז קרא את chat.id שלך מ-https://api.telegram.org/bot$TG_TOKEN/getUpdates ושמור ב-export TG_CHAT=...; (4) ודא ששני ה-export מופיעים ב-crontab לפני שורת הפקודה — אחרת ה-watchdog יקרוס על KeyError בריצה הראשונה בגלל חוסר במשתני סביבה.
  4. הפעל את ה-brief, קבל הודעת digest אמיתית בעברית עם נתוני ₪.

פלט נראה-לעין: הודעת morning brief אמיתית שמגיעה אליך ל-Telegram/email ב-06:00, בעברית, עם נתוני ₪ — וקובץ git שבו כל הקוד וה-.context/. הצי שלך עכשיו עובד, נבדק ומשתפר בזמן שאתה ישן.

📖 מילון מונחים

Cron
תוכנת תזמון ותיקה (לינוקס/Mac). שורה בקובץ crontab מגדירה מתי להריץ פקודה, בתחביר של 5 שדות (דקה, שעה, יום-בחודש, חודש, יום-בשבוע).
Heartbeat
חותמת-זמן שסוכן כותב בכל ריצה מוצלחת ("אני חי"). watchdog נפרד בודק שהיא טרייה ומתריע אם הסוכן שתק.
Daemon
תהליך-רקע שרץ ברציפות בלי חלון/ממשק. Hermes Agent הוא daemon שמתזמן משימות ושומר זיכרון בין סשנים.
Eval gate
scorer אוטומטי שמנקד פלט של סוכן מול golden dataset לפני שהוא יוצא ללקוח; חוסם regression או מסמן ירידת איכות.
Golden dataset
אוסף מקרי-ייחוס (קלט + פלט מצופה) שאתה כבר יודע שהם טובים. בלעדיו ה-eval הוא רק LLM שמנחש. הזרע: תוצרי ה-dry-run מפרק 3.
Reflexion
טכניקה (Shinn et al., arXiv 2303.11366) שבה סוכן מנסח במילים לקח מכישלון ושומר אותו בזיכרון אפיזודי — שיפור בלי אימון מחדש של המשקלים.
Actor / Evaluator / Self-Reflection
שלושת המודולים של ה-loop: Actor פועל, Evaluator מנקד, Self-Reflection כותב לקח מכישלון.
Episodic memory
זיכרון של "מה קרה בריצות קודמות" — אצלנו קובץ .context/lessons.md ב-git שצובר לקחים.
Idempotency
תכונה שבה הרצת פעולה פעמיים נותנת אותה תוצאה כמו פעם אחת. נדרשת ל-cron כדי למנוע עיבוד כפול ושליחה כפולה ללקוח. ממומשת ב-already-done marker.
Poisoned lesson (לקח מורעל)
reflexion שגוי שנשמר בזיכרון ומרעיל כל ריצה עתידית. נמנע ע"י review: לקח חדש מסומן **לבדיקה** ולא משפיע עד אישור אנושי.
pass-rate
אחוז ה-golden cases שעברו את ה-eval. המספר הזה הופך בפרק 5 לסף שמטפסים בו בסולם האוטונומיה.
Adaptive scheduling (תזמון אדפטיבי)
דפוס תזמון שבו הסוכן עצמו מחליט מתי לרוץ שוב — למשל, research agent שבדק ולא מצא שינויים, וקובע לעצמו "בדוק שוב בעוד 48 שעות". ל-operator סולו: מורכב לניפוי, השתמש רק לאחר שה-loop הבסיסי (cron) מוכח ויציב.
CI/CD eval gate (שער eval כמו CI/CD)
הרצת ה-eval gate לפני כל שינוי ב-SOP, בדיוק כמו unit tests לפני deploy בקוד. אם ה-pass-rate ירד — השינוי לא "עולה לאוויר". ב-Braintrust זה workflow מובנה; ב-scorer הביתי שבנינו — מריצים python eval_gate.py ידנית לפני כל עדכון ל-SOP.
Idempotency key (מפתח-idempotency)
מזהה ייחודי לכל פריט שסוכן מעבד (מייל, lead, חשבונית) — ה-ID שנכנס לרשימת ה-already-done. חייב להיות קבוע ועקבי: email_id, lead_id, invoice_number — לא timestamp (timestamps משתנים). מפתח לא עקבי גורם ל-marker לא לתפוס, וחוזרים לבעיית הכפילות.
Watchdog (שומר)
תהליך/cron עצמאי שבודק שסוכן אחר כתב heartbeat בחלון הזמן הצפוי, ומתריע אם לא. חייב לרוץ בתהליך נפרד — watchdog שרץ באותו תהליך כמו הסוכן מת יחד איתו ולא מזהה את הכשל.

🎯 Just One Thing

אם תיקח מהפרק הזה דבר אחד: "רץ לבד" בלי "נבדק לבד" הוא אסון בקנה מידה. סוכן מתוזמן בלי eval gate מייצר את אותה טעות 30 פעם בחודש, אוטומטית, בלי שתדע. לפני שאתה מתזמן כל סוכן — קודם בנה לו golden set ו-scorer. ה-cron בלי ה-eval הוא לא קסם; הוא מכונה שמשכפלת את הבעיה.

📝 סיכום הפרק

  1. ה-loop הוא מנוע השיפור של הצי: cron (רץ לבד) + eval (נבדק לבד) + reflexion (לומד לבד). שלושתם חבילה אחת — כל אחד בלי השניים האחרים הוא חסר ערך או מסוכן.
  2. Cron — חמישה דפוסי תזמון (cron/event/heartbeat/queue/adaptive); ל-operator סולו, cron + Python הוא ברירת המחדל. self-host (Hermes, $0 infra) מול managed (Claude Code Routines, בלי שרת).
  3. Eval gate — golden dataset הוא הלב: בלי מקרי-ייחוס אמיתיים (מתוצרי ה-dry-run של פרק 3) ה-eval הוא ניחוש. ה-scorer מחזיר pass-rate, חוסם regression, ומייצר את המספר שמניע את פרק 5.
  4. Reflexion — שיפור בלי אימון: Actor → Evaluator → Self-Reflection. הסוכן כותב לקח מכישלון לזיכרון אפיזודי, וקורא לקחים מאושרים לפני כל ריצה. הרווחים מהמאמר מאומתים (HumanEval 80%→91%).
  5. שלושת כשלי-הליבה ופתרונותיהם: idempotency (already-done marker, מסומן רק אחרי הצלחה) נגד עבודה כפולה; dead-agent alerting (watchdog בתהליך נפרד) נגד daemon שמת בשקט; review של reflections (תג **לבדיקה**) נגד לקח מורעל. בונוס: prune של זיכרון נגד append-forever.

🌉 גשר לפרק 5 — Owner-as-Operator: עכשיו יש לך loop שרץ, נבדק ומשתפר. אבל יש שאלה שלא ענינו עליה: מתי מותר לסוכן לפעול לבד, ומתי הוא חייב לחכות לחתימה שלך? ה-pass-rate שבנינו כאן הוא בדיוק הסף שמטפסים בו בסולם האוטונומיה. בפרק 5 ניקח אותו ונבנה calibrated autonomy: approval gates על פעולות כסף, tripwires שמתריעים רק כשחשוב, וסולם שמעלה כל תפקיד מ"אשר כל פעולה" ל"רק נטר" — לפי הנתונים, לא לפי תחושה.

✅ צ'קליסט סיום פרק 4

סמן כל פריט כשהוא מוכן. הפרק הושלם כשכל ה-15 מסומנים:

⛔ שלוש הטעויות שתופסות את רוב המתחילים

  1. cron בלי idempotency — הסוכן מעבד שוב את אותם לידים/הודעות בכל ריצה, שורף טוקנים ושולח כפול ללקוחות. תיקון: already-done marker, מסומן רק אחרי הצלחת הפעולה.
  2. אמון עיוור ב-reflexion — לקח שגוי נשמר בזיכרון האפיזודי ומרעיל כל ריצה עתידית. תיקון: תג **לבדיקה** + review שבועי; שום לקח לא משפיע עד אישור אנושי.
  3. eval gate בלי golden dataset אמיתי — ה"הערכה" היא עוד LLM שמנחש, ולא תופסת regression אמיתי. תיקון: בנה golden set מתוצרי ה-dry-run של פרק 3, 10+ מקרים כולל מקרי-קצה.