Une spécification open source pour l’orchestration de Codex : Symphony
Par Alex Kotliarskyi, Victor Zhu et Zach Brock
Il y a six mois, pendant que nous travaillions sur un outil de productivité interne, notre équipe a pris une décision controversée (à l’époque) : nous construirions notre dépôt sans code écrit par des humains. Chaque ligne de notre dépôt de projet devait être générée par Codex.
Pour que ça fonctionne, nous avons repensé notre flux d’ingénierie de fond en comble. Nous avons construit un dépôt compatible avec les agents, investi massivement dans les tests automatisés et les garde-fous, et traité Codex comme un véritable coéquipier. Nous avons documenté ce parcours dans notre précédent billet de blogue sur l’ingénierie d’exploitation.
Et ça a fonctionné, mais nous avons ensuite rencontré le prochain goulot d’étranglement : le changement de contexte.
Pour résoudre ce nouveau problème, nous avons créé un système appelé Symphony. Symphony(s'ouvre dans une nouvelle fenêtre) est un orchestrateur d’agents qui transforme un tableau de gestion de projet comme Linear en plan de contrôle pour les agents de codage. Chaque tâche ouverte reçoit un agent, les agents s’exécutent en continu, et les humains examinent les résultats.
Ce billet explique comment nous avons créé Symphony—ce qui a entraîné une augmentation de 500 % des pull requests intégrées dans certaines équipes—et comment l’utiliser pour transformer votre propre système de suivi des tickets en orchestrateur d’agents toujours actif.
Le plafond des agents de codage interactifs
Même s’ils deviennent plus faciles à utiliser, les agents de codage—qu’on y accède par des applis web ou en CLI—restent des outils interactifs.
À mesure que l’ampleur du travail agentique augmentait chez OpenAI, nous avons découvert un nouveau type de fardeau. Chaque ingénieur ouvrait quelques sessions Codex, assignait des tâches, examinait les résultats, orientait l’agent, puis recommençait. En pratique, la plupart des gens pouvaient gérer confortablement de trois à cinq sessions à la fois avant que le changement de contexte devienne pénible. Au-delà, la productivité chutait. Nous oubliions quelle session faisait quoi, passions d’un terminal à l’autre pour remettre les agents sur la bonne voie, et déboguions des tâches de longue durée qui se bloquaient à mi-chemin.
Les agents étaient rapides, mais nous avions un goulot d’étranglement système : l’attention humaine. Nous avions en pratique bâti une équipe d’ingénieurs juniors extrêmement compétents, puis demandé à nos ingénieurs humains de les microgérer. Ce n’était pas viable à grande échelle.
Un changement de perspective
Nous avons réalisé que nous optimisions la mauvaise chose. Nous organisions notre système autour des sessions de codage et des PR fusionnées, alors que les PR et les sessions ne sont en réalité qu’un moyen d’arriver à une fin. Les flux logiciels sont largement organisés autour de livrables : problèmes, tâches, tickets, jalons.
Nous nous sommes donc demandé ce qui se passerait si nous cessions de superviser directement les agents et les laissions plutôt tirer le travail de notre outil de suivi des tâches.
Cette idée est devenue Symphony, une spécification écrite qui agit comme superviseur pour orchestrer le travail agentique.
Transformer notre outil de suivi des tickets en orchestrateur d’agents
Symphony a commencé avec un concept simple : toute tâche ouverte devrait être prise en charge et complétée par un agent. Au lieu de gérer des sessions Codex dans plusieurs onglets, nous avons fait de notre outil de suivi des tickets le plan de contrôle.
Dans cette configuration, chaque ticket Linear ouvert correspond à un espace de travail d’agent dédié. Symphony surveille continuellement le tableau des tâches et s’assure que chaque tâche active a un agent qui tourne dans la boucle jusqu’à ce qu’elle soit terminée. Si un agent plante ou se bloque, Symphony le redémarre. Si du nouveau travail apparaît, Symphony le prend en charge et commence à organiser le travail.
Nous avons bâti notre flux de travail sur les statuts des tickets, en utilisant le gestionnaire de tâches Linear comme machine à états.
En pratique, Symphony dissocie le travail des sessions et des pull requests. Certains problèmes donnent lieu à plusieurs PR dans différents dépôts; d’autres sont de simples enquêtes ou analyses qui ne touchent jamais à la base de code.
Une fois le travail abstrait de cette façon, les tickets peuvent représenter des unités de travail beaucoup plus grandes.
Nous utilisons régulièrement Symphony pour orchestrer des fonctionnalités complexes et des migrations d’infrastructure. Par exemple, nous pouvons créer une tâche demandant à l’agent d’analyser la base de code, Slack ou Notion et de produire un plan d’implémentation. Une fois satisfaits du plan, l’agent génère un arbre de tâches, divisant le travail en étapes et définissant les dépendances entre les tâches.
Les agents commencent à travailler uniquement sur les tâches qui ne sont pas bloquées, donc l’exécution se déroule naturellement et de façon optimale en parallèle pour ce DAG (une séquence d’étapes d’exécution). Dans l’exemple ci-dessous, nous avons indiqué que la mise à niveau de React était bloquée par une migration vers Vite. Comme prévu, les agents ont commencé la mise à niveau de React seulement après la fin de la migration vers Vite.
Les agents peuvent aussi créer eux-mêmes du travail. Pendant l’implémentation ou la révision, ils remarquent souvent des améliorations qui sortent du cadre de la tâche en cours : un problème de performance, une occasion de refactorisation ou une meilleure architecture. Quand cela arrive, ils créent simplement un nouveau ticket que nous pouvons évaluer et planifier plus tard—beaucoup de ces tâches de suivi sont aussi prises en charge par des agents. Pendant que nous supervisons ce processus, les agents restent organisés et font avancer le travail.
Cette façon de travailler réduit considérablement le coût cognitif du lancement d’un travail ambigu. Si l’agent se trompe, c’est quand même une information utile, et le coût pour nous est presque nul. Nous pouvons créer des tickets à très faible coût pour que l’agent fasse des prototypes et de l’exploration, puis jeter toute exploration qui ne nous plaît pas.
Comme l’orchestrateur fonctionne sur des devboxes et ne dort jamais, nous pouvons ajouter des tâches de n’importe où en sachant qu’un agent les prendra en charge. Par exemple, un ingénieur de notre équipe a effectué trois changements importants depuis l’appli Linear sur son téléphone, dans un chalet douillet avec un wifi médiocre.
Une hausse de l’exploration grâce à cette façon de travailler
En observant les effets du travail avec Symphony, le changement le plus évident a été le rendement. Dans certaines équipes chez OpenAI, nous avons vu le nombre de PR intégrées augmenter de 6x au cours des trois premières semaines. À l’extérieur d’OpenAI, le fondateur de Linear, Karri Saarinen, a souligné un bond du nombre d’espaces de travail créés(s'ouvre dans une nouvelle fenêtre) au moment de la sortie de Symphony. Cependant, le changement le plus profond concerne la façon dont les équipes pensent au travail.
Quand nos ingénieurs ne passent plus leur temps à superviser des sessions Codex, l’économie des changements de code change complètement. Le coût perçu de chaque changement diminue parce que nous n’investissons plus d’effort humain dans l’exécution même de l’implémentation.
Cela a changé notre comportement. Il est devenu trivial de lancer des tâches spéculatives dans Symphony. Essayez une idée, explorez une refactorisation, testez une hypothèse et ne gardez que les résultats qui semblent prometteurs.
Cela élargit aussi le cercle de ceux qui peuvent lancer du travail. Notre gestionnaire de produit et notre designer peuvent maintenant soumettre directement des demandes de fonctionnalités dans Symphony. Ils n’ont pas besoin de récupérer le dépôt ni de gérer une session Codex. Ils décrivent la fonctionnalité et reçoivent en retour un dossier de révision qui comprend une vidéo guidée de la fonctionnalité en action dans le vrai produit.
Symphony brille aussi dans les grands monodépôts (comme celui que nous avons chez OpenAI), où la dernière étape avant l’intégration d’une PR est lente et fragile. Le système surveille la CI, rebase au besoin, résout les conflits, réessaie les vérifications instables et, de manière générale, accompagne les changements tout au long du pipeline. Au moment où un ticket atteint le statut Merging, nous avons un haut niveau de confiance que le changement sera intégré à la branche principale sans gardiennage humain.
Les progrès s’accompagnent de nouveaux problèmes, différents
Travailler à ce niveau comporte des compromis. Quand nous sommes passés du pilotage interactif des agents à l’assignation de travail au niveau des tickets, nous avons perdu la capacité de les réorienter constamment en cours de route et de corriger le tir au besoin. Parfois, l’agent produisait quelque chose qui ratait complètement la cible. C’était utile—ces échecs révélaient des lacunes du système et nous aidaient à le rendre plus robuste.
Au lieu de corriger manuellement le résultat, nous avons ajouté des garde-fous et des compétences pour que les agents réussissent la fois suivante. Avec le temps, cela nous a amenés à ajouter de nouvelles capacités à notre exploitation, comme l’exécution de tests de bout en bout, le pilotage de l’application via Chrome DevTools et la gestion des tests de validation de base de QA. Nous avons considérablement amélioré notre documentation et clarifié ce à quoi ressemble un bon résultat.
Toutes les tâches ne conviennent pas au style de travail de Symphony. Certains problèmes exigent encore que des ingénieurs travaillent directement avec des sessions Codex interactives, surtout les problèmes ambigus ou les travaux qui nécessitent un bon jugement et de l’expertise. En pratique, ce sont habituellement les tâches les plus intéressantes et les plus agréables sur lesquelles nos ingénieurs passent du temps.
La différence, c’est que Symphony peut prendre en charge l’essentiel du travail d’implémentation routinier. Cela permet aux ingénieurs de se concentrer sur un seul problème difficile à la fois, au lieu de changer constamment de contexte entre de plus petites tâches.
Nous avons aussi appris qu’il n’est pas très efficace de traiter les agents comme des nœuds rigides dans une machine à états. Les modèles deviennent plus intelligents et peuvent résoudre des problèmes plus vastes que la boîte dans laquelle nous essayons de les faire entrer. Par exemple, dans les premières versions, toutes les intégrations GitHub faisaient partie de l’exploitation externe—par exemple, les premières versions s’attendaient à ce que Codex ne fasse que des modifications de code, le reste du processus (soumettre les changements, exécuter les tests) étant spécifié dans le code. Nos premières versions du travail agentique ne demandaient à Codex que d’implémenter la tâche. Cette approche s’est révélée trop limitative. Codex est tout à fait capable de créer plusieurs PR ainsi que de lire les commentaires de révision et d’y répondre. Nous lui avons donc donné des outils—la CLI gh, des compétences pour lire les journaux de CI, etc.—et maintenant nous pouvons demander à Codex d’en faire davantage, comme fermer d’anciennes PR ou produire des rapports sur le travail terminé par rapport au travail abandonné. Ces types de tâches sortaient largement du cadre initial d’implémentation de fonctionnalité.
Nous avons donc fini par évoluer vers une approche où l’on donne aux agents des objectifs plutôt que des transitions strictes, un peu comme un bon gestionnaire assignerait un objectif à une personne sous sa responsabilité dans son équipe. La puissance des modèles vient de leur capacité de raisonnement, alors donnez-leur des outils et du contexte, puis laissez-les faire.
Utiliser Symphony pour créer Symphony
Quand vous ouvrez le dépôt Symphony, la première chose que vous remarquerez, c’est que Symphony n’est techniquement qu’un fichier SPEC.md—une définition du problème et de la solution visée. Plutôt que de bâtir un système de supervision complexe, nous avons défini le problème et les solutions visées, en donnant aux agents un guidage de haut niveau.
L’implémentation de référence est écrite en Elixir—parce que lorsque le code est essentiellement gratuit, vous pouvez enfin choisir les langages pour leurs forces, comme la concurrence d’Elixir—mais l’idée de base peut s’exprimer dans un simple document Markdown. Nous vous encourageons à diriger votre agent de codage préféré vers la spécification et à lui faire implémenter sa propre version.
La première version de Symphony n’était qu’une session Codex exécutée dans tmux, qui sondait Linear et lançait des sous-agents pour les nouvelles tâches. Ça fonctionnait, mais ce n’était pas particulièrement fiable. La deuxième version vivait dans notre principal dépôt de projet, qui avait été conçu en pensant aux agents. Nous avions déjà créé le cadre d’exécution des agents pour leur donner les capacités et le contexte nécessaires afin d’effectuer un travail de grande qualité dans ce dépôt, alors Symphony ne faisait que tout relier.
Une fois les fonctionnalités de base en place, nous avons utilisé Symphony pour créer Symphony.
Lorsque nous avons présenté en interne le système gérant les tâches et joignant sa vidéo de preuve de travail, la réaction a été extrêmement positive : notre canal de projet Symphony a pris de l’ampleur, et des équipes partout dans l’organisation ont commencé à l’utiliser spontanément. Une adéquation produit-marché en interne est un préalable à un lancement externe chez OpenAI. D’après l’usage que nous avons observé chez OpenAI, il est devenu clair que nous devions partager Symphony au-delà des murs de l’entreprise.
Nous avons donc extrait l’idée dans un SPEC.md autonome et demandé à Codex de l’implémenter. Pour l’implémentation de référence, nous avons choisi Elixir, un langage relativement niché avec d’excellentes primitives pour orchestrer et superviser des processus concurrents. Codex a produit l’implémentation Elixir d’un seul coup, puis nous avons continué à itérer sur la spécification et l’implémentation à partir de là. Pour peaufiner la spécification, nous avons même demandé à Codex de l’implémenter dans plusieurs autres langages—TypeScript, Go, Rust, Java, Python—et d’utiliser les résultats pour relever les ambiguïtés et simplifier le système. Il a réussi dans tous les langages.
Au fil du processus de création de Codex, nous avons éliminé beaucoup de complexité accessoire, comme les dépendances à des dépôts précis ou à Linear MCP. Symphony ne dépend plus de nos dépôts ni de nos flux de travail internes. L’approche de base est devenue simple :
Pour chaque tâche ouverte, garantir qu’un agent s’exécute dans son propre espace de travail.
En plus d’aider avec le travail en cours, le flux de développement est maintenant quelque chose que les agents connaissent et suivent. Le flux de développement—travailler sur un ticket, récupérer un dépôt, le mettre en cours pour que le PM sache qu’il est en traitement, ajouter la PR, le déplacer au statut Review, joindre des vidéos, etc.—est maintenant consigné dans un simple fichier WORKFLOW.md. Tout cela correspond à un processus que les humains suivaient, mais qui n’avait jamais été documenté. Plutôt que de nous appuyer sur cet ensemble implicite d’étapes, nous le documentons maintenant, et Symphony veille à ce que les agents le suivent. Cela nous permet de créer des agents qui travaillent à nos côtés. Si nous décidons que les agents doivent aussi joindre une auto-réflexion au travail terminé, nous l’ajouterons au WORKFLOW.md, et Symphony guidera les agents vers cette étape.
Nous avons aussi pu utiliser Codex en mode serveur d’application(s'ouvre dans une nouvelle fenêtre), un mode headless intégré pour Codex. Ce mode nous a permis d’exécuter Codex et de communiquer avec lui par programmation via une API JSON-RPC bien documentée pour des actions comme démarrer un fil ou réagir à des tours. C’est une façon beaucoup plus pratique et évolutive que d’essayer d’interagir avec Codex via la CLI ou des sessions tmux en direct.
Codex App Server convenait parfaitement à notre cas d’usage : nous tirons parti de l’exploitation fournie par Codex tout en ayant des boutons et des hooks auxquels nous brancher. Par exemple, pour éviter d’exposer le token d’accès Linear aux sous-agents, nous utilisons des appels d’outils dynamiques(s'ouvre dans une nouvelle fenêtre) pour exposer la fonction brute linear_graphql qui exécute des requêtes arbitraires contre Linear, sans dépendre de MCP ni exposer le token d’accès aux conteneurs.
Et ensuite?
Symphony est une couche d’orchestration volontairement minimale. Nous l’ouvrons en source pour démontrer la puissance de Codex App Server lorsqu’il est jumelé à différents outils de flux de travail, comme Linear. À ce titre, nous ne prévoyons pas maintenir Symphony comme produit autonome. Voyez-le comme une implémentation de référence. De la même façon que de nombreux développeurs ont pointé leurs agents de codage vers le billet sur l’ingénierie d’exploitation pour structurer leurs dépôts, nous espérons que vous pointerez votre agent de codage préféré vers la spécification(s'ouvre dans une nouvelle fenêtre) et le dépôt(s'ouvre dans une nouvelle fenêtre) Symphony afin de créer vos propres versions adaptées à vos environnements.
La puissance vient de Codex et de son serveur d’application. Symphony était une façon de connecter Codex à Linear, deux choses que nous utilisions déjà, pour résoudre le problème de la gestion du travail. À mesure que les agents de codage deviennent meilleurs en raisonnement et en suivi d’instructions, nous soupçonnons que, dans d’autres entreprises aussi, le goulot d’étranglement passera de l’écriture de code à la gestion du travail agentique. Ce qui est enthousiasmant, c’est que la barrière à l’expérimentation avec ces systèmes d’agents de codage est maintenant étonnamment basse. Vous pouvez simplement créer des choses avec Codex.
Mentions à la communauté
Nous sommes ravis de voir la communauté de l’ingénierie utiliser Symphony dans les semaines suivant sa sortie, avec plus de 15 k étoiles sur GitHub(s'ouvre dans une nouvelle fenêtre) en date du 23 avril.