Specyfikacja open source do zarządzania Codex: Symphony
Alex Kotliarskyi, Victor Zhu i Zach Brock
Sześć miesięcy temu, podczas pracy nad wewnętrznym narzędziem zwiększającym produktywność, nasz zespół podjął kontrowersyjną (w tamtym czasie) decyzję: stworzymy nasze repozytorium bez kodu napisanego przez ludzi. Każda linia w repozytorium naszego projektu musiała zostać wygenerowana przez Codex.
W tym celu od zera stworzyliśmy nowy inżynieryjny przepływ zadań. Zbudowaliśmy repozytorium usprawniające pracę agentów, skupiliśmy się na automatycznych testach i zabezpieczeniach oraz potraktowaliśmy Codex jak pełnoprawnego członka zespołu. Opisaliśmy tę drogę we wcześniejszym wpisie na blogu o inżynierii strukturalnej.
Udało nam się, jednak potem natrafiliśmy na problemy dotyczące przełączania kontekstu.
Aby je usunąć, zbudowaliśmy system o nazwie Symphony. Symphony(otwiera nowe okno) to rozwiązanie do zarządzania agentami, które zamienia tablicę do obsługi projektów, taką jak Linear, w obszar sterowania agentami kodującymi. Do każdego otwartego zadania przypisywany jest agent, który działa bez przerwy, a ludzie weryfikują wyniki jego pracy.
W tym wpisie wyjaśniamy, jak stworzyliśmy system Symphony, który pozwolił o 500% zwiększyć liczbę wdrożonych żądań pull request w niektórych zespołach, i w jaki sposób za jego pomocą przekształciliśmy własne rozwiązanie do śledzenia zgłoszeń w działający bez przerwy mechanizm zarządzania agentami.
Szczyt możliwości interaktywnych agentów kodujących
Agenty kodujące są coraz łatwiejsze w obsłudze, niezależnie od tego, czy korzysta się z nich przez aplikacje webowe, czy CLI, ale nadal są narzędziami interaktywnymi.
Wraz ze wzrostem ilości pracy wykonywanej przez agentów w OpenAI zauważyliśmy nowy rodzaj obciążenia: każdy programista otwierał kilka sesji Codex, przydzielał zadania, przeglądał wyniki, sterował agentem i powtarzał ten cykl. W praktyce większość osób mogła komfortowo zarządzać 3-5 sesjami jednocześnie, ponieważ przy większej liczbie przełączanie kontekstów stawało się uciążliwe i poziom produktywności pracowników spadał. Zapominaliśmy, która sesja czym się zajmuje, przeskakiwaliśmy między terminalami, by naprowadzać agentów na właściwy tor, i debugowaliśmy długotrwałe zadania, które zatrzymywały się w połowie.
Agenty były szybkie, jednak problemem była inna zmienna – ludzka uwaga. Stworzyliśmy zespół niezwykle zdolnych młodszych programistów agentowych, a następnie przydzieliliśmy im ludzkich inżynierów jako mikromenedżerów pracy. To nie miało prawa zadziałać w szerszym ujęciu.
Zmiana perspektywy
Zrozumieliśmy, że skupiamy się na optymalizowaniu niewłaściwego aspektu. Organizowaliśmy nasz system wokół sesji kodowania i scalonych żądań pull request, podczas gdy PR-y i sesje są tak naprawdę tylko środkiem do celu. Działania związane z tworzeniem oprogramowania w dużej mierze skupiają się na wynikach: problemach, zadaniach, zgłoszeniach i kamieniach milowych.
Zadaliśmy więc sobie pytanie, co by się stało, gdybyśmy przestali bezpośrednio nadzorować agenty i pozwolili im pobierać pracę z naszego systemu śledzenia zadań.
Ten pomysł przerodził się w opracowanie systemu Symphony, czyli spisanej specyfikacji, która pełni funkcję nadzorcy zarządzającego działaniami agentów.
Zamiana naszego systemu śledzenia zgłoszeń w system zarządzania agentami
Pomysł na Symphony był początkowo prosty: każde otwarte zadanie powinno zostać podjęte i ukończone przez agenta. Zamiast zarządzać sesjami Codex w wielu kartach, zmieniliśmy nasz system zarządzania zgłoszeniami w system sterowania.
W tej konfiguracji każdy otwarty problem w Linear jest przyporządkowywany do dedykowanej przestrzeń roboczej agenta. Symphony stale obserwuje tablicę zadań i zapewnia, że w przypadku każdego aktywnego zadania działa agent w pętli i wykonuje zadanie, dopóki nie zostanie ono ukończone. Jeśli agent ulegnie awarii lub się zawiesi, Symphony uruchamia go ponownie. Jeśli pojawi się nowe zadanie, Symphony je przejmuje i zaczyna organizować pracę.
Ten przepływ pracy stworzyliśmy w oparciu o statusy zgłoszeń, używając menedżera zadań Linear w charakterze maszyny stanów.
W praktyce Symphony oddziela pracę od sesji i od żądań pull request. Niektóre problemy prowadzą do wielu PR-ów w różnych repozytoriach, a inne to czyste badania lub analiza, które nigdy nie korzystają nawet z bazy kodu.
Gdy praca zostanie doprowadzona do tego poziomy abstrakcji, zgłoszenia mogą reprezentować znacznie większe jednostki pracy.
Regularnie używamy Symphony do zarządzania złożonymi funkcjami i migrowania infrastruktury. Na przykład możemy utworzyć zadanie z prośbą, by agent przeanalizował bazę kodu, Slack lub Notion i przygotował plan implementacji. Jeśli jesteśmy zadowoleni z opracowanego planu, agent generuje drzewo zadań, dzieląc pracę na etapy i definiując zależności między zadaniami.
Agenty zajmują się wyłącznie zadaniami, które nie są zablokowane, więc realizacja jest rozwijana naturalnie i optymalnie w przypadku tego DAG-a (sekwencji kroków wykonania). W poniższym przykładzie oznaczyliśmy aktualizację React jako zablokowaną przez migrację do Vite. Zgodnie z oczekiwaniami agenty zaczęły aktualizować React dopiero po zakończeniu migracji do Vite.
Agenty mogą też same tworzyć pracę. Podczas implementacji lub przeglądu często zauważają usprawnienia wykraczające poza zakres bieżącego zadania takie jak problemy z wydajnością, okazje do refaktoryzacji albo dopracowania architektury. W takich sytuacjach tworzą nowe zgłoszenia, które później oceniamy i przygotowujemy do wdrożenia; wiele z takich zadań następczych jest również podejmowanych przez agenty. Nadzorujemy ten proces, jednak to agenty utrzymują postęp prac i poziom organizacji.
Taki sposób pracy niesamowicie obniża koszt poznawczy rozpoczynania niejednoznacznej pracy. Jeśli agent zrobi coś źle, to nadal takie działanie jest użyteczne, a koszt dla nas jest bliski zeru. Możemy bardzo tanio tworzyć zgłoszenia, aby agent prototypował i eksplorował rozwiązania, a potem odrzucać wszystkie wyniki jego działań, które nam nie odpowiadają.
Ponieważ system zarządzania działa na devboxach i nigdy nie przestaje pracować, możemy zawsze dodawać zadania i mieć pewność, że agent je podejmie. Na przykład jeden z inżynierów w naszym zespole wprowadził trzy istotne zmiany z poziomu aplikacji Linear w telefonie, siedząc w przytulnej chatce z kiepskim dostępem do Wi‑Fi.
Większa skłonność do badania różnych rozwiązań
Gdy przyjrzeliśmy się skutkom pracy z Symphony, najbardziej oczywistą zmianą były wyniki. W niektórych zespołach w OpenAI liczba wdrożonych PR-ów wzrosła 6-krotnie w ciągu pierwszych trzech tygodni. W ramach działań poza OpenAI np. założyciel Linear, Karri Saarinen zwrócił uwagę na wzrost liczby tworzonych przestrzeni roboczych(otwiera nowe okno) po udostępnieniu Symphony. Jednak głębsza zmiana dotyczy zmiany podejścia do pracy przez zespoły.
Jeśli nasi programiści nie muszą już poświęcać czasu na nadzorowanie sesji Codex, gospodarka zmian w kodzie całkowicie się zmienia. Postrzegany koszt każdej zmiany spada, ponieważ nie musimy poświęcać pracy ludzi na samo prowadzenie implementacji.
Przez to zmieniło się nasze zachowanie. Uruchamianie spekulatywnych zadań w Symphony stało się banalne. Działamy już na zasadzie: wypróbuj pomysł, zbadaj refaktoryzację, przetestuj hipotezę i zachowaj tylko te wyniki, które wyglądają obiecująco.
Dzięki temu poszerza się również grono osób, które mogą inicjować pracę. Nasz menedżer produktu i projektant mogą teraz bezpośrednio zgłaszać prośby o funkcje do Symphony. Nie muszą pobierać repozytorium ani zarządzać sesją Codex. Wystarczy, że opiszą funkcję i dostają pakiet do weryfikacji, który zawiera film pokazujący działanie funkcji w prawdziwym produkcie.
Symphony świetnie sprawdza się też w dużych monorepozytoriach (takich jak to, które mamy w OpenAI), gdzie finalizacja wdrożenia PR-a to powolny i delikatny proces. System monitoruje CI, robi rebase, gdy to wymagane, rozwiązuje konflikty, ponawia niestabilne kontrole i ogólnie przeprowadza zmiany przez pipeline. Zanim zgłoszenie osiągnie status scalenia, mamy dużą pewność, że zmiana trafi do głównej gałęzi bez pomocy człowieka.
Postęp przynosi nowe problemy
Realizowanie pracy na takim poziomie wiąże się z kompromisami. Kiedy przeszliśmy od interaktywnego sterowania agentami do przydzielania im pracy na poziomie zgłoszeń, straciliśmy możliwość ciągłego korygowania ich w trakcie realizacji zadania, gdy było to wymagane. Czasem agent tworzył coś, co zupełnie rozmijało się z celem, jednak nadal było to użyteczne działanie, ponieważ takie porażki ujawniały luki w systemie i pomagały nam usprawniać.
Zamiast ręcznie łatać opracowany element, dodawaliśmy zabezpieczenia i umiejętności, aby agenty mogły wykonać swoje zadanie poprawnie następnym razem. Z czasem spowodowało to dodanie nowych funkcjonalności do naszego układu strukturalnego, takich jak uruchamianie testów całościowych, sterowanie aplikacją przez Chrome DevTools i zarządzanie testami smoke QA. Znacząco poprawiliśmy też naszą dokumentację i doprecyzowaliśmy definicję dobrego rezultatu.
Nie każde zadanie da się wykonać poprawnie z użyciem Symphony. Niektóre problemy nadal wymagają działań inżynierów wykonywanych bezpośrednio w interaktywnych sesjach Codex, zwłaszcza dotyczy to problemów niejednoznacznych lub prac wymagających doświadczenia lub podjęcia decyzji. W praktyce są to zwykle najciekawsze i najbardziej satysfakcjonujące zadania dla naszych inżynierów.
Symphony może obsłużyć większość rutynowej pracy implementacyjnej, a dzięki temu inżynierowie mogą skupić się na jednym trudnym problemie naraz, zamiast stale przeskakiwać między wieloma pomniejszymi zadaniami.
Zrozumieliśmy też, że traktowanie agentów jako sztywnych węzłów w maszynie stanów nie działa dobrze. Modele stają się mądrzejsze i potrafią rozwiązywać poważniejsze problemy wykraczające poza schemat, w który próbujemy je wcisnąć. Na przykład we wczesnych wersjach wszystkie integracje z GitHub były częścią zewnętrznego systemu: wczesne wersje zakładały, że Codex ma tylko wprowadzać zmiany w kodzie, a resztę procesu (zgłaszanie zmian, uruchamianie testów) określaliśmy w kodzie. Nasze pierwsze wersje systemów pracy agentowej polegały tylko na proszeniu Codex o zaimplementowanie zadania. Podejście takie okazało się zbyt ograniczające, ponieważ Codex potrafi tworzyć wiele PR-ów, a także czytać uwagi przekazane w ramach weryfikacji i na nie odpowiadać. Daliśmy mu więc narzędzia – CLI gh, umiejętności czytania dzienników CI itd. – i teraz możemy prosić Codex o więcej, na przykład o zamykanie starych PR-ów albo pobieranie raportów dotyczących ukończonej i porzuconej pracy. Tego typu zadania wykraczały daleko poza początkowe ramy implementacji funkcji.
Ostatecznie zaczęliśmy więc dawać agentom cele zamiast wytyczać im drogę do realizacji zadań, podobnie jak dobry menedżer przydziela cel podwładnemu w zespole. Siła modeli wynika z ich zdolności do rozumowania, więc należy im dać narzędzia i kontekst, a potem pozwolić działać.
Symphony buduje Symphony
Po otwarciu repozytorium Symphony pierwszą rzeczą, która rzuca się w oczy, jest to, że Symphony to tak właściwie plik SPEC.md z definicją problemu i zamierzonego rozwiązania. Zamiast budować złożony system nadzoru, zdefiniowaliśmy problem i oczekiwane rozwiązania, zapewniając agentom sterowanie na wysokim poziomie.
Implementacja referencyjna jest napisana w Elixir, bo jeśli kod jest praktycznie darmowy, można dobierać języki ze względu na ich zalety, takie jak współbieżność w przypadku Elixir, ale samą główną ideę można wyrazić w prostym dokumencie markdown. Warto odesłać swojego ulubionego agenta kodującego do specyfikacji i poprosić go o zaimplementowanie własnej wersji.
Pierwsza wersja Symphony była po prostu sesją Codex działającą w tmux, odpytującą Linear i uruchamiającą agenty podrzędne dla nowych zadań. Rozwiązanie to działało, ale było zawodne. Druga wersja działała w naszym głównym repozytorium projektu, które opracowano z myślą o agentach. Mieliśmy już układ strukturalny dla agentów, który zapewniał im umiejętności i kontekst potrzebne do wykonywania wysokiej jakości pracy w tym repozytorium, więc Symphony po prostu to wszystko łączy.
Gdy podstawowe funkcjonalności już istniały, użyliśmy Symphony do zbudowania Symphony.
Reakcja na wewnętrznie zaprezentowany przez nas system zarządzający zadaniami i dołączający film potwierdzający wykonanie pracy była wyjątkowo pozytywna: nasz kanał projektu Symphony rozwinął się, a zespoły w całej organizacji zaczęły używać go oddolnie. Wewnętrzne funkcje dopasowania produktu do rynku to warunek wstępny uruchomienia rozwiązania zewnętrznie w OpenAI. Na podstawie zastosowania, które widzieliśmy w OpenAI, wiedzieliśmy, że powinniśmy udostępnić Symphony zewnętrznie.
Dlatego wyodrębniliśmy tę ideę do oddzielnego pliku SPEC.md i poprosiliśmy Codex o jego implementację. Do implementacji referencyjnej wybraliśmy Elixir, dość niszowy język cechujący się doskonałymi mechanizmami do zarządzania współbieżnymi procesami i nadzorowania ich. Codex opracował implementację w Elixir za jednym podejściem, a następnie rozwijaliśmy specyfikację i samą implementację. W celu dopracowania specyfikacji poprosiliśmy Codex o zaimplementowanie jej w kilku innych językach (TypeScript, Go, Rust, Java, Python) i wykorzystanie wyników do wykrywania niejednoznaczności oraz uproszczenia systemu. Zadanie to udało się zrealizować każdym języku.
W trakcie tworzenia Codex usunęliśmy wiele niepotrzebnie złożonych aspektów, na przykład zależności względem konkretnych repozytoriów czy Linear MCP. System Symphony nie jest już zależny od naszych wewnętrznych repozytoriów ani przepływów pracy. Nasze główne założenie było proste:
W przypadku każdego otwartego zadania należy się upewnić, że agent działa w ramach własnej przestrzeni roboczej.
Agenty nie tylko pomagają w pracy ale rozumieją przepływ działań programistycznych i przestrzegają go. Przepływ działań programistycznych obejmujący pracę nad zgłoszeniem, pobranie repozytorium, oznaczenie zadania jako „w toku”, aby PM wiedział, że trwają nad nim prace, dodanie PR-a, zmianę jego statusu na „weryfikowany”, dołączanie filmów itd. jest teraz zapisany w prostym pliku WORKFLOW.md. Cały ten zakres zadań był procesem realizowanym przez pracowników, ale nigdy nie został formalnie udokumentowany. Więc w ramach standaryzowania spisaliśmy go, a Symphony pilnuje, by agenci go przestrzegali. Dzięki temu możemy budować agenty, które pracują wraz z nami. Jeśli uznamy, że agenty powinny także dołączać opinie i komentarze własne do ukończonej pracy, dodamy odpowiednie zapisy w WORKFLOW.md, a Symphony zapewni, że agenty zrealizują też i to zadanie.
Używaliśmy też Codex z wykorzystaniem wbudowanego trybu app server(otwiera nowe okno). Pozwoliło nam to uruchamiać Codex i komunikować się z tą aplikacją programowo za pośrednictwem dobrze udokumentowanego API JSON-RPC i w ten sposób uruchamiać wątki i reagować na tury. To znacznie wygodniejsze i bardziej skalowalne rozwiązanie niż realizacja działań z Codex przez CLI lub aktywne sesje tmux.
Codex App Server idealnie pasował do naszego przypadku użycia: korzystamy ze struktury zapewnianej przez Codex, a jednocześnie mamy możliwości podłączenia innych rozwiązań. Na przykład, aby nie ujawniać tokenu dostępu do Linear podagentom, używamy dynamicznych wywołań narzędzi(otwiera nowe okno), co pozwala nam udostępnić surową funkcję linear_graphql wykonującą dowolne żądania względem Linear bez polegania na MCP i bez ujawniania tokenu dostępu kontenerom.
Co dalej?
Symphony to warstwa zarządzająca, której zaletą jest jej minimalistyczność. Udostępniamy ją na licencji open source, aby zaprezentować moc Codex App Server w połączeniu z różnymi narzędziami do obsługi przepływu pracy, takimi jak Linear. Dlatego też nie planujemy utrzymywać Symphony jako samodzielnego produktu; traktujemy to rozwiązanie jako implementację referencyjną. Podobnie jak wielu programistów skierowało swoje agenty kodujące do wpisu o inżynierii strukturalnej, aby stworzyć szkielety swoich repozytoriów, mamy nadzieję, że skierują je też do specyfikacji(otwiera nowe okno) i repozytorium(otwiera nowe okno) Symphony, aby zbudować własne wersje tego rozwiązania dostosowane do różnych środowisk.
Siła tego systemu leży w Codex i jego trybie app server. Symphony stanowi połączenie Codex z Linear, czyli dwóch systemów, z których już korzystaliśmy, aby rozwiązać problem zarządzania pracą. W miarę jak agenty kodujące będą coraz lepiej rozumować i wykonywać instrukcje, podejrzewamy, że inne firmy też coraz bardziej zaczną się skupiać na zarządzaniu pracą agentową i stopniowo będą rezygnować z pisania kodu. Cieszy nas to, że poprzeczka wyznaczająca wymagany poziom kompetencji do rozpoczęcia eksperymentowania z tymi systemami agentów kodujących jest teraz ustawiona zaskakująco nisko. Z pomocą Codex można poprostu natychmiast zacząć tworzyć.
Wyróżnienia społeczności
Cieszy nas, że społeczność programistów korzysta z Symphony i w ciągu kilku tygodni od premiery projekt zdobył już ponad 15 tys. gwiazdek na GitHubie(otwiera nowe okno) według stanu na 23 kwietnia.