طراحی اسکیل‌های بزرگ ایجنت به‌صورت سیستم‌های قطعی و فازمحور

یک معماری مرجع برای جریان‌کارهای قابل اعتماد و بهینه در مصرف کانتکست

English version

درباره‌ی این مقاله

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

یک اسکیل کوچک ایجنت می‌تواند صرفاً یک فایل دستورالعمل خوب باشد.

می‌تواند یک وظیفه را توضیح دهد، چند ابزار را نام ببرد، مثال‌هایی ارائه کند و به ایجنت بگوید چه زمانی متوقف شود. در بسیاری از موارد همین کافی است. افزودن یک runtime، ماشین حالت یا پوشه‌ای پر از قراردادها فقط فهم اسکیل را دشوارتر می‌کند.

اما یک اسکیل بزرگ متفاوت است.

وقتی یک اسکیل باید یک مخزن را بررسی کند، کاری را از یک صف انتخاب کند، قفل بگیرد، برنچ بسازد، اجرا را به یک متخصص واگذار کند، برای بازبینی متوقف شود، کار را روی همان commit از سر بگیرد، pull request منتشر کند، وضعیت خارجی را به‌روزرسانی کند و آموخته‌ها را ثبت کند، دیگر فقط یک prompt نیست. به یک runtime برای جریان‌کار تبدیل شده است که بخشی از تصمیم‌هایش را یک مدل زبانی می‌گیرد.

نادیده‌گرفتن این تغییر ماهیت آسان است. ممکن است اسکیل همچنان در پوشه‌ای شامل SKILL.md، چند script و چند reference نگهداری شود، اما از نظر عملیاتی اکنون به یک سیستم توزیع‌شده‌ی کوچک شباهت دارد:

  • حالت‌ها و انتقال‌های مشخص دارد؛
  • سیستم‌های خارجی را مشاهده می‌کند که ممکن است بین دو خواندن تغییر کنند؛
  • اثرات جانبی ایجاد می‌کند که ممکن است به‌صورت ناقص شکست بخورند؛
  • به کنترل هم‌زمانی نیاز دارد؛
  • ممکن است متوقف شود و بعداً ادامه پیدا کند؛
  • باید وضعیت پایدار را از کانتکست موقت اجرا جدا کند؛
  • باید پنجره‌ی کانتکست خود را درون یک بودجه نگه دارد؛
  • و باید هم برای مدل و هم برای انسان‌هایی که آن را نگهداری می‌کنند قابل فهم بماند.

استدلال مرکزی این مقاله ساده است:

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

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

فهرست مطالب
  1. چه زمانی یک اسکیل دیگر فقط یک prompt نیست؟
  2. پوسته‌ی قطعی و هسته‌ی احتمالاتی
    1. پوسته‌ی قطعی
    2. هسته‌ی احتمالاتی
  3. فاز، قرارداد اجراست؛ نه یک مورد در checklist
  4. وضعیت پایدار جریان‌کار را از فاز موقت اجرا جدا کنید
    1. وضعیت پایدار دامنه
    2. فاز موقت اجرا
  5. یک گراف فاز عمومی برای اسکیل عملیاتی بزرگ
  6. قراردادهای انتقال
  7. معماری یک اسکیل فازمحور
    1. SKILL.md
    2. phases/
    3. references/
    4. scripts/
    5. adapters/
  8. بارگذاری تنبل سه لایه‌ی جدا دارد
    1. ۱. بارگذاری تنبل referenceها
    2. ۲. بارگذاری تنبل ماژول‌ها
    3. ۳. بارگذاری تنبل جزئیات داده
  9. کانتکست را یک گراف وابستگی بودجه‌بندی‌شده در نظر بگیرید
  10. مشاهده، تصمیم و اثرات را جدا کنید
    1. مشاهده
    2. تصمیم
    3. برنامه‌ریزی اثرات
    4. اجرای اثرات
    5. بازخوانی
  11. منظور از قطعی‌بودن چه باید باشد؟
    1. ترتیب پایدار
    2. snapshotهای صریح
    3. تصمیم‌های ساخت‌یافته‌ی مدل
    4. نبود عدم قطعیت پنهان در پوسته
    5. معنای run-to-completion برای انتقال
  12. واگذاری‌ها نقاط توقف typeشده‌اند
  13. capabilityها، اثرات جانبی و مرزهای transaction
    1. قفل‌ها باید invariantها را پوشش دهند، نه کل وظیفه را
    2. idempotency باید طراحی شود، نه اینکه به آن امید داشته باشیم
    3. compensation همان rollback نیست
  14. adapterهای دامنه هسته را کوچک نگه می‌دارند
  15. مشاهده‌پذیری باید از مدل فاز پیروی کند
  16. تست یک اسکیل فازمحور
    1. unit test فاز
    2. تست انتقال
    3. تست‌های golden workflow
    4. تست‌های fault injection
    5. تست‌های lazy loading
    6. تست‌های replay و resume
    7. property testها
  17. الگوهای شکست رایج
    1. indexای که همه‌چیز را لازم دارد
    2. reference چندمنظوره
    3. referenceهای پویا، کد eager
    4. کد lazy، داده eager
    5. مدل به‌عنوان هماهنگ‌کننده‌ی transaction
    6. mutation پنهان
    7. labelهای فاز در سیستم دامنه
    8. resume از طریق بازسازی
    9. انفجار فازها
    10. نمایش ظاهری determinism
  18. یک مسیر عملی برای مهاجرت
    1. ۱. یک baseline رفتاری بسازید
    2. ۲. واژگان فاز را معرفی کنید
    3. ۳. referenceها را براساس مسئولیت جدا کنید
    4. ۴. یک kernel حداقلی بسازید
    5. ۵. routing سمت خواندن را استخراج کنید
    6. ۶. پرریسک‌ترین transaction را استخراج کنید
    7. ۷. finalization و adapterها را استخراج کنید
    8. ۸. مسیرهای ثانویه را استخراج کنید
    9. ۹. بودجه‌ی capability و lazy loading را اعمال کنید
    10. ۱۰. مسیر legacy را حذف کنید
  19. یک manifest فاز فشرده
  20. یک envelope مشترک برای نتیجه
  21. چه چیزهایی باید هوشمند باقی بمانند؟
  22. قواعد طراحی
  23. از مهندسی prompt تا مهندسی runtime
  24. منابع
  25. تاریخچه‌ی بازنگری

چه زمانی یک اسکیل دیگر فقط یک prompt نیست؟

مشخصات Agent Skills یک اسکیل را به‌صورت پوشه‌ای تعریف می‌کند که یک SKILL.md الزامی و پوشه‌های اختیاری scripts/، references/ و assets/ دارد. این مشخصات همچنین افشای تدریجی را پیشنهاد می‌کند: ابتدا metadata بارگذاری می‌شود، هنگام فعال‌شدن اسکیل دستورالعمل اصلی خوانده می‌شود و منابع پشتیبان فقط در زمان نیاز وارد می‌شوند.

این ساختار نقطه‌ی شروع قدرتمندی است. اجازه نمی‌دهد تمام دستورالعمل‌ها، referenceها، scriptها و templateهای ممکن هم‌زمان وارد کانتکست مدل شوند.

اما افشای تدریجی در سطح فایل، به‌تنهایی یک runtime با معماری مناسب ایجاد نمی‌کند.

ممکن است یک اسکیل از نظر فنی referenceهای جداگانه داشته باشد، اما همه‌ی آن‌ها را هنگام شروع بخواند. ممکن است scriptهای جدا داشته باشد، اما entrypoint تمام ماژول‌ها را import کند. ممکن است برای دریافت داده‌ی خلاصه از یک سیستم خارجی query بزند و سپس بلافاصله تمام issueها، commentها، reviewها و attachmentها را با جزئیات کامل واکشی کند. ساختار پوشه ماژولار به نظر می‌رسد، در حالی که اجرا همچنان eager و یکپارچه است.

وقتی اسکیل بزرگ می‌شود، مشکل آشکار می‌شود:

  1. قرارداد اصلی بیش از اندازه بزرگ می‌شود و بارگذاری و استدلال امن درباره‌ی آن دشوار خواهد بود.
  2. referenceها موضوعات نامرتبط را با هم ترکیب می‌کنند؛ بنابراین خواندن قانون یک فاز، چند فاز دیگر را نیز وارد کانتکست می‌کند.
  3. هر command ماژول‌هایی را import می‌کند که هرگز از آن‌ها استفاده نخواهد کرد.
  4. scanهای گسترده، جزئیاتی را واکشی می‌کنند که فقط به آیتم انتخاب‌شده مربوط‌اند.
  5. مدل باید قوانین برنچ، بازبینی، انتشار، یادگیری، rollback و اعتبارسنجی تخصصی دامنه را هم‌زمان به خاطر بسپارد.
  6. مشخص‌کردن محل شکست دشوار می‌شود، چون runtime مرز فازی صریح ندارد.
  7. پس از restart، سیستم به‌جای ادامه از یک checkpoint تأییدشده، آنچه احتمالاً اتفاق افتاده است را بازسازی می‌کند.
  8. تست‌ها نتیجه‌ی نهایی را بررسی می‌کنند، اما مسیر، کانتکست بارگذاری‌شده یا اثرات جانبی رسیدن به آن نتیجه را نمی‌سنجند.

در این مرحله پاسخ صرفاً این نیست که «prompt بهتری بنویسیم». اسکیل به معماری اجرا نیاز دارد.

پوسته‌ی قطعی و هسته‌ی احتمالاتی

مدل‌های زبانی سیستم‌هایی احتمالاتی‌اند. حتی با ورودی‌های ثابت و تنظیمات محافظه‌کارانه، استدلال داخلی و نحوه‌ی بیان آن‌ها را نباید مانند یک برنامه‌ی قطعی در نظر گرفت.

اما این به آن معنا نیست که کل جریان‌کار باید احتمالاتی باشد.

یک اسکیل قابل اعتماد می‌تواند دو نوع کار را از هم جدا کند.

پوسته‌ی قطعی

پوسته باید مالک بخش‌هایی از اجرا باشد که می‌توان آن‌ها را به‌شکل قانون بیان کرد:

  • parse کردن command؛
  • انتخاب فاز در شرایطی که وضعیت ابهام ندارد؛
  • ترتیب پایدار و قواعد شکستن تساوی؛
  • اعتبارسنجی schema؛
  • بررسی capabilityها؛
  • گرفتن قفل؛
  • کلیدهای idempotency؛
  • اعتبارسنجی خواندن پیش از نوشتن و خواندن پس از نوشتن؛
  • قانونی‌بودن انتقال وضعیت؛
  • ساخت checkpoint؛
  • تشخیص snapshot قدیمی؛
  • سیاست retry و compensation؛
  • بارگذاری ماژول و reference؛
  • tracing؛
  • شرایط توقف.

با ورودی‌های تأییدشده‌ی یکسان، این پوسته باید همان مسیر را انتخاب کند و همان نوع اثرات را مجاز بداند.

هسته‌ی احتمالاتی

مدل باید مالک کارهایی باشد که واقعاً به تفسیر یا تولید نیاز دارند:

  • فهم یک درخواست مبهم؛
  • خلاصه‌کردن یک issue انتخاب‌شده؛
  • انتخاب میان چند strategy، وقتی هیچ قانون کاملی نمی‌تواند trade-off را کدگذاری کند؛
  • نوشتن یا تغییر کد؛
  • ارزیابی شواهد کیفی؛
  • نوشتن یک توضیح؛
  • پیشنهاد یک آموخته؛
  • حل یک edge case تازه درون مرزهای صریح.

هدف پوسته حذف هوشمندی نیست؛ هدف این است که هوشمندی در جایی به کار رود که ارزش ایجاد می‌کند و از بخش‌هایی دور بماند که ابهام در آن‌ها ریسک عملیاتی قابل اجتناب می‌سازد.

این تمایز شبیه تفاوتی است که Anthropic میان workflowها و agentها بیان می‌کند: workflowها مسیرهای ازپیش‌تعریف‌شده‌ی کد را دنبال می‌کنند، در حالی که agentها فرایند و استفاده از ابزارهایشان را به‌شکل پویا هدایت می‌کنند. یک اسکیل بزرگ معمولاً به هر دو نیاز دارد. مسیر بیرونی می‌تواند workflow باشد، حتی اگر یک یا چند فاز درون آن ماهیت agentic داشته باشند.

فاز، قرارداد اجراست؛ نه یک مورد در checklist

تیم‌ها اغلب واژه‌ی فاز را به‌معنای عنوانی در یک فایل دستورالعمل طولانی به کار می‌برند:

  1. بررسی؛
  2. برنامه‌ریزی؛
  3. پیاده‌سازی؛
  4. تست؛
  5. انتشار.

این تقسیم‌بندی برای انسان مفید است، اما برای یک runtime کافی نیست.

یک فاز واقعی اجرا باید قرارداد داشته باشد. این قرارداد دست‌کم باید موارد زیر را تعریف کند:

  • هویت — نامی پایدار برای فاز؛
  • هدف — تنها مسئولیت فاز؛
  • شرایط ورود — چه چیزهایی باید از قبل برقرار باشند؛
  • مشاهدات الزامی — چه داده‌هایی باید خوانده شوند؛
  • referenceهای مجاز — چه دستورالعمل‌هایی اجازه دارند وارد کانتکست شوند؛
  • ماژول‌های مجاز — چه کدی اجازه دارد بارگذاری شود؛
  • capabilityها — فاز مجاز است چه اثرات خارجی‌ای را درخواست کند؛
  • schema تصمیم — نتیجه‌ی فاز چه شکلی دارد؛
  • پس‌شرط‌ها — پس از موفقیت چه چیزهایی باید برقرار باشند؛
  • معنای شکست — retry، block، compensate یا stop؛
  • قواعد فاز بعدی — انتقال‌های قانونی؛
  • الزامات ازسرگیری — برای ادامه در آینده چه شواهدی لازم است.

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

اگر یک فاز بتواند هم وظیفه‌ای را claim کند، هم فایل‌های منبع را تغییر دهد، هم pull request منتشر کند و هم پیشنهاد یادگیری را به‌روزرسانی کند، بیش از اندازه بزرگ است. اگر هر خواندن فایل یا هر tool call به یک فاز جدا تبدیل شود، مدل بیش از اندازه تکه‌تکه می‌شود و runtime به مجموعه‌ای تشریفاتی از مراحل تبدیل خواهد شد.

مرز درست معمولاً جایی ظاهر می‌شود که یکی از این موارد تغییر می‌کند:

  • منبع مرجع وضعیت؛
  • مجموعه‌ی اثرات جانبی مجاز؛
  • مالک اجرا؛
  • سیاست شکست و جبران؛
  • کانتکستی که باید بارگذاری شود؛
  • checkpoint لازم برای ازسرگیری.

وضعیت پایدار جریان‌کار را از فاز موقت اجرا جدا کنید

یکی از مهم‌ترین تصمیم‌های طراحی، تفکیک دو لایه‌ی وضعیت است.

وضعیت پایدار دامنه

وضعیت پایدار به system of record تعلق دارد و باید پس از پایان اجرای فعلی نیز باقی بماند. برای مثال:

  • یک issue در صف، فعال، مسدود یا آماده‌ی بازبینی است؛
  • یک pull request پیش‌نویس، باز، تأییدشده یا merge شده است؛
  • یک proposal منتظر بازخورد یا برای اعمال تأیید شده است؛
  • یک branch به commit مشخصی اشاره می‌کند؛
  • یک review comment رسیدگی شده است؛
  • یک event جریان‌کار ثبت شده است.

این وضعیت باید برای فرایندها و انسان‌های دیگر قابل مشاهده باشد. معمولاً جای آن در GitHub، پایگاه داده، event log یا یک سیستم خارجی مرجع دیگر است.

فاز موقت اجرا

فاز runtime توضیح می‌دهد invocation فعلی چه کاری انجام می‌دهد:

  • scan کردن گزینه‌های بازبینی؛
  • واکشی جزئیات issue انتخاب‌شده؛
  • اعتبارسنجی پیش‌شرط‌های claim؛
  • انتظار برای واگذاری به متخصص؛
  • ازسرگیری بازبینی پیش از انتشار؛
  • render کردن artifact؛
  • تأیید انتشار؛
  • انجام یادگیری پس از وظیفه.

این فازها معمولاً نباید به labelهای پایدار در سیستم خارجی تبدیل شوند. ساختن یک label برای هر گام داخلی، مدل دامنه را شلوغ می‌کند و وضعیت خارجی را به جزئیات پیاده‌سازی گره می‌زند.

ممکن است یک وظیفه در وضعیت پایدار «در حال انجام» باقی بماند، در حالی که runtime از چند فاز داخلی عبور می‌کند. این وضعیت سالم است. وضعیت پایدار پاسخ می‌دهد: «وضعیت عمومی کار چیست؟» و فاز پاسخ می‌دهد: «این اجرا در قدم بعدی باید چه کاری انجام دهد؟»

یک گراف فاز عمومی برای اسکیل عملیاتی بزرگ

فازهای دقیق به دامنه بستگی دارند، اما گراف عمومی زیر می‌تواند مفید باشد:

COMMAND_ROUTE
      |
      v
MINIMAL_BOOTSTRAP
      |
      v
OBSERVE_SUMMARIES
      |
      v
ROUTE_WORK
      |
      +-------------------------+
      |                         |
      v                         v
HYDRATE_SELECTED_ITEM       IDLE_OR_STOP
      |
      v
DECIDE
      |
      v
PLAN_EFFECTS
      |
      v
EXECUTE_EFFECTS
      |
      v
READBACK_VALIDATE
      |
      +-------------------------+
      |                         |
      v                         v
HANDOFF_OR_RESUME          FINALIZE
      |                         |
      +-----------<-------------+

این گراف چند اصل را نشان می‌دهد.

نخست، مشاهده‌ی گسترده از واکشی جزئیات جداست. اسکیل می‌تواند تعداد زیادی گزینه را با استفاده از summaryها scan کند، اما body کامل، commentها، diffها یا attachmentها را فقط برای آیتمی می‌گیرد که انتخاب شده است.

دوم، تصمیم‌گیری از mutation جداست. runtime باید پیش از دریافت قابلیت‌های نوشتن بداند قصد دارد چه کاری انجام دهد.

سوم، پس از هر mutation مهم، بازخوانی از منبع مرجع انجام می‌شود. پاسخ موفق API نشان می‌دهد درخواست پذیرفته شده است، اما همیشه اثبات نمی‌کند وضعیت مطلوب واقعاً ایجاد شده است.

چهارم، واگذاری و ازسرگیری حالت‌های صریح‌اند. ممکن است یک انسان یا ایجنت متخصص مالک بخشی از کار باشد، اما جریان‌کار همچنان پیش و پس از آن کار به یک مرز تأییدشده نیاز دارد.

پنجم، توقف یک نتیجه‌ی معتبر برای فاز است. اسکیل باید بتواند بدون بداهه‌سازی مسیر بعدی، نتیجه‌ای مانند idle، blocked، current-session-required یا stale-resume برگرداند.

قراردادهای انتقال

یک گراف فاز زمانی قابل اعتماد می‌شود که قراردادهای صریح بر انتقال‌ها حاکم باشند.

یک قرارداد انتقال را می‌توان به این صورت نمایش داد:

transition:
  from: CLAIM_PREFLIGHT
  to: CLAIM_TRANSACTION

preconditions:
  - no_active_task
  - candidate_is_still_eligible
  - repository_identity_matches

inputs:
  - candidate_snapshot
  - source_boundary
  - prospective_handoff

capabilities:
  - repository_lock
  - issue_read
  - issue_write
  - branch_write

effects:
  - acquire_lock
  - revalidate_authoritatively
  - update_task_state
  - write_claim_record
  - prepare_branch_and_handoff

postconditions:
  - exactly_one_active_task
  - selected_task_is_active
  - handoff_is_valid

compensation:
  when: handoff_preparation_fails_after_state_change
  effects:
    - restore_task_to_queue
    - record_compensation
    - release_lock

next:
  success: EXECUTION_HANDOFF
  stale: ROUTE_WORK
  conflict: BLOCKED

خود قرارداد مهم‌تر از syntax آن است.

این قرارداد از چند خطای رایج جلوگیری می‌کند:

  • ورود به فاز نوشتن با مشاهدات قدیمی؛
  • انجام‌دادن فقط نیمی از یک تغییر وضعیت چندمرحله‌ای؛
  • گم‌کردن اینکه قفل آزاد شده است یا نه؛
  • کامل اعلام‌کردن کار پیش از تأیید انتشار؛
  • ادامه‌دادن از واگذاری‌ای که به commit قدیمی تعلق دارد؛
  • retryپذیر دانستن همه‌ی exceptionها؛
  • تکیه بر حافظه‌ی مدل برای یک قانون rollback نوشته‌نشده.

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

معماری یک اسکیل فازمحور

یک اسکیل بزرگ نباید تمام منطق خود را در SKILL.md قرار دهد. فایل اصلی باید مانند قرارداد فعال‌سازی و فهرست فازها عمل کند.

یک ساختار ممکن چنین است:

large-skill/
├── SKILL.md
├── phases/
│   ├── command-route.md
│   ├── bootstrap.md
│   ├── observe.md
│   ├── decide.md
│   ├── mutate.md
│   ├── handoff.md
│   ├── resume.md
│   └── finalize.md
├── references/
│   ├── global-invariants.md
│   ├── state-model.md
│   ├── naming.md
│   ├── publication.md
│   ├── review-handling.md
│   └── learning.md
├── scripts/
│   ├── cli.ts
│   ├── phase-registry.ts
│   ├── phase-runner.ts
│   ├── effect-executor.ts
│   ├── gateways/
│   ├── phases/
│   └── adapters/
├── schemas/
│   ├── phase-result.schema.json
│   ├── checkpoint.schema.json
│   └── effect-plan.schema.json
├── tests/
│   ├── phases/
│   ├── transitions/
│   ├── fault-injection/
│   └── lazy-loading/
└── assets/

مسئولیت‌ها باید روشن باشند.

SKILL.md

فایل اصلی باید شامل این موارد باشد:

  • اسکیل چه کاری انجام می‌دهد؛
  • چه زمانی باید فعال شود؛
  • invariantهای ایمنی عمومی؛
  • گراف فاز؛
  • قانون انتخاب اولین فاز؛
  • واژگان نتیجه؛
  • لینک مستقیم به قرارداد هر فاز.

این فایل نباید تمام قوانین دامنه، templateهای انتشار، سیاست‌های بازبینی و مثال‌های شکست را در خود نگه دارد.

phases/

هر فایل باید یک فاز را با زبانی مناسب برای مدل توضیح دهد:

  • چرا فاز وجود دارد؛
  • چه چیزهایی باید بررسی شوند؛
  • چه چیزهایی نباید بررسی شوند؛
  • چه خروجی‌ای باید تولید شود؛
  • چه ابزارهایی مجازند؛
  • چگونه باید به‌صورت امن متوقف شد.

این فایل‌های فاز نباید به یک راهنمای یکپارچه‌ی دوم تبدیل شوند. فاز فعال فقط باید قرارداد خودش و تعداد کمی reference را که مستقیماً لازم دارد بخواند.

references/

referenceها باید سیاست‌های قابل استفاده‌ی مجدد یا دانش دامنه را توضیح دهند:

  • نام‌گذاری؛
  • قانونی‌بودن انتقال وضعیت؛
  • الزامات شواهد؛
  • معنای بازبینی؛
  • محدودیت‌های ایمنی؛
  • قالب artifactها.

یک reference نباید چند جریان‌کار نامرتبط را صرفاً به این دلیل ترکیب کند که زمانی در یک سند قرار داشته‌اند.

scripts/

scriptها باید عملیات قطعی را پیاده‌سازی کنند:

  • parsing؛
  • routing؛
  • validation؛
  • sorting؛
  • locking؛
  • اجرای effectها؛
  • readback؛
  • checkpointing؛
  • اعمال schema.

scriptها همچنین می‌توانند ابزارهایی امن و محدود در اختیار مدل قرار دهند. آن‌ها باید به‌جای null مبهم یا پیام شکست عمومی، خطاهای تشخیصی برگردانند.

adapters/

رفتار تخصصی دامنه باید پشت یک مرز adapter قرار گیرد.

runtime اصلی نباید تمام فیلدهای لازم برای هر نوع وظیفه را بشناسد. در عوض باید از adapter انتخاب‌شده بخواهد:

  • شواهد دامنه را اعتبارسنجی کند؛
  • بخش تخصصی artifact را render کند؛
  • blockerهای دامنه را دسته‌بندی کند؛
  • gateهای تکمیل تخصصی دامنه را تعریف کند.

این کار مانع می‌شود یک task runner عمومی تمام validatorهای تخصصی را import کند و در سراسر هسته شاخه‌های if kind === ... انباشته شوند.

بارگذاری تنبل سه لایه‌ی جدا دارد

وقتی درباره‌ی lazy loading در اسکیل‌ها صحبت می‌شود، اغلب فقط referenceها منظورند. یک طراحی کامل باید سه لایه‌ی متفاوت را کنترل کند.

۱. بارگذاری تنبل referenceها

مدل باید فقط دستورالعمل‌های لازم برای فاز فعلی را بارگذاری کند.

یک manifest فاز می‌تواند چنین چیزی را تعریف کند:

phase: REVIEW_CLASSIFY
references:
  - references/global-invariants.md
  - references/review-classification.md

رویه‌ی رسیدگی به feedbackهای inline نباید فقط به این دلیل بارگذاری شود که ممکن است کار بازبینی وجود داشته باشد. این رویه باید زمانی خوانده شود که دسته‌بندی، feedback جدید inline را پیدا کرده است.

این کار مصرف token را کاهش می‌دهد، اما مزیت مهم‌تر آن جداسازی شناختی است. قوانین نامرتبط حتی زمانی که پنجره‌ی کانتکست از نظر فنی فضای کافی دارد، می‌توانند در کار مدل اختلال ایجاد کنند.

بارگذاری تنبل referenceها در این شرایط شکست می‌خورد:

  • SKILL.md به مدل می‌گوید پیش از شروع همه‌ی referenceها را بخواند؛
  • یک فایل «قرارداد» قوانین چند فاز را در خود دارد؛
  • referenceها زنجیره‌های عمیقی تشکیل می‌دهند که به خواندن اکتشافی گسترده نیاز دارند؛
  • یک فاز بدون خواندن همه‌ی referenceها نمی‌تواند تشخیص دهد کدام‌یک مرتبط است؛
  • runtime فاز فعلی را به‌وضوح نشان نمی‌دهد.

مشخصات Agent Skills استفاده از referenceهای متمرکز و بارگذاری منابع برحسب نیاز را پیشنهاد می‌کند. manifest فاز این پیشنهاد را به یک ویژگی قابل اعمال در runtime تبدیل می‌کند.

۲. بارگذاری تنبل ماژول‌ها

بهره‌وری کانتکست و بهره‌وری فرایند دو چیز متفاوت‌اند.

یک entrypoint جاوااسکریپت می‌تواند referenceها را بیرون از کانتکست مدل نگه دارد، اما همچنان تمام ماژول‌های اجرا را eager import کند:

const claim = require("./claim");
const review = require("./review");
const publish = require("./publish");
const learning = require("./learning");
const strategyAdapter = require("./adapters/strategy");

در این وضعیت حتی --help یا list کد انتشار، یادگیری و یک adapter تخصصی را initialize می‌کند.

الگوی بهتر این است که handler پس از routing بارگذاری شود:

async function runPhase(phase, context) {
  const definition = PHASES[phase];
  const handler = await import(definition.handler);
  return handler.run(context);
}

بارگذاری تنبل ماژول، هزینه‌ی startup را کاهش می‌دهد، coupling ناخواسته را کم می‌کند و یک نشانه‌ی قابل آزمون فراهم می‌کند که مرزهای فاز واقعی‌اند.

همچنین بازبینی capabilityها را ساده‌تر می‌کند. اگر یک command فقط‌خواندنی هرگز write gateway را import نکند، شواهد قوی‌تری داریم که نمی‌تواند به‌صورت ناخواسته وضعیت خارجی را تغییر دهد.

۳. بارگذاری تنبل جزئیات داده

لایه‌ی سوم اغلب پرهزینه‌ترین لایه است.

برای scan یک صف ممکن است فقط این موارد لازم باشند:

  • شناسه؛
  • عنوان؛
  • وضعیت؛
  • labelها؛
  • اولویت؛
  • زمان ایجاد؛
  • زمان آخرین به‌روزرسانی.

این scan به body کامل هر issue، commentها، attachmentها، review threadها و سندهای لینک‌شده نیاز ندارد.

برای scan بازبینی ممکن است فقط summaryهای pull request لازم باشند. commentهای کامل بازبینی باید تنها پس از انتخاب یک pull request واکشی شوند.

این موضوع به یک قانون عمومی می‌رسد:

با summaryها به‌شکل گسترده scan کنید؛ پس از انتخاب، جزئیات را محدود و هدفمند واکشی کنید.

بارگذاری جزئیات داده باید در گراف فاز قابل مشاهده باشد. در غیر این صورت یک «scan فقط‌خواندنی» می‌تواند بی‌سروصدا به بزرگ‌ترین منبع مصرف token و latency تبدیل شود.

کانتکست را یک گراف وابستگی بودجه‌بندی‌شده در نظر بگیرید

یک اسکیل نه‌تنها باید بداند در چه فازی قرار دارد، بلکه باید بداند آن فاز اجازه دارد چه چیزهایی را بارگذاری کند.

manifest فاز می‌تواند چهار بودجه تعریف کند:

type PhaseDefinition = {
  handler: string;
  references: string[];
  modules: string[];
  capabilities: Capability[];
  contextBudget: {
    maxReferenceTokens: number;
    maxHydratedItems: number;
    maxToolResultTokens: number;
  };
};

runtime سپس می‌تواند این موارد را trace کند:

  • referenceهای درخواست‌شده؛
  • token تخمینی referenceها؛
  • ماژول‌های importشده؛
  • آیتم‌هایی که جزئیاتشان واکشی شده است؛
  • اندازه‌ی نتیجه‌ی ابزار؛
  • کل ورودی فاز؛
  • کل خروجی فاز.

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

بدون اندازه‌گیری، «بارگذاری تنبل» فقط یک نیت باقی می‌ماند. با اندازه‌گیری، به ویژگی‌ای تبدیل می‌شود که می‌تواند regression پیدا کند، در تست شکست بخورد و بهبود یابد.

مشاهده، تصمیم و اثرات را جدا کنید

یک اسکیل فازمحور باید جریان منضبط زیر را دنبال کند:

observe -> decide -> plan effects -> execute -> read back -> verify

هر مرحله مسئله‌ی متفاوتی را حل می‌کند.

مشاهده

مشاهده، واقعیت‌های مرجع را جمع می‌کند و snapshotی با هویت مشخص می‌سازد:

{
  "item_id": 2459,
  "state": "in-queue",
  "labels": ["priority:high", "kind:code-change"],
  "updated_at": "2026-07-18T08:15:00Z",
  "source_version": "main@abc123"
}

تصمیم

تصمیم باید تا حد ممکن pure باشد:

{
  "decision": "claim",
  "reason": "highest eligible priority tier; oldest item",
  "snapshot_id": "sha256:..."
}

وقتی قواعد قطعی‌اند، یک تصمیم pure را می‌توان بدون GitHub، filesystem یا مدل زبانی unit test کرد.

برنامه‌ریزی اثرات

plan پیش از وقوع mutationها، قصد سیستم را بیان می‌کند:

{
  "effects": [
    {"type": "acquire_lock", "scope": "repository"},
    {"type": "replace_state", "item_id": 2459, "to": "in-progress"},
    {"type": "create_branch", "name": "agent/issue-2459"},
    {"type": "write_claim_record", "item_id": 2459}
  ],
  "postconditions": [
    "exactly_one_active_item",
    "selected_item_is_active",
    "branch_exists"
  ]
}

اجرای اثرات

یک executor متمرکز، capabilityها، idempotency، ترتیب و compensation را اعمال می‌کند.

بازخوانی

runtime سیستم مرجع را دوباره می‌خواند و پس‌شرط‌ها را تأیید می‌کند.

این ساختار به‌صورت طبیعی از dry run پشتیبانی می‌کند. dry run می‌تواند پس از برنامه‌ریزی اثرات، بدون import کردن write gatewayها متوقف شود.

این ساختار auditing را نیز ممکن می‌کند. یک trace می‌تواند نشان دهد اسکیل چه چیزی مشاهده کرد، چه تصمیمی گرفت، قصد داشت چه چیزی را تغییر دهد، واقعاً چه تغییری ایجاد کرد و پس از آن چه چیزی را تأیید کرد.

منظور از قطعی‌بودن چه باید باشد؟

واژه‌ی deterministic در سیستم‌های ایجنت اغلب بیش از اندازه آزادانه استفاده می‌شود.

این واژه نباید به این معنا باشد که:

  • مدل همیشه نثر کاملاً یکسان تولید می‌کند؛
  • هر مسیر استدلالی token به token قابل تکرار است؛
  • سیستم می‌تواند عدم قطعیت را حذف کند؛
  • هیچ قضاوت انسانی لازم نیست.

برای یک اسکیل فازمحور، deterministic باید معنایی عملیاتی‌تر داشته باشد:

با snapshot خارجی اعتبارسنجی‌شده، تنظیمات، قرارداد فاز و تصمیم ثبت‌شده‌ی مدل که یکسان باشند، runtime همان انتقال قانونی را انتخاب می‌کند، همان نوع اثرات را مجاز می‌داند، همان قواعد ترتیب را اعمال می‌کند و همان پس‌شرط‌ها را ارزیابی می‌کند.

چند رویه از این تعریف پشتیبانی می‌کنند.

ترتیب پایدار

صف‌ها باید ترتیب صریح و قواعد مشخص شکستن تساوی داشته باشند:

priority descending
created_at ascending
identifier ascending

هرگز اجازه ندهید ترتیب پاسخ API ناخواسته به قانون تصمیم‌گیری تبدیل شود.

snapshotهای صریح

واگذاری‌ها و mutationها باید به شناسه‌هایی مانند این‌ها متصل باشند:

  • commit SHA؛
  • review head OID؛
  • زمان به‌روزرسانی issue؛
  • شناسه‌ی commentها؛
  • نسخه‌ی تنظیمات؛
  • نسخه‌ی منبع اسکیل؛
  • digest خروجی ساخت‌یافته‌ی مدل.

تصمیم‌های ساخت‌یافته‌ی مدل

وقتی مدل باید دسته‌بندی یا انتخاب کند، یک schema الزامی کنید:

{
  "classification": "actionable-inline-feedback",
  "confidence": "high",
  "evidence_ids": [81234, 81241],
  "unresolved_questions": []
}

runtime ابتدا schema را اعتبارسنجی می‌کند و سپس قواعد قطعی routing را روی نتیجه اعمال می‌کند.

نبود عدم قطعیت پنهان در پوسته

زمان، randomness، متغیرهای محیطی، خواندن شبکه و وضعیت filesystem باید ورودی‌های صریح یا اثرات جداشده باشند.

مدل workflow در Temporal از این نظر آموزنده است: replay قطعی ممکن است، چون تعامل‌های خارجی به‌عنوان event ثبت و به‌جای تکرار پنهانی دوباره استفاده می‌شوند. یک اسکیل برای یادگیری از این اصل لازم نیست حتماً Temporal را به کار گیرد.

معنای run-to-completion برای انتقال

درون یک انتقال بحرانی، کار نامرتبط را interleave نکنید.

مشخصات W3C SCXML مفهوم run-to-completion را توضیح می‌دهد: یک event خارجی فقط پس از تکمیل microstepهای انتقال فعلی پردازش می‌شود. همین ایده برای اسکیل‌ها نیز مفید است. برای مثال، یک claim transaction نباید در میانه‌ی کار متوقف شود تا صف دیگری را scan یا وظیفه‌ی دیگری را شروع کند.

واگذاری‌ها نقاط توقف typeشده‌اند

واگذاری نباید یک جمله‌ی غیررسمی مانند «با اسکیل بازبینی ادامه بده» باشد.

واگذاری باید نقطه‌ی توقف typeشده‌ای با قرارداد ازسرگیری باشد.

حداقل payload واگذاری می‌تواند شامل این موارد باشد:

{
  "schema_version": 1,
  "workflow_id": "task-2459",
  "phase": "PRE_PUBLICATION_REVIEW",
  "next_phase": "PRE_PUBLICATION_RESUME",
  "source_version": "skill@def456",
  "item_id": 2459,
  "branch": "agent/issue-2459",
  "head_sha": "abc123",
  "execution_result_digest": "sha256:...",
  "required_skill": "code-review",
  "required_references": [
    "references/review-policy.md"
  ]
}

اسکیل دریافت‌کننده فقط باید کانتکستی را بگیرد که واقعاً به آن نیاز دارد. مدل handoff در OpenAI Agents SDK نیز از ورودی typeشده‌ی handoff و input filterها پشتیبانی می‌کند و metadata تولیدشده توسط مدل را از کانتکست برنامه جدا نگه می‌دارد.

هنگام ازسرگیری، runtime باید این موارد را بررسی کند:

  • آیتم همچنان فعال است؛
  • branch تغییر نکرده است؛
  • head SHA همچنان تطابق دارد؛
  • digest نتیجه تغییر نکرده است؛
  • snapshot خارجی بازبینی جلو نرفته است؛
  • نسخه‌ی منبع اسکیل سازگار است؛
  • handoff مورد انتظار واقعاً کامل شده است.

اگر هر binding قدیمی شده باشد، runtime نباید حدس بزند. باید به فاز مشاهده‌ی مناسب بازگردد.

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

capabilityها، اثرات جانبی و مرزهای transaction

یک اسکیل بزرگ زمانی امن‌تر می‌شود که هر فاز allowlist صریحی از capabilityها داشته باشد.

capabilityهای ممکن عبارت‌اند از:

repository:read
repository:branch-write
repository:push
issue:read
issue:write
pull-request:read
pull-request:write
review:write
worktree:create
lock:acquire
host:handoff

یک scan خلاصه ممکن است فقط capability خواندن داشته باشد. dry run نباید effect executor را بارگذاری کند. فاز انتشار نباید بتواند یک آیتم صف را claim کند. adapter دامنه نیز نباید فقط به این دلیل که شواهد را اعتبارسنجی می‌کند، دسترسی نوشتن در GitHub بگیرد.

قفل‌ها باید invariantها را پوشش دهند، نه کل وظیفه را

قفل زمانی مناسب است که فرایندهای هم‌زمان بتوانند یک invariant پایدار را نقض کنند.

برای مثال invariant «فقط یک وظیفه می‌تواند فعال باشد» ممکن است به یک قفل در سطح repository پیرامون این مراحل نیاز داشته باشد:

  1. بازخوانی مرجع؛
  2. اعتبارسنجی eligibility؛
  3. mutation وضعیت؛
  4. ثبت claim؛
  5. آماده‌سازی branch یا handoff؛
  6. compensation هنگام شکست.

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

idempotency باید طراحی شود، نه اینکه به آن امید داشته باشیم

هر اثر قابل مشاهده‌ی خارجی باید به این پرسش‌ها پاسخ دهد:

  • کلید idempotency آن چیست؟
  • runtime چگونه تشخیص می‌دهد قبلاً انجام شده است؟
  • آیا retry امن است؟
  • چه readbackای موفقیت را اثبات می‌کند؟
  • اگر فرایند پس از effect اما پیش از ثبت تکمیل متوقف شود چه اتفاقی می‌افتد؟

برای مثال:

  • نام branch می‌تواند کلید idempotency ساخت branch باشد؛
  • یک marker پنهان می‌تواند بخش تولیدشده‌ی pull request را مشخص کند؛
  • شناسه‌ی review commentهای رسیدگی‌شده می‌تواند مانع پاسخ تکراری شود؛
  • شناسه‌ی event جریان‌کار می‌تواند ingestion تکراری را حذف کند؛
  • digest یک checkpoint می‌تواند جلوی finalization قدیمی را بگیرد.

compensation همان rollback نیست

اثرات جانبی توزیع‌شده اغلب به‌طور کامل rollbackشدنی نیستند.

اگر claim، label یک issue را تغییر دهد و سپس آماده‌سازی branch شکست بخورد، runtime می‌تواند با برگرداندن label صف و ثبت دلیل، وضعیت را compensate کند. این با وانمودکردن به اینکه mutation اولیه هرگز رخ نداده است یکسان نیست.

compensation باید صریح، قابل مشاهده و تست‌شده باشد.

adapterهای دامنه هسته را کوچک نگه می‌دارند

یک الگوی شکست رایج این است که runner عمومی به‌تدریج منطق تخصصی دامنه را در خود جمع کند.

فرض کنید یک نوع وظیفه به این موارد نیاز دارد:

  • آستانه‌های KPI؛
  • جدول‌های شواهد؛
  • اعتبارسنجی ویژه‌ی منبع؛
  • projection اختصاصی pull request؛
  • دسته‌بندی‌های تخصصی blocker.

این قوانین نباید توسط همه‌ی وظیفه‌ها import شوند و نباید در سراسر کد عمومی finalization پراکنده باشند.

از یک adapter کلیددار استفاده کنید:

interface KindAdapter {
  validateResult(input: ExecutionResult): ValidationResult;
  renderArtifact(input: ExecutionResult): ArtifactSection;
  classifyBlocker(input: ExecutionResult): BlockerClassification;
  completionGates(input: ExecutionResult): GateResult[];
}

runtime تنها پس از آنکه نوع وظیفه را شناخت و به فازی رسید که به رفتار تخصصی نیاز دارد، adapter را انتخاب می‌کند.

این کار دو شکل از lazy loading ایجاد می‌کند:

  • وظیفه‌های نامرتبط کد adapter را بارگذاری نمی‌کنند؛
  • فازهای نامرتبط referenceهای adapter را بارگذاری نمی‌کنند.

همچنین یک مرز بازبینی تمیز می‌سازد. تیم هسته می‌تواند orchestration را نگهداری کند و نگهدارندگان دامنه مالک قوانین شواهد و تکمیل باشند.

مشاهده‌پذیری باید از مدل فاز پیروی کند

trace باید اجرا را با همان واژگانی توصیف کند که معماری از آن‌ها استفاده می‌کند.

OpenAI Agents SDK generationها، tool callها، handoffها، guardrailها و eventهای سفارشی را trace می‌کند. یک اسکیل فازمحور می‌تواند پیرامون این primitiveها spanهای تخصصی دامنه اضافه کند.

یک span مفید برای فاز، این موارد را ثبت می‌کند:

{
  "workflow_id": "task-2459",
  "phase": "CLAIM_TRANSACTION",
  "source_version": "skill@def456",
  "started_at": "...",
  "ended_at": "...",
  "references_loaded": [
    "global-invariants.md",
    "claim-transaction.md"
  ],
  "estimated_reference_tokens": 1830,
  "modules_loaded": [
    "claim-transaction.ts",
    "issue-write-gateway.ts"
  ],
  "observations": {
    "candidate_count": 12,
    "hydrated_count": 1
  },
  "effects_requested": 4,
  "effects_completed": 4,
  "postconditions": {
    "passed": 3,
    "failed": 0
  },
  "next_phase": "EXECUTION_HANDOFF"
}

مشاهده‌پذیری باید به چیزی بیش از «آیا وظیفه تمام شد؟» پاسخ دهد.

باید بتواند پاسخ دهد:

  • چرا این مسیر انتخاب شد؟
  • کدام snapshot مبنای تصمیم بود؟
  • چه کانتکستی وارد مدل شد؟
  • کدام importهای ماژول انجام شدند؟
  • کدام capabilityها استفاده شدند؟
  • کدام callهای خارجی انجام شدند؟
  • کدام انتقال بیشترین زمان را گرفت؟
  • مصرف token در کجا رشد کرد؟
  • کدام پس‌شرط شکست خورد؟
  • آیا retry امن بود؟
  • آیا جریان‌کار از checkpoint مورد انتظار ادامه پیدا کرد؟

به این شکل یک تیم می‌تواند مشکل استدلال را از مشکل routing، وضعیت قدیمی، طراحی ابزار یا بارگذاری کانتکست تشخیص دهد.

تست یک اسکیل فازمحور

تست end-to-end لازم است، اما کافی نیست. یک اسکیل قابل اعتماد به تست در چند سطح نیاز دارد.

unit test فاز

تابع‌های pure را تست کنید:

  • ترتیب صف؛
  • دسته‌بندی؛
  • انتخاب انتقال؛
  • اعتبارسنجی schema؛
  • render کردن artifact؛
  • ساخت digest برای checkpoint؛
  • تشخیص snapshot قدیمی.

این تست‌ها نباید به دسترسی شبکه نیاز داشته باشند.

تست انتقال

با یک وضعیت و event اولیه، این موارد را assert کنید:

  • فاز بعدی قانونی؛
  • capabilityهای لازم؛
  • plan اثرات؛
  • پس‌شرط‌ها؛
  • action نهایی.

یک جدول انتقال، مسیرهای گمشده را آشکار می‌کند.

تست‌های golden workflow

جریان‌کارهای نمونه را ثبت کنید:

  • idle؛
  • ادامه‌ی وظیفه‌ی فعال؛
  • claim موفق؛
  • conflict هنگام claim؛
  • بازبینی بدون feedback جدید؛
  • feedback قابل اقدام؛
  • اجرای مسدودشده؛
  • انتشار آماده برای بازبینی؛
  • ازسرگیری handoff قدیمی؛
  • یادگیری بدون تغییر؛
  • یادگیری با چند proposal.

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

تست‌های fault injection

باید در هر مرز معنادار اثر جانبی، شکست تزریق شود:

  • پیش از قفل؛
  • پس از قفل؛
  • پیش از mutation؛
  • پس از mutation؛
  • هنگام ساخت comment؛
  • هنگام ساخت branch؛
  • هنگام انتشار؛
  • پس از انتشار اما پیش از readback؛
  • هنگام compensation؛
  • هنگام آزادکردن قفل.

تست باید invariantها را بررسی کند، نه فقط پیام خطا را.

تست‌های lazy loading

بررسی کنید برای هر command چه چیزهایی بارگذاری شده‌اند:

help:
  no network gateway
  no write module
  no domain adapter

list:
  summary read gateway only

dry-run:
  no effect executor

generic finalization:
  no specialist adapter

review with no feedback:
  no inline-feedback write helper

این تست‌ها نیت معماری را به یک بودجه‌ی قابل اعمال تبدیل می‌کنند.

تست‌های replay و resume

یک checkpoint ثبت کنید، یکی از bindingها را تغییر دهید و assert کنید که ازسرگیری به‌شکل fail-closed متوقف می‌شود:

  • head SHA تغییر کرده است؛
  • وضعیت issue تغییر کرده است؛
  • cursor بازبینی جلو رفته است؛
  • نسخه‌ی تنظیمات تغییر کرده است؛
  • digest نتیجه تغییر کرده است؛
  • منبع اسکیل ناسازگار شده است.

سپس مسیر معتبر را با bindingهای یکسان تست کنید.

property testها

بعضی invariantها برای caseهای تولیدشده مناسب‌اند:

  • sort پایدار همیشه همان ترتیب را تولید می‌کند؛
  • هیچ انتقال قانونی دو وظیفه‌ی فعال ایجاد نمی‌کند؛
  • فاز فقط‌خواندنی هرگز capability نوشتن درخواست نمی‌کند؛
  • compensation هرگز قفل را گرفته باقی نمی‌گذارد؛
  • هر فاز غیرنهایی دست‌کم یک فاز بعدی قانونی دارد؛
  • هر فاز mutation دارای پس‌شرط‌های readback است.

الگوهای شکست رایج

چند طراحی ممکن است ماژولار به نظر برسند، اما از نظر عملیاتی همچنان یکپارچه باقی بمانند.

indexای که همه‌چیز را لازم دارد

SKILL.md به فایل‌های جدا لینک می‌دهد، اما به ایجنت می‌گوید پیش از شروع همه‌ی آن‌ها را بخواند.

این جداسازی فایل است، نه اجرای تدریجی.

reference چندمنظوره

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

هر فازی که به یکی از قوانین نیاز دارد، هر پنج موضوع را بارگذاری می‌کند.

referenceهای پویا، کد eager

مدل referenceها را برحسب نیاز می‌خواند، اما entrypoint اسکریپت تمام coordinatorها و adapterها را import می‌کند.

کانتکست بهتر می‌شود، اما coupling runtime نه.

کد lazy، داده eager

ماژول‌ها برحسب نیاز بارگذاری می‌شوند، اما scan نخست تمام bodyهای issue و commentها را واکشی می‌کند.

startup بهتر می‌شود، اما هزینه‌ی token و latency نه.

مدل به‌عنوان هماهنگ‌کننده‌ی transaction

prompt می‌گوید: «label را به‌روزرسانی کن، branch بساز، comment بنویس و اگر چیزی شکست خورد label را برگردان.»

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

mutation پنهان

تابعی با نام inspect هم‌زمان branch را refresh می‌کند یا cursor می‌نویسد.

فازهای خواندن نباید اثرات جانبی غافلگیرکننده داشته باشند.

labelهای فاز در سیستم دامنه

هر گام داخلی پیاده‌سازی به یک issue label تبدیل می‌شود.

مدل وضعیت خارجی شکننده می‌شود و تفسیر آن برای انسان دشوار خواهد بود.

resume از طریق بازسازی

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

این کار می‌تواند کار بازبینی‌نشده را finalize یا feedback قدیمی را acknowledge کند.

انفجار فازها

هر tool call به یک فاز تبدیل می‌شود و handoff و boilerplate بیش از اندازه ایجاد می‌کند.

فازها باید با مرزهای مسئولیت، capability و شکست هم‌راستا باشند؛ نه با تک‌تک خطوط کد.

نمایش ظاهری determinism

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

خروجی ساخت‌یافته کافی نیست. runtime باید تعریف کند آن خروجی چگونه اعتبارسنجی و به effect تبدیل می‌شود.

یک مسیر عملی برای مهاجرت

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

روش امن‌تر، strangler refactor است.

۱. یک baseline رفتاری بسازید

این موارد را ثبت کنید:

  • خروجی commandها؛
  • callهای خارجی؛
  • اثرات جانبی؛
  • referenceهای بارگذاری‌شده؛
  • ماژول‌های بارگذاری‌شده؛
  • رفتار هنگام شکست؛
  • رفتار compensation.

پیش از تغییر معماری، golden workflowها را بسازید.

۲. واژگان فاز را معرفی کنید

lane، phase، next_phase و یک envelope مشترک نتیجه را پیرامون پیاده‌سازی موجود اضافه کنید.

monolith می‌تواند موقتاً به‌صورت یک فاز legacy باقی بماند.

۳. referenceها را براساس مسئولیت جدا کنید

قرارداد اصلی را به یک index کوتاه تبدیل کنید. قوانین بازبینی، انتشار، یادگیری، نام‌گذاری و دامنه را از هم جدا کنید.

برای referenceهای لازم هر فاز تست اضافه کنید.

۴. یک kernel حداقلی بسازید

این موارد را استخراج کنید:

  • command parser؛
  • registry فاز؛
  • schema نتیجه؛
  • gate قابلیت؛
  • loader پویای handler.

kernel باید تقریباً هیچ چیزی درباره‌ی دامنه نداند.

۵. routing سمت خواندن را استخراج کنید

scanهای summary را از واکشی جزئیات جدا کنید. اولویت مسیر و ترتیب پایدار را deterministic کنید.

این کار معمولاً با ریسک mutation پایین، بلافاصله مصرف کانتکست را کاهش می‌دهد.

۶. پرریسک‌ترین transaction را استخراج کنید

claim، lock، اعتبارسنجی مجدد مرجع، mutation، compensation و release را در یک فاز تست‌شده قرار دهید.

برای ساده‌ترشدن استخراج، invariantها را تضعیف نکنید.

۷. finalization و adapterها را استخراج کنید

تکمیل عمومی را از شواهد و render تخصصی دامنه جدا کنید.

بازبینی متوقف‌شده و اعتبارسنجی resume را اضافه کنید.

۸. مسیرهای ثانویه را استخراج کنید

proposalهای یادگیری، maintenance یا synchronization بازبینی می‌توانند پس از پایدارشدن مسیر اصلی وظیفه منتقل شوند.

۹. بودجه‌ی capability و lazy loading را اعمال کنید

وقتی reference، ماژول، داده یا capability نوشتن نامرتبط ظاهر شد، تست باید شکست بخورد.

۱۰. مسیر legacy را حذف کنید

monolith را فقط پس از آن حذف کنید که اجراهای فازبندی‌شده و legacy روی golden workflowها به نتیجه‌ی یکسان برسند.

یک manifest فاز فشرده

registry فاز می‌تواند معماری را قابل اجرا کند:

type Capability =
  | "issue:read"
  | "issue:write"
  | "pull-request:read"
  | "pull-request:write"
  | "git:read"
  | "git:branch-write"
  | "git:push"
  | "lock:acquire"
  | "host:handoff";

type PhaseDefinition = {
  handler: string;
  references: string[];
  capabilities: Capability[];
  terminal?: boolean;
  legalNext: string[];
  contextBudget: {
    maxReferenceTokens: number;
    maxHydratedItems: number;
  };
};

export const PHASES: Record<string, PhaseDefinition> = {
  QUEUE_SCAN: {
    handler: "./phases/queue-scan.js",
    references: [
      "references/global-invariants.md",
      "references/queue-selection.md"
    ],
    capabilities: ["issue:read"],
    legalNext: ["ITEM_HYDRATE", "IDLE", "BLOCKED"],
    contextBudget: {
      maxReferenceTokens: 2500,
      maxHydratedItems: 0
    }
  },

  CLAIM_TRANSACTION: {
    handler: "./phases/claim-transaction.js",
    references: [
      "references/global-invariants.md",
      "references/claim-transaction.md"
    ],
    capabilities: [
      "issue:read",
      "issue:write",
      "git:branch-write",
      "lock:acquire"
    ],
    legalNext: ["EXECUTION_HANDOFF", "QUEUE_SCAN", "BLOCKED"],
    contextBudget: {
      maxReferenceTokens: 3500,
      maxHydratedItems: 1
    }
  }
};

registry فقط configuration نیست؛ نقشه‌ای قابل بازبینی از مرزهای اعتماد سیستم است.

یک envelope مشترک برای نتیجه

هر فاز باید یک ساختار بیرونی مشترک برگرداند:

{
  "schema_version": 1,
  "workflow_id": "task-2459",
  "lane": "task",
  "phase": "CLAIM_TRANSACTION",
  "status": "succeeded",
  "action": "continue",
  "snapshot_id": "sha256:...",
  "decision": {
    "type": "claimed",
    "reason": "highest eligible candidate"
  },
  "effects": {
    "requested": 4,
    "completed": 4,
    "compensated": 0
  },
  "postconditions": {
    "passed": true
  },
  "next_phase": "EXECUTION_HANDOFF",
  "resume_context": null,
  "diagnostics": []
}

actionهای ممکن باید کم و پایدار باشند:

continue
idle
blocked
current-session-required
spawn-parallel-workers
completed
stale-resume

واژگان کوچک action، تست orchestrator بیرونی را ساده‌تر می‌کند و اجازه نمی‌دهد هر فاز معنای توقف مخصوص خودش را بسازد.

چه چیزهایی باید هوشمند باقی بمانند؟

اگر یک پوسته‌ی قطعی تلاش کند هر نوع قضاوتی را کدگذاری کند، می‌تواند زیان‌آور شود.

بعضی کارها به‌طور تقلیل‌ناپذیری تفسیری‌اند:

  • تصمیم درباره‌ی اینکه شواهد قانع‌کننده‌اند یا نه؛
  • انتخاب یک تغییر کد از میان چند طراحی معتبر؛
  • ترکیب review commentهای متعارض؛
  • شناسایی یک الگوی شکست تازه؛
  • تصمیم درباره‌ی اینکه یک آموخته آن‌قدر عمومی هست که حفظ شود؛
  • توضیح صادقانه‌ی عدم قطعیت.

معماری نباید فقط برای اینکه سیستم را deterministic بنامد، این تصمیم‌ها را به قوانین شکننده تبدیل کند.

در عوض:

  1. کوچک‌ترین کانتکست مرتبط را به مدل بدهید.
  2. مرز تصمیم را به‌وضوح بیان کنید.
  3. شواهد و عدم قطعیت ساخت‌یافته بخواهید.
  4. schema خروجی را اعتبارسنجی کنید.
  5. اثرات برگشت‌ناپذیر را بیرون از model call نگه دارید.
  6. جایی که پیامدها توجیه می‌کنند، بازبینی انسانی را حفظ کنید.
  7. آن‌قدر کانتکست ثبت کنید که بعداً بتوان تصمیم را فهمید.

هدف این نیست که مدل را به ماشین حالت تبدیل کنیم. هدف این است که ماشین حالت، فضای امن و قابل فهمی برای قضاوت مدل بسازد.

قواعد طراحی

قواعد زیر این معماری را خلاصه می‌کنند.

  1. اسکیل‌های ساده را ساده نگه دارید. معماری فازمحور برای پیچیدگی عملیاتی است، نه برای هر فایل دستورالعمل.
  2. از SKILL.md به‌عنوان index و قرارداد فعال‌سازی استفاده کنید. آن را به کل راهنمای runtime تبدیل نکنید.
  3. به هر فاز فقط یک مسئولیت بدهید.
  4. وضعیت پایدار دامنه را از فاز موقت اجرا جدا کنید.
  5. پیش از بارگذاری کانتکست مخصوص فاز، routing را انجام دهید.
  6. referenceها، ماژول‌ها و داده‌های دارای جزئیات را مستقل از یکدیگر lazy-load کنید.
  7. با summaryها به‌شکل گسترده scan کنید و پس از انتخاب، جزئیات را محدود واکشی کنید.
  8. مشاهده، تصمیم، برنامه‌ریزی effect، اجرا و readback را جدا کنید.
  9. تصمیم‌ها و handoffها را به snapshotهای صریح متصل کنید.
  10. اثرات جانبی را پشت capability gateها نگه دارید.
  11. قفل‌ها را فقط در بخش‌های بحرانی حفظ invariant نگه دارید.
  12. idempotency و compensation را پیش از وقوع شکست طراحی کنید.
  13. handoffها را نقاط توقف typeشده با قرارداد resume در نظر بگیرید.
  14. resume قدیمی را به‌جای بازسازی قصد، رد کنید.
  15. اعتبارسنجی و rendering تخصصی دامنه را پشت adapterها قرار دهید.
  16. فازها را با همان واژگانی trace کنید که در طراحی استفاده شده‌اند.
  17. بودجه‌ی کانتکست، ماژول، واکشی جزئیات و استفاده از ابزار را اندازه‌گیری کنید.
  18. lazy loading را مستقیماً تست کنید.
  19. برای تست مرزهای transaction از fault injection استفاده کنید.
  20. determinism را تکرارپذیری routing و effectها روی ورودی‌های ثبت‌شده تعریف کنید، نه یکسان‌بودن نثر مدل.
  21. هسته‌ی احتمالاتی را محدود، مبتنی بر شواهد و قابل بازبینی نگه دارید.
  22. فقط زمانی پیچیدگی اضافه کنید که قابلیت اعتماد یا فهم سیستم را بهبود می‌دهد.

از مهندسی prompt تا مهندسی runtime

نسل نخست اسکیل‌ها به‌طور طبیعی promptمحور است. می‌پرسیم آیا دستورالعمل‌ها روشن‌اند، مثال‌ها مناسب‌اند و مدل می‌داند چه زمانی از اسکیل استفاده کند.

این پرسش‌ها همچنان مهم‌اند. اما اسکیل‌های عملیاتی بزرگ، رشته‌ی دیگری را وارد کار می‌کنند: مهندسی runtime.

مهندسی runtime می‌پرسد:

  • در چه فازی هستیم؟
  • چه چیزی مرجع است؟
  • چه چیزی اجازه دارد بارگذاری شود؟
  • چه چیزی اجازه دارد تغییر کند؟
  • کدام snapshot مبنای این تصمیم است؟
  • اگر فرایند در اینجا متوقف شود چه اتفاقی می‌افتد؟
  • چگونه کار را از سر می‌گیریم؟
  • چه چیزی موفقیت را اثبات می‌کند؟
  • چه چیزی باید compensate شود؟
  • چه چیزی قابل replay است؟
  • چه چیزی را می‌توان بدون مدل تست کرد؟
  • چه چیزی باید قضاوت مدل باقی بماند؟

اسکیلی که بتواند به این پرسش‌ها پاسخ دهد، debug کردنش آسان‌تر است، با امنیت بیشتری تکامل پیدا می‌کند و احتمال کمتری دارد پنجره‌ی کانتکست خود را صرف قوانین و داده‌هایی کند که هنوز اهمیتی ندارند.

بنابراین مفیدترین مدل ذهنی این نیست: «یک prompt بسیار طولانی همراه ابزارها».

بلکه یک سیستم جریان‌کار کوچک است که یک مدل زبانی درون آن قرار دارد.

جریان‌کار فازهای صریح، انتقال‌های قانونی، capabilityهای محدود، وضعیت تأییدشده، checkpoint و مشاهده‌پذیری را فراهم می‌کند. مدل نیز در جایی که قواعد کافی نیستند، تفسیر، خلاقیت، ترکیب و قضاوت را ارائه می‌دهد.

همین تقسیم کار است که اجازه می‌دهد یک اسکیل بزرگ هم هوشمند و هم قابل اتکا باقی بماند.

منابع

  • Agent Skills Specification — ساختار پوشه، referenceهای متمرکز، scriptها و افشای تدریجی.
  • Building Effective Agents — تمایز میان workflowهای ازپیش‌تعریف‌شده و agentهای با هدایت پویا، همراه با الگوهای routing، chaining، evaluator و orchestrator.
  • State Chart XML (SCXML): State Machine Notation for Control Abstraction — مفاهیم رسمی ماشین حالت، انتقال‌ها، determinism و معنای run-to-completion.
  • Temporal Workflows — تاریخچه‌ی event، replay قطعی، اجرای پایدار و جداسازی تصمیم‌های workflow از activityهای خارجی.
  • OpenAI Agents SDK: Handoffs — metadata typeشده‌ی handoff و کنترل کانتکستی که به agent دریافت‌کننده منتقل می‌شود.
  • OpenAI Agents SDK: Guardrails — مرزهای اعتبارسنجی پیرامون اجرای agent و ابزار.
  • OpenAI Agents SDK: Tracing — traceها و spanهای generation، ابزار، handoff، guardrail و eventهای سفارشی workflow.

تاریخچه‌ی بازنگری

  • ۱۸ ژوئیه‌ی ۲۰۲۶: نسخه‌ی فارسی برای نخستین بار منتشر شد.