تصور کنید یک عامل کدنویس را برای رفع یک باگ ساده استخدام کردهاید، اما او بهجای حل مشکل، وارد یک «چرخ همستر» میشود و پیش از آنکه شما متوجه شوید، هزاران دلار از بودجه ابری شما را میبلعد. طبق گزارشی که در ۱۳ سپتامبر ۲۰۲۶ در وبسایت dev.to منتشر شد، این حادثه شکافی مهندسی را افشا میکند: تفاوت میان مدلی که «باهوش» است و سیستمی که «محدود» است.
بسیاری از تیمهایی که از عاملهای دارای قابلیت استفاده از ابزار (Tool Use) استفاده میکنند، در پرامپت سیستمی (System Prompt) — همان دستورالعملهای بنیادینی که رفتار مدل را تعیین میکند — به مدل میگویند «تا زمانی که تستها پاس نشدند، ادامه بده». در محیط عملیاتی، این دستور شبیه به دادن یک چک سفید است. در حالی که یک برنامهنویس انسان بعد از پنج بار شکست در یک باگ، خسته یا عصبی میشود، یک عامل (Agent) — مثل دستیاری دیجیتال که میتواند بهطور مستقل ابزارها را اجرا کند — فقط توکنهای بیشتری مصرف میکند. این وضعیت ریسکی است که در آن «تحرک» با «پیشرفت» اشتباه گرفته میشود.
همانطور که در تحلیلهای قبلی ما دربارهی امنیت مدلهای بازمتن اشاره کردیم، نبودِ حفاظهای سختافزاری و نرمافزاری میتواند منجر به رفتارهای پیشبینیناپذیر شود. در این مورد خاص، مشکل از هوش مدل نبود، بلکه نبودِ مرزهای عملیاتی بود. این چالش با یافتههای اخیر ما همسو است که نشان میدهد نقص در مدیریت اجرا عامل اصلی شکست عاملهای کدنویس در LoopArena بوده است.
کالبدشکافی یک حلقه runaway
به نقل از گزارش dev.to، این شکست در ساعت ۰۰:۰۲ آغاز شد؛ زمانی که یک پردازش شبانه سعی داشت یک خطای متناوب (Flake) را در فایل test_invoice_total رفع کند. تا ساعت ۰۰:۰۴، عامل فایل را خوانده، یک ادعا (Assertion) را بازنویسی کرده و تستها را اجرا کرد، اما تستها بهدلیل یک نشت حافظه (Fixture Leak) غیرمرتبط شکست خوردند. چون سیاست ابزارها فاقد یک نقطه توقف سخت بود، عامل این شکست جدید را دستوری برای ادامه ویرایش تلقی کرد.
بین ساعت ۰۰:۰۷ تا ۰۱:۱۱، عامل وارد یک چرخه تخریبی شد. او اجازه داشت بدون هیچ محدودیتی توابع run_tests ،edit_file و git_commit را فراخوانی کند. هر تلاش در ظاهر منطقی به نظر میرسید، درست مثل برنامهنویسی که یک بار دیگر روی دکمه اجرای تست کلیک میکند. اما بعد از ۲۹ چرخه، کد شلوغتر شده بود و تستها همچنان قرمز بودند.
در این بازه زمانی، عامل موارد زیر را تولید کرد:
- ۱۷ ویرایش فایل و ۲۹ اجرای تست در یک محیط تکشاخه.
- ویرایشهای نوسانی: یک تغییر در گرد کردن اعداد اعمال شد، سپس حذف شد و دوباره با کامنتی متفاوت بازگشت.
- فراخوانیهای مکرر ابزارها با آرگومانهای کاملاً یکسان، که در واقع همان «درجا زدن» بود.
این فرآیند تنها در ساعت ۰۱:۱۴ پایان یافت، آنگاه که هشدار بودجه فعال شد. این هشدار بهدلیل سقف ماهانه فعال شده بود، نه سقف هر پردازش. عامل در نهایت در ساعت ۰۱:۱۶ در حالی که داشت کامیتی مینوشت تا برای کامیت قبلیاش عذرخواهی کند، با دستور SIGTERM متوقف شد. گزارش تأکید میکند که برای این شکست نیازی به رفتار پیچیده مدلهای پیشرو نبود؛ این صرفاً نتیجه نبودِ محدودیتها، داشتن یک دایرکتوری کاری چسبنده (Sticky Working Directory) و سیاست بازگشتی بود که تحرک را با پیشرفت اشتباه گرفت.
پنج خطای مهندسی عامل در این حادثه
این تحلیل پسمرگ پنج دلیل مشخص را برای نامرئی بودن این حلقه تا لحظه دریافت صورتحساب ذکر میکند:
۱. بودجههای نامحدود ابزار: پرامپت سیستمی مدل را به پافشاری تشویق میکرد بدون اینکه حداکثر تعداد تلاشها را تعیین کند. گفتن این جمله که «ادامه بده تا تستها پاس شوند» دقیقاً مانند دادن یک چک سفید است.
۲. نبود هشینگ فراخوانیها: سیستم ردیابی نمیکرد که آیا عامل همان ابزار را با همان آرگومانها تکرار میکند یا خیر، زیرا فراخوانیها را هش (Hash) نمیکرد.
۳. مسمومیت وضعیت مشترک: هر ویرایش، تنها نسخه موجود از شاخه را تغییر میداد و این موضوع استدلالهای بعدی عامل را مختل و مسموم میکرد.
۴. هشدارهای پرداخت کلی: تیم بهجای فیوز (Fuse) برای هر پردازش، به سقف ماهانه حساب تکیه کرده بود؛ نویسنده این وضعیت را شبیه داشتن دزدگیر در لابی ساختمان اما نبود آن در آشپزخانه توصیف میکند.
۵. سکوت لاگها: با وجود لاگها، هیچ سیستمی برای هشدار (Paging) به انسانها هنگام تکرار فراخوانیهای یکسان وجود نداشت. اجراکننده (Runner) صرفاً همان استثنا (Exception) را میبلعید و یک تلاش مجدد دیگر انجام میداد.
راهکار پایدار: LoopGuard
برای جلوگیری از این اتفاق، نویسنده ابزاری به نام LoopGuard را پیشنهاد میدهد. این یک ناظر مبتنی بر پایتون است که در کنار اجرای عامل قرار میگیرد و سه معیار را رصد میکند: مصرف توکن، تکرار فراخوانیهای یکسان و نوسان ویرایشها. اگر هر یک از اینها از حد مجاز بگذرد، خطای LoopIncident صادر میشود که ارکستراتور نباید آن را نادیده بگیرد.
محدودیتهای کلیدی LoopGuard عبارتند از:
- حداکثر توکن: یک سقف سخت (مثلاً ۸۰,۰۰۰ توکن) برای جلوگیری از جهش هزینهها. متد
chargeدر صورتی کهtokens_usedازmax_tokensبیشتر شود، خطایLoopIncidentرا صادر میکند. - حداکثر گامها: سقفی برای تعداد کل فراخوانیهای ابزار (مثلاً ۱۲ گام). متد
observe_callتعداد گامها را ردیابی میکند. - حد تکرار فراخوانی: اگر یک جفت «ابزار/آرگومان» بیش از دو بار استفاده شود، هشدار فعال میشود. این کار با ایجاد یک اثر انگشت SHA256 از نام ابزار و آرگومانها انجام میشود.
- تشخیص نوسان: اگر محتوای یک فایل چندین بار به حالت قبلی بازگردد، سیستم آن را شناسایی میکند. متد
observe_editتاریخچهای از دایجستهای محتوا را ذخیره میکند تا تشخیص دهد آیا یک فایل بیش از حد مجاز (مثلاً ۲ بار) نوسان کرده است یا خیر.
جزئیات پیادهسازی و تست
فایل loop_guard.py از یک Counter برای ردیابی فراخوانیها و دیکشنری از لیستها برای file_versions استفاده میکند. متد observe_call یک هش SHA256 از نام ابزار و آرگومانها تولید میکند تا تکرارها را شناسایی کند. متد observe_edit دایجست محتویات فایل را محاسبه میکند؛ اگر دایجست فعلی با آخرین نسخه یکی باشد، نادیده گرفته میشود، اما اگر با هر یک از نسخههای قبلی در تاریخچه مطابقت داشته باشد، شمارنده نوسان افزایش مییابد.
تابع run_agent حلقه را تا زمانی که مدل سیگنال «توقف» بدهد یا خطای LoopIncident رخ دهد، پیش میبرد. این یعنی ارکستراتور «خستهکننده» باقی میماند: اگر حلقهای شناسایی شود، پردازش شکست میخورد، شاخه (Branch) پوش (Push) نمیشود و یک انسان باید ردپای خطا (Trace) را بخواند.
نویسنده تأکید میکند که یک تحلیل پسمرگ بدون تست رگرسیون بیفایده است. او مجموعهای از تستها را با pytest ارائه داده است تا اطمینان حاصل شود که فیوز واقعاً میسوزد. این تستها اجرای یکسان تستها و نوسانات فایل را شبیهسازی میکنند تا ثابت شود سیستم فارغ از میزان «اعتمادبهنفس» مدل، از «کندن» (Digging) عامل جلوگیری میکند.
موردهای تست خاص عبارتند از:
test_identical_test_runs_fail_closed: شبیهسازی چهار فراخوانی یکسان برایrun_testsروی فایلtest_invoice_total.pyبا هزینه ۹۰۰ توکن برای هر بار جهت فعال کردن سیاست تکرار.test_file_oscillation_fail_closed: شبیهسازی تغییر یک فایل بینreturn round(x, 2)وreturn round(x, 4)تا زمانی که حد نوسان فعال شود.test_token_fuse_is_per_job_not_monthly: اطمینان از اینکه شارژ ۶۰۰ توکن روی ۱,۵۰۰ توکن قبلی، در صورتی که سقف ۲,۰۰۰ باشد، باعث فعال شدن فیوز شود.
برای تثبیت این سیاست، نویسنده پیشنهاد میکند مجموعه تستها با دستور python -m pytest tests/test_loop_guard.py -q اجرا شوند و همین محدودیتها از طریق متغیرهای محیطی مانند AGENT_MAX_TOKENS=80000 و AGENT_MAX_STEPS=12 در دستورات شبانه قرار گیرند. یک نمونه دستور بازپخش (Replay) میتواند python -m nightly_agent --branch replay/invoice-flake --dry-run باشد.
بازسازی بدون سوزاندن کلیدهای تولید
برای تیمهایی که میخواهند این شکستها را بدون هزینه واقعی و سوزاندن کلیدهای محیط Production بازسازی کنند، استفاده از محیطهای «پیشنویس» (Scratch) پیشنهاد شده است. تستهای واحد (Unit Tests) نمیتوانند درباره تغییرات پرامپت (Prompt Drift) یا تأخیر ابزارها (Tool Latency) نظر دهند، بنابراین بازپخش برای مشاهده لحظه سوختن فیوز در گام ثبتشده ضروری است. این کار نیازمند یک نقطه اتصال (Endpoint) مدل پیشنویس و یک ماشین مجزا است.
در این زمینه، MonkeyCode بهعنوان ارائهدهنده دسترسی رایگان به مدلها و سرورها معرفی شده است که میتواند بهعنوان این میدان تمرین آتشنشانی ایزوله عمل کند. این به توسعهدهندگان اجازه میدهد پیش از بازگرداندن اعتبار پرداختشده به پردازشهای زمانبندی شده، حفاظ را آزمایش کنند. با این حال، نویسنده هشدار میدهد که هرگز کدهای منبع مشتری را روی سرورهای پیشنویس قرار ندهید؛ بهجای آن، محتواها را سانسور کرده و از فایلهای مصنوعی استفاده کنید که همچنان باعث فعال شدن فیوز شوند.
آنچه LoopGuard حل نمیکند
این روش شکستهای قطعی تستها، فیکسچرهای مسموم یا مشخصات (Specifications) غلط را حل نمیکند. اگر یک مجموعه تست ذاتاً ناپایدار (Flaky) باشد، عامل باز هم شکستهای جدیدی پیدا خواهد کرد؛ اما LoopGuard تضمین میکند که این شکست «ارزان و سریع» باشد، نه «گران و بینهایت». در واقع، باید پذیرفت که پنجرههای متنی بزرگتر راهکار حل مشکل توقف عاملهای کدنویسی نیستند و تنها محدودیتهای ساختاری میتوانند مانع از اتلاف منابع شوند.
علاوه بر این، این الگو نباید برای پنهان کردن یک حلقه runaway پشت یک سقف (Quota) بزرگتر استفاده شود. افزایش max_tokens تا زمانی که یک پردازش تمام شود، دقیقاً همان اتفاقی است که در حادثه سهشنبه شب رخ داد. همچنین اگر به اثبات منشأ مدل (Model Provenance) یا اقامت دادهها (Data Residency) نیاز دارید، یک نقطه اتصال رایگان و مشترک، محیط مناسبی برای بازسازی نیست.
برای توسعهدهندگان، این یک چرخش از «مهندسی پرامپت» به «مهندسی حفاظ» (Harness Engineering) است. هدف این نیست که عامل را داناتر کنیم، بلکه هدف این است که سیستم را بهگونهای طراحی کنیم که در صورت خطا، بهصورت بسته (Fail-closed) متوقف شود. در همین راستا، تلاشهایی برای بهینهسازی سرعت این حفاظها صورت گرفته است، مانند سیستمی که تأخیر امنیتی عاملهای هوش مصنوعی را به ۲۹ میکروثانیه رساند تا بدون کاهش کارایی، امنیت افزایش یابد.
اگر شما به یک اسکریپت کرون (Cron) کارت اعتباری نامحدود نمیدهید، نباید به یک حلقه هوش مصنوعی زاینده (Generative AI) — مثل آشپزی که بدون توقف مواد اولیه را مصرف میکند اما هیچ غذایی نمیپزد — چنین دسترسیای بدهید. بحث گستردهتر اغلب این است که آیا مدلها از برنامهنویسان پیشی میگیرند، اما سؤال آرامتر و مهمتر این است که آیا ارکستراسیون شما زمانی که مدل، تست یا طرح ابزار شروع به چرخش میکند، بهصورت بسته متوقف میشود یا خیر.
گام بعدی شما
- بررسی کنید آیا عاملهای شما سقف توکن «بهازای هر درخواست» دارند یا فقط سقف کلی ماهانه.
- پیادهسازی یک سیستم هشینگ ساده برای شناسایی فراخوانیهای تکراری ابزارها در ارکستراتور خود.
- جایگزینی دستورات «ادامه بده تا موفق شوی» با محدودیتهای عددی سخت در پرامپتهای سیستمی.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو