پرش به محتوای اصلی
پرش به محتوای مقاله

درون نقص عملیاتی عامل‌ها؛ وقتی کد در محیط ایزوله متوقف می‌شود

·۱۵ شهریور ۱۴۰۵۸ دقیقه مطالعه
عنوان مقاله: «عوامل من تمام شدند. کدشان هرگز به Main نرسید.»
عنوان مقاله: «عوامل من تمام شدند. کدشان هرگز به Main نرسید.»
اشتراک‌گذاری
واقعاً چه چیز جدید است؟

معرفی متد تأیید Blob برای جایگزینی بررسی‌های سلسله‌مراتبی کامیت در عامل‌های کدنویس؛ این روش اجازه می‌دهد تحویل‌های معادل (مانند Cherry-pick) که هش کامیت را تغییر می‌دهند، به‌درستی شناسایی شوند.

تصور کنید یک برنامه‌نویس در تیمی کوچک، تمام تیکت‌های تخته کانبان را سبز می‌بیند، اما وقتی کد را در محیط عملیاتی بررسی می‌کند، متوجه می‌شود هیچ‌کدام از تغییرات تأییدشده اعمال نشده‌اند. این کابوس مدیریتی، دقیقاً همان شکافی است که در سیستم ارکستراسیون عامل‌های هوش مصنوعی شرکت Ekioo کشف شد.

طبق گزارش‌های داخلی این شرکت، بررسی‌های مربوط به تیکت شماره ۲۵۰ نشان داد که با وجود وضعیت «موفقیت» در سیستم ردیابی، محتوای مورد نظر در شاخه origin/master وجود نداشت. در واقع، در حالی که تیکت به آخرین ستون خود رسیده بود و شاخه ریموت و درخت کاری (Worktree) مربوطه وجود داشتند، اما کد واقعی در درخت‌های کاری ایزوله عامل‌ها گیر کرده بود. این تضاد بین «گزارش تحویل» و «استقرار واقعی» که در جریان ممیزی یک تغییر تحریری خاص کشف شد، یک منبع حقیقت جعلی ایجاد کرده بود که در آن همه چیز روی کاغذ درست به نظر می‌رسید، اما مخزن کد (Repository) چیز دیگری می‌گفت.

این حالت شکست به‌ویژه خطرناک است زیرا توهمی از پیشرفت ایجاد می‌کند. این مشکل به دلیل ابزارهایی مثل Claude Code 2.1.233 نبود؛ مدلی که در ۱۴ اوت ۲۰۲۶ با پشتیبانی از URLهای Merge Request در گیت‌لب به‌روزرسانی شد و قابلیت نمایش درخواست‌های ادغام به صورت !N را به پرچم --worktree و نمای عامل‌ها اضافه کرد. بلکه مشکل در تعریف «پایان کار» (Definition of Done) در لایه ارکستراسیون بود.

یک کارخانه را تصور کنید که در آن یک ربات گزارش می‌دهد قطعه‌ای ساخته شده و کنترل‌کننده کیفیت آن را تأیید می‌کند، اما قطعه هنوز روی میز مونتاژ ربات مانده و به سکوی ارسال نرسیده است. در گردش‌کارهای عامل‌های هوش مصنوعی، این «میز مونتاژ» همان Git Worktree است. ایزولاسیون مانع از برخورد عامل‌ها با یکدیگر می‌شود، اما تحویل نهایی را تضمین نمی‌کند.

نقش ایزولاسیون در درخت‌های کاری

عامل‌های کدنویس در درخت‌های کاری ایزوله اجرا می‌شوند تا تضمین شود که عامل‌های موازی، ایندکس، دایرکتوری کاری یا مجموعه‌ای از فایل‌های نیمه‌نوشته را به اشتراک نمی‌گذارند. این ساختار اجازه می‌دهد یک عامل روی یک مجموعه تست کار کند و دیگری روی یک ویژگی غیرمرتبط، بدون اینکه هیچ‌کدام تغییرات ثبت‌نشده‌ی دیگری را ببینند. این رویکرد در واقع پاسخی به چالش‌های مدیریت موازی است که پیش‌تر در بررسی مزایای Git Worktrees در برابر روش‌های سنتی برای جلوگیری از تداخل در کدنویسی AI تحلیل شده بود.

بر اساس مستندات رسمی Claude Code، جلسات ایزوله عامل‌ها اکنون به گردش‌کار بررسی (Review) نزدیک‌تر شده‌اند. اما ایزولاسیون فقط برخوردها را حل می‌کند، نه تحویل را. یک درخت کاری یک شاخه منسجم می‌سازد و یک Merge Request اجازه بررسی می‌دهد، اما هیچ‌کدام از این ابزارهای اولیه تعیین نمی‌کنند که چه زمانی یک سیستم تجاری می‌تواند ادعا کند کاربران نهایی نتیجه را می‌بینند. این تصمیم بر عهده لایه ارکستراسیون است.

در سیستم شکست‌خورده، چهار رویداد مجزا به اشتباه در یک مفهوم کلی و مبهم به نام «موفقیت» ادغام شده بودند:

  • تکمیل عامل: توقف عامل و گزارش نتیجه.
  • تکمیل بررسی: پذیرش شاخه توسط یک کنترل‌کننده مستقل.
  • تکمیل ادغام: پذیرش تلاش برای یکپارچه‌سازی توسط گیت.
  • محتوای تحویل‌شده: حضور واقعی فایل‌های مورد انتظار در شاخه یکپارچه‌سازی ریموت.

در ابتدا، تلقی این چهار مورد به عنوان یک رویداد واحد منطقی به نظر می‌رسید، اما با ظهور مفاهیمی چون هم‌زمانی (Concurrency)، عملیات Cherry-pick، تداخلات (Conflicts) و مسابقه‌های Push، این منطق فرو پاشید.

شکست هویت کامیت (Commit Identity)

سیستم اولیه فرض می‌کرد که هویت کامیت همان هویت تحویل است. یعنی اگر یک کامیت وجود داشت و تأیید شده بود، پس تحویل شده است. اما در گردش‌کارهای واقعی گیت، عملیاتی مثل Cherry-pick، Squash-merge و مصالحه‌های دستی (Manual Reconciliations) باعث تغییر هش‌های کامیت می‌شوند، در حالی که محتوای فایل‌ها کاملاً یکسان می‌ماند.

به همین دلیل، یک بررسی سلسله‌مراتبی ساده با استفاده از دستور git merge-base --is-ancestor نمی‌تواند تأیید کند که محتوای واقعی رسیده است. این دستور می‌تواند بگوید آیا یک کامیت در تاریخچه کامیت دیگر هست یا خیر، اما نمی‌تواند تشخیص دهد که آیا محتوای معادل از طریق یک Squash یا مصالحه دستی منتقل شده است یا خیر. این نقص منجر به هفت تیکت «پایانی» شد که در واقع در شاخه اصلی غایب بودند و یک مشکل یکپارچگی خاموش ایجاد کردند. برای مقابله با چنین خطاهای نامرئی، ابزارهایی توسعه یافته‌اند که ترکیب Copilot CLI و MCP را برای ردیابی خطاهای خاموش عامل‌ها به کار می‌گیرند.

پیاده‌سازی وضعیت یکپارچه‌سازی

برای رفع این مشکل، گردش‌کار بازطراحی شد تا تیکت‌های تأییدشده مستقیماً به ستون نهایی «موفقیت» نروند. در عوض، آن‌ها وارد یک «وضعیت یکپارچه‌سازی» (Integration State) غیرپایانی می‌شوند که توسط یک پردازنده فعال مدیریت می‌شود. این پردازنده یک انتقال سخت‌گیرانه را تضمین می‌کند: تأیید $ \rightarrow $ در حال یکپارچه‌سازی $ \rightarrow $ تحویل‌شده.

عوامل من تمام شدند. کدشان هرگز به Main نرسید.

این وضعیت میانی مکانی برای ثبت شکست‌های صادقانه فراهم می‌کند. به‌جای یک موفقیت مبهم، سیستم اکنون خطاهای ماشین‌خوان دقیقی گزارش می‌کند. پیاده‌سازی این بخش (که از یک کمکی PowerShell اقتباس شده) از ساختار نتایجی شبیه به این استفاده می‌کند:

if ($mergeFailed) {
    [pscustomobject]@{ outcome = "failed" reason = "merge-conflict" detail = $mergeOutput } | ConvertTo-Json -Compress
    exit 21
}
if (-not (Test-ExpectedContent $branchRef $remoteMain $paths)) {
    [pscustomobject]@{ outcome = "failed" reason = "remote-content-mismatch" paths = $paths } | ConvertTo-Json -Compress
    exit 23
}

پیامدهای شکست اکنون شامل موارد زیر است:

  • merge-conflict: شکست یکپارچه‌سازی به دلیل تداخل کدها.
  • push-rejected: جابجایی شاخه ریموت پیش از آنکه عامل بتواند Push کند.
  • remote-content-mismatch: عدم تطابق فایل‌های شاخه ریموت با نسخه تأییدشده.
  • lock-timeout: عدم امکان دستیابی به قفل مخزن.
  • unexpected-integration-error: شکست کلی در طول فرآیند.

مکانیزم تأیید Blob

چرخش فنی اصلی، حرکت به سمت تأیید Blob (بلاک‌های داده‌ای) است. به‌جای بررسی وجود یک هش خاص از کامیت در تاریخچه، سیستم اکنون Blob گیت (محتوای واقعی فایل) را برای هر مسیر تغییریافته استخراج می‌کند.

برای هر مسیری که توسط شاخه تأییدشده نسبت به Merge Base آن تغییر کرده است، سیستم هش Blob را در شاخه تأییدشده و در origin/master بررسی می‌کند. منطق سیستم به این صورت است:

foreach ($path in $changedPaths) {
    $expected = git rev-parse "$branchRef`:$path"
    $actual = git rev-parse "$remoteMain`:$path"
    if ($expected -ne $actual) { return $false }
}
return $true

اگر مسیری در یک طرف وجود داشته باشد اما در طرف دیگر نباشد، یا اگر هش‌های Blob متفاوت باشند، تحویل ناقص تلقی می‌شود. این روش تحویل‌های معادل با Cherry-pick را شناسایی می‌کند، زیرا محتوای یکسان فایل، فارغ از تغییر هش کامیت، همیشه یک Blob یکسان تولید می‌کند. این رویکرد تأیید محتوا بدون تکیه بر مدل‌های زبانی، یادآور متدولوژی AgentCheck است که برای تأیید تغییرات کد توسط هوش مصنوعی طراحی شده است. اگرچه این روش نسبت به بررسی سلسله‌مراتبی کامیت‌ها هزینه محاسباتی بیشتری دارد (زیرا نیاز به Fetch وضعیت ریموت و تفکیک اشیاء دارد)، اما مانع از آن می‌شود که تخته کانبان در حالی که وضعیت ریموت غیرقابل اعتماد است، موفقیت اعلام کند.

سریال‌سازی تغییرات

برای جلوگیری از «مسابقه‌ی Push» (زمانی که دو عامل هم‌زمان سعی در به‌روزرسانی یک مخزن دارند)، سیستم اکنون یکپارچه‌سازی را با استفاده از یک قفل انحصاری (Exclusive Lock) بر اساس مسیر تفکیک‌شده‌ی مخزن سریال‌سازی می‌کند. این کار اجازه می‌دهد مخازن غیرمرتبط به کار خود ادامه دهند، در حالی که تغییرات در یک مقصد مشترک در صف انتظار می‌مانند.

در داخل این قفل، سیستم یک حلقه سخت‌گیرانه را دنبال می‌کند (در اینجا به صورت شبه‌کد):

for ($attempt = 1; $attempt -le $MaxPushAttempts; $attempt++) {
    Fetch-LatestRemoteState
    $paths = Get-ChangedPaths $remoteMain $branchRef
    if (Test-ExpectedContent $branchRef $remoteMain $paths) {
        return Complete "content-already-integrated"
    }
    $worktree = New-DetachedIntegrationWorktree $remoteMain
    Merge-ApprovedBranch $worktree $branchRef
    if (Push-IntegrationBranch $worktree) { break }
}

این فرآیند تضمین می‌کند که سیستم در حالی که قفل را در اختیار دارد، آخرین وضعیت ریموت را دریافت کند، محتوای از پیش یکپارچه‌شده را شناسایی کند و پس از هرگونه رد شدن Push، از یک وضعیت ریموت تازه شروع به تلاش مجدد کند.

تسویه بدهی‌های تاریخی

اصلاح خط لوله، اشتباهات گذشته را پاک نکرد. بنابراین یک دستور تسویه (Reconciliation) اضافه شد تا تیکت‌های پایانی تاریخی را بازرسی کند. این ابزار شاخه تأییدشده را شناسایی کرده و قرارداد جدید یکپارچه‌سازی تأییدشده را روی آن اجرا می‌کند. اگر عملیات شکست بخورد، تیکت با یک دلیل مشخص به وضعیت «در انتظار تأیید» بازگردانده می‌شود.

این فرآیند به گونه‌ای طراحی شد تا از ایجاد صف‌های تعمیر بازگشتی (Recursive Repair Queues) جلوگیری کند؛ وضعیتی که در آن یک تیکت بازیابی، در صورت شکست، تیکت بازیابی دیگری ایجاد کند و یک ادغام گم‌شده را به یک «آب‌وهوای اداری» دائمی تبدیل نماید.

این بازبینی تاریخی «بدهی گردش‌کار» (Workflow Debt) قابل توجهی را آشکار کرد و نشان داد که برخی وضعیت‌های پایانی در واقع منسوخ شده بودند یا کارهایی بودند که عمداً حذف شده بودند. اگرچه این موضوع در ابتدا وضعیت تخته کانبان را بدتر نشان داد، اما حس امنیت کاذب را با حقیقت قابل اندازه‌گیری جایگزین کرد.

درس‌های معماری نهایی

این تغییر، فرض بنیادی ارکستراسیون عامل‌ها را عوض می‌کند: موفقیت دیگر به معنای «توقف عامل» نیست، بلکه به معنای «حضور محتوای تأییدشده در سیستم ثبت ریموت» است.

برای کسانی که خط لوله‌های عامل‌محور می‌سازند، درس ساده و صریح است: یک عامل می‌تواند کارش را تمام کند بدون اینکه چیزی تحویل دهد. یک بررسی می‌تواند پاس شود بدون اینکه کد منتقل شود. یک ادغام می‌تواند در سطح محلی موفق باشد بدون اینکه تحویل شود. تنها مدرکی که اهمیت دارد، وضعیت ریموت مخزن پس از اتمام کامل Push است.

برای مقاوم‌تر کردن این سیستم، گام بعدی تعریف «اقلام تحویلی» (Deliverables) صریح است؛ یعنی یک مانیفست از فایل‌های مورد انتظار در زمان ایجاد تیکت. این کار به سیستم اجازه می‌دهد بین کد ضروری محصول و حافظه‌های اتفاقی عامل، مصنوعات تولید شده یا تست‌ها تمایز قائل شود و گیتِ محتوای ریموت را روی مجموعه‌ای محدودتر و هدفمندتر تأیید کند.

این گردش‌کار در KittyClaw — یک چارچوب تحت مجوز AGPL-3.0 برای اجرای کارهای عامل‌ها روی کانبان — پیاده‌سازی شد و در سایت Ekioo (سایت مشاوره تولیدی) مورد آزمایش قرار گرفت و در نهایت شکاف بین درخت کاری و شاخه اصلی را برطرف کرد.

گام بعدی شما

  • در سیستم‌های عامل‌محور خود، تعریف «Done» را از وضعیت داخلی عامل به وضعیت نهایی مخزن ریموت تغییر دهید.
  • برای تأیید تحویل کد، به‌جای تکیه بر Commit Hash، از مقایسه Blobهای فایل‌های تغییریافته استفاده کنید.
  • یک مکانیسم قفل (Locking) برای عملیات Push ایجاد کنید تا از تداخل عامل‌های موازی در مخازن مشترک جلوگیری شود.

اما داستان سخت‌افزاری این تحول حتی شگفت‌انگیزتر است — به تحلیل ما درباره‌ی تراشه‌های Blackwell مراجعه کنید.

چرا این موضوع مهم است؟

این رویکرد با تکیه بر تجربه عملی در محیط‌های Production، ریسک «شکست‌های خاموش» در استقرار کد توسط AI را حذف می‌کند. اعتبار سیستم‌های عامل‌محور تنها زمانی تأمین می‌شود که لایه نظارتی، حقیقتِ ریموت را بر گزارشِ عامل ترجیح دهد.

تأثیر برای ایران

برای توسعه‌دهندگان ایرانی که از عامل‌های کدنویس در پروژه‌های تیمی استفاده می‌کنند، پیاده‌سازی این لایه تأیید در CI/CD می‌تواند از تداخلات کد و گم شدن تغییرات در مخازن مشترک جلوگیری کند.

·نگاه ما
تحریریه دات‌هوش

جایگزینی هویت کامیت با هویت محتوا (Blob) نشان می‌دهد که ما در حال گذار از اعتماد به «فرآیند» به اعتماد به «نتیجه» در سیستم‌های عامل‌محور هستیم. این رویکرد، لایه ارکستراسیون را از یک ناظر ساده به یک بازرس سخت‌گیر تبدیل می‌کند که تنها خروجی فیزیکی را می‌پذیرد. در واقع، این یک حرکت به سمت Determinism در دنیای احتمالی هوش مصنوعی است.

منابع

این گزارش با خط‌لولهٔ خودکار دات‌هوش از منابع معتبر جهانی تدوین و زیر نظر تحریریه منتشر شده است. روش کار ما

گفتگو

پنج‌شنبه‌های هوش‌محور

بسته‌ی هفتگی دات‌هوش

۵ خبر، ۲ ابزار، ۱ پرامپت در هر شماره. به‌زودی راه‌اندازی می‌شود — هر پنج‌شنبه صبح.

خبر کلیدی
ابزار کاربردی
پرامپت حرفه‌ای
تحلیل پژوهش
به‌زودی
زاویه‌ی ایرانی
به‌زودی
تمرین این هفته
به‌زودی

راهنماهای دات‌هوش

راهنماهای کاربردیِ دات‌هوش برای کار با هوش مصنوعی — از همین‌جا شروع کنید:

دات‌هوش

راهنمای فارسی هوش مصنوعی — با نگاه به ایران

اخبار روزانه، معرفی ابزارها و مدل‌ها، و آموزشِ کار با هوش مصنوعی؛ همیشه با این پرسش که از ایران چه چیزی کار می‌کند و چه چیزی نه.