דילוג לתוכן הראשי
aivrit

אימות בקלוד קוד: איך הסוכן יודע שהוא באמת סיים

מאת ירון דויטשר עודכן לאחרונה:

למה סוכן עוצר לפני שהעבודה גמורה?

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

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

בוריס צ’רני, יוצר קלוד קוד, ניסח את זה בראיון ל-Root Access של Y Combinator בחדות: “האימות הוא כנראה הדבר החשוב ביותר שאנשים לא עושים נכון.” לא ניסוח הפרומפט, לא בחירת המודל — האימות. ההסבר המלא על הראיון ההוא נמצא בטיפ שנכתב עליו; העמוד הזה הוא המדריך המעשי שמתחיל היכן שהטיפ נגמר.

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

מה נחשב בדיקה שסוגרת את הלולאה?

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

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

התיעוד של אנתרופיק מדגים את זה כשכתוב מחדש של אותה בקשה:

מה עושיםניסוח חלשניסוח שסוגר לולאה
מספקים קריטריון”תכתוב פונקציה שמאמתת כתובות אימייל""תכתוב פונקציה שמאמתת כתובות אימייל. דוגמאות: כתובת תקינה מחזירה true, מחרוזת בלי שטרודל מחזירה false. תריץ את הבדיקות אחרי המימוש”
מאמתים ויזואלית”תשפר את המראה של הדשבורד""[צילום מסך] תממש את העיצוב הזה. תצלם את התוצאה, תשווה למקור, תפרט את ההבדלים ותתקן אותם”
מטפלים בשורש”הבילד נכשל""הבילד נכשל עם השגיאה הזו: [שגיאה]. תתקן ותוודא שהבילד עובר. תטפל בסיבה, לא תשתיק את השגיאה”

שימו לב מה משותף לעמודה הימנית: בכל שורה יש מה לבדוק ואיך יידעו שזה עבר. זה כל המתכון.

ארבע דרגות, לפי מי מחזיק את ההכרעה

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

  • בתוך הפרומפט — המודל פוסק על עצמו: מבקשים באותה הודעה להריץ את הבדיקה ולתקן עד שהיא עוברת. אפס הגדרה, עובד היום על כל משימה. החולשה ברורה: מי שעשה את העבודה הוא גם מי שמכריז שסיים
  • תנאי סיום לסשן — מודל נפרד פוסק: הפקודה ‎/goal‎ מגדירה קריטריון, ואחרי כל תור מודל קטן ומהיר בודק אם הוא מתקיים. אם לא, קלוד מתחיל תור נוסף במקום להחזיר לכם שליטה
  • hook מסוג Stop — סקריפט פוסק: הבדיקה רצה כקוד, והשער לסיום התור פשוט אינו נפתח לפניה. אין כאן שיפוט של מודל בכלל
  • סוכן משנה או workflow — מודל טרי פוסק: בודק שרואה רק את הדיף ואת הקריטריונים, בלי ההיגיון שהוביל לשינוי, מנסה להפריך את התוצאה

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

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

איך כותבים תנאי סיום שמחזיק לאורך תורים?

הפקודה ‎/goal‎ מקבלת תנאי בשפה חופשית, וקובעת אותו כיעד הסשן:

/goal all tests in test/auth pass and the lint step is clean

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

תנאי שמחזיק לאורך הרבה תורים בנוי בדרך כלל משלושה חלקים:

  • מצב סיום אחד ומדיד: תוצאת בדיקות, קוד יציאה, ספירת קבצים, תור ריק
  • דרך ההוכחה: איך קלוד אמור להראות שזה קרה, למשל ש-‎npm test‎ מחזיר אפס
  • האילוצים שחשובים: מה אסור שישתנה בדרך, למשל שאף קובץ בדיקות אחר לא נגעו בו

כדי לתחום את משך הריצה כותבים סעיף עצירה לתוך התנאי עצמו — “או תעצור אחרי 20 תורים”. קלוד מדווח על ההתקדמות מול הסעיף הזה בכל תור. אורך התנאי מוגבל ל-4,000 תווים, ואפשר יעד אחד פעיל בכל סשן; יעד חדש מחליף את הקודם.

הפקודה ‎/goal‎ בלי ארגומנטים מציגה את המצב: התנאי, כמה זמן הוא רץ, כמה תורים הוערכו, הצריכה עד כה, והנימוק האחרון של המעריך. ‎/goal clear‎ מבטלת יעד פעיל (גם stop, off, reset, none ו-cancel מתקבלות).

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

מה המעריך לא רואה

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

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

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

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

ולבסוף שתי מגבלות הפעלה: ‎/goal‎ עובדת רק במרחבי עבודה שאישרתם בדיאלוג האמון, כי המעריך הוא חלק ממערכת ה-hooks. היא גם אינה זמינה כשמוגדר disableAllHooks באיזושהי רמה, או allowManagedHooksOnly בהגדרות מנוהלות. בכל מקרה כזה הפקודה אומרת למה, במקום לא לעשות כלום בשקט.

שער דטרמיניסטי: hook מסוג Stop

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

{
  "decision": "block",
  "reason": "הבילד נכשל, תתקן את השגיאות לפני שתסיים"
}

השדה decision עם הערך block הוא מה שמונע מקלוד לעצור, ו-reason הוא ההסבר שהוא מקבל. כשרוצים להזרים לו הערה בלי לחסום, משתמשים ב-hookSpecificOutput.additionalContext; וכשרוצים לעצור אותו לגמרי בלי קשר לשאר, continue: false.

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

אפשר גם לצמצם hook לסוכן אחד בלבד, על ידי הגדרתו ב-frontmatter של הסוכן. אצל סוכן משנה האירוע Stop מומר אוטומטית ל-SubagentStop, כי זה האירוע שנורה כשסוכן משנה מסיים:

---
name: my-agent
description: Agent with completion validation
hooks:
  Stop:
    - matcher: "*"
      hooks:
        - type: command
          command: "./scripts/validate-completion.sh"
---

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

דעה שנייה: בודק בהקשר נקי

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

לבדיקת נכונות פשוטה מריצים את הפקודה המובנית ‎/code-review‎, שסורקת את הדיף הנוכחי בסוכן משנה ומחזירה ממצאים לשיחה. כשרוצים לבדוק את הדיף מול התוכנית ולא מול באגים, כותבים את הפרומפט לבד ומציינים שלושה דברים: מה לבדוק, מול מה, ומה נחשב ממצא.

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

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

מתי אימות הופך לנזק?

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

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

הצד השני של אותה טעות הוא להשקיע בשער לפני שהוא נדרש. משימה שאפשר לתאר את הדיף שלה במשפט אחד אינה זקוקה ל-hook ולא ליעד; היא זקוקה למשפט אחד בפרומפט. ההשקעה בדרגות הגבוהות משתלמת כשהריצה ארוכה, כשאף אחד אינו יושב מולה, או כשהמחיר של תוצאה שגויה גבוה.

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

שורה תחתונה

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

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

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

שאלות נפוצות

איך גורמים לקלוד קוד להריץ בדיקות ולתקן לבד עד שהן עוברות?

מבקשים את זה באותה הודעה שבה מגדירים את המשימה: 'תוסיף ולידציה לשדה האימייל, תריץ את הבדיקות, ותמשיך לתקן עד שהן עוברות'. זו הדרגה הראשונה והיא עובדת על כל משימה בלי שום הגדרה מוקדמת. לריצה ארוכה יותר מגדירים תנאי סיום בפקודה ‎/goal‎, או hook מסוג Stop שמריץ את הבדיקות כסקריפט.

מה עושה הפקודה ‎/goal‎ בקלוד קוד?

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

האם המעריך של ‎/goal‎ באמת מריץ את הבדיקות?

לא, וזו הנקודה החשובה ביותר בפקודה. המעריך אינו מריץ פקודות ואינו קורא קבצים בעצמו — הוא שופט רק את מה שקלוד כבר הציג בשיחה. לכן תנאי טוב הוא כזה שהפלט של קלוד עצמו יכול להוכיח, למשל 'הבדיקות בתיקייה test/auth עוברות', שהתוצאה שלהן נוחתת בתמליל.

מה ההבדל בין ‎/goal‎ ל-hook מסוג Stop?

שניהם רצים אחרי כל תור, והראשון בנוי מעל השני: ‎/goal‎ הוא עטיפה סביב hook מסוג Stop מבוסס-פרומפט שחי רק בסשן הנוכחי. ההבדל המעשי הוא היקף וסוג ההכרעה — יעד נכתב בשורה אחת ומת עם הסשן, ואילו hook יושב בקובץ ההגדרות, חל על כל סשן בטווח שלו, ויכול להריץ סקריפט דטרמיניסטי במקום לשאול מודל.

האם אימות יכול להזיק?

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

מדריכים קשורים