דיבאגינג לסוכנים: למה הוא מחק את הקובץ במקום לשמור? (ואיך תופסים אותו על חם)

יש רגעים בחיים שבהם הכול זורם: הסוכן האוטונומי (Agent) עובד, משימות נסגרות, קבצים נוצרים… ואז פתאום—בום—הקובץ נעלם. לא “לא נשמר”, לא “נשמר במקום אחר”, אלא ממש נמחק. ואתה יושב מול הלוגים כמו בלש בסדרה: “למה עשית את זה? מי אמר לך? ולמה זה נשמע לך רעיון טוב?”

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

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

מה בכלל אומר “הסוכן החליט למחוק”?

כשאנשים אומרים “הסוכן החליט למחוק”, לפעמים הם מתכוונים לאחד מהדברים הבאים:

  • הסוכן קרא לפונקציה delete במפורש (מודע לגמרי)

  • הסוכן ביקש “לנקות” והרכיב הביצועי פירש את זה כ-delete

  • הסוכן עבר על קובץ זמני/ביניים והחליט שזה זבל (לטעמו) ושיחרר דיסק

  • הסוכן רצה לשמור, אבל נכשל בכתיבה ואז ביצע רוטינת rollback שמוחקת את מה שנוצר חלקית

  • הסוכן שמר במקום אחר, ואז ניקוי אוטומטי מחק את המקור

  • כלי חיצוני (policy, filesystem watcher, DLP, antivirus, cleanup job) מחק—והסוכן רק “קושר” לזה בדיעבד

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

3 שכבות שחייבים להפריד (אחרת תשתגע)

כדי להבין התנהגות של סוכן, תפריד בין:

  1. שכבת הכוונה (Intent): מה הסוכן חשב שהוא עושה: “לשמור את הקובץ”, “לנקות זמניים”, “לא להשאיר עקבות”, “להקטין שימוש בדיסק”, “למנוע דליפת מידע”.

  2. שכבת התכנון (Plan): איך זה תורגם לצעדים: “write temp → validate → move → delete temp”.

  3. שכבת הביצוע (Execution): מה באמת קרה במערכת: קריאות מערכת, הרשאות, partial writes, חריגות, retries, טרנזקציות, timeouts.

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

החשוד המרכזי: “ניקוי” שהוא בעצם גרזן

אחת המילים הכי מסוכנות בעולם של סוכנים היא “cleanup”.

למה? כי “cleanup” הוא מושג:

  • לפעמים זה delete

  • לפעמים זה truncate

  • לפעמים זה archive

  • לפעמים זה move ל-trash

  • לפעמים זה “remove if exists”

  • לפעמים זה “רוץ על כל התיקייה ותעשה סדר” (כן, ברור, למה לא)

אם אתה נותן לסוכן משימה בסגנון “תסדר את התיקייה ותשאיר רק את התוצרים הסופיים”, בלי מסגרת ברורה—הוא עלול להיות חרוץ מדי.

איך מוכיחים מי אשם? לא “אשמה”, אחריות תהליכית

המטרה שלך היא להגיע למסמך ראיות קצר שמספר סיפור חד:

  • מה היה הקובץ (שם/נתיב/ hash אם אפשר)

  • מתי נוצר, מתי שונה, מתי נמחק

  • מי קרא למחיקה (process / container / user)

  • איזה “רציונל” הופיע לפני המחיקה (ב-LLM trace / tool trace)

  • האם הייתה חריגה שקדמה לזה (write failed, validation failed, permission denied)

  • מה היה מצב עולם התוכן (הקובץ הוגדר כזמני? הייתה מדיניות “לא להשאיר PII”? היה לחץ דיסק?)

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

7 סוגי טריגרים שמובילים למחיקה (ותמיד מתחפשים למשהו אחר)

  1. קובץ זמני שהתחזה לקבוע: דפוס נפוץ: כתיבה לקובץ temp ואז rename ליעד. אם ה-rename נכשל, מנקים את temp—ואז מסתבר שה-temp היה בעצם הדבר היחיד שהיה.

  2. “אם לא הצליח, נחזיר לקדמותו”: Rollback חכם מדי: נוצר קובץ, ואז בדיקת תקינות (checksum / schema / validation) נכשלת, ואז המערכת מוחקת “כדי לא להשאיר קובץ פגום”.

  3. סוכן שמבין “אל תשמור מידע רגיש” בצורה ליטרלית: הסוכן מזהה בטקסט/קובץ משהו שנראה כמו PII/סוד, ומפעיל “השמדה מהירה”.

  4. ניהול מקום בדיסק: “בוא נרוויח קצת ג’יגה”: אם הריצה מתבצעת בסביבה מוגבלת, ניקוי יכול להופיע כאופטימיזציה.

  5. טעות נתיב: מחקנו את הלא נכון (אבל בהתלהבות): path bug קלאסי: משתנה נתיב ריק, ואז מוחקים תיקייה במקום קובץ; או join לא נכון שגורם לעלות רמה אחת למעלה.

  6. תחרות (Race): מישהו אחר נגע בקובץ: שני Agents, או Agent וכלי אחר. אחד כותב, השני מנקה.

  7. “נכתב ואז הועבר” ואז המקור נעלם (כי ככה זה Move): לפעמים המשתמש חשב ששומרים עותק, אבל בפועל עשו move.

איך מסדרים תצפית: טלמטריה שעושה שכל (ולא רק רעש)

אם אתה רוצה להבין החלטות, אתה צריך שני דברים: Trace של מחשבה + Trace של כלים. לא אחד מהם.

מה לשמור בכל צעד משמעותי:

  • Action ID (מזהה פעולה חד-חד ערכי)

  • Parent Action ID (כדי לבנות עץ פעולות)

  • tool name (write_file, delete_file, move_file)

  • arguments (נתיב מקור/יעד, דגלים)

  • preconditions (מה הסוכן בדק לפני)

  • outcome (success/failure + error)

  • rationale קצר: משפט אחד למה הפעולה נדרשת

הטוויסט: “rationale קצר” חייב להיות מובנה, לא חפירה. למשל:

  • reason_code: CLEANUP_TEMP

  • reason_details: “temp created during compile step”

  • confidence: 0.72

כי אם תשאיר “הסוכן כתב פסקה”, זה יפה, אבל לא דיבאג-פרנדלי.

מלכודת מוכרת: הפרומפט נתן רמז מחיקה בלי שהתכוונת

מספיק משפט אחד תמים כדי להכניס את הסוכן למוד “היגיינה קשוחה”:

  • “תשאיר רק את מה שצריך”

  • “תנקה אחריך”

  • “בלי קבצים מיותרים”

  • “אל תשמור מידע מקומי”

  • “שמור רק את הפלט הסופי”

אם כתוב כך, הסוכן עושה את מה שביקשת: הוא מפרש “מיותר” בצורה יצירתית. לפעמים מאוד יצירתית.

מה לעשות במקום:

  • תגדיר תיקיית עבודה ברור: workspace/

  • תגדיר תיקיית תוצרים: output/

  • תכתוב כלל: “מותר למחוק רק מתוך workspace/tmp”

  • תכתוב כלל: “אסור למחוק מתוך output”

  • תכתוב כלל: “אם יש ספק—אל מוחקים, מסמנים למחיקה”

כן, זה פחות “זורם”. גם פחות “איפה הקובץ שלי?”

תכל’ס: 9 צעדים לשחזור “למה מחקת?”

  1. אסוף את הראיות הבסיסיות: שם קובץ, נתיב, זמן משוער להיעלמות, environment (local/container).

  2. חפש את אירוע המחיקה בפועל: audit logs, fs events, application logs.

  3. קשר את האירוע לישות מבצעת: process/container/user/service account.

  4. בנה timeline מסודר: create → modify → validate → move → delete.

  5. מצא את הטריגר שקדם למחיקה: error, cleanup signal, policy trigger, disk pressure.

  6. השווה בין “התכנון” ל”ביצוע”: האם התוכנית רצתה למחוק temp אבל התבלבלה בנתיב?

  7. בדוק guardrails והנחיות: “אל תשמור”, “תנקה”, “מחק אחרי שימוש”.

  8. בצע ריצה חוזרת עם observability מוגברת: trace IDs, tool call logging, pre/post state snapshots.

  9. קבע תיקון שמונע הישנות: denylist/allowlist למחיקות, safe delete (trash), require confirmation, dry-run.

מה משנים כדי שזה לא יקרה שוב (ולא להפוך את המערכת לבירוקרטיה)

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

  • “מחיקה רכה” כברירת מחדל: במקום delete: move ל- trash/ עם TTL (למשל 24 שעות). ככה יש לך גם סדר וגם יכולת התאוששות.

  • כל מחיקה חייבת לעבור Validator: כלומר פונקציה אחת שאומרת: “מותר למחוק רק אם הנתיב תחת /workspace/tmp”.

  • מצבי ריצה: dry-run / safe-mode: בפיתוח: אין מחיקות אמיתיות, רק לוג “הייתי מוחק”. בפרוד: מחיקות רק בקבצים שסומנו temp.

  • סימון קבצים במקום מחיקה: הסוכן מוסיף suffix כמו .to_delete או רושם manifest, ורק Job נפרד מוחק מאוחר יותר.

  • תוצרים חתומים: כל מה שבתיקיית output מקבל “protected marker” (למשל קובץ .keep), וכל delete מתעלם מזה.

שאלות ותשובות (כי ברור שזה מה שכולם שואלים באמצע)

ש: איך אני יודע אם הסוכן באמת קרא ל-delete או שמישהו אחר מחק? ת: אתה צריך audit trail של filesystem (או לפחות לוגים של ה-process) שמראה מי ביצע את הקריאה. בלי זה, אתה מנחש. עם זה, אתה בלש עם מצלמות אבטחה.

ש: למה לפעמים אין שום לוג שמדבר על מחיקה? ת: כי הרבה מחיקות קורות בשכבת מערכת/תשתית (cleanup jobs, policies) או כי הלוגים לא כוללים tool calls. הפתרון: להוסיף tool tracing ו-audit.

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

ש: מה ההבדל בין “move” ל-“copy” ולמה זה חשוב? ת: move בדרך כלל גורם להיעלמות המקור. אם אתה מצפה לעותק, אתה חייב copy. הרבה “היעלמויות” הן פשוט move לא מתועד.

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

ש: איך מנסחים הנחיות לסוכן כדי שלא יהיה “חיית ניקיון”? ת: תגדיר גבולות: “מותר למחוק רק תחת X”, “אסור למחוק תחת Y”, “אם לא בטוח—אל למחוק”. תן לו מסגרת, הוא יודה לך בשקט.

סיכום קצר, אבל כזה שסוגר פינות

כשסוכן מוחק קובץ במקום לשמור אותו, זה כמעט אף פעם לא “באג מקרי”. זו תוצאה של כוונה, תכנון וביצוע—ולפעמים גם מדיניות סביבתית שעושה סדר. הדרך להבין למה זה קרה היא לבנות timeline, לקשור מחיקה לישות מבצעת, ולאסוף את הרציונל (בצורה מובנית) בדיוק ברגע שהסוכן בחר לפעול.

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