פרומפטים הם הצעות. Hooks הם עצירות מוחלטות.

במשך חודשים התייחסתי ל-Claude Code כמו למפתח ג'וניור שפשוט זקוק לכללי יסוד ברורים. הוראות הפרויקט שלי היו מפורשות: לעולם לא לבצע force-push, לעולם לא למחוק branches, ולעולם לא להריץ פקודות הרסניות. ברוב הערבים, זה עבד. הסוכן כתב טסטים, ביצע refactoring לפונקציות, ושמר על הידיים שלו רחוקות מה-git history. ואז, rebase אחד השתבש.

חלון ההקשר (context window) התמלא בפלט של שגיאות git. סימני קונפליקט, הודעות detached HEAD ואזהרות על הסתעפות של branches נערמו, טוקן אחר טוקן. תחת הרעש הזה הייתה קבורה ההוראה המנומסת שלי להימנע מ-force-pushing. עבור המודל, הטקסט האחרון והבולט ביותר בשרשור היה זרם השגיאות. תשומת הלב הסטטיסטית גברה על המדיניות. הסוכן ביצע פקודה שמחתה שעתיים של שינויים מקומיים שלא עברו commit. זה לא היה זדוני; זה היה מוסח דעת. ההבחנה הזו חשובה. LLM לא מפר חוקים מתוך עוינות. הוא מפר אותם כי תבנית רועשת יותר בחלון ההקשר דורסת זמנית הוראה קודמת.

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

Claude Code hooks פותרים בדיוק את זה. אלו סקריפטים קטנים שמת intercepts (עוצרים) קריאות לכלים בשלושה רגעים ספציפיים: לפני שהכלי מבוצע (PreToolUse), אחרי שהכלי מסתיים (PostToolUse), וכאשר הסוכן מחליט שהוא סיים (Stop). מכיוון שהם רצים כקוד חיצוני, הם אינם תלויים בזיכרון, במצב הרוח או בלחץ ההקשר של המודל. המודל יכול לשכוח כל הוראה שנתת לו אי פעם; ה-hook עדיין יגיד לא.

הנה המנגנון שבניתי אחרי הערב האבוד ההוא.

The Guard Hook: עצירה לפני נזק

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

התבניות שאני חוסם הן פשוטות וחד-משמעיות:

  • git push --force או כל וריאציה של force-with-lease שאני עדיין לא סומך עליה
  • git reset --hard
  • rm -rf

זה לא מחקר אבטחה מתוחכם. זה חגורת בטיחות. אבל הפרט הקריטי הוא מה קורה אחרי החסימה.

אני אף פעם לא מחזיר "Blocked" גס. סירוב גורף מבלבל את הסוכן ויכול לכלוט אותו בלופ שבו הוא מנסה וריאציות של אותה פקודה הרסנית. במקום זאת, הודעת השגיאה כוללת נתיב מילוט. כשה-hook תופס hard reset, הוא אומר לסוכן: “This command is blocked to protect uncommitted work. Commit a checkpoint first, then reassess.” המשפט הנוסף הזה משנה לחלוטין את התנהגות הסוכן. הוא עובר מניסיון של בקרת נזקים ליצירת בטיחות. ה-hook הוא לא רק חומה; הוא בקרת תנועה.

בחרתי גם ב-denylist על פני allowlist עבור פקודות shell. בהתחלה שקלתי לאפשר רק סט מפורש של תתי-פקודות git בטוחות. זה נכשל מהר מאוד. סוכנים הם מילוליים בצורה יצירתית. הם מריצים פקודות לגיטימיות אך לא צפויות כמו git stash push -m "wip" או git branch --show-current כדי לבדוק מצב. allowlist שוברת את זרימת העבודה הרגילה ברגע שהמודל ממציא פקודה תקפה אך לא רשומה. denylist קצר ומסונן של תבניות הרסניות באמת נותן לסוכן מרחב תמרון תוך הגנה על הגבולות.

The Formatter Hook: אוטומציה של עבודות השגרה

בעבר בזבזתי טוקנים של פרומפט כדי להגיד לסוכן "תמיד תריץ את ה-formatter אחרי עריכת קובץ". הוא שכח את זה בחצי מהזמן. בחצי השני, הוא היה עוצר ושואל אם לעשות format, ובכך מבזבז קריאת כלי (tool call) על החלטה שיש לה רק תשובה נכונה אחת.

עכשיו אני מטפל בזה באמצעות PostToolUse hook. אחרי שהסוכן עורך קובץ, ה-hook בודק את סיומת הקובץ. אם זה Python, הוא מריץ Ruff. אם זה JavaScript או TypeScript, הוא מריץ Prettier. אם זה Go, הוא מריץ gofmt. הסוכן לא יודע שה-formatter קיים. הוא לא צריך לדעת.

להוצאת הפעולה הזו מחוץ לפרומפט היו שתי תוצאות. ראשית, הקוד נקי בעקביות מבלי להוסיף עומס קוגניטיבי למודל. שנית, הוראות הפרויקט שלי התקצרו. כל "תמיד" ו"לעולם לא" שאתה מסיר מפרומפט הם טוקנים שהמודל יכול להשקיע בפתרון בעיות אמיתי. ה-hook אחראי על האינווריאנט (invariant); הפרומפט אחראי על הכוונה (intent).

The Quality Gate: הגדרה מחדש של "בוצע"

The Stop hook runs when the agent decides it has finished the task and attempts to terminate the session. I do not let it. Instead, the hook runs the full test suite. If any test fails, the hook blocks the stop command and returns the failure output to the agent.

This changes the definition of completion. “Done” is no longer a feeling the model has. It is a measurable gate. The agent can only finish when the harness confirms the code works. In practice, this creates a tight feedback loop. The agent writes code, thinks it is finished, hits the stop button, and immediately sees a pytest traceback. It then self-corrects, fixes the import error or broken assertion, and tries to stop again. I have watched agents iterate three or four times inside this loop without human intervention. The harness enforces quality; the model supplies the patches.

What This Teaches About Agent Engineering

Building reliable autonomous systems requires a shift in mindset. You move from writing longer prompts to building tighter harnesses.

Use hooks for enforcement and prompts for policy. If a rule must hold one hundred percent of the time, it belongs in code, not in natural language. Prompts excel at ambiguity, taste, and architecture. They are terrible at invariants. If a mistake would cost you an afternoon of recovery time or, worse, production uptime, write a hook.

Shorter prompts produce better results. When you move mechanical rules into scripts, the model has less to remember and less to contradict. The agent’s context window is a scarce resource. Do not fill it with formatting reminders.

Finally, accept that your role is changing. As agents gain autonomy, the human’s job shifts from generating content to designing the guardrails. You are building the harness that decides what the model can touch, when it can finish, and how it must behave when things go wrong. That is engineering, not prompting.

The source that inspired this approach and additional implementation detail can be found here.

If you are building with AI agents and want to trade notes with other practitioners, you can find the GyaanSetu learning community here.