تصور کنید یک برنامهنویس هستید که برای رفع باگهای پیچیده، یک عامل کدنویسی محلی را به خدمت گرفته است و مدل با اطمینان کامل گزارش میدهد که مشکل حل شده، اما در واقع هیچ خط کدی تغییر نکرده است. این سناریوی واقعی برای توسعهدهندهای رخ داد که از مدل Qwen3 27B روی یک کارت گرافیک دستدوم RTX 3090 و با استفاده از لاماسیپلاسپلاس (llama.cpp) استفاده میکرد و متوجه شد سامانه او در حال تولید «تیکهای سبز» (گزارش موفقیت) از دل فایلهای خالی است. این اتفاق که در جریان تلاش برای حل باگهای پولی در گیتهاب (GitHub Bounties) رخ داد، شکافی حیاتی را در نحوه اعتبارسنجی کارهای توسط عاملهای هوش مصنوعی (AI Agents) — شبیه به کارآموزی که برای پنهان کردن اشتباهاتش، گزارشهای جعلی از پیشرفت کار مینویسد — فاش کرد.
جزئیات پیادهسازی
طبق گزارش این توسعهدهنده، خط لوله (Pipeline) او برای پایداری حداکثری، عمداً به گونهای طراحی شده بود که «خستهکننده» و پیشبینیپذیر باشد. یک اجراکننده پایتون تمام چرخه اصلاح باگ را از طریق توالی زیر مدیریت میکرد:
- کلون کردن مخزن هدف.
- نصب تمام وابستگیهای لازم.
- اجرای مجموعه تستهای موجود برای تعیین یک خط مبنا (Baseline).
- درخواست از مدل برای رفع مشکل در حداکثر ۶ دور تکرار.
در هر دور، یک حلقه سختگیرانه اجرا میشد: تولید یک وصله (Patch)، اعمال آن، اجرای تستها و بازگرداندن هرگونه شکست یا خطا به مدل. اگر تستها پاس میشدند، وصله برای بررسی انسانی ذخیره میشد.
ساخت عاملهای خودمختار معمولاً شامل همین حلقه است. در حالی که مدلهای پیشرو (Frontier Models) معمولاً Diffهای درستی مینویسند، مدلهای محلی کوچکتر — مانند مدل ۲۷ میلیارد پارامتری کوانتیده در این مورد — اغلب وصلههایی بدشکل تولید میکنند که اصلاً اعمال نمیشوند. مدیریت این شکافِ شکست، هسته اصلی چالش مهندسی در عاملهای محلی است. این موضوع بازتابدهنده همان کلنجار رفتن گسترده با موتورهای LLM محلی است، همانطور که پیشتر در مقایسه خود بین اولاما (Ollama)، vLLM و llama.cpp در رابطه با همروندی (Concurrency) و عملکرد بررسی کردیم.
تحلیل «تیک سبز» جعلی
شکست مذکور به این دلیل رخ داد که عامل، یک Diff (تفاوت کد) با سرآیندهای (Hunk Headers) جعلی تولید کرد. نسخه سادهی فرآیند اعمال کد از دستور subprocess.run(["git", "apply", "-"], input=diff, ...) استفاده میکرد. در این مورد، دستور git apply وصلهی فاسد را رد کرد، اما خط لوله بدون توقف و به اشتباه به سراغ اجرای مجموعه تستها رفت.
از آنجا که کد اصلی یا از قبل کار میکرد و یا تستها نتوانستند باگ خاص را شناسایی کنند، تستها با کد خروجی ۰ (موفق) به پایان رسیدند. سیستم سپس این موفقیت را به وصلهای نسبت داد که در واقع صفر بایت بود. توسعهدهنده عملاً ماشینی ساخته بود که از هیچ، تیکهای سبز تولید میکرد.
لایههای حفاظتی و فنی
برای جلوگیری از این وضعیت، بر اساس مستندات این پروژه، چندین حفاظ (Guardrails) — شبیه به نردههای ایمنی در لبه یک پل که مانع سقوط کاربر میشود — پیادهسازی شد تا اطمینان حاصل شود مدل درباره پیشرفت خود دروغ نمیگوید:
- اعتبارسنجی سیستمفایل: توسعهدهنده دو خط کد «پارانوئید» اضافه کرد:
changed = subprocess.run(["git", "status", "--porcelain"], cwd=repo, capture_output=True, text=True).stdout.strip(). اگر متغیرchangedخالی باشد، خط لوله به دور بعدی میرود؛ زیرا پاس شدن تستها روی یک درخت کاری دستنخورده (Pristine Working Tree) هیچ معنایی ندارد. - سقف اندازه وصله: برای باگهای متمرکز، هر وصلهای با بیش از ۶۰ خط تغییر رد میشود. این کار مانع از «رانش پژواکی» (Echo Drift) میشود؛ وضعیتی که مدل کل فایل را بازنویسی کرده و باگهای جدیدی ایجاد میکند. برای مثال، در یک مورد، مدل یک فایل ۵۸ کیلوبایتی React Context را برای رفع یک باگ تکخطی در صفحهبندی بازنویسی کرد. اگرچه کد از نظر TypeScript تایید شد، اما این Diff با ۱۳۷۹ خط، باعث حذف توکنهای Refresh در localStorage و جایگزینی اشتباه یک Endpoint از نوع PATCH با POST شد.
- آگاهی از وضعیت ایندکس: یک باگ ظریف در
git apply --3wayکشف شد که ادغامها را در ایندکس Stage میکرد. یکgit diffمعمولی فقط تغییرات Stage نشده را نشان میدهد، که منجر به یک Diff خالی میشد حتی زمانی که وصله با موفقیت اعمال شده بود. راهکار این بود که برای استخراج وصله نهایی ازgit diff HEADاستفاده شود.
بهینهسازی خروجی مدل
علاوه بر اعتبارسنجی، این تجربه نشان داد که درخواست Diffهای استاندارد (Unified Diffs) از مدلهای کوچک، نبردی شکستخورده است. مدل «شاید هرگز» نتوانست Diffهای یکپارچه و معتبر تولید کند. برای حل این مشکل، دو استراتژی جدید جایگزین شد:
۱. بلاکهای SEARCH/REPLACE: مدل به جای Diff، بلوکهایی را ارائه میدهد که شامل خطوطی است که باید دقیقاً کاراکتر به کاراکتر کپی شوند. اجراکننده پایتون ابتدا تایید میکند که متن SEARCH دقیقاً در فایل وجود دارد و در غیر این صورت، دور فعلی با خطای شدید متوقف میشود.
۲. ارائه بستر کامل (Full Context): محدود کردن بستر فایل به ۲۰ هزار کاراکتر متوقف شد. وقتی یک فایل ۵۸ کیلوبایتی قطع میشد، مدل نیمه دوم فایل را بر اساس «حس و حال» (Vibes) ابداع میکرد. در حالی که این ابداعات کامپایل میشدند، اما از نظر عملکردی غلط بودند. قطع کردن متن باعث میشود مدلهای کوچک به جای دقت، با اعتمادبهنفس اشتباه کنند.
این رویکرد در نهایت نتایج واقعی داد. خط لوله توانست یک باگ در وضعیت صفحهبندی (Pagination) را که در آن hasMoreBounties از یک مقدار Closure قدیمی محاسبه میشد، و همچنین یک آسیبپذیری DoS در نقطه پایانی کد QR را برطرف کند (جایی که پارامتر size بدون هیچ حد بالایی مستقیماً وارد تخصیص بافر PNG میشد). هر دو وصله موفق زیر ۲۵ خط بودند و ثابت کردند که مدلهای کوچک اگر مسیر اعتبارسنجی آنها «پارانوئید» و سختگیرانه باشد، میتوانند موثر باشند.
برای توسعهدهندگان، این یعنی «پرامپت» سادهترین بخش طراحی عامل است. کار واقعی در لایه اعتبارسنجی نهفته است. اگر یک مدل محلی را به یک سامانه خودمختار متصل میکنید، باید فرض کنید مدل اشتباه میکند تا زمانی که سیستمفایل خلاف آن را ثابت کند.
در نهایت، انسان آخرین حفاظ است. بررسی تکتک وصلهها پیش از انتشار، تنها راه شکار باگهایی است که حفاظهای خودکار برای پیشبینی آنها برنامهریزی نشدهاند. داشتن یک گفتگو با نگهدارنده پروژه (Maintainer) بر سر یک وصله خوب، بسیار بهتر از ارائه یک وصله ساختگی است.
گام بعدی شما
- اگر از عاملهای کدنویسی استفاده میکنید، اعتبارسنجی را از سطح «خروجی مدل» به سطح «تغییرات واقعی فایل» منتقل کنید.
- به جای درخواست Diff، از مدل بخواهید بلوکهای جایگزینی (Search/Replace) تولید کند تا احتمال خطای ساختاری کاهش یابد.
- هرگز اجازه ندهید مدلهای کوچک فایلهای حجیم را بازنویسی کنند؛ سقف تعداد خطوط تغییر را محدود کنید.
اما داستان سختافزاری این تحول حتی شگفتانگیزتر است — به تحلیل ما دربارهی تراشههای Blackwell مراجعه کنید.




گفتگو