Una especificación de código abierto para la orquestación de Codex: Symphony
Por Alex Kotliarskyi, Victor Zhu y Zach Brock
Hace seis meses, mientras trabajábamos en una herramienta interna de productividad, nuestro equipo tomó una decisión controvertida (en aquel momento): crearíamos nuestro repositorio sin código escrito por humanos. Cada línea de nuestro repositorio del proyecto tenía que ser generada por Codex.
Para que eso funcionara, rediseñamos nuestro flujo de trabajo de ingeniería desde cero. Creamos un repositorio pensado para agentes, invertimos mucho en pruebas automatizadas y medidas de protección, y tratamos a Codex como un compañero de equipo de pleno derecho. Documentamos ese proceso en nuestra anterior entrada del blog sobre ingeniería de arneses.
Y funcionó, pero luego nos encontramos con el siguiente cuello de botella: el cambio de contexto.
Para resolver este nuevo problema, creamos un sistema llamado Symphony. Symphony(se abre en una ventana nueva) es un orquestador de agentes que convierte un tablero de gestión de proyectos como Linear en un plano de control para agentes de programación. Cada tarea abierta recibe un agente, los agentes se ejecutan de forma continua y los humanos revisan los resultados.
Esta publicación explica cómo creamos Symphony, lo que dio como resultado un aumento del 500 % en pull requests integradas en algunos equipos, y cómo usarlo para convertir tu propio rastreador de incidencias en un orquestador de agentes siempre activo.
El techo de los agentes de programación interactivos
Aunque cada vez sean más fáciles de usar, los agentes de programación —ya sea a través de aplicaciones web o de la CLI— siguen siendo herramientas interactivas.
A medida que la escala del trabajo con agentes aumentaba en OpenAI, encontramos un nuevo tipo de carga. Cada ingeniero abría unas cuantas sesiones de Codex, asignaba tareas, revisaba la salida, guiaba al agente y repetía. En la práctica, la mayoría de las personas podían gestionar cómodamente entre tres y cinco sesiones a la vez antes de que el cambio de contexto se volviera una pesadilla. Más allá de eso, la productividad bajaba. Olvidábamos qué estaba haciendo cada sesión, saltábamos de un terminal a otro para volver a encarrilar a los agentes y depurábamos tareas de larga duración que se quedaban atascadas a mitad de camino.
Los agentes eran rápidos, pero teníamos un cuello de botella en el sistema: la atención humana. En la práctica, habíamos creado un equipo de ingenieros júnior extremadamente capaces y luego habíamos asignado a nuestros ingenieros humanos la tarea de microgestionarlos. Eso no iba a funcionar a gran escala.
Un cambio de perspectiva
Nos dimos cuenta de que estábamos optimizando lo equivocado. Estábamos orientando nuestro sistema en torno a sesiones de programación y pull requests fusionadas, cuando las pull requests y las sesiones en realidad son un medio para alcanzar un fin. Los flujos de trabajo de software se organizan en gran medida en torno a entregables: incidencias, tareas, tickets, hitos.
Así que nos preguntamos qué pasaría si dejáramos de supervisar directamente a los agentes y, en su lugar, les permitiéramos extraer trabajo de nuestro rastreador de tareas.
Esa idea se convirtió en Symphony: una especificación escrita que funciona como supervisor para orquestar el trabajo con agentes.
Convertir nuestro rastreador de incidencias en un orquestador de agentes
Symphony partió de una idea sencilla: cualquier tarea pendiente debía ser asignada y completada por un agente. En lugar de gestionar sesiones de Codex en varias pestañas, hicimos de nuestro rastreador de incidencias el plano de control.
En esta configuración, cada incidencia abierta en Linear se asigna a un área de trabajo específica para un agente. Symphony supervisa continuamente el tablero de tareas y se asegura de que cada tarea activa tenga un agente ejecutándose en el bucle hasta que esté terminada. Si un agente falla o se queda bloqueado, Symphony lo reinicia. Si aparece trabajo nuevo, Symphony lo detecta y empieza a organizarlo.
Creamos nuestro flujo de trabajo basándonos en los estados de los tickets, usando el gestor de tareas Linear como una máquina de estados.
En la práctica, Symphony desacopla el trabajo de las sesiones y de las pull requests. Algunas incidencias generan varias pull requests en distintos repositorios; otras son pura investigación o análisis y nunca tocan la base de código.
Una vez que el trabajo se abstrae de esta manera, los tickets pueden representar unidades de trabajo mucho mayores.
Usamos Symphony con frecuencia para orquestar funciones complejas y migraciones de infraestructura. Por ejemplo, podemos crear una tarea pidiendo al agente que analice la base de código, Slack o Notion y produzca un plan de implementación. Una vez que estamos satisfechos con el plan, el agente genera un árbol de tareas, dividiendo el trabajo en etapas y definiendo dependencias entre tareas.
Los agentes solo empiezan a trabajar en tareas que no están bloqueadas, por lo que la ejecución se desarrolla de forma natural y óptima en paralelo para este DAG (una secuencia de pasos de ejecución). En el ejemplo de abajo, marcamos la actualización de React como bloqueada por una migración a Vite. Como era de esperar, los agentes empezaron a actualizar React solo después de que la migración a Vite se completara.
Los agentes también pueden crear trabajo por sí mismos. Durante la implementación o la revisión, a menudo detectan mejoras que quedan fuera del alcance de la tarea actual: un problema de rendimiento, una oportunidad de refactorización o una mejor arquitectura. Cuando eso ocurre, simplemente crean una nueva incidencia que podemos evaluar y programar más adelante; muchas de estas tareas de seguimiento también las asumen agentes. Aunque supervisamos este proceso, los agentes organizan su trabajo y lo hacen avanzar.
Esta forma de trabajar reduce drásticamente el coste cognitivo de poner en marcha trabajo ambiguo. Si el agente se equivoca en algo, eso sigue siendo información útil y el coste para nosotros es casi nulo. Podemos crear tickets a muy bajo coste para que el agente haga prototipos y explore, y descartar cualquier exploración que no nos guste.
Como el orquestador se ejecuta en devboxes y nunca duerme, podemos añadir tareas desde cualquier lugar y saber que un agente las asumirá. Por ejemplo, un ingeniero de nuestro equipo hizo tres cambios importantes desde la app de Linear de su teléfono, sentadito en una cabaña acogedora con una conexión wifi pésima.
Mayor exploración por trabajar de esta manera
Al observar los efectos de trabajar con Symphony, el cambio más evidente fue la producción. En algunos equipos de OpenAI, vimos que el número de pull requests integradas aumentó 6 veces en las primeras tres semanas. Fuera de OpenAI, Karri Saarinen, fundador de Linear, destacó un aumento en las áreas de trabajo creadas(se abre en una ventana nueva) cuando lanzamos Symphony. Sin embargo, el cambio más profundo es la forma en que los equipos conciben el trabajo.
Cuando nuestros ingenieros ya no dedican tiempo a supervisar sesiones de Codex, la economía de los cambios de código cambia por completo. El coste percibido de cada cambio baja porque ya no estamos invirtiendo esfuerzo humano en impulsar la implementación en sí.
Eso cambió nuestra forma de trabajar. Ahora es muy fácil poner en marcha tareas experimentales en Symphony. Prueba una idea, explora una refactorización, comprueba una hipótesis y quédate solo con los resultados que parezcan prometedores.
Además, amplía el número de personas que pueden iniciar un trabajo. Nuestro responsable de producto y nuestro diseñador ahora pueden crear solicitudes de funciones directamente en Symphony. No necesitan consultar el repositorio ni gestionar una sesión de Codex. Describen la función y reciben un paquete de revisión que incluye un vídeo en el que se muestra la función dentro del producto real.
Symphony también destaca en monorepositorios grandes (como el que tenemos en OpenAI), donde la última etapa para integrar una pull request es lenta y delicada. El sistema supervisa la integración continua, hace rebase cuando es necesario, resuelve conflictos, vuelve a intentar las comprobaciones que fallan y, en general, guía los cambios a lo largo del flujo. Para cuando un ticket llega a Merging, confiamos plenamente en que el cambio se incorporará a la rama principal sin necesidad de supervisión humana constante.
El progreso trae consigo problemas nuevos y diferentes
Operar a este nivel tiene sus pros y sus contras. Cuando pasamos de dirigir a los agentes de forma interactiva a asignarles tareas a nivel de ticket, perdimos la capacidad de guiarlos constantemente sobre la marcha y corregir el rumbo cuando fuera necesario. A veces, el agente producía algo que se desviaba por completo del objetivo. Eso fue útil: esos fallos revelaron las deficiencias del sistema y nos ayudaron a hacerlo más robusto.
En lugar de arreglar el resultado a mano, añadimos medidas de seguridad y herramientas para que los agentes pudieran hacerlo bien la próxima vez. Con el tiempo, esto nos llevó a añadir nuevas funciones a nuestro arnés, como ejecutar pruebas integrales, controlar la aplicación a través de Chrome DevTools y gestionar las pruebas de control de calidad básicas. Mejoramos significativamente nuestra documentación y dejamos claro qué es lo que se espera.
No todas las tareas encajan en el estilo de trabajo de Symphony. Algunos problemas siguen requiriendo que los ingenieros trabajen directamente con sesiones interactivas de Codex, sobre todo los problemas ambiguos o las tareas que exigen un buen criterio y experiencia. En la práctica, estas suelen ser las tareas más interesantes y gratificantes en las que nuestros ingenieros invierten su tiempo.
La diferencia es que Symphony se encarga de la mayor parte del trabajo rutinario de implementación. Eso permite a los ingenieros centrarse en un solo problema complejo cada vez, en lugar de tener que cambiar constantemente de un contexto a otro para realizar tareas más pequeñas.
También aprendimos que tratar a los agentes como nodos rígidos en una máquina de estados no funciona bien. Los modelos se vuelven más inteligentes y pueden resolver problemas más complejos de lo que nos permite el marco en el que intentamos encajarlos. Por ejemplo, las primeras versiones tenían todas las integraciones de GitHub como parte del arnés externo; de hecho, las primeras versiones esperaban que Codex solo realizara cambios en el código, especificando el resto del proceso (enviar cambios, ejecutar pruebas) en el código. Nuestras primeras versiones del trabajo con agentes solo le pedían a Codex que implementara la tarea. Ese enfoque resultó demasiado limitante. Codex es perfectamente capaz de crear varias pull requests, así como de leer los comentarios de las revisiones y abordarlos. Así que le dimos herramientas (CLI de gh, habilidades para leer registros de integración continua, etc.) y ahora podemos pedirle a Codex que haga más cosas, como cerrar pull requests antiguas o generar informes sobre el trabajo completado frente al abandonado. Este tipo de tareas quedaban totalmente fuera del marco inicial de implementación de funciones.
Así que al final decidimos dar a los agentes objetivos en lugar de transiciones estrictas, igual que un buen gerente asignaría una meta a un subordinado directo de su equipo. El poder de los modelos reside en su capacidad de razonar, así que hay que darles herramientas y contexto, y dejar que hagan su trabajo.
Usar Symphony para crear Symphony
Cuando abres el repositorio de Symphony, lo primero que notarás es que Symphony técnicamente es solo un archivo SPEC.md: una definición del problema y de la solución prevista. En lugar de crear un sistema complejo de supervisión, definimos el problema y las soluciones previstas, dando a los agentes una guía de alto nivel.
La implementación de referencia está escrita en Elixir —porque cuando el código es prácticamente gratis, por fin puedes elegir lenguajes por sus puntos fuertes, como la concurrencia de Elixir—, pero la idea central puede expresarse en un sencillo documento Markdown. Te animamos a apuntar tu agente de programación favorito a la especificación y hacer que implemente su propia versión.
La primera versión de Symphony era simplemente una sesión de Codex ejecutándose en tmux, que consultaba a Linear y creaba subagentes para nuevas tareas. Funcionaba, pero no era especialmente fiable. La segunda versión vivía dentro de nuestro repositorio principal del proyecto, que se había diseñado pensando en los agentes. Ya habíamos creado el arneses de agentes para darles las capacidades y el contexto necesarios para hacer trabajo de alta calidad en este repositorio, así que Symphony simplemente lo conecta todo.
Una vez que existió la funcionalidad básica, usamos Symphony para crear Symphony.
Cuando internamente hicimos una demo del sistema gestionando tareas y adjuntando su vídeo de prueba de trabajo, la reacción fue abrumadoramente positiva: nuestro canal del proyecto Symphony creció y equipos de toda la organización empezaron a usarlo de forma orgánica. Que un producto encaje en el mercado interno es un requisito imprescindible para lanzarlo al mercado externo en OpenAI. Basándonos en el uso que vimos en OpenAI, quedó claro que debíamos compartir Symphony más allá de los muros de la empresa.
Así que extrajimos la idea a un SPEC.md independiente y le pedimos a Codex que lo implementara. Para la implementación de referencia, elegimos Elixir, un lenguaje relativamente especializado con excelentes primitivas para orquestar y supervisar procesos concurrentes. Codex creó la implementación en Elixir con un ejemplo, y desde ahí seguimos iterando tanto en la especificación como en la implementación. Para pulir la especificación, incluso le pedimos a Codex que la implementara en otros lenguajes —TypeScript, Go, Rust, Java, Python— y que usara los resultados para identificar ambigüedades y simplificar el sistema. Funcionó en todos los lenguajes.
Durante el proceso de crear Codex, eliminamos mucha complejidad accidental, como las dependencias de repositorios específicos o de Linear MCP. Symphony ya no depende de nuestros repositorios internos ni de nuestros flujos de trabajo. El enfoque principal se simplificó:
Para cada tarea abierta, garantiza que haya un agente ejecutándose en su propia área de trabajo.
Además de ayudar con el trabajo activo, el flujo de trabajo de desarrollo es ahora algo que los agentes conocen y siguen. El flujo de trabajo de desarrollo —trabajar en una incidencia, consultar un repositorio, marcarla como «en curso» para que el gestor de proyectos sepa que se está trabajando en ello, añadir la pull request, pasarla al estado «En revisión», adjuntar vídeos, etc.— ahora está recogido en un sencillo archivo WORKFLOW.md. Todo esto es un proceso que los humanos seguían, pero que nunca se había documentado. En lugar de basarnos en este conjunto implícito de pasos, ahora lo documentamos, y Symphony se asegura de que los agentes lo sigan. Esto nos permite crear agentes que trabajen en equipo con nosotros. Si decidimos que los agentes también deben adjuntar una autorreflexión al trabajo terminado, lo añadiremos al WORKFLOW.md, y Symphony guiará a los agentes hacia ese paso.
También pudimos usar Codex en modo de servidor de la aplicación(se abre en una ventana nueva), un modo sin interfaz integrado en Codex. Este modo nos permitió ejecutar Codex y comunicarnos con él de forma programática a través de una API JSON-RPC bien documentada para cosas como iniciar un hilo o reaccionar a los turnos. Es una forma mucho más cómoda y escalable que intentar interactuar con Codex a través de la CLI o de sesiones de tmux en directo.
El App Server de Codex encajaba perfectamente con nuestro caso de uso: aprovechamos el arnés que proporciona Codex y, al mismo tiempo, disponemos de controles y hooks a los que recurrir. Por ejemplo, para evitar exponer el token de acceso de Linear a los subagentes, usamos dynamic tool calls(se abre en una ventana nueva) para exponer la función linear_graphql sin procesar, que ejecuta solicitudes arbitrarias contra Linear, sin depender de MCP ni exponer el token de acceso a los contenedores.
Qué viene ahora
Symphony es una capa de orquestación intencionadamente mínima. La hemos convertido en código abierto para demostrar el poder del App Server de Codex cuando se combina con distintas herramientas del flujo de trabajo, como Linear. Por eso, no tenemos previsto continuar con Symphony como producto independiente. Piensa en ello como una implementación de referencia. Del mismo modo que muchos desarrolladores apuntaron sus agentes de programación a la publicación sobre ingeniería de arneses para estructurar sus repositorios, esperamos que apuntes tu agente de programación favorito a la especificación(se abre en una ventana nueva) y al repositorio(se abre en una ventana nueva) de Symphony para crear tus propias versiones adaptadas a tus entornos.
La potencia viene de Codex y de su servidor de la aplicación. Symphony fue una forma de conectar Codex con Linear, dos elementos que ya usábamos, para resolver el problema de la gestión del trabajo. A medida que los agentes de programación mejoren en su capacidad de razonamiento y de seguir instrucciones, es probable que el cuello de botella en otras empresas pase de la escritura de código a la gestión del trabajo de los agentes. Lo emocionante es que ahora la barrera para experimentar con estos sistemas de agentes de programación es sorprendentemente baja. Basta con crear cosas con Codex.
Menciones a la comunidad
Estamos encantados de ver que la comunidad de ingenieros está usando Symphony desde su lanzamiento, y que ya ha conseguido más de 15 000 estrellas en GitHub(se abre en una ventana nueva) hasta el 23 de abril.