یک مشخصات متنباز برای هماهنگسازی Codex: Symphony
نوشته Alex Kotliarskyi، Victor Zhu و Zach Brock
شش ماه پیش، زمانی که روی یک ابزار بهرهوری داخلی کار میکردیم، تیم ما تصمیمی گرفت که در آن زمان بحثبرانگیز بود: مخزن پروژهمان را بدون هیچ کدِ نوشتهشده توسط انسان بسازیم. هر خط در مخزن پروژه باید توسط Codex تولید میشد.
برای اینکه این کار عملی شود، جریان کاری مهندسی خود را از پایه بازطراحی کردیم. یک مخزن سازگار با عاملها ساختیم، بهطور گسترده روی تستهای خودکار و سازوکارهای کنترلی سرمایهگذاری کردیم و Codex را بهعنوان یک همتیمی تمامعیار در نظر گرفتیم. این مسیر را در پست وبلاگ قبلیمان دربارهٔ مهندسی چارچوب مستند کردهایم.
و جواب داد، اما بعد به گلوگاه بعدی رسیدیم: جابهجایی مداوم بین زمینهها.
برای حل این مشکل جدید، سیستمی به نام Symphony ساختیم. Symphony(در یک پنجره جدید باز میشود) یک هماهنگکننده عامل است که یک تابلوی مدیریت پروژه مثل Linear را به صفحه کنترل عاملهای کدنویسی تبدیل میکند. هر وظیفه باز یک عامل میگیرد، عاملها بهطور پیوسته اجرا میشوند و انسانها نتایج را بازبینی میکنند.
این مطلب توضیح میدهد که چگونه Symphony را ایجاد کردیم—که در برخی تیمها به افزایش ۵۰۰٪ در تعداد درخواستهای ادغام کد منجر شد—و چگونه میتوانید از آن استفاده کنید تا سامانهٔ پیگیری مسائل خود را به یک هماهنگکنندهٔ هموارهفعال برای عاملها تبدیل کنید.
سقف عاملهای کدنویسی تعاملی
عاملهای کدنویسی، حتی با وجود اینکه استفاده از آنها آسانتر شده، چه از طریق اپهای وب و چه CLI، هنوز ابزارهایی تعاملی هستند.
با افزایش مقیاس کار عاملمحور در OpenAI، با نوع تازهای از بار مواجه شدیم. هر مهندس چند نشست Codex باز میکرد، وظایف را تخصیص میداد، خروجی را بازبینی میکرد، عامل را هدایت میکرد و این چرخه را تکرار میکرد. در عمل، بیشتر افراد میتوانستند همزمان سه تا پنج نشست را با راحتی مدیریت کنند، پیش از آنکه جابهجایی بین زمینهها دردناک شود. فراتر از آن، بهرهوری افت میکرد. یادمان میرفت کدام نشست مشغول چه کاری است، بین ترمینالها جابهجا میشدیم تا عاملها را دوباره به مسیر برگردانیم، و وظایف طولانیمدتی را که در میانه راه متوقف شده بودند دیباگ میکردیم.
عاملها سریع بودند، اما با یک گلوگاه سیستمی روبهرو شدیم: توجه انسان. عملاً تیمی از مهندسان تازهکار اما بسیار توانمند ساخته بودیم و سپس مهندسان انسانیمان را مسئول ریزمدیریت آنها کرده بودیم. این رویکرد قابل مقیاسپذیری نبود.
تغییر زاویه دید
متوجه شدیم که روی چیز اشتباهی بهینهسازی میکنیم. تمرکز سیستممان را بر جلسات کدنویسی و درخواستهای ادغام کد قرار داده بودیم، در حالی که اینها در واقع فقط وسیلهای برای رسیدن به هدف هستند. جریانهای کاری نرمافزار عمدتاً بر اساس خروجیها سازماندهی میشوند: مسئلهها، وظیفهها، تیکتها و نقاط عطف.
پس از خودمان پرسیدیم چه میشود اگر بهجای نظارت مستقیم بر عاملها، اجازه دهیم خودشان کار را از سیستم پیگیری وظایف بردارند.
این ایده به Symphony تبدیل شد؛ یک مشخصاتِ مکتوب که بهعنوان یک ناظر عمل میکند و کارهای عاملمحور را هماهنگ میسازد.
تبدیل سیستم پیگیری مسائل به یک هماهنگکنندهٔ عاملها
Symphony با یک مفهوم ساده شروع شد: هر وظیفهٔ باز باید توسط یک عامل برداشته و تکمیل شود. بهجای مدیریت سشنهای Codex در چندین تب، سیستم پیگیری مسائل را به صفحهٔ کنترل تبدیل کردیم.
در این تنظیم، هر مسئلهٔ باز در Linear به یک فضای کاری اختصاصی برای یک عامل نگاشت میشود. Symphony بهطور مداوم بردِ وظایف را پایش میکند و مطمئن میشود هر وظیفهٔ فعال، یک عامل دارد که تا زمان اتمام در چرخهٔ کار در حال اجراست. اگر عاملی از کار بیفتد یا متوقف شود، Symphony آن را دوباره راهاندازی میکند. اگر کار جدیدی ظاهر شود، Symphony آن را دریافت کرده و شروع به سازماندهی کار میکند.
ما گردشکار خود را بر اساس وضعیت تیکتها ساختیم و از مدیر وظیفه Linear بهعنوان یک ماشین حالت استفاده کردیم.
در عمل، Symphony کار را از سشنها و از درخواستهای ادغام کد جدا میکند. برخی مسئلهها منجر به چندین درخواست ادغام در مخازن مختلف میشوند؛ برخی دیگر صرفاً شامل بررسی یا تحلیل هستند و اصلاً به پایگاه کد دست نمیزنند.
وقتی کار به این شکل انتزاعی شود، تیکتها میتوانند نماینده واحدهای کاری بسیار بزرگتری باشند.
ما بهطور منظم از Symphony برای هماهنگسازی ویژگیهای پیچیده و مهاجرتهای زیرساختی استفاده میکنیم. برای مثال، ممکن است تیکتی ثبت کنیم که از عامل بخواهد پایگاه کد، Slack یا Notion را تحلیل کند و یک برنامهٔ پیادهسازی ارائه دهد. وقتی از برنامه راضی باشیم، عامل یک درخت از وظایف ایجاد میکند و کار را به مراحل مختلف تقسیم کرده و وابستگیهای بین آنها را مشخص میکند.
عاملها فقط روی وظایفی کار را شروع میکنند که مسدود نشده باشند، بنابراین اجرای کار برای این DAG (یک دنباله از مراحل اجرا) بهصورت طبیعی و بهینه بهطور موازی پیش میرود. در مثال زیر، ارتقای React را وابسته به مهاجرت به Vite علامتگذاری کرده بودیم. همانطور که انتظار میرفت، عاملها تنها پس از تکمیل مهاجرت به Vite شروع به ارتقای React کردند.
عاملها همچنین میتوانند خودشان کار ایجاد کنند. در طول پیادهسازی یا بازبینی، اغلب به بهبودهایی برمیخورند که خارج از محدودهٔ وظیفهٔ فعلی است: یک مشکل عملکردی، فرصتی برای بازساختاردهی کد، یا معماری بهتر. در چنین مواردی، بهسادگی یک مسئلهٔ جدید ثبت میکنند تا بعداً آن را ارزیابی و زمانبندی کنیم—بسیاری از این کارهای پیگیری نیز توسط عاملها انجام میشوند. در حالی که ما بر این فرایند نظارت داریم، عاملها منظم میمانند و جریان کار را به جلو پیش میبرند.
این شیوه کار، هزینه شناختی شروع کردن کارهای مبهم را بهطور چشمگیری کاهش میدهد. اگر عامل چیزی را اشتباه انجام دهد، باز هم اطلاعات مفیدی است، و هزینه آن برای ما تقریباً صفر است. ما میتوانیم با هزینهای بسیار کم برای عامل تیکت ثبت کنیم تا نمونهسازی و اکتشاف انجام دهد، و هر اکتشافی را که نپسندیدیم دور بیندازیم.
از آنجا که هماهنگکننده روی devboxها (محیطهای توسعه) اجرا میشود و هیچوقت متوقف نمیشود، میتوانیم از هر جایی وظیفه اضافه کنیم و مطمئن باشیم یک عامل آن را برمیدارد. برای مثال، یکی از مهندسان تیم ما از داخل اپ Linear روی گوشیاش، در حالی که در یک کلبهٔ دنج با اینترنت ضعیف بود، سه تغییر مهم ایجاد کرد.
افزایش اکتشاف با این شیوه کار
وقتی تأثیر کار با Symphony را بررسی کردیم، واضحترین تغییر در خروجی بود. در برخی تیمهای OpenAI، تعداد درخواستهای ادغام کد نهاییشده در سه هفتهٔ اول تا ۶ برابر افزایش یافت. خارج از OpenAI نیز، بنیانگذار Linear، Karri Saarinen، همزمان با انتشار Symphony به افزایش ناگهانی در ایجاد فضایهای کاری(در یک پنجره جدید باز میشود) اشاره کرد. با این حال، تغییر عمیقتر در نحوهٔ نگاه تیمها به کار است.
وقتی مهندسان ما دیگر زمانی را صرف نظارت بر نشستهای Codex نمیکنند، اقتصاد تغییرات کد کاملاً عوض میشود. هزینه ادراکشده هر تغییر کاهش مییابد، چون دیگر تلاش انسانی را صرف پیشبرد خودِ پیادهسازی نمیکنیم.
این رفتار ما را تغییر داد. حالا راهاندازی وظایف حدسی در Symphony بسیار ساده شده است. یک ایده را امتحان کنید، یک بازآرایی را بررسی کنید، یک فرضیه را بیازمایید، و فقط نتایجی را نگه دارید که امیدوارکننده به نظر میرسند.
همچنین دامنهٔ افرادی را که میتوانند کار را آغاز کنند گسترش میدهد. مدیر محصول و طراح ما اکنون میتوانند درخواستهای ویژگی را مستقیماً در Symphony ثبت کنند. آنها نیازی ندارند مخزن را کلون کنند یا یک سشن Codex را مدیریت کنند. فقط ویژگی را توضیح میدهند و در پاسخ یک بستهٔ بازبینی دریافت میکنند که شامل یک ویدئوی نمایشی از کارکرد آن ویژگی در داخل محصول واقعی است.
Symphony در مخازن یکپارچهٔ کد—مثل آنچه ما در OpenAI داریم—هم عملکرد بسیار خوبی دارد؛ جایی که مرحلهٔ نهاییِ ادغام یک درخواست ادغام کد معمولاً کند و حساس است. این سیستم یکپارچهسازی مداوم (CI) را پایش میکند، در صورت نیاز بازپایهسازی انجام میدهد، تعارضها را برطرف میکند، بررسیهای ناپایدار را دوباره اجرا میکند و بهطور کلی تغییرات را در طول خط لوله هدایت میکند. تا زمانی که یک تیکت به مرحلهٔ Merging میرسد، اطمینان بالایی داریم که تغییر بدون نیاز به نظارت مداوم انسانی وارد شاخهٔ اصلی خواهد شد.
پس از پیادهسازی Symphony، کار بیشتری را به عاملها واگذار میکنیم و روی وظایف سختتر و اکتشافیتر تمرکز میکنیم.
پیشرفت با مشکلات تازه و متفاوت همراه است
فعالیت در این سطح با بدهبستانهایی همراه است. وقتی از هدایت تعاملی عاملها به واگذاری کار در سطح تیکت رفتیم، توانایی این را از دست دادیم که مدام در میانه مسیر به آنها تلنگر بزنیم و هر وقت لازم بود اصلاح مسیر کنیم. گاهی عامل چیزی تولید میکرد که کاملاً از هدف دور بود. این مفید بود—چون این شکستها شکافهای سیستم را آشکار میکردند و به ما کمک میکردند آن را مقاومتر کنیم.
بهجای اینکه نتیجه را بهصورت دستی اصلاح کنیم، سازوکارهای کنترلی و مهارتهایی اضافه کردیم تا عاملها دفعهٔ بعد بتوانند موفق شوند. با گذشت زمان، این کار باعث شد قابلیتهای جدیدی به چارچوبمان اضافه کنیم؛ مثل اجرای تستهای سرتاسری، هدایت برنامه از طریق Chrome DevTools و مدیریت تستهای سریع تضمین کیفیت (QA). همچنین مستنداتمان را بهطور قابلتوجهی بهبود دادیم و مشخصتر کردیم که یک نتیجهٔ مطلوب چه ویژگیهایی دارد.
هر وظیفهای با سبک کار Symphony سازگار نیست. بعضی مسائل هنوز به مهندسانی نیاز دارند که مستقیماً با نشستهای تعاملی Codex کار کنند، بهویژه مسائل مبهم یا کارهایی که به قضاوت و تخصص قوی نیاز دارند. در عمل، اینها معمولاً همان وظایف جالبتر و لذتبخشتری هستند که مهندسان ما دوست دارند برایشان وقت بگذارند.
تفاوت در این است که Symphony میتواند بخش عمده کارهای روتین پیادهسازی را بر عهده بگیرد. این به مهندسان اجازه میدهد بهجای جابهجایی مداوم بین وظایف کوچکتر، هر بار روی یک مسئله دشوار تمرکز کنند.
همچنین یاد گرفتیم که در نظر گرفتن عاملها بهعنوان گرههای ثابت در یک ماشین حالت خوب جواب نمیدهد. مدلها هوشمندتر میشوند و میتوانند مسائل بزرگتری را نسبت به چارچوبی که سعی میکنیم در آن محدودشان کنیم حل کنند. برای مثال، در نسخههای اولیه، همهٔ یکپارچهسازیهای GitHub بخشی از چارچوب بیرونی بودند—به این معنا که انتظار داشتیم Codex فقط تغییرات کد را انجام دهد و بقیهٔ فرایند (مثل ارسال تغییرات و اجرای تستها) را در کد تعریف کرده بودیم. در نسخههای اولیهٔ کار عاملمحور، فقط از Codex میخواستیم وظیفه را پیادهسازی کند. این رویکرد بیش از حد محدودکننده از آب درآمد.Codex کاملاً قادر است چندین درخواست ادغام کد ایجاد کند، بازخوردهای بازبینی را بخواند و آنها را برطرف کند. بنابراین ابزارهایی در اختیارش گذاشتیم—مثل gh CLI (رابط خط فرمان GitHub)، مهارتهایی برای خواندن گزارشهای یکپارچهسازی مداوم (CI) و غیره—و حالا میتوانیم از Codex بخواهیم کارهای بیشتری انجام دهد، مثل بستن درخواستهای ادغام قدیمی یا گرفتن گزارش از کارهای انجامشده در مقابل کارهای رهاشده. این نوع کارها کاملاً خارج از محدودهٔ اولیهٔ پیادهسازی ویژگی بودند.
پس در نهایت به سمت این حرکت کردیم که به عاملها بهجای گذارهای سختگیرانه، هدف بدهیم؛ خیلی شبیه مدیری خوب که به یکی از اعضای تیمش یک هدف واگذار میکند. قدرت مدلها از تواناییشان در استدلال میآید، پس به آنها ابزار و بافت بدهید و بگذارید کارشان را بکنند.
استفاده از Symphony برای ساختن Symphony
وقتی محل نگهداری Symphony را باز میکنید، اولین چیزی که توجهتان را جلب میکند این است که Symphony از نظر فنی فقط یک فایل SPEC.md است—تعریفی از مسئله و راهحل مورد نظر. بهجای ساختن یک سیستم نظارت پیچیده، ما مسئله و راهحلهای مورد نظر را تعریف کردیم و به عاملها هدایت سطحبالا دادیم.
پیادهسازی مرجع با زبان Elixir نوشته شده است—چون وقتی نوشتن کد عملاً بدون هزینه میشود، میتوان بالاخره زبانها را بر اساس نقاط قوتشان انتخاب کرد، مثل قابلیت همزمانی در Elixir—اما ایدهٔ اصلی را میتوان در یک سند ساده Markdown هم بیان کرد. ما شما را تشویق میکنیم که عامل برنامهنویسی موردعلاقهتان را به این مشخصات هدایت کنید تا نسخهٔ خودش را پیادهسازی کند.
نسخهٔ اول Symphony فقط یک سشن Codex بود که داخل tmux اجرا میشد، Linear را بهصورت دورهای بررسی میکرد و برای وظایف جدید زیرعاملها را اجرا میکرد. کار میکرد، اما چندان قابلاعتماد نبود. نسخهٔ دوم داخل مخزن اصلی پروژهٔ ما قرار داشت که از ابتدا با در نظر گرفتن عاملها ساخته شده بود. ما قبلاً چارچوب عامل را ساخته بودیم تا مهارتها و زمینهٔ لازم را در اختیار عاملها بگذارد تا در این مخزن کار باکیفیتی انجام دهند، بنابراین Symphony فقط همهٔ اینها را به هم متصل میکند.
وقتی قابلیتهای پایه به وجود آمد، از Symphony برای ساختن خود Symphony استفاده کردیم.
وقتی بهصورت داخلی دمو دادیم که سیستم چگونه وظایف را مدیریت میکند و ویدئوی اثبات انجام کار را هم ضمیمه میکند، واکنشها بهشدت مثبت بود: کانال پروژهٔ Symphony ما رشد کرد و تیمهای مختلف در سراسر سازمان بهطور طبیعی شروع به استفاده از آن کردند. رسیدن به تناسب محصول با نیازهای داخلی، پیشنیاز عرضهٔ بیرونی در OpenAI است. بر اساس میزان استفادهای که در OpenAI دیدیم، روشن شد که باید Symphony را فراتر از دیوارهای شرکت به اشتراک بگذاریم.
بنابراین این ایده را جدا کردیم و در قالب یک فایل مستقل SPEC.md درآوردیم و از Codex خواستیم آن را پیادهسازی کند. برای پیادهسازی مرجع، Elixir را انتخاب کردیم—زبانی نسبتاً خاص با قابلیتهای عالی برای هماهنگسازی و نظارت بر فرایندهای همزمان. Codex پیادهسازی Elixir را در یک مرحله انجام داد و از آنجا به بعد هم مشخصات و هم پیادهسازی را بهصورت تدریجی بهبود دادیم. برای صیقل دادن مشخصات، حتی از Codex خواستیم آن را در چند زبان دیگر—TypeScript، Go، Rust، Java و Python—هم پیادهسازی کند و از نتایج برای شناسایی ابهامها و سادهسازی سیستم استفاده کنیم. این کار در همهٔ زبانها موفق بود.
در فرایند توسعهٔ Codex، مقدار زیادی از پیچیدگیهای جانبی مثل وابستگی به مخازن خاص یا Linear MCP را حذف کردیم. Symphony دیگر به مخازن یا جریانهای کاری داخلی ما وابسته نیست. رویکرد اصلی به این شکل ساده شد:
برای هر وظیفه باز، اطمینان یابید که یک عامل در فضای کاری خودش در حال اجرا باشد.
علاوه بر کمک به کارهای در حال انجام، اکنون جریان کاری توسعه چیزی است که عاملها آن را میشناسند و دنبال میکنند. این جریان کاری—کار روی یک مسئله، کلون کردن یک مخزن، قرار دادن آن در وضعیت در حال انجام تا مدیر محصول (PM) بداند روی آن کار میشود، افزودن درخواست ادغام کد، انتقال آن به وضعیت بازبینی، پیوست کردن ویدئوها و غیره—اکنون در یک فایل سادهٔ WORKFLOW.md ثبت شده است. تمام اینها فرایندی بود که انسانها دنبال میکردند، اما هرگز مستند نشده بود. بهجای تکیه بر این مجموعه مراحل ضمنی، اکنون آن را مستندسازی کردهایم و Symphony اطمینان میدهد که عاملها از آن پیروی میکنند. این کار به ما اجازه میدهد عاملهایی بسازیم که در کنار ما کار کنند. اگر تصمیم بگیریم عاملها باید به کارهای تمامشده خودبازنگری هم اضافه کنند، کافی است آن را به WORKFLOW.md اضافه کنیم و Symphony عاملها را به انجام آن مرحله هدایت خواهد کرد.
ما همچنین توانستیم از Codex در حالت app server(در یک پنجره جدید باز میشود) استفاده کنیم، که یک حالت بدون رابط کاربری (headless) داخلی برای Codex است. این حالت به ما اجازه داد Codex را اجرا کنیم و بهصورت برنامهنویسی از طریق یک رابط برنامهنویسی کاربردی مبتنی بر JSON-RPC (API) که بهخوبی مستند شده است—برای کارهایی مثل شروع یک thread یا واکنش به مراحل تعامل—با آن ارتباط برقرار کنیم. این روش بسیار راحتتر و مقیاسپذیرتر از تلاش برای تعامل با Codex از طریق رابط خط فرمان (CLI) یا سشنهای زندهٔ tmux است.
Codex App Server کاملاً با مورد استفادهٔ ما سازگار بود: از چارچوبی که Codex فراهم میکند بهره میبریم، در عین حال کنترلها و نقاط اتصال لازم برای یکپارچهسازی را هم در اختیار داریم. برای مثال، برای جلوگیری از افشای توکن دسترسی Linear به زیرعاملها، از dynamic tool calls(در یک پنجره جدید باز میشود) (فراخوانیهای پویا ابزار) استفاده میکنیم تا تابع خام linear_graphql را در اختیار بگذاریم که درخواستهای دلخواه را روی Linear اجرا میکند—بدون تکیه بر پروتکل زمینهٔ مدل (MCP) و بدون افشای توکن دسترسی به کانتینرها.
قدم بعدی چیست
Symphony یک لایهٔ هماهنگسازیِ عمداً مینیمال است. ما آن را بهصورت متنباز منتشر میکنیم تا قدرت Codex App Server را در کنار ابزارهای مختلف جریان کار—مثل Linear—نشان دهیم. بنابراین قصد نداریم Symphony را بهعنوان یک محصول مستقل نگهداری کنیم. آن را بهعنوان یک پیادهسازی مرجع در نظر بگیرید.همانطور که بسیاری از توسعهدهندگان عاملهای کدنویسی خود را به پست مهندسی چارچوب اجرایی (harness engineering) هدایت کردند تا مخازن خود را بسازند، امیدواریم شما هم عامل کدنویسی موردعلاقهتان را به spec(در یک پنجره جدید باز میشود) و repository(در یک پنجره جدید باز میشود) مربوط به Symphony هدایت کنید تا نسخههای خودتان را متناسب با محیطتان بسازید.
قدرت اصلی از Codex و App Server آن میآید. Symphony راهی بود برای وصلکردن Codex به Linear—دو ابزاری که از قبل استفاده میکردیم—تا مسئلهٔ مدیریت کار را حل کنیم. با بهتر شدن عاملهای کدنویسی در استدلال و دنبالکردن دستورالعملها، احتمال میدهیم گلوگاه در شرکتهای دیگر هم از «نوشتن کد» به «مدیریت کارِ عاملمحور» منتقل شود. بخش هیجانانگیز این است که حالا مانع ورود برای آزمایش این سیستمهای عامل کدنویسی بهطرز شگفتآوری پایین است؛ میتوانید خیلی ساده با Codex شروع به ساختن کنید.
قدردانی از جامعه
خیلی خوشحالیم که در هفتههای پس از انتشار، جامعه مهندسی را میبینیم که از Symphony استفاده میکند؛ تا ۲۳ آوریل بیش از ۱۵ هزار ستاره GitHub(در یک پنجره جدید باز میشود) به دست آورده است.