Спецификация с отворен код за оркестрация на Codex: Symphony
От Alex Kotliarskyi, Victor Zhu и Zach Brock
Преди шест месеца, докато работеше по вътрешен инструмент за продуктивност, нашият екип взе спорно (по онова време) решение: щяхме да изградим хранилището си без никакъв код, написан от човек. Всеки ред в проектното ни хранилище трябваше да бъде генериран от Codex.
За да проработи това, преработихме инженерния си работен процес из основи. Изградихме хранилище, подходящо за агенти, инвестирахме сериозно в автоматизирани тестове и предпазни механизми и възприехме Codex като пълноправен съотборник. Документирахме това пътуване в предишната ни публикация в блога за harness engineering.
И това проработи, но после се натъкнахме на следващия спиращ фактор: превключването на контекст.
За да решим този нов проблем, изградихме система, наречена Symphony. Symphony(отваря се в нов прозорец) е оркестратор на агенти, който превръща табло за управление на проекти като Linear в контролна плоскост за кодиращи агенти. Всяка отворена задача получава Агент, агентите работят непрекъснато, а хората преглеждат резултатите.
Тази публикация обяснява как създадохме Symphony — което доведе до 500% увеличение на приетите искания за предложения в някои екипи — и как да я използвате, за да превърнете собствения си тракер за проблеми във винаги активен оркестратор на агенти.
Таванът на интерактивните кодиращи агенти
Дори когато стават по-лесни за използване, кодиращите агенти — независимо дали се достъпват през уеб приложения или CLI — все още са интерактивни инструменти.
С увеличаването на мащаба на агентната работа в OpenAI открихме нов вид натоварване. Всеки инженер отваряше по няколко сесии на Codex, възлагаше задачи, преглеждаше резултатите, насочваше агента и повтаряше. На практика повечето хора можеха комфортно да управляват от три до пет сесии едновременно, преди превключването на контекст да стане болезнено. Оттам нататък продуктивността спадаше. Забравяхме коя сесия какво прави, прескачахме между терминали, за да върнем агентите в правилната посока, и отстранявахме проблеми при дълги задачи, които зацикляха по средата.
Агентите бяха бързи, но имахме системен спиращ фактор: човешкото внимание. На практика бяхме изградили екип от изключително способни младши инженери, а след това бяхме възложили на човешките ни инженери да ги микроменажират. Това нямаше как да се мащабира.
Промяна в гледната точка
Осъзнахме, че оптимизираме грешното нещо. Бяхме ориентирали системата си около кодиращи сесии и слети PRs, когато PRs и сесиите всъщност са само средство за постигане на цел. Софтуерните работни процеси до голяма степен са организирани около резултати: проблеми, задачи, тикети, етапи.
Затова се запитахме какво би станало, ако спрем да ръководим агентите директно и вместо това им позволим да поемат работа от нашия тракер за задачи.
Тази идея се превърна в Symphony — писмена спецификация, която функционира като супервайзър за оркестрация на агентна работа.
Превръщане на нашия тракер за проблеми в оркестратор на агенти
Symphony започна с проста концепция: всяка отворена задача трябва да бъде поета и завършена от Агент. Вместо да управляваме сесиите на Codex в множество раздели, превърнахме тракера си за проблеми в контролна плоскост.
В тази конфигурация всеки отворен проблем в Linear съответства на отделно работно пространство на агент. Symphony непрекъснато следи таблото със задачи и гарантира, че за всяка активна задача има Агент, който работи в цикъл, докато тя не бъде завършена. Ако Агент се срине или блокира, Symphony го рестартира. Ако се появи нова работа, Symphony я поема и започва да организира изпълнението ѝ.
Изградихме работния си процес върху статусите на тикетите, използвайки мениджъра на задачи Linear като машина на състоянията.
На практика Symphony разделя работата от сесиите и от исканията за предложения. Някои проблеми водят до множество PRs в различни хранилища; други са чисто проучване или анализ и никога не засягат кодовата база.
След като работата се абстрахира по този начин, тикетите могат да представляват много по-големи единици работа.
Редовно използваме Symphony, за да оркестрираме сложни функционалности и инфраструктурни миграции. Например може да подадем задача, с която искаме от агента да анализира кодовата база, Slack или Notion и да изготви план за реализация. След като останем доволни от плана, агентът генерира дърво от задачи, като разделя работата на етапи и определя зависимости между задачите.
Агентите започват работа само по задачи, които не са блокирани, така че изпълнението се развива естествено и оптимално паралелно за този DAG (последователност от стъпки за изпълнение). В примера по-долу отбелязахме ъпгрейда на React като блокиран от миграция към Vite. Както се очакваше, агентите започнаха да обновяват React едва след като миграцията към Vite беше завършена.
Агентите могат и сами да създават работа. По време на реализация или преглед те често забелязват подобрения, които излизат извън обхвата на текущата задача: проблем с производителността, възможност за рефакториране или по-добра архитектура. Когато това се случи, те просто подават нов проблем, който можем да оценим и планираме по-късно — много от тези последващи задачи също се поемат от агенти. Докато ние осигуряваме надзор над процеса, агентите остават организирани и поддържат работата в движение.
Този начин на работа драстично намалява когнитивната цена за стартиране на неясна работа. Ако агентът сбърка нещо, това пак е полезна информация, а цената за нас е почти нулева. Можем много евтино да подаваме тикети, за да накараме агента да прототипира и изследва, и да изхвърляме всички експерименти, които не ни харесват.
Тъй като оркестраторът работи на devboxes и никога не спи, можем да добавяме задачи отвсякъде и да знаем, че Агент ще ги поеме. Например един инженер от екипа ни направи три значителни промени от приложението на Linear на телефона си от уютна хижа с нестабилен wifi.
Повече експериментиране при този начин на работа
Когато наблюдавахме ефектите от работата със Symphony, най-очевидната промяна беше продукцията. В някои екипи в OpenAI видяхме броят на приетите PRs да се увеличи 6 пъти през първите три седмици. Извън OpenAI основателят на Linear Karri Saarinen отбеляза скок в създадените работни пространства(отваря се в нов прозорец), когато пуснахме Symphony. По-дълбоката промяна обаче е в начина, по който екипите мислят за работата.
Когато нашите инженери вече не прекарват време в наблюдение на сесиите на Codex, икономиката на промените в кода се променя изцяло. Възприеманата цена на всяка промяна спада, защото вече не влагаме човешки усилия в самото водене на реализацията.
Това промени поведението ни. Стана тривиално лесно да стартираме спекулативни задачи в Symphony. Пробвай идея, изследвай рефакториране, тествай хипотеза и запази само резултатите, които изглеждат обещаващи.
Това също така разширява кръга от хора, които могат да инициират работа. Нашите продуктов мениджър и дизайнер вече могат да подават заявки за функционалности директно в Symphony. Не им се налага да checkout-ват хранилището или да управляват сесия на Codex. Те описват функционалността и получават пакет за преглед, който включва видео обхождане на работещата функционалност в реалния продукт.
Symphony също така блести в големи монорепота (като това, което имаме в OpenAI), където последната миля до приемането на PR е бавна и крехка. Системата следи CI, прави rebase, когато е нужно, разрешава конфликти, повтаря нестабилни проверки и като цяло превежда промените през конвейера. Докато тикетът достигне Merging, имаме висока увереност, че промяната ще влезе в основния клон без непрекъснато наблюдение от човек.
Напредъкът идва с нови, различни проблеми
Работата на това ниво идва с компромиси. Когато преминахме от интерактивно насочване на агентите към възлагане на работа на ниво тикет, загубихме възможността постоянно да ги побутваме по време на изпълнение и да коригираме курса при нужда. Понякога агентът произвеждаше нещо, което напълно не уцелваше целта. Това беше полезно — тези провали разкриваха пропуски в системата и ни помагаха да я направим по-устойчива.
Вместо да поправяме резултата ръчно, добавихме предпазни механизми и умения, така че агентите да могат да успяват следващия път. С времето това ни доведе до добавяне на нови възможности към нашия harness, като изпълнение на end-to-end тестове, управление на приложението чрез Chrome DevTools и управление на QA smoke тестове. Значително подобрихме документацията си и изяснихме как изглежда добрият резултат.
Не всяка задача пасва на стила на работа на Symphony. Някои проблеми все още изискват инженери, които работят директно с интерактивни сесии на Codex, особено неясни проблеми или работа, която изисква силна преценка и експертиза. На практика това обикновено са най-интересните и приятни задачи, на които нашите инженери отделят време.
Разликата е, че Symphony може да поеме по-голямата част от рутинната работа по реализацията. Това позволява на инженерите да се фокусират върху един труден проблем наведнъж, вместо постоянно да превключват контекста между по-малки задачи.
Научихме също, че третирането на агентите като твърди възли в машина на състоянията не работи добре. Моделите стават по-умни и могат да решават по-големи проблеми от рамката, в която се опитваме да ги поставим. Например в ранните версии всички GitHub интеграции бяха част от външния harness — например ранните версии очакваха Codex само да прави промени по кода, като останалата част от процеса (подаване на промени, изпълнение на тестове) беше зададена в код. В ранните ни версии на агентна работа просто молехме Codex да реализира задачата. Този подход се оказа твърде ограничаващ. Codex е напълно способен да създава множество PRs, както и да чете обратна връзка от преглед и да я адресира. Затова му дадохме инструменти — CLI gh, умения за четене на CI логове и т.н. — и сега можем да искаме от Codex да прави повече, например да затваря стари PRs или да изважда отчети за завършена спрямо изоставена работа. Тези видове задачи излизаха далеч извън първоначалната рамка за реализация на функционалност.
Затова в крайна сметка преминахме към даване на цели на агентите вместо строги преходи, подобно на това как добрият мениджър възлага цел на пряко подчинен в екипа си. Силата на моделите идва от способността им за структурирано анализиране, затова им дайте инструменти и контекст и ги оставете да действат.
Използване на 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 вече не зависи от вътрешните ни хранилища или работни процеси. Основният подход стана прост:
За всяка отворена задача гарантирайте, че има Агент, работещ в собствено работно пространство.
Освен че помага за активната работа, процесът на разработка вече е нещо, което агентите познават и следват. Процесът на разработка — работа по проблем, checkout на хранилище, отбелязване като in progress, за да знае PM, че се работи по него, добавяне на PR, преместване в статус Review, прикачване на видеа и т.н. — вече е описан в прост файл WORKFLOW.md. Всичко това е процес, който хората са следвали, но никога не е бил документиран. Вместо да разчитаме на този неявен набор от стъпки, сега го документираме и Symphony гарантира, че агентите го следват. Това ни позволява да изграждаме агенти, които работят редом с нас. Ако решим, че агентите трябва също да прикачват саморефлексия към завършената работа, ще добавим това в WORKFLOW.md, а Symphony ще насочи агентите към тази стъпка.
Успяхме също да използваме Codex в режим app server(отваря се в нов прозорец), вграден безглав режим за Codex. Този режим ни позволи да стартираме Codex и да комуникираме с него програмно чрез добре документиран JSON-RPC API за неща като стартиране на нишка или реакция на ходове. Това е много по-удобен и мащабируем начин в сравнение с опитите за взаимодействие с Codex чрез CLI или живи сесии в tmux.
Codex App Server беше идеално решение за нашия случай на употреба: използваме harness-а, който Codex предоставя, като същевременно имаме точки за настройка и закачане. Например, за да избегнем разкриването на токена за достъп до Linear на подагенти, използваме dynamic tool calls(отваря се в нов прозорец), за да изложим суровата функция linear_graphql, която изпълнява произволни заявки към Linear, без да разчитаме на MCP или да излагаме токена за достъп на контейнерите.
Какво следва
Symphony е умишлено минимален слой за оркестрация. Пускаме го като отворен код, за да демонстрираме силата на Codex App Server, когато се комбинира с различни инструменти за работен процес, като Linear. Затова не планираме да поддържаме Symphony като самостоятелен продукт. Мислете за него като за референтна реализация. Подобно на това как много разработчици насочиха кодиращите си агенти към публикацията за harness engineering, за да изградят скелета на своите хранилища, надяваме се да насочите любимия си кодиращ Агент към спецификацията(отваря се в нов прозорец) и хранилището(отваря се в нов прозорец) на Symphony, за да изградите свои собствени версии, съобразени с вашите среди.
Силата идва от Codex и неговия app server. Symphony беше начин да свържем Codex с Linear, две неща, които вече използвахме, за да решим проблема с управлението на работата. Тъй като кодиращите агенти стават по-добри в структурирано анализиране и следване на инструкции, подозираме, че спиращият фактор в други компании също ще се измести от писането на код към управлението на агентна работа. Вълнуващото е, че бариерата за експериментиране с тези системи от кодиращи агенти вече е изненадващо ниска. Можете просто да изграждате неща с Codex.
Поздрави към общността
Много се радваме да видим как инженерната общност използва Symphony в седмиците след пускането ѝ, като към 23 април тя е събрала над 15 хил. звезди в GitHub(отваря се в нов прозорец).