فاز 19 · درس 20

قرارداد حلقه در بستر آزمایشی ایجنت

نوع
ساختنی
زمان
حدود 90 دقیقه
زبان‌ها
Python
طرح شماتیک یک چرخ‌دنده‌ی دقیق با شیارهای مشخص و مسیرهای وضعیت متصل به یک پنل کنترل بازرسی
فهرست مطالب
  1. اهداف یادگیری
  2. صورت مسئله
  3. وضعیت‌ها
  4. موضوع‌های هوک
  5. نقاط واگذاری
  6. جریان رویدادها
  7. چارچوب بودجه
  8. مواردی که در این درس بررسی نمی‌شوند
  9. راهنمای مطالعه‌ی کد
  10. گام بعدی

وقتی یک ایجنت نرم‌افزاری برای چند ده نوبت متوالی در محیط واقعی اجرا می‌شود، دیگر نمی‌توان آن را با یک حلقه‌ی ساده‌ی چت مدیریت کرد. تغییر مدل، بازبینی دسترسی ابزارها یا ثبت تله‌متری بدون بازنویسی کل سامانه نیازمند قراردادی شفاف و ایزوله‌شده میان حلقه و اجزای بیرونی است.

در این درس ساختار پایدار یک حلقه‌ی بستر آزمایشی ایجنت (Agent Harness Loop) را به‌صورت یک ماشین وضعیت کاملاً قطعی پیاده‌سازی می‌کنید؛ الگویی استاندارد که به شما اجازه می‌دهد مدل زبانی را صرفاً یک پردازنده‌ی کمکی در نظر بگیرید و مرز میان منطق اجرای ابزارها، بودجه‌ی زمانی و هوک‌های نظارتی را بدون وابستگی حفظ کنید.

بستر آزمایشی (harness) همان ایجنت است. مدل صرفاً یک پردازنده‌ی کمکی (coprocessor) است. این درس قرارداد حلقه‌ای را تثبیت می‌کند که می‌توانید هر مدلی را به آن متصل کنید.

اهداف یادگیری

  • تعریف حلقه‌ی بستر آزمایشی ایجنت (agent harness) به‌صورت یک ماشین وضعیت (state machine) قطعی با گذارهای صریح.
  • پیاده‌سازی 10 موضوع هوک چرخه‌ی حیات (lifecycle hook topics) تا اپراتورها بتوانند سیاست‌ها، تله‌متری و گاردریل‌ها (سازوکارهای ایمنی) را به آن متصل کنند.
  • تعیین دو نقطه‌ی واگذاری (pull point) که در آن‌ها حلقه کنترل را به فراخواننده بازمی‌گرداند و با دریافت ورودی تازه کار را از سر می‌گیرد.
  • اعمال بودجه به ازای هر نشست (نوبت‌ها، فراخوانی‌های ابزار، زمان واقعی) بدون نشت وضعیت ناقص در صورت عبور از سقف مجاز.
  • انتشار جریانی تایپ‌شده از 11 نوع رویداد تا رابط‌های کاربری و ابزارهای ردگیری محاسبات (tracerها) در لایه‌های بعدی بتوانند بدون وارسی مستقیم حلقه، مشترک آن شوند.
قرارداد حلقه بستر آزمایشیشش وضعیت، یک پیمایش ممیزی‌پذیر
HARNESS LOOP CONTRACTThe loop is a deterministic state machine, not a chat while-loop. One run token walks IDLE through PLANNING, EXECUTING, AWAITING_TOOL, and REFLECTING, takes the inner execute-reflect cycle as many turns as the budget allows, and lands in DONE. Every transition emits a typed event on the stream, so UIs and tracers subscribe instead of inspecting the loop.IDLEPLANNINGEXECUTINGAWAITING_TOOLREFLECTINGDONEجریان رویداد نوع‌داربودجه: نوبت‌ها · فراخوانی ابزار · زمان واقعی
این حلقه یک ماشین وضعیت قطعی است، نه یک حلقه while چت. یک توکن اجرا وضعیت IDLE را از میان PLANNING، EXECUTING، AWAITING_TOOL و REFLECTING عبور می‌دهد، چرخه داخلی اجرا-بازتاب را تا جایی که بودجه اجازه دهد تکرار می‌کند و در DONE فرود می‌آید. هر انتقال وضعیتی، رویدادی نوع‌دار در جریان ساطع می‌کند؛ بنابراین واسط‌های کاربری و ردیاب‌ها به جای سرک کشیدن به داخل حلقه، مشترک آن می‌شوند.

صورت مسئله

ایجنت کدنویسی که 40 نوبت گفت‌وگو (turn) بدون نظارت اجرا می‌شود، یک حلقه‌ی چت ساده نیست؛ بلکه ماشین وضعیتی است که اپراتور می‌تواند گره‌های آن را رهگیری کند و یال‌هایش را مورد بازرسی قرار دهد. وقتی این قرارداد را مکتوب و شفاف کنید، تعویض مدل‌ها، ابزارها یا سیاست‌ها دیگر نیازی به بازنویسی کد (refactor) نخواهد داشت و به یک فراخوانی ساده برای ثبت تبدیل می‌شود.

در این درس این قرارداد را پیاده می‌کنیم: شش وضعیت، 10 موضوع هوک، دو نقطه‌ی واگذاری، 11 نوع رویداد و یک چارچوب بودجه تعیین خواهیم کرد. تمام بخش‌های دیگر بستر آزمایشی (رجیستری ابزار، بستر انتقال JSON-RPC، توزیع‌کننده و برنامه‌ریز) روی همین ساختار سوار می‌شوند.

وضعیت‌ها

این حلقه شش وضعیت دارد: پنج وضعیت فعال و یک وضعیت پایانی.

run(goal)طرح commit شدنیاز به tool_callنتیجهگام بدون ابزار انجام شدگام بعدیبرنامه‌ریزی مجددهدف محقق شدبدون طرحIDLEPLANNINGEXECUTINGAWAITING_TOOLREFLECTINGDONE

وضعیت 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، حلقه‌ای که در این فایل ساختید برنامه‌ای واقعی را با ابزارهای واقعی و با اعمال دقیق بودجه‌ها اجرا خواهد کرد.

خودآزمایی

به سؤال‌ها جواب بدهید تا ببینید چقدر از درس در ذهنتان مانده است.

  1. پیش از درسچرا حلقه بستر آزمایشی به‌جای یک حلقه while با پرچم‌ها، به‌صورت یک ماشین وضعیت صریح مدل‌سازی می‌شود؟
  2. پیش از درسیک «نقطه واکشی» (pull point) در حلقه بیانگر چیست؟
  3. حین درسکدام وضعیت تنها نقطه واکشی مجاز برای یک فراخوانی ابزار است؟
  4. حین درسوقتی یک هوک در before_tool_call استثنای HookAbort پرتاب می‌کند، چه رخ می‌دهد؟
  5. حین درسچرا هوک‌ها و رویدادها به‌عنوان مفاهیم مجزا نگهداری می‌شوند؟
  6. پس از درسهنگامی که بودجه زمان واقعی به پایان می‌رسد، وضعیت بعدی چیست؟
  7. پس از درسچرا این درس به‌جای فراخوانی یک مدل، برنامه‌ریز را با کد ساختگی (stub) پیاده‌سازی می‌کند؟

سؤالات متداول

نقاط واگذاری (Pull Points) در حلقه‌ی ایجنت چه نقشی دارند؟

نقاط واگذاری زمان‌هایی هستند که حلقه بدون پرتاب خطا کنترل را به فراخواننده واگذار می‌کند (مانند منتظر ماندن برای نتیجه‌ی ابزار یا تایید انسانی بر اثر اتمام بودجه) و سپس با متد resume کار را از همان نقطه ادامه می‌دهد.

تفاوت میان هوک‌ها (Hooks) و رویدادها (Events) در این قرارداد چیست؟

هوک‌ها ماهیت دستوری دارند و امکان تغییر ورودی، توقف نوبت یا نادیده گرفتن مرحله را به اپراتور می‌دهند؛ در حالی که رویدادها فقط‌افزودنی و مشاهده‌ای هستند و برای لاگ‌گیری و مانیتورینگ بیرونی استفاده می‌شوند.

آیا اتمام بودجه در بستر آزمایشی باعث متوقف شدن فوری فرآیند می‌شود؟

خیر، بودجه یک کلید قطع اضطراری نیست؛ بلکه پس از رسیدن به سقف، وضعیت به حالت انتظار برای تصمیم فراخواننده می‌رود تا بودجه تمدید شود یا نشست به پایان برسد.

ما رو توی گوگل بیشتر ببین

با افزودن نکست ادمین به «منابع ترجیحی» گوگل، درس‌ها و مطالب ما را در بخش اخبار برتر جستجو بیشتر می‌بینید.