وقتی یک ایجنت نرمافزاری برای چند ده نوبت متوالی در محیط واقعی اجرا میشود، دیگر نمیتوان آن را با یک حلقهی سادهی چت مدیریت کرد. تغییر مدل، بازبینی دسترسی ابزارها یا ثبت تلهمتری بدون بازنویسی کل سامانه نیازمند قراردادی شفاف و ایزولهشده میان حلقه و اجزای بیرونی است.
در این درس ساختار پایدار یک حلقهی بستر آزمایشی ایجنت (Agent Harness Loop) را بهصورت یک ماشین وضعیت کاملاً قطعی پیادهسازی میکنید؛ الگویی استاندارد که به شما اجازه میدهد مدل زبانی را صرفاً یک پردازندهی کمکی در نظر بگیرید و مرز میان منطق اجرای ابزارها، بودجهی زمانی و هوکهای نظارتی را بدون وابستگی حفظ کنید.
بستر آزمایشی (harness) همان ایجنت است. مدل صرفاً یک پردازندهی کمکی (coprocessor) است. این درس قرارداد حلقهای را تثبیت میکند که میتوانید هر مدلی را به آن متصل کنید.
اهداف یادگیری
- تعریف حلقهی بستر آزمایشی ایجنت (agent harness) بهصورت یک ماشین وضعیت (state machine) قطعی با گذارهای صریح.
- پیادهسازی 10 موضوع هوک چرخهی حیات (lifecycle hook topics) تا اپراتورها بتوانند سیاستها، تلهمتری و گاردریلها (سازوکارهای ایمنی) را به آن متصل کنند.
- تعیین دو نقطهی واگذاری (pull point) که در آنها حلقه کنترل را به فراخواننده بازمیگرداند و با دریافت ورودی تازه کار را از سر میگیرد.
- اعمال بودجه به ازای هر نشست (نوبتها، فراخوانیهای ابزار، زمان واقعی) بدون نشت وضعیت ناقص در صورت عبور از سقف مجاز.
- انتشار جریانی تایپشده از 11 نوع رویداد تا رابطهای کاربری و ابزارهای ردگیری محاسبات (tracerها) در لایههای بعدی بتوانند بدون وارسی مستقیم حلقه، مشترک آن شوند.
صورت مسئله
ایجنت کدنویسی که 40 نوبت گفتوگو (turn) بدون نظارت اجرا میشود، یک حلقهی چت ساده نیست؛ بلکه ماشین وضعیتی است که اپراتور میتواند گرههای آن را رهگیری کند و یالهایش را مورد بازرسی قرار دهد. وقتی این قرارداد را مکتوب و شفاف کنید، تعویض مدلها، ابزارها یا سیاستها دیگر نیازی به بازنویسی کد (refactor) نخواهد داشت و به یک فراخوانی ساده برای ثبت تبدیل میشود.
در این درس این قرارداد را پیاده میکنیم: شش وضعیت، 10 موضوع هوک، دو نقطهی واگذاری، 11 نوع رویداد و یک چارچوب بودجه تعیین خواهیم کرد. تمام بخشهای دیگر بستر آزمایشی (رجیستری ابزار، بستر انتقال JSON-RPC، توزیعکننده و برنامهریز) روی همین ساختار سوار میشوند.
وضعیتها
این حلقه شش وضعیت دارد: پنج وضعیت فعال و یک وضعیت پایانی.
وضعیت IDLE تنها نقطهی ورود مجاز است و DONE تنها نقطهی خروج مجاز. وضعیت AWAITING_TOOL تنها وضعیتی است که بهخودیخود به یک نقطهی واگذاری ختم میشود. دومین نقطهی واگذاری، یعنی on_pause، یک وضعیت مستقل نیست؛ بلکه با اتمام بودجه یا درخواست بازبینی از سوی یک هوک، از هر وضعیت فعالی فعال میشود. سایر گذارها همگی درونی هستند.
این ماشین وضعیت قطعی است. با دریافت همان لاگ رویدادها (event log)، بستر آزمایشی مجدداً وارد همان وضعیت میشود. همین ویژگی به شما امکان میدهد نشستها را برای اشکالزدایی بازپخش (replay) کنید، بدون آنکه نیازی به فراخوانی دوبارهی مدل باشد.
موضوعهای هوک
هوکها محل اتصال اپراتور به درون حلقه هستند. بستر آزمایشی 10 موضوع را فراخوانی میکند. هر موضوع میتواند هر تعداد مشترک بپذیرد. مشترکان به ترتیب ثبتنام اجرا میشوند. یک مشترک میتواند دادهها (payload) را تغییر دهد، با پرتاب خطا کل نوبت را متوقف کند، یا مقداری نشانه بازگرداند تا مرحلهی بعدی نادیده گرفته شود.
before_plan after_plan
before_tool_call after_tool_call
before_step after_step
on_error
on_pause
on_budget_exceeded
on_complete
این ساختار همان الگویی را بازتاب میدهد که Claude Code، Cursor و OpenCode تا اواسط سال 2025 بر سر آن به توافق رسیدند. نامگذاریها بر اساس کارکرد انتخاب شدهاند، نه نامهای تجاری. هوکی که جلوی دستور rm -rf را میگیرد در before_tool_call قرار میگیرد. هوکی که یک اسپن (span) در OpenTelemetry ارسال میکند در after_step مینشیند. هوکی هم که نشست متوقفشده را از سر میگیرد در on_pause اجرا میشود.
نقاط واگذاری
حلقه در دو نقطه کنترل را واگذار میکند: اول در AWAITING_TOOL، وقتی بدون نتیجهی ابزار امکان پیشروی ندارد؛ دوم در on_pause، هنگامی که بودجه تمام شود یا یک هوک صراحتاً درخواست بازبینی انسانی کند.
نقطهی واگذاری یک استثنا (exception) نیست، بلکه یک بازگشت مقدار ساده است. فراخواننده وضعیت بستر آزمایشی را بررسی میکند، دادهی مورد نیاز بستر را فراهم میآورد و متد resume(payload) را صدا میزند. سپس بستر آزمایشی کار را دقیقاً از نقطهی توقف ادامه میدهد. این الگو درست مانند یک generator در Python کار میکند. انتخاب بستر انتقال (transport) روی این نقطهی واگذاری بر عهدهی شماست: در یک رابط متنی ترمینال (TUI) میتواند فشردن یک کلید باشد، در MCP متد tools/call است، و در یک صف پردازش میتواند واکشی یک تسک کاری باشد.
جریان رویدادها
حلقه رویدادها را در نقاط مشخصی از قرارداد به یک جریان تایپشده اضافه میکند. این جریان فقطافزودنی (append-only) است و مشترکان میتوانند آن را از هر آفستی دوباره بازپخش کنند. 11 نوع رویداد پیادهسازیشده عبارتاند از:
session.start— یکبار در زمان فراخوانیrun(goal)منتشر میشودplan.draft— وقتی برنامهریز پیشنویس برنامه را برمیگرداند منتشر میشودplan.commit— پس از commit شدن پیشنویس بهعنوان برنامهی فعال منتشر میشودstep.start— در شروع هر مرحلهی اجرایی منتشر میشودstep.end— در پایان هر مرحلهی اجرایی منتشر میشودtool.call— وقتی مرحلهی نیازمند ابزار کنترل را به فراخواننده واگذار میکند منتشر میشودtool.result— هنگام ادامهی کار همراه با نتیجهی ابزار منتشر میشودtool.error— هنگام ادامهی کار با خطا یا زمانی که یک هوک فراخوانی را لغو کند منتشر میشودbudget.warn— با رسیدن به سقف یکی از محدودیتهای بودجه منتشر میشودsession.pause— با توقف حلقه در یک نقطهی توقف (بهدلیل بودجه یا هوک) منتشر میشودsession.complete— یکبار با رسیدن حلقه به وضعیتDONEمنتشر میشود
رویدادها دادههای هوک را دوباره تکرار نمیکنند. هوکها جنبهی دستوری دارند (تغییر دادن، لغو کردن). رویدادها جنبهی مشاهدهای دارند (ثبت کردن، ارسال داده). به آنها به چشم دو سازوکار کاملاً مستقل نگاه کنید.
چارچوب بودجه
هر نشست سه محدودیت همراه خود دارد: تعداد نوبتها، تعداد فراخوانیهای ابزار، و ثانیههای زمان واقعی (wall-clock). هر نوبت گفتوگو یک واحد به نوبتها اضافه میکند. هر فراخوانی ابزار یک واحد به تعداد ابزارها میافزاید. زمان واقعی نیز در هر گذار وضعیت بررسی میشود. هرگاه یکی از محدودیتها پر شود، حلقه on_budget_exceeded را فراخوانی میکند، رویداد budget.warn را انتشار میدهد و سپس در نقطهی واگذاری بعدی، با ارائهی دلیل عبور از سقف بودجه به وضعیت IDLE میرود.
بودجه یک سوییچ قطع اضطراری (kill switch) نیست، بلکه صرفاً واگذاری کنترل است. این فراخواننده است که تصمیم میگیرد بودجه را تمدید کند و کار را ادامه دهد، یا اینکه نشست را کلاً ببندد.
مواردی که در این درس بررسی نمیشوند
این درس مدلی را فراخوانی نمیکند، ابزارهای واقعی ثبت نمیکند و بستر انتقالی نمیسازد. همهی اینها در چهار درس بعدی بررسی میشوند. هدف این درس تثبیت دقیق قرارداد است تا چهار درس بعدی بدون نیاز به بازنویسی روی آن سوار شوند.
برنامهریز قطعی در فایل main.py صرفاً یک نمونک موقت است. این بخش برنامهای ثابت با سه مرحله برمیگرداند که دو مرحلهی آن نیازمند نتیجهی ابزار است. هدف اصلی بررسی خود حلقه است، نه برنامهای که تولید میشود.
راهنمای مطالعهی کد
کلاس اصلی HarnessLoop است. این کلاس وضعیت را نگه میدارد، هوکها را صدا میزند و رویدادها را منتشر میکند. کلاس Budget محدودیتها را پیگیری میکند. کلاس Event پوشش تایپشده روی جریان داده است و HookRegistry جدول توزیع را مدیریت میکند. تابع _transition تنها تابعی است که وضعیت را تغییر میدهد؛ بنابراین پایدارها (invariants) ماشین وضعیت همگی در یک نقطه متمرکز شدهاند.
فایل main.py را از بالا به پایین بخوانید. سپس به سراغ code/tests/test_loop.py بروید. این تستها تمام گذارها و ترتیب اجرای هوکها را دقیقاً تثبیت و بررسی میکنند.
گام بعدی
سختترین بخش ساخت بستر آزمایشی در محیط عملیاتی (پروداکشن)، پیادهسازی ماشین وضعیت نیست؛ بلکه الزامآور کردن قرارداد است. این قرارداد باید بتواند بارگذاری مجدد و پویای برنامهریز (hot reload) را تاب بیاورد. باید در برابر ابزاری که JSON نامعتبر بازمیگرداند دوام بیاورد. باید در شرایطی که یک هوک پس از دو سوم مسیر یک نشست 40 نوبتی داخل before_tool_call خطایی پرتاب میکند، پایداری خود را حفظ کند. تستهای این درس دقیقاً این حالتهای شکست (failure modes) را میآزمایند. آنها را اجرا کنید، سعی کنید سیستم را به چالش بکشید و سناریوهای جدید بنویسید.
در درس بعدی، رجیستری ابزار را اضافه میکنیم. پس از آن نوبت به بستر انتقال JSON-RPC میرسد و سپس توزیعکننده را میسازیم. تا درس 24، حلقهای که در این فایل ساختید برنامهای واقعی را با ابزارهای واقعی و با اعمال دقیق بودجهها اجرا خواهد کرد.
