پرامپتها پیشنهاد هستند. هوکها (Hooks) توقفهای قطعی هستند.
ماهها بود که با Claude Code مثل یک برنامهنویس تازهکار رفتار میکردم که فقط به قوانین شفاف نیاز دارد. دستورالعملهای پروژهام صریح بودند: هرگز force-push نکن، هرگز شاخهها (branches) را حذف نکن، هرگز دستورات مخرب اجرا نکن. بیشتر شبها، این روش جواب میداد. عامل (agent) تستها را مینوشت، توابع را بازنویسی (refactor) میکرد و با تاریخچه git کاری نداشت. سپس یک rebase به مشکل خورد.
پنجره بافت (context window) با خروجی خطاهای git پر شد. نشانگرهای تداخل (conflict markers)، پیامهای detached HEAD و هشدارهای واگرایی شاخه، توکن به توکن روی هم انباشته شدند. زیر آن همه سر و صدا، دستور مؤدبانه من برای اجتناب از force-pushing دفن شده بود. برای مدل، جدیدترین و برجستهترین متن در رشته (thread)، جریان خطا بود. توجه آماری بر سیاست (policy) غلبه کرد. عامل دستوری را اجرا کرد که دو ساعت تغییرات محلی ذخیره نشده (uncommitted) را پاک کرد. این کار از روی بدخواهی نبود؛ بلکه از روی حواسپرتی بود. این تمایز مهم است. یک LLM قوانین را از روی کینه نمیشکند؛ بلکه آنها را میشکند چون یک الگوی پرصداتر در پنجره بافت، موقتاً دستور قبلی را نادیده میگیرد.
آن حادثه طرز فکر من را درباره امنیت عامل (agent safety) تغییر داد. حفاظی (guardrail) که نود و نه درصد مواقع کار میکند، یک ریسک است. اگر حالت شکست (failure mode) باعث از دست رفتن زمان، پول یا دادههای عملیاتی شود، نمیتوانید آن را داخل پرامپت رها کنید. شما به اجرای اجباری خارج از حلقه استدلال مدل نیاز دارید.
هوکهای Claude Code دقیقاً همین مشکل را حل میکنند. آنها اسکریپتهای کوچکی هستند که فراخوانی ابزارها را در سه لحظه خاص رهگیری میکنند: قبل از اجرای یک ابزار (PreToolUse)، بعد از اتمام یک ابزار (PostToolUse)، و زمانی که عامل تصمیم میگیرد کارش تمام شده است (Stop). از آنجایی که آنها به عنوان کد خارجی اجرا میشوند، به حافظه، خلقوخو یا فشار بافت (context pressure) مدل وابسته نیستند. مدل میتواند هر دستوری را که تا به حال به آن دادهاید فراموش کند؛ اما هوک همچنان خواهد گفت «نه».
در اینجا ابزاری (harness) که پس از آن شبِ از دست رفته ساختم را میبینید.
هوک محافظ: رهگیری پیش از آسیب
هوک PreToolUse من هر دستور Bash را قبل از اینکه شل (shell) آن را لمس کند، بررسی میکند. من یک لیست سیاه (denylist) دقیق از الگوهای مخرب نگه میدارم. اگر رشته دستور با چیزی خطرناک مطابقت داشته باشد، هوک اجرای آن را متوقف کرده و خطا را مستقیماً به عامل بازمیگرداند.
الگوهایی که مسدود میکنم ساده و بدون ابهام هستند:
git push --forceیا هر گونه نسخهforce-with-leaseکه هنوز به آن اعتماد ندارمgit reset --hardrm -rf
این یک تحقیق امنیتی پیچیده نیست؛ بلکه مثل یک کمربند ایمنی است. اما نکته حیاتی، اتفاقی است که پس از مسدودسازی رخ میدهد.
من هرگز یک پاسخ خشک و خالی مثل "Blocked" برنمیگردانم. یک رد کردنِ ساده، عامل را گیج میکند و میتواند او را در حلقهای گرفتار کند که در آن سعی میکند نسخههای مختلف همان دستور مخرب را اجرا کند. در عوض، پیام خطا شامل یک راه فرار است. وقتی هوک یک hard reset را شناسایی میکند، به عامل میگوید: "این دستور برای محافظت از کارهای ذخیره نشده مسدود شده است. ابتدا یک نقطه بازگشت (checkpoint) ثبت کنید، سپس دوباره بررسی کنید." آن جمله اضافی، رفتار عامل را کاملاً تغییر میدهد. او از تلاش برای کنترل خسارت، به سمت ایجاد امنیت تغییر مسیر میدهد. هوک فقط یک دیوار نیست؛ بلکه یک کنترل ترافیک است.
من همچنین لیست سیاه (denylist) را به لیست سفید (allowlist) برای دستورات شل ترجیح دادم. در ابتدا، به فکر این بودم که فقط مجموعهای صریح از زیردستورهای امن git را مجاز کنم. اما این ایده خیلی زود شکست خورد. عاملها به شکلی خلاقانه و تحتاللفظی عمل میکنند. آنها دستورات مشروع اما غیرمنتظرهای مثل git stash push -m "wip" یا git branch --show-current را برای بررسی وضعیت اجرا میکنند. یک لیست سفید، به محض اینکه مدل یک دستور معتبر اما فهرستنشده را ابداع کند، جریان کاری عادی را مختل میکند. یک لیست سیاه کوتاه و منتخب از الگوهای واقعاً مخرب، به عامل اجازه حرکت میدهد و در عین حال مرزها را محافظت میکند.
هوک فرمتکننده: خودکارسازی کارهای تکراری
قبلاً توکنهای پرامپت را با گفتن این جمله به عامل که «همیشه بعد از ویرایش یک فایل، فرمتکننده را اجرا کن» هدر میدادم. او نصف اوقات فراموش میکرد. نصف دیگر اوقات، مکث میکرد و میپرسید که آیا فرمت کند یا نه، و با این تصمیم که فقط یک پاسخ درست داشت، یک فراخوانی ابزار (tool call) را هدر میداد.
اکنون این کار را با یک هوک PostToolUse انجام میدهم. بعد از اینکه عامل فایلی را ویرایش کرد، هوک پسوند فایل را بررسی میکند. اگر پایتون باشد، Ruff را اجرا میکند. اگر JavaScript یا TypeScript باشد، Prettier را اجرا میکند. اگر Go باشد، gofmt را اجرا میکند. عامل اصلاً نمیداند که فرمتکننده وجود دارد؛ و نیازی هم ندارد.
انتقال این کار از پرامپت، دو اثر داشت. اول اینکه کدها بدون اضافه کردن بار شناختی (cognitive load) به مدل، همیشه تمیز هستند. دوم اینکه دستورالعملهای پروژهام کوتاهتر شدند. هر "همیشه" و "هرگز" که از یک پرامپت حذف میکنید، توکنی است که مدل میتواند صرف حل مسئله واقعی کند. هوک مسئول قوانین ثابت (invariant) است؛ پرامپت مسئول هدف (intent) است.
دروازه کیفیت: بازتعریف «انجام شده»
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.
