Специфікація з відкритим кодом для оркестрації Codex: Symphony
Алекс Котлярський, Віктор Чжу та Зак Брок
Шість місяців тому, працюючи над внутрішнім інструментом продуктивності, наша команда ухвалила суперечливе (на той момент) рішення: ми побудуємо наш репозиторій без жодного рядку коду, написаного людьми. Кожен рядок у нашому репозиторії проєкту мав бути згенерований Codex.
Щоб це запрацювало, ми з нуля перепроєктували наш інженерний робочий процес. Ми створили дружній до агентів репозиторій, серйозно інвестували в автоматизовані тести й захисні механізми та ставилися до Codex як до повноцінного члена команди. Ми задокументували цей шлях у нашому попередньому дописі в блозі про harness engineering.
І це спрацювало, але потім ми зіткнулися з наступним вузьким місцем: перемиканням контексту.
Щоб розв’язати цю нову проблему, ми створили систему під назвою Symphony. Symphony(відкривається у новому вікні) — це оркестратор агентів, який перетворює дошку керування проєктами на кшталт Linear на площину керування для агентів програмування. Кожне відкрите завдання отримує агента, агенти працюють безперервно, а люди перевіряють результати.
У цьому дописі ми пояснюємо, як створили Symphony — що дало 500% зростання кількості запитів на злиття, які були прийняті, у деяких командах, — і як використати її, щоб перетворити ваш власний трекер задач на постійно активний оркестратор агентів.
Межа інтерактивних агентів для програмування
Хоча ними стає дедалі легше користуватися, агенти для програмування — незалежно від того, чи це вебзастосунки, чи CLI, — усе ще залишаються інтерактивними інструментами.
Коли масштаби агентної роботи в OpenAI зросли, ми відчули новий тип навантаження. Кожен інженер відкривав кілька сеансів Codex, призначав завдання, переглядав результат, спрямовував агента — і повторював знову. На практиці більшість людей могли комфортно вести одночасно від трьох до п’яти сеансів, перш ніж перемикання контексту ставало болісним. Понад це продуктивність падала. Ми забували, який сеанс що робить, перескакували між терміналами, щоб повернути агентів на правильний шлях, і налагоджували довгі завдання, які зависали на півдорозі.
Агенти були швидкими, але ми вперлися в системне вузьке місце: людську увагу. Ми фактично побудували команду надзвичайно здібних молодших інженерів, а потім доручили нашим інженерам-людям мікрокерування ними. Це не можна було масштабувати.
Зміна перспективи
Ми зрозуміли, що оптимізуємо не те. Ми будували нашу систему навколо сеансів програмування та об’єднаних PR, хоча PR і сеанси насправді лише засіб для досягнення мети. Робочі процеси розробки ПЗ здебільшого організовані навколо результатів: issues, задач, тікетів, етапів.
Тож ми запитали себе: що буде, якщо перестати безпосередньо контролювати агентів і натомість дозволити їм брати роботу з нашого трекера завдань?
Ця ідея перетворилася на Symphony — письмову специфікацію, що виконує роль супервізора для оркестрації агентної роботи.
Як ми перетворили наш трекер задач на оркестратор агентів
Symphony почалася з простої концепції: будь-яке відкрите завдання має бути підхоплене й виконане агентом. Замість того щоб керувати сеансами Codex у кількох вкладках, ми зробили наш трекер задач площиною керування.
У цій конфігурації кожне відкрите завдання Linear відповідає окремому робочому простору агента. Symphony безперервно стежить за дошкою завдань і гарантує, що для кожного активного завдання агент працює в циклі, доки його не буде завершено. Якщо агент аварійно завершується або зависає, Symphony перезапускає його. Якщо з’являється нова робота, Symphony підхоплює її й починає організовувати виконання.
Ми побудували наш робочий процес на основі статусів тікетів, використовуючи менеджер завдань Linear як автомат станів.
На практиці Symphony відокремлює роботу від сеансів і від запитів на злиття. Деякі issues породжують кілька PR у різних репозиторіях; інші є суто дослідженням або аналізом і взагалі не торкаються кодової бази.
Коли роботу абстраговано таким чином, тікети можуть представляти значно більші одиниці роботи.
Ми регулярно використовуємо Symphony для оркестрації складних функцій і міграцій інфраструктури. Наприклад, ми можемо створити завдання, у якому попросимо агента проаналізувати кодову базу, Slack або Notion і підготувати план реалізації. Коли план нас влаштовує, агент генерує дерево завдань, розбиваючи роботу на етапи й визначаючи залежності між завданнями.
Агенти починають працювати лише над тими завданнями, які нічим не заблоковані, тож виконання для цього DAG (послідовності кроків виконання) природно й оптимально розгортається паралельно. У наведеному нижче прикладі ми позначили оновлення React як заблоковане міграцією на Vite. Як і очікувалося, агенти почали оновлювати React лише після завершення міграції на Vite.
Агенти також можуть самі створювати роботу. Під час реалізації або перегляду вони часто помічають покращення, що виходять за межі поточного завдання: проблему продуктивності, можливість рефакторингу або кращу архітектуру. Коли це стається, вони просто створюють новий issue, який ми можемо оцінити й запланувати на потім, — багато з цих наступних завдань також підхоплюють агенти. Поки ми наглядаємо за цим процесом, агенти залишаються організованими й підтримують рух роботи вперед.
Такий спосіб роботи разюче знижує когнітивну вартість запуску неоднозначної роботи. Якщо агент помиляється, це все одно корисна інформація, а витрати для нас майже нульові. Ми можемо дуже дешево створювати тікети, щоб агент прототипував і досліджував, а потім відкидати будь-які варіанти, які нам не подобаються.
Оскільки оркестратор працює на devbox і ніколи не спить, ми можемо додавати завдання звідки завгодно й знати, що агент їх підхопить. Наприклад, один інженер у нашій команді вніс три суттєві зміни з застосунку Linear на своєму телефоні, сидячи в затишній хатинці за містом з нестабільним Wi-Fi.
Більше досліджень завдяки такому способу роботи
Коли ми спостерігали за наслідками роботи з Symphony, найочевиднішою зміною був результат. У деяких командах OpenAI ми побачили, що кількість прийнятих PR зросла у 6 разів за перші три тижні. Поза OpenAI засновник Linear Каррі Саарінен відзначив сплеск у створенні робочих просторів(відкривається у новому вікні) після випуску Symphony. Однак глибша зміна полягає в тому, як команди мислять про роботу.
Коли наші інженери більше не витрачають час на нагляд за сеансами Codex, економіка змін у коді змінюється повністю. Уявна вартість кожної зміни знижується, бо ми більше не вкладаємо людські зусилля в те, щоб безпосередньо керувати реалізацією.
Це змінило нашу поведінку. У Symphony стало надзвичайно просто запускати ризиковані завдання. Спробуйте ідею, дослідіть рефакторинг, перевірте гіпотезу й залишайте лише ті результати, які виглядають перспективно.
Це також розширює коло тих, хто може ініціювати роботу. Наш менеджер продукту й дизайнер тепер можуть напряму створювати запити на функції в Symphony. Їм не потрібно клонувати репозиторій чи керувати сеансом Codex. Вони описують функцію й отримують пакет для перегляду, який містить відеоогляд роботи функції в реальному продукті.
Symphony також особливо добре проявляє себе у великих монорепозиторіях (як той, що є в нас в OpenAI), де останній етап до злиття PR є найбільш повільним та нестабільним. Система стежить за CI, робить rebase за потреби, розв’язує конфлікти, повторює нестабільні перевірки та загалом проводить зміни через увесь процес. До моменту, коли тікет досягає статусу Merging, ми маємо високу впевненість, що зміна потрапить до основної гілки без людського нагляду.
Прогрес приносить нові, інші проблеми
Робота на такому рівні пов’язана з компромісами. Коли ми перейшли від інтерактивного спрямування агентів до призначення їм роботи на рівні тікетів, ми втратили можливість постійно підштовхувати їх у процесі й коригувати курс за потреби. Іноді агент видавав результат, що повністю не відповідав очікуванням. Це було корисно: такі невдачі виявляли прогалини в системі й допомагали нам робити її надійнішою.
Замість того щоб «латати» результат вручну, ми додали захисні механізми й навички, щоб агенти могли досягти успіху наступного разу. Із часом це привело нас до додавання нових можливостей у нашу обв’язку, як-от запуск end-to-end тестів, керування застосунком через Chrome DevTools і проведення smoke-тестів QA. Ми значно покращили нашу документацію й чіткіше визначили, як виглядає хороший результат.
Не кожне завдання підходить під стиль роботи Symphony. Деякі проблеми й далі потребують, щоб інженери працювали безпосередньо з інтерактивними сеансами Codex — особливо це стосується неоднозначних проблем чи роботи, що вимагає серйозного судження й експертизи. На практиці це зазвичай і є найцікавіші та найприємніші завдання, над якими найдовше працюють наші інженери.
Різниця в тому, що Symphony може взяти на себе основну масу саме рутинної реалізаційної роботи. Це дає інженерам змогу зосереджуватися на якійсь одній складній проблемі, а не постійно перемикатися між дрібнішими завданнями.
Ми також зрозуміли, що ставитися до агентів як до жорстких вузлів у автоматі станів — не надто ефективно. Моделі розумнішають і можуть розв’язувати більші проблеми, ніж та рамка, у яку ми намагаємося їх втиснути. Наприклад, у ранніх версіях усі інтеграції з GitHub були частиною зовнішньої обв’язки — скажімо, ранні версії очікували, що Codex лише вноситиме зміни в код, а решту процесу (подання змін, запуск тестів) задавали в коді. У ранніх версіях агентної роботи ми просили Codex лише реалізувати завдання. Такий підхід виявився надто обмежувальним. Codex цілком здатний створювати кілька PR, а також читати фідбек огляду й реагувати на нього. Тож ми дали йому інструменти — CLI gh, навички для читання логів CI тощо — і тепер можемо просити Codex робити більше, наприклад закривати старі PR або витягувати звіти про завершену й покинуту роботу. Такі типи завдань значно виходили за межі початкового сценарію реалізації функції.
Зрештою ми стали рухатися в бік того, щоб давати агентам мету, а не жорсткі переходи, — приблизно так само, як хороший менеджер ставить мету своєму безпосередньому підлеглому в команді. Сила моделей походить із їхньої здатності до міркування, тож просто дайте їм інструменти, контекст, і не заважайте.
Використання Symphony для створення Symphony
Коли ви відкриваєте репозиторій Symphony, перше, що впадає в око, — це те, що технічно Symphony є лише файлом SPEC.md — визначенням проблеми та бажаного розв’язання. Замість побудови складної системи нагляду ми визначили проблему й задумане розв’язання, надаючи агентам високорівневе спрямування.
Еталонну реалізацію написано на Elixir — бо коли код фактично безкоштовний, нарешті можна обирати мови за їхніми сильними сторонами, як-от конкурентність Elixir, — але саму основну ідею можна викласти в простому документі Markdown. Ми радимо спрямувати ваш улюблений агент для програмування на специфікацію й попросити його реалізувати власну версію.
Перша версія Symphony була просто сеансом Codex у tmux, який опитував Linear і запускав субагентів для нових завдань. Це працювало, але було не надто надійно. Друга версія жила в нашому основному репозиторії проєкту, який будувався з урахуванням агентів. Ми вже створили обв’язку агента, щоб дати агентам навички й контекст для якісної роботи в цьому репозиторії, тож Symphony просто все це поєднує.
Щойно з’явилася базова функціональність, ми використали Symphony, щоб створити Symphony.
Коли ми внутрішньо продемонстрували систему, що керує завданнями й прикріплює відео з доказом виконаної роботи, реакція була вкрай позитивною: наш канал проєкту Symphony зріс, а команди по всій організації почали користуватися нею природним чином. Внутрішня відповідність продукту ринку — обов’язкова передумова для зовнішнього запуску в OpenAI. З огляду на те, як Symphony використовували в OpenAI, стало ясно, що нею варто поділитися й за межами компанії.
Тож ми винесли ідею в окремий SPEC.md і попросили Codex реалізувати її. Для еталонної реалізації ми обрали Elixir — відносно нішеву мову з чудовими примітивами для оркестрації та нагляду за конкурентними процесами. Codex створив реалізацію на Elixir за один підхід, а далі ми продовжили ітерації і над специфікацією, і над реалізацією. Щоб відшліфувати специфікацію, ми навіть попросили Codex реалізувати її ще кількома мовами — TypeScript, Go, Rust, Java, Python — і використати результати, щоб виявити неоднозначності та спростити систему. Він упорався з кожною мовою.
У процесі створення Codex ми прибрали багато випадкової складності, як-от залежності від конкретних репозиторіїв або Linear MCP. Symphony більше не залежить від наших внутрішніх репозиторіїв чи робочих процесів. Базовий підхід став простим:
Для кожного відкритого завдання гарантуйте, що агент працює у власному робочому просторі.
Окрім допомоги з активною роботою, процес розробки тепер став чимось, що агенти знають і чого дотримуються. Процес розробки — робота над issue, клонування репозиторію, переведення його в in progress, щоб PM знав, що над ним працюють, додавання PR, переміщення в статус Review, прикріплення відео тощо — тепер зафіксований у простому файлі WORKFLOW.md. Усе це був процес, якого дотримувалися люди, але його ніколи не документували. Замість того щоб покладатися на цей неявний набір кроків, ми тепер його документуємо, а Symphony гарантує, що агенти йому слідують. Це дає нам змогу створювати агентів, які працюють поруч із нами. Якщо ми вирішимо, що агенти також мають додавати саморефлексію до завершеної роботи, ми додамо це до WORKFLOW.md, і Symphony проведе агентів до цього кроку.
Ми також змогли використовувати Codex у режимі app server(відкривається у новому вікні), вбудованому headless-режимі для Codex. Цей режим дозволив нам запускати Codex і програмно взаємодіяти з ним через добре задокументований JSON-RPC API для таких речей, як запуск thread або реакція на turns. Це набагато зручніший і придатний до масштабування спосіб, ніж намагатися взаємодіяти з Codex через CLI або живі сеанси tmux.
Codex App Server ідеально підійшов для нашого сценарію використання: ми користуємося перевагами обв’язки, яку надає Codex, маючи водночас регулятори й хуки для інтеграції. Наприклад, щоб не розкривати токен доступу до Linear субагентам, ми використовуємо динамічні виклики інструментів(відкривається у новому вікні), щоб надати необроблену функцію linear_graphql, яка виконує довільні запити до Linear, не покладаючись на MCP і не розкриваючи токен доступу контейнерам.
Що далі
Symphony — це навмисно мінімалістичний шар оркестрації. Ми відкриваємо його код, щоб продемонструвати силу Codex App Server у парі з різними інструментами робочого процесу, як-от Linear. Відповідно, ми не плануємо підтримувати Symphony як окремий продукт. Думайте про нього як про еталонну реалізацію. Так само як багато розробників спрямували своїх агентів для програмування на допис про harness engineering, щоб згенерувати каркас своїх репозиторіїв, ми сподіваємося, що ви спрямуєте свого улюбленого агента для програмування на специфікацію(відкривається у новому вікні) та репозиторій(відкривається у новому вікні) Symphony, щоб створити власні версії, адаптовані до ваших середовищ.
Продуктивність забезпечується Codex і його app server. Symphony була способом поєднати Codex і Linear — дві речі, якими ми вже користувалися, щоб розв’язати проблему керування роботою. Оскільки агенти для програмування стають кращими в міркуванні й дотриманні інструкцій, ми підозрюємо, що вузьке місце в інших компаніях теж зміститься від написання коду до керування агентною роботою. Найцікавіше те, що бар’єр для експериментів із такими системами агентів для програмування тепер напрочуд низький. Ви можете просто створювати щось нове за допомогою Codex.
Подяка спільноті
Ми раді бачити, як інженерна спільнота використовує Symphony впродовж тижнів після релізу, завдяки чому станом на 23 квітня проєкт уже зібрав понад 15 тис. зірок на GitHub(відкривається у новому вікні).