ایجنتهای کدنویسی اغلب کار را با نیت خیر گسترش میدهند؛ تسکی که صرفاً رفع یک باگ کوچک در صفحه ورود بوده، ناگهان به دستکاری تنظیمات پایگاه داده، اسکریپتهای انتشار و فایلهای مستندات ختم میشود. این پدیده که خزش دامنه (Scope Creep) نامیده میشود، با سختگیرانهتر کردن پرامپت حل نمیشود؛ چون مدل زبانی چارچوب و انتهای مشخصی برای کار نمیشناسد.
راهحل مهندسی این مشکل، تدوین یک قرارداد دامنه (Scope Contract) در قالب فایلی ساختاریافته روی دیسک است. این فایل با مشخص کردن الگوهای مجاز و ممنوع، معیارهای پذیرش و برنامهی بازگشت به عقب، تعهدات وظیفه را ثبت میکند. سپس ابزارهای بررسی خودکار، تغییرات فایلها (diff) را با این قرارداد میسنجند و جلوی خروج ایجنت از مسیر اصلی را میگیرند.
مدل نمیداند کار کجا تمام میشود. «قرارداد دامنه» (Scope contract) فایلی به ازای هر وظیفه است که مشخص میکند کار از کجا شروع میشود، کجا پایان مییابد، و اگر فراتر رفت، چطور به عقب برگردد. این قرارداد، خواستهی «در چارچوب دامنه بمان» را از یک آرزو به یک بررسیِ واقعی تبدیل میکند.
اهداف یادگیری
- نوشتن یک قرارداد دامنه که ایجنت در ابتدای تسک و مدل اعتبارسنج (Verifier) در پایان تسک آن را بخوانند.
- مشخص کردن فایلهای مجاز، فایلهای ممنوع، معیارهای پذیرش، طرح بازگشت به عقب (Rollback Plan) و مرزهای تأیید.
- پیادهسازی یک بررسیکنندهی دامنه (Scope checker) که diff را با قرارداد مقایسه کند و موارد نقض را علامت بزند.
- تبدیل خزش دامنه (Scope creep) به پدیدهای آشکار، خودکار و قابلبررسی.
صورت مسئله
ایجنتها دچار خزش دامنه میشوند. وظیفه این است: «باگ ورود را برطرف کن». نتیجهی diff اما علاوه بر مسیر لاگین، به ماژول کمکی ایمیل، درایور پایگاه داده، README و اسکریپت انتشار هم دست زده است. تغییر هر کدام در همان لحظه دلیل موجهی داشته؛ اما روی هم رفته، با تغییری کاملاً متفاوت از آنچه بازبینی شده بود روبهرو هستیم.
خزش دامنه کمنظارتترین حالت شکست در کارهای مبتنی بر ایجنت است؛ چون ایجنت با حسن نیت تکتک مراحل را توجیه و گزارش میکند. راهحل این مشکل، پرامپت سختگیرانهتر نیست؛ بلکه قراردادی ذخیرهشده روی دیسک است که وعدهها را ثبت میکند، به همراه سازوکار کنترلیای که نتیجه را با همان وعده میسنجد.
مفاهیم پایه
محتوای قرارداد دامنه
| فیلد | کاربرد |
|---|---|
task_id |
پیوند به تسک روی بورد کاری |
goal |
یک تکجمله که بازبین بتواند درستیاش را تأیید کند |
allowed_files |
الگوهای گلاب (Globs) از فایلهایی که ایجنت اجازهی نوشتن روی آنها را دارد |
forbidden_files |
الگوهای گلابی که ایجنت حتی اتفاقی نباید به آنها دست بزند |
acceptance_criteria |
دستورهای تست یا خطوط assertion که اتمام کار را اثبات میکنند |
rollback_plan |
یک پاراگراف که اپراتور در صورت نیاز به توقف اجرا، بتواند پیادهاش کند |
approvals_required |
اقدامهای خارج از دامنه که تأیید صریح انسان را لازم دارند |
قراردادی که forbidden_files نداشته باشد ناقص است؛ فضای منفی، نیمی از خود قرارداد است.
استفاده از گلاب بهجای مسیرهای مستقیم
در مخازن کد واقعی، فایلها جابهجا میشوند. قراردادها را با الگوهای گلاب ببندید (app/**/*.py، tests/test_signup*.py) تا ریفکتور میان نشستها، اعتبار قرارداد را باطل نکند.
بازگشت به عقب، بخشی از دامنه است
ثبت نحوهی بازگشت به عقب، نویسندهی قرارداد را مجبور میکند به سناریوهای شکست فکر کند. قراردادی که بازگشتپذیر نباشد، اصلاً نباید تأیید شود.
بررسی دامنه یعنی بررسی diff
ایجنت یک diff مینویسد. ابزار بررسی، این diff، الگوهای گلاب مجاز و ممنوع، و فهرست دستورهای پذیرشِ اجراشده را میخواند. هر تخلف بهشکل یک یافتهی برچسبخورده ثبت میشود که درگاه اعتبارسنجی میتواند آن را رد کند.
دو سطح از دامنه: فهرست قابلیتها و قرارداد وظیفه
قرارداد دامنه فقط یک وظیفه را محدود میکند، نه کل پروژه را. ممکن است ایجنت کاملاً در چارچوب قراردادِ رفع باگ ورود بماند، اما در نوبت گفتوگو (Turn) بعدی تصمیم بگیرد که پروژه به یک صفحهی تنظیمات، دکمهی جابهجایی حالت تاریک و بازنویسی مسیریاب (Router) هم نیاز دارد. از این قرارداد هیچوقت نپرسیده بودند چه کارهایی در دامنهی کل پروژه میگنجد؛ فقط پرسیده بودند چه فایلهایی در دامنهی این وظیفه هستند.
این سطح دوم به یک مفهوم اولیهی مستقل نیاز دارد: یک فایل feature_list.json که ایجنت در ابتدای هر نشست (Session) میخواند. این فایل همان بکلاگ پروژه بهشکل مرتبشده و ماشینخوان است. ایجنت دقیقاً یک قابلیت با وضعیت todo را انتخاب میکند، شناسهی (id) آن را درون قرارداد دامنهی فعال مینویسد و اجازهی شروع دومین قابلیت در همان نشست را ندارد. با این روش، «در هر بار فقط یک قابلیت» دیگر صرفاً جملهای در پرامپت نیست که ایجنت دورش بزند؛ بلکه مقداری است که از دیسک میخواند و درگاه آن را اعمال میکند.
{
"project": "knowledge-base",
"active": "import-pdf",
"features": [
{ "id": "import-pdf", "status": "in_progress", "goal": "import a PDF into the library", "done_when": "pytest tests/test_import.py && a sample PDF appears in the library view" },
{ "id": "full-text-search", "status": "todo", "goal": "search document text and rank hits", "done_when": "query returns ranked results with snippets" },
{ "id": "cite-answers", "status": "todo", "goal": "answers carry source citations", "done_when": "every answer renders at least one clickable citation" }
]
}
| فیلد | کاربرد |
|---|---|
active |
تنها قابلیتی که نشست جاری مجاز به تغییر آن است؛ خالی بودن یعنی یکی را انتخاب و ثبت کن |
features[].id |
شناسهی متنیِ پایداری (Slug) که task_id در قرارداد دامنه به آن اشاره میکند |
features[].status |
مقادیر todo، in_progress، done، blocked؛ همزمان فقط یک مورد میتواند in_progress باشد |
features[].goal |
یک تکجمله که بازبین بتواند درستیاش را تأیید کند |
features[].done_when |
خط شرطِ پذیرش که وضعیت را از in_progress به done تغییر میدهد |
دو قاعده باعث میشوند این فهرست نقش ستون باربر را بازی کند، نه اینکه صرفاً تزیینی باشد. اول اینکه، اصل «حداکثر یک قابلیت در حال انجام (in_progress)» خودش یک بررسی آغازین (فاز 14 · 33) است: اگر فهرست دو مورد فعال را نشان دهد، نشست تا زمان تعیین تکلیف توسط انسان اصلاً شروع نمیشود. دوم اینکه، فهرست قابلیتها یک فایل است، نه یک پیام در چت؛ چرا که پیامها با اسکرول شدن از بافت بیرون میروند، اما فایل بین نشستها و ایجنتهای مختلف باقی میماند. فرایند تحویل کار (فاز 14 · 40) وضعیت قابلیت تکمیلشده را به done برمیگرداند تا نشست بعدی بهجای تلاش مجدد برای استخراج کارهای باقیمانده، کارش را با بورد دقیقی آغاز کند.
قرارداد و فهرست با اصل حداقل دسترسی ترکیب میشوند، درست مانند منطقِ merge در ادامه: مقدار allowed_files در قرارداد وظیفه باید همیشه زیرمجموعهای از فایلهای مربوط به همان قابلیت فعال باشد، نه بیرون از آن.
پیادهسازی
فایل code/main.py این بخشها را پیادهسازی میکند:
- اسکیمای
scope_contract.json(زیرمجموعهای از JSON Schema، آرایههایی از الگوهای گلاب). - یک پارسر diff که فهرستی از فایلهای تغییرکرده به همراه دستورهای اجراشده را به شیء
RunSummaryتبدیل میکند. - تابع
scope_checkکه نتیجهی مقایسه با قرارداد را بهصورت(violations, in_scope, off_scope)برمیگرداند. - دو اجرای نمایشی: یکی که در دامنه باقی میماند و دیگری که دچار خزش میشود. بررسیکننده مورد خزش را با ذکر دقیق فایل و علت تخلف پرچمگذاری میکند.
اجرای برنامه:
python3 code/main.py
خروجی: متن قرارداد، دو اجرا، احکام صادرشده برای هر اجرا و فایل ذخیرهشدهی scope_report.json.
الگوهای کاربردی در محیط تولید
یکی از برنامهنویسانی که از رویکرد «مشخصاتمحوریِ حداکثری» (specsmaxxing؛ یعنی نوشتن قراردادهای دامنه در فرمت YAML پیش از فراخوانی ایجنت) استفاده کرده، گزارش میدهد که نرخ افتادن در چرخهی بیراهه و هدررفت کار در عرض سه هفته، بدون دست زدن به مدل ایجنت، از 52% به 21% کاهش یافته است. قرارداد این تفاوت را رقم زد، نه خود مدل. سه الگو این دستاورد را پایدار نگه میدارند:
بودجهی نقض بهجای شکست دودویی. ابزار agent-guardrails (درگاه merge متنبازی که با ابزارهایی مانند Claude Code، Cursor، Windsurf، و Codex از طریق پروتکل MCP استفاده میشود) قابلیتی به نام violationBudget به ازای هر وظیفه دارد: انحرافهای جزئیِ داخل سقف بودجه صرفاً بهشکل هشدار نمایش داده میشوند؛ تنها زمانی که از بودجه فراتر برود، درگاه مانع merge خواهد شد. این قابلیت را با violationSeverity: "error" | "warning" همراه کنید. همین بودجه است که فرق میان گاردریل (ریل ایمنی) پایدار در خط لولهی تحویل را با گاردریلی رقم میزند که اعضای کلافهشدهی تیم غیرفعالش میکنند.
عدم تقارن سطح حساسیت بر اساس دستهی مسیرها. تغییرات خارج از دامنه در مسیر docs/** معمولاً در حد warn است؛ اما تغییر خارج از دامنه در scripts/**، migrations/**، یا config/prod/** همیشه سطح block میگیرد. این تفاوت در حساسیت باید در خود قرارداد تعیین شود نه درون محیط اجرا (Runtime)؛ چون مختص هر پروژه است و وظیفه به وظیفه تغییر میکند.
بودجههای زمانی و شبکه در کنار بودجهی فایل. فیلد time_budget_minutes سقف زمان واقعی (wall clock) را مشخص میکند؛ اگر کار بیشتر طول بکشد، محیط اجرا بدون تأیید مجدد اجازهی ادامهی کار را نمیدهد. یک لیست مجاز با فیلد network_egress روی هاستها مانع از آن میشود که ایجنت بیصدا با یک API خارجی نامربوط به تسک ارتباط برقرار کند. این موارد هم ابعاد دامنه هستند؛ الگوهای گلاب لازماند، اما کافی نیستند.
منطقِ merge چند قرارداد بر اساس حداقل دسترسی. وقتی دو قرارداد دامنه همزمان اعمال میشوند (مثلاً یک قرارداد در سطح پروژه بههمراه یک قرارداد ویژهی تسک)، ترکیب به این شکل انجام میشود: اشتراک فایلهای مجاز (allowed_files یعنی هر دو قرارداد باید مسیر را مجاز بدانند)، اجتماع فایلهای ممنوع (forbidden_files یعنی اگر یکی هم مسیر را منع کند کافی است)، انتخاب محدودترین مقدار (حداقل) برای time_budget_minutes و تجمیع موارد approvals_required. برای فیلد network_egress: مقدار None بهمعنی عدم اعمال محدودیت، [] بهمعنی رد همهچیز، و [...] بهصورت لیست مجاز عمل میکند؛ هنگام ترکیب، None اولویت را به طرف دیگر میدهد، دو لیست با هم اشتراک گرفته میشوند و وضعیت رد همهچیز همچنان رد همهچیز باقی میماند. این منطق را صریحاً در اسکیمای قرارداد بیاورید تا عملیات merge مکانیکی و قابلبازبینی باشد.
کاربرد در عمل
الگوهای استفاده در محیط تولید:
- دستورات خط مورب در Claude Code. دستور
/scopeقرارداد را مینویسد و آن را در بافت نشست سنجاق میکند. سابایجنتها قبل از اقدام، قرارداد را میخوانند. - پولریکوئستهای گیتهاب. فایل JSON قرارداد را در متن PR یا بهصورت یک دستسازه (Artifact) اضافه و push کنید. ابزار CI بررسیکنندهی دامنه را روی diff عملِ merge اجرا میکند.
- وقفهها در LangGraph. هرگونه نقض دامنه یک وقفه (Interrupt) ایجاد میکند؛ گردانندهی وقفه از انسان میپرسد که آیا دامنهی قرارداد باید گسترش یابد یا اینکه ایجنت باید عقبنشینی کند.
قرارداد همیشه همراه با وظیفه پیش میرود. وقتی وظیفه بسته شد، قرارداد در مسیر outputs/scope/closed/ بایگانی میشود.
تحویل نهایی
فایل outputs/skill-scope-contract.md برای شرح یک وظیفه، قرارداد دامنهای همراه با یک بررسیکنندهی آگاه به الگوهای گلاب تولید میکند که در CI روی تمام diffهای ایجادشده توسط ایجنت اجرا میشود.
تمرینها
- یک فیلد
network_egressاضافه کنید که فهرست هاستهای خارجیِ مجاز را در خود نگه دارد. اجرای کارهایی را که به سایر هاستها متصل میشوند رد کنید. - ابزار بررسی را طوری تغییر دهید که نقض در مسیر
docs/**را با تساهل رد کند (fail soft)، اما روی مسیرscripts/**واکنش سخت نشان دهد (fail hard). دلیل این عدم تقارن را توضیح دهید. - کاری کنید قرارداد بتواند با استفاده از مجموعهای از قوانین ایستا (بدون LLM)، فیلد
allowed_filesرا از رویgoalاستخراج کند. در اولین حالت خاص (Edge case) چه مشکلی پیش میآید؟ - فیلد
time_budget_minutesرا بیفزایید و اگر زمان سپریشده از آن فراتر رفت، از ادامهی کار جلوگیری کنید. - دو قرارداد را روی یک diff یکسان اعمال کنید. در صورت اِعمال همزمان هر دو، منطقِ merge درست چیست؟
اصطلاحات کلیدی
| اصطلاح | اصطلاح رایج افراد | معنای دقیق فنی |
|---|---|---|
| Scope contract (قرارداد دامنه) | «شرح وظیفه» | فایل JSON به ازای هر تسک، شامل فایلهای مجاز/ممنوع، معیارهای پذیرش و طرح بازگشت به عقب |
| Scope creep (خزش دامنه) | «به فلان جا هم دست زد...» | تغییر فایلهای خارج از قرارداد در جریان انجام همان تسک |
| Rollback plan (طرح بازگشت به عقب) | «میتونیم ریورت کنیم» | دستورالعمل یکپاراگرافیِ اپراتور برای توقف کار |
| Approval boundary (مرز تأیید) | «نیاز به تأیید داره» | اقدامی که در قرارداد مشخص شده انجامش نیازمند تأیید صریح انسان است |
| Diff check (بررسی diff) | «بررسی مسیرها» | تطبیق فایلهای تغییریافته با الگوهای گلابِ قرارداد |
برای مطالعهی بیشتر
- وقفههای انسان در چرخه در LangGraph
- خطمشیهای تأیید ابزار در OpenAI Agents SDK
- درگاههای merge و اعتبارسنجی دامنه در logi-cmd/agent-guardrails — بودجههای نقض و سطوح حساسیت
- وبلاگ Dev|Journal، جلوگیری از انحراف پیکربندی ایجنت با تست قرارداد — اجرای حالت
--strictبدون وابستگیهای خارجی - کدنویسی با ایجنت دام نیست (لاگهای محیط تولید) — شواهد specsmaxxing: کاهش خطا از 52% به 21%
- الگوهای گلابِ مجوز در OpenCode — محدودسازی ریزدانهی دامنه به ازای هر مجوز
- شرکت Knostic، امنیت ایجنتهای کدنویسی: مدلهای تهدید و استراتژیهای محافظت — دامنه بهعنوان جزئی از اصل حداقل دسترسی
- قالب AI Spec از شرکت Augment Code — سیستم مرزبندی سهسطحی (باید/بپرس/هرگز)
- فاز 14 · 27 — دفاع در برابر تزریق پرامپت (Prompt injection) که با قفلهای دامنه همراه میشود
- فاز 14 · 33 — مجموعهقوانینی که این قرارداد برای هر وظیفه سفارشی میکند
- فاز 14 · 38 — درگاه اعتبارسنجی که ابزار بررسی نتایجش را به آن گزارش میدهد
