صبح دوشنبه با پنج گزارش باگ بحرانی از خواب بیدار می‌شوید. ابزار نظارتی شما کارش را به درستی انجام داده است. این ابزار تمام گزارش‌های کرش، تمام نظرات یک‌ستاره‌ی ناراضی و تمام پیام‌هایی مثل «وقتی روی ذخیره می‌زنم، اپلیکیشن هنگ می‌کند» را شکار کرده است. شما دقیقاً می‌دانید چه چیزی خراب است، اما نمی‌دانید کجا را باید بگردید.

این همان دیواری بود که بعد از ساخت اولین پایپ‌لاینم با آن برخورد کردم. آن سیستم بدون مشکل نظرات اپلیکیشن و لاگ‌های کرش ورودی را مانیتور می‌کرد و هر بازخورد را در دسته‌های مرتبی قرار می‌داد: باگ‌ها، کرش‌ها یا درخواست‌های ویژگی جدید. داشبورد سالم به نظر می‌رسید، اما فرآیند واقعی عیب‌یابی (debugging) اصلاً اینطور نبود.

دانستن اینکه باگی وجود دارد، تنها قدم اول از یک مسیر طولانی است. من هنوز باید IDE را باز می‌کردم، در ماژول‌ها با grep جستجو می‌کردم، استک تریس‌ها را با کدبیس فعلی مطابقت می‌دادم و مسیر شکست را در ذهنم بازسازی می‌کردم. وقتی تیکت‌ها روی هم انباشته می‌شوند و قهوه هنوز داغ است، این «باستان‌شناسی دستی» زمانی را می‌گیرد که اصلاً در اختیار ندارید. من نیاز داشتم که پایپ‌لاین فراتر از فقط علامت‌گذاری مشکلات عمل کند؛ نیاز داشتم که آن‌ها را بررسی کند.

بنابراین سیستم را با یک هدف واحد بازسازی کردم: گرفتن یک گزارش باگ خام و بازگرداندن یک تشخیص معتبر. نه یک پاراگراف از تفکرات LLM، بلکه یک یافته‌ی ساختاریافته که نام فایل را بگوید، به خط مورد نظر اشاره کند، ریسک را تخمین بزند و یک راه حل پیشنهاد دهد. در ادامه می‌گویم که این سیستم چگونه شکل گرفت.

چرا ساختار بر چت‌لاگ برتری دارد

من عامل بررسی (investigating agent) را با PydanticAI ساختم. دلیلش ساده بود. وقتی از یک مدل زبانی می‌خواهید درباره کد استدلال کند، خروجی پیش‌فرض آن یک جریان دوستانه از متن است. این ممکن است به یک خواننده انسانی کمک کند، اما برای یک اسکریپت در مراحل بعدی بی‌فایده است. من به یک قرارداد ماشین‌خوان (machine-readable contract) نیاز داشتم.

این عامل، یک مدل داده‌ای معتبر با چهار فیلد مشخص را برمی‌گرداند: علت اصلی (root cause)، فایل‌های تحت تأثیر، تغییرات پیشنهادی، و ارزیابی پیچیدگی و ریسک. اگر مدل فیلدی را کم داشته باشد یا یک مسیر فایل را از خود درآورد (hallucinate)، اعتبارسنجی شکست می‌خورد و من بلافاصله متوجه می‌شوم. این دقت باعث می‌شود پایپ‌لاین صادق و قابل اعتماد باقی بماند.

برای انجام کار کارآگاهی واقعی، عامل تنها چهار ابزارِ «فقط خواندنی» (read-only) در اختیار دارد و نه هیچ چیز دیگر. این عامل می‌تواند کد را از طریق grep جستجو کند، بازه‌های خطی خاصی از یک فایل را بخواند، محتویات دایرکتوری را لیست کند و نمادهایی مثل کلاس‌ها یا توابع را پیدا کند. «فقط خواندنی» بودن بخش مهم ماجراست. من نمی‌خواستم عاملی با دسترسی نوشتن (write access) ساعت ۲ صبح در مخزن من پرسه بزند. اول درک کن، بعد ویرایش کن.

نقشه مخزن: کانتکست قبل از ابزارها

نسخه اول این عامل دقیق بود اما بسیار پرهزینه. توکن‌ها را مثل توریستی که دور خودش می‌چرخد، هدر می‌داد. مدل ابتدا list-dir را صدا می‌زد، سپس grep می‌کرد، سپس یک فایل را می‌خواند و دوباره list-dir را صدا می‌زد؛ و به این ترتیب، مدل ذهنی از ساختار پروژه را ذره‌ذره و با مصرف توکن‌های گران‌قیمت می‌ساخت.

راه حل این بود که قبل از شروع کارِ عامل، یک نقشه فشرده از مخزن (repo map) تولید کنیم. این نقشه، یک نمای کلی و خلاصه از مخزن است: فایل‌های کلیدی، توابع یا کلاس‌های اصلی آن‌ها و نحوه اتصال ماژول‌های اصلی به یکدیگر. آن را مثل این تصور کنید که به جای اینکه از عامل بخواهید با آزمون و خطا جاده‌ها را کشف کند، یک GPS به او می‌دهید.

با داشتن آن نقشه در پنجره کانتکست (context window)، عامل برای فهمیدن اینکه src/utils/parser.ts وجود دارد، وقت خود را با فراخوانی‌های اضافی تلف نمی‌کند. او از قبل با زمین آشناست و مستقیماً به سمت قله‌ای می‌رود که دود از آن بلند می‌شود. این تغییرِ واحد، مرحله‌ی سردرگمی و پرسه زدن را کاملاً حذف کرد.

قیف ابزار: وادار کردن به نتیجه‌گیری

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

من یک «قیف ابزار» سه مرحله‌ای پیاده‌سازی کردم که محدودیت‌های عملکردی عامل را با پیشرفت کار افزایش می‌دهد.

مرحله اول، کاوش (exploration) است. عامل دسترسی کامل به هر چهار ابزار را دارد. او می‌تواند برای بازسازی باگ در استدلال خود، هر آنچه نیاز دارد را جستجو، مرور و مطالعه کند.

مرحله دوم، بررسی عمیق (deep-dive) است. زمانی که عامل خطوط احتمالی خطا را شناسایی کرد، ابزارهای اکتشافی را از دست می‌دهد. او فقط می‌تواند فایل‌ها را بخواند. دیگر خبری از grep یا لیست کردن دایرکتوری‌ها نیست. در این مرحله، او باید کدی را که قبلاً پیدا کرده مطالعه کند و زنجیره شواهد خود را بسازد.

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

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

قابل تعویض نگه داشتن بک‌اند (Backend)

I did not want to hardcode the system against a single model provider. I use different engines depending on the task. Sometimes Claude Code, sometimes Grok Build, sometimes whatever is cheapest at the moment. To keep the core logic provider-agnostic, I split the work into two stages.

Stage one is exploration. The coding agent, which can be any capable model, reads the repo map, uses the tools, and produces a raw markdown report. This is the expensive thinking part.

Stage two is structuring. A cheap, fast LLM takes that markdown and reformats it into the strict Pydantic model. This stage requires almost no reasoning. It is just extraction and formatting, so it runs on lightweight hardware.

Because the boundary is clean, I can swap the backend without touching the validation logic. The markdown report acts as a universal adapter between the exploratory brain and the structured output I actually use.

What Actually Worked

This setup changed how I handle incoming issues. The classification layer still sorts bugs from feature requests, but now the analysis layer picks up immediately after. By the time I open my editor, I have a file path, a line range, and a proposed change waiting for me. I still review everything manually. This is assistance, not autopilot. But the context gathering that used to