Diseño de un entorno de ejecución programable para la orquestación de agentes

@realmcore_
INGLÉShace 23 horas · 03 jul 2026
283K
260
45
10
456

TL;DR

Onyx presenta un entorno de ejecución programable para agentes de IA, lo que permite a los desarrolladores crear sistemas deterministas utilizando TypeScript, estado persistente y manejo estructurado de errores.

Este artículo presenta Onyx, nuestra VM para orquestación programable de agentes. Y, por extensión, un runtime que convierte la orquestación en ingeniería de software. Al final de este artículo, comprenderás las restricciones y decisiones de diseño que intervinieron en la construcción de la VM, así como cómo crear tus propios programas y arquitectar tus sistemas de agentes.

Introducción

Los agentes son inherentemente no deterministas. Ese es el punto central. Si quisieras determinismo, estarías escribiendo software.

Pero en algún momento, todos los que usaban agentes quisieron llevarlos más lejos colectivamente. Aprendimos que dividir la ejecución en pasos estructurados ayuda al rendimiento: Planificar, Implementar, Revisar, QA, etc. Luego, acordamos aparentemente escribir scripts, herramientas y habilidades para dirigir cada agente, compartir contexto entre ellos y ponerles barreras de contención. Después, unimos estos scripts pasando texto entre agentes y, como solo pasamos texto, más o menos funciona.

Si dedicabas suficiente tiempo al problema y eras particularmente ingenioso, habrías descubierto cómo obtener garantías de tu sistema para poder tener ejecución condicional basada en un estado determinado. Y probablemente almacenarías ese estado en un archivo de marcado analizable o en un conjunto de archivos de marcado para dirigir tus scripts de bash. Incluso podrías haber creado una CLI personalizada para que tus agentes la usen.

Como ingenieros, esto nos resulta familiar, usamos scripts al hacer ingeniería de software. Sin embargo, el software moderno no se construye encadenando scripts de bash y herramientas de CLI. En cambio, tenemos lenguajes de programación, runtimes y cadenas de herramientas para ayudarnos a diseñar nuestros sistemas. Escribimos software con lenguajes de programación porque vienen con una biblioteca estándar, semántica clara y un modelo de ejecución en el que podemos confiar. Tienen ecosistemas ricos con cadenas de herramientas para todas nuestras necesidades.

Las garantías que nos brindan sobre nuestros sistemas nos permiten razonar a niveles más altos de abstracción.

Pero no existe un equivalente para diseñar sistemas de agentes. Para construir sistemas, la orquestación de agentes necesita ser programable exactamente de la misma manera que el software moderno.

Hoy, presentamos la especificación para PROGRAMS (*.program.ts), y Onyx, nuestra VM construida para la orquestación determinista de agentes. Este artículo explora una historia de la orquestación de agentes, la semántica estática y de runtime de una VM que puede ejecutar un programa, y sus implicaciones para hacia dónde se dirige el campo.

Suena costoso, pero en realidad no lo es. Explico esto más adelante en el artículo.

Para los curiosos, así es como se ve el Autoresearch de Andrej Karpathy como un programa:

akira - inline image

Problemas no resueltos en la orquestación de agentes

Para entender lo que debería incluir un runtime para la orquestación de agentes, necesitamos comprender las limitaciones de los agentes.

Un agente LLM puede considerarse como un generador de flujo JSON, alimentado a un analizador sintáctico, que luego envía llamadas a herramientas a un entorno en un bucle.

Cada llamada a una herramienta tiene exactamente la misma forma de esquema externo, pero el contenido de este flujo de salida no es determinista.

La combinación de determinismo y no determinismo es lo que ha hecho que los agentes sean tan valiosos. Son lo suficientemente flexibles para encadenar secuencias de acciones de maneras únicas, pero lo suficientemente deterministas para interactuar con una computadora a través de llamadas a herramientas.

La componibilidad es casi gratuita si estás dispuesto a renunciar al requisito de que el contenido de ese flujo esté tipado. Los modelos son lo suficientemente buenos para canalizar texto hacia adentro y hacia afuera dentro de los rieles que les proporcionamos: prompts, mensajes y llamadas a herramientas.

Esto expone una interfaz muy componible: texto

El texto es una interfaz universal. Todo en una computadora se puede serializar a texto, incluso si es solo código máquina. Si puedes hacer que un LLM ingrese y genere texto a través de esta interfaz universal, obtienes componibilidad sobre flujos de texto.

Esto significa que la fiabilidad de tu comportamiento del agente está directamente relacionada con la consistencia de la salida del modelo. Una alta variabilidad en la salida implica un comportamiento del agente más errático.

Una vez que tienes una interfaz para componer piezas, la siguiente restricción que te importa es la dirigibilidad:

lo que quieres que el agente haga, y cómo consigues consistentemente que haga lo que quieres

Dirigimos agentes cambiando la distribución de la que muestrea, en otras palabras, prompting.

En 2022, ReAct apareció y esencialmente fue pionero en la dirigibilidad de agentes. De hecho, podemos llegar tan lejos como para decir que hizo que los agentes tal como los conocemos fueran una realidad. Pensar y razonar sobre la salida de una herramienta antes de dar el siguiente paso es lo que mantiene el bucle coherente.

akira - inline image

Todavía necesitábamos que los agentes fueran más inteligentes. El uso de la escala de cómputo en tiempo de prueba, industrializado por los modelos de la serie O de @OpenAI, les dio a los laboratorios de modelos la capacidad de incorporar un mejor comportamiento agente [[11]](http://localhost:5173/blog/onyx#ref-11). Generar más tokens antes de llamar a una herramienta permite que el modelo escape de la distribución de salida en la que se habría quedado atascado si se le hubiera restringido la longitud de la salida de razonamiento. Puedes elegir entrenar cómo el modelo atraviesa su panorama de distribución de salida y, por lo tanto, tener la libertad de entrenar un comportamiento más claro similar al de un agente en las tareas que te importan.

A medida que la longitud del contexto crece sin límites, dirigir al agente se vuelve difícil y la finalización de la tarea se vuelve menos probable. Incluso con un modelo de razonamiento, no hay garantía de recuperación, y el agente muere justo ahí. El agente puede alcanzar su límite de contexto, declarar una finalización anticipada, quedarse atrapado en un bucle, etc.

Extrayendo garantías de un sistema no determinista

Las soluciones para esto fueron variadas, pero una destaca: El Bucle Ralph, creado por @GeoffreyHuntley. [[3]](http://localhost:5173/blog/onyx#ref-3

Introdujo la idea de que podías acotar la ejecución del agente y luego usar esos límites para razonar sobre la finalización de la tarea. Esto permite que el Bucle Ralph haga algo mágico: proporciona algo en lo que puedes confiar en un sistema no determinista.

Una chispa de determinismo.

Mejor garantizar el fracaso y avanzar progresivamente hacia algo correcto, que tirar de la palanca de la máquina tragamonedas una vez más. Este límite definido te da algo concreto sobre lo que razonar y, una vez que puedes razonar sobre los límites de algo, puedes construir un sistema a partir de ello.

Luchando contra los límites de la longitud del contexto

Hay un problema, sin embargo: un agente nuevo pierde coherencia entre ejecuciones, pero un solo agente se queda sin contexto con el tiempo suficiente.

Aquí entra RLM de @lateinteraction @a1zhang. RLM nos dio un concepto sobre cómo interactuar con un contexto largo (es decir, una ejecución de agente) de manera estructurada [[4]](http://localhost:5173/blog/onyx#ref-4). RLM se inspiró en CodeAct, un artículo de 2024 que demostró el uso de código para orquestar operaciones [[5]](http://localhost:5173/blog/onyx#ref-5). El agente escribe scripts que orquestan operaciones dentro de un REPL para luego recuperar una salida. RLM opera de la misma manera con la salvedad adicional de que usa variables para almacenar contexto y realizar operaciones sobre ese contexto. Además, permite llamadas LLM recursivas en el REPL. Podrías perder algo de la reactividad que tienen otros bucles, pero ganas la capacidad de trabajar programáticamente con el contexto. Lo clave aquí es que los scripts en el REPL son efímeros. Obtienes un runtime de scripting y gestión de contexto, pero no hay reutilización ni componibilidad. Solo escribe el script, ejecútalo y desaparece. En términos de construcción de sistemas, esto es estrictamente peor que simplemente encadenar agentes y archivos markdown con scripts de bash porque pierdes persistencia y ejecución acotada.

Pasando de bucles individuales a la orquestación a escala

Deep research de OpenAI[[6]](http://localhost:5173/blog/onyx#ref-6) fue uno de los primeros ejemplos de un flujo de trabajo determinista que tenía una forma o esquema de ejecución general con pequeña variabilidad en cada ejecución. La forma en que funciona es planificando un lote de consultas, ejecutándolas en la web, revisando los resultados y planificando el siguiente lote de consultas. Cada lote profundiza más en el espacio del problema.

akira - inline image

Cursor llevó la idea del determinismo mucho más lejos cuando @wilsonzlin demostró un arnés que orquestaba agentes para construir un navegador. Construyó un arnés hecho a medida para coordinar grandes cantidades de trabajo utilizando agentes planificadores paralelos y agentes de tareas [[7]](http://localhost:5173/blog/onyx#ref-7). Lo relevante aquí es que la relación entre cada parte del arnés es fija. Hay planificadores, que exploran el estado actual del sistema y generan tareas, y hay ejecutores, que toman las tareas y las implementan en paralelo. Hay barreras de contención fijas entre agentes y canales fijos para comunicar información. Para hacer una buena coordinación, necesitas garantías en las interfaces.

Usando condiciones de terminación para la ejecución acotada

En mayo, Codex introdujo la idea de un objetivo que utiliza un bucle verificador para escalar (hillclimb) hacia algún estado final deseado hasta que una tarea se completa. Puedes pensar en esto como una versión lista para producción del bucle Ralph, integrada en Codex. Te permite especificar un objetivo y tiene un bucle automatizado que ejecuta y revisa, integrado.

akira - inline image

El Autoresearch de Karpathy [[9]](http://localhost:5173/blog/onyx#ref-9) es similar a /goal de Codex y al bucle Ralph. Combina la condición de terminación verificable de goal con la acotación de ejecución de un bucle Ralph a lo largo de las iteraciones, lo que le permite avanzar continuamente hacia un objetivo. Progresa buscando en el espacio de ideas, mejorando iterativamente con el tiempo.

akira - inline image

Hasta este punto, todas las soluciones que externalizan la orquestación fuera del agente son fijas en la forma de su gráfico de ejecución. Se ejecutan usando un patrón escrito a mano y tienen una especie de esquema para las formas permitidas en las que pueden operar. No se adaptan por tarea o no tienen garantías sólidas sobre la forma del gráfico de ejecución en absoluto.

Haciendo la orquestación flexible

En marzo de este año, presentamos Slate, el primer agente de codificación en usar código para la orquestación en vivo de subagentes al estilo de RLM. Sigue siendo el único agente de codificación muy utilizado que usa código para hacer orquestación de agentes en vivo. En Slate, los hilos pueden ser creados, pausados, reanudados y dirigidos en tiempo real. El agente principal comprende profundamente cómo orquestar todos los subagentes en ejecución para que tú no tengas que hacerlo. Sin embargo, de manera similar a RLM, todavía enfrentábamos el desafío de compartir estado entre subagentes y el scripting efímero, que no es algo que te encuentres usando un script de bash y un archivo markdown.

Aun así, si el modelo es el que hace la orquestación, ¿cómo lo diriges? ¿Le dices que escriba su código de orquestación de una manera específica? ¿Qué haces?

Nuestra solución inicial (como un parche antes de lanzar el runtime de Onyx) se llamó orchestration skills [[13]](http://localhost:5173/blog/onyx#ref-13). La idea era simple: permitir que el usuario proporcionara una habilidad para dirigir cómo el agente aborda su orquestación. Eso es todo. Funcionaba más o menos bien, pero tenía muchos problemas.

Es decir, una habilidad no es un contrato de comportamiento vinculante. No puedes obtener una garantía del texto.

Esto significa que el orquestador no tenía que seguir el patrón de ejecución deseado porque no había una forma real de imponerlo. Uno de los mayores beneficios del runtime de Onyx es que resolvimos este problema.

Ninguno de los sistemas mencionados tiene contratos de comportamiento vinculantes.

Bueno, entonces, ¿y si el agente pudiera escribir su código de orquestación en un script por tarea para que el gráfico de ejecución sea fijo? Esto es lo que son los flujos de trabajo dinámicos de Claude.[[10]]([http://localhost:5173/blog/onyx#ref-10)[[12]](http://localhost:5173/blog/onyx#ref-12](http://localhost:5173/blog/onyx#ref-10)[[12]](http://localhost:5173/blog/onyx#ref-12) De la misma manera que RLM y Slate, al escribir código para orquestar subagentes, los flujos de trabajo dinámicos permiten a Claude escribir y guardar formas de flujo de trabajo. Esto se combina con /loop para poder iterar sobre patrones específicos. Proporciona un contrato declarativo para el comportamiento de un conjunto de agentes. Sigue sin ser lo mismo que escribir software, ya que carece de cosas como la composición funcional, pero obtienes persistencia y una garantía sólida sobre cómo se ejecutará la tarea. Son scripts de flujo de trabajo escritos dinámicamente para una tarea específica ad hoc.[[12]](http://localhost:5173/blog/onyx#ref-12) Y dado que se persisten en el disco, tienen un beneficio adicional: pueden volver a ejecutarse y envolverse con pegamento de orquestación como /loop.

akira - inline image

Si te fijas, todas las soluciones anteriores apuntan a lo mismo: una forma determinista de controlar cómo se ejecutan los agentes a lo largo del tiempo.

Esta es una historia que ya vimos desarrollarse en la ingeniería de software como campo. Empezamos uniendo sistemas dispares y programando trabajos con scripts, y luego nuestros lenguajes se volvieron más flexibles y potentes. Obtuvimos cada vez más apalancamiento sobre el proceso de ingeniería con ecosistemas más sólidos que nos permitieron construir sistemas más fiables a niveles de abstracción más altos.

Ahora mismo, los agentes están en la misma trayectoria, y hoy estamos lanzando el siguiente paso en esa trayectoria para permitirte diseñar los sistemas que ejecutan tus agentes. Los lenguajes de programación a menudo usan intérpretes o VMs para programar recursos automáticamente. Esto es lo que te da apalancamiento como ingeniero al usar el lenguaje.

Si una VM tuviera sentido para la orquestación de agentes, necesitarías algunas cosas:

  1. Gestión de estado persistente: deberíamos poder definir estado, referenciarlo por nombre, persistirlo y manipularlo programáticamente.
  1. Garantías de tipo. Deberíamos respetar las formas de entrada y salida definidas y seguirlas, y poder confiar en ellas.
  1. Primitivas de flujo de control, preferiblemente las bien conocidas que un LLM entendería.
  1. Estructura clara para el manejo de errores (por ejemplo, try-catch).
  1. Gestión de recursos: controles definidos sobre recursos como el paralelismo de agentes, el costo, qué modelos se están ejecutando, etc.
  1. Aislamiento de ejecución: un agente o programa en ejecución debe estar aislado de otro a menos que el estado se comparta explícitamente.
  1. Control del ciclo de vida: cómo se ve un programa de agente y la semántica para ejecutarlo, cancelarlo y dirigirlo. Sin esto, no tienes un camino claro para la limpieza y no puedes controlar la gestión del ciclo de vida.
  1. Componibilidad: los programas deben poder componerse entre sí y deben ser invocables con tipos de entrada y salida definidos.
  1. Visibilidad: Deberíamos poder saber qué se ejecutó, cuándo, y deberíamos poder rastrear una falla de ejecución hasta su origen.
  1. Durabilidad: Deberíamos tener un modelo claro de cómo podemos recuperarnos de fallos y reanudar.

Cada uno de estos es un problema que ya fue resuelto por los lenguajes de programación hace décadas. La orquestación de agentes solo los está enfrentando todos de nuevo por primera vez.

Para poder escribir software para esto, un programa "program.ts" debe ser creado en un runtime que soporte todo lo anterior, para que podamos razonar sobre lo que sucederá cuando un programa no funcione e ingeniar una solución para la falla.

Es por esto que construimos Onyx. Es una VM de orquestación de agentes diseñada precisamente para soportar tanto programas componibles persistentes como una capa de scripting interpretada. Así es como funciona, y lo que un runtime compatible con "program.ts" necesita soportar.

Diseñando el runtime

Cuando diseñamos un lenguaje y un runtime para ese lenguaje, necesitamos pensar en las restricciones sobre las que queremos poder razonar y en lo que nos importa que sea fácilmente expresable. Luego podemos dividir la semántica resultante en dos categorías: semántica estática y semántica de runtime.

La semántica estática son todas las cosas que se pueden inferir sobre un programa con solo mirarlo. Las cosas que un compilador o un verificador de tipos saben sobre un programa dado.

La semántica de runtime define lo que el código realmente significa y cómo se ejecuta realmente el programa. Esto incluye la asignación de recursos subyacente y la mecánica de programación.

Nuestro objetivo con un runtime para agentes es convertir el flujo de control de la orquestación en código, y queremos que el estado de ejecución sea persistente y esté tipado para poder usarlo de manera fiable para dirigir la orquestación.

Algunos requisitos de la VM

Hay 3 cosas específicas de la VM que nos importan más allá de algo como la ejecución normal de TypeScript.

  1. Como runtime de orquestación de agentes, necesita poder orquestar agentes. Esto significa crearlos, rastrear sus ciclos de vida, etc. Queremos que el runtime pueda ejecutarlos de manera bloqueante o no bloqueante y programarlos correctamente.
  1. Queremos control sobre las formas de salida de los agentes y queremos una aplicación estricta del contrato de salida.
  1. Queremos tener control en tiempo de ejecución sobre recursos externos como modelos y costos.

Ejecutando agentes y programas

Para ejecutar un agente, seleccionamos dos verbos básicos: run y spawn. Run ejecuta un agente bloqueante en primer plano. Spawn ejecuta un agente en segundo plano. Esto está en línea con las comprensiones comunes de spawn, como posix_spawn, lo que facilita que un modelo entienda nuestros nuevos verbos ya que están conceptualmente en los datos de entrenamiento. Spawn y run te permiten invocar directamente agentes y programas leídos del disco, devolviendo suficiente información para un identificador de ejecución.

Run también soporta algunas cosas. Soporta tipos de salida directamente aplicados a través de zod @colinhacks, y soporta anulaciones de modelo directas, lo que facilita escribir y ejecutar programas donde tenga sentido abrirse en abanico a múltiples modelos diferentes para diferentes soluciones o diferentes pasos de una tarea.

typescript
1function run<S extends z.ZodType>(
2 name: string,
3 options: ...
4): Promise<z.infer<S>>

Run te permite encadenar subagentes directamente en línea.

typescript
1// ejecución de agente simple
2const out = await run({ type: "read", prompt: () => "Responde con: ok" })
3// ejecución nombrada (string = workflowId hijo)
4const review = await run("reviewer", {
5 type: "general",
6 prompt: () => "Revisa el diff",
7})
8// salida estructurada (resultado tipado)
9const Verdict = z.object({ risk: z.enum(["low", "high"]), why: z.string() })
10const v = await run({
11 type: "general",
12 prompt: () => "Evalúa el riesgo",
13 output: Verdict,
14})

Spawn es similar a run pero crea un agente en segundo plano. Los subagentes generados con spawn no se esperan (await) y el flujo de control continúa. Spawn es muy útil para iniciar varios agentes de ejecución no bloqueante.

typescript
1// agente en segundo plano
2const h = await spawn("worker", { type: "general", prompt: "Tarea larga" })

Interactuando con agentes en ejecución

Queremos poder hacer dos tipos de operaciones en agentes en ejecución: dirigir y detener.

Un mensaje de dirección (steering message) es un mensaje enviado al agente que el LLM recibirá mientras se está ejecutando para impulsarlo en una dirección. Esto es útil para actualizar el contexto de la tarea del agente sin necesidad de eliminar el worker.

La cancelación también es importante, queremos poder eliminar activamente un subagente si no debería estar ejecutándose.

Poder ejecutar estas operaciones tanto desde el REPL en vivo como desde un programa previamente creado le da a Slate su capacidad de orquestar todo en tiempo real. Puede definir dinámicamente la forma de la orquestación en tiempo de ejecución, o puede crear e iterar sobre software real para hacer la orquestación.

Slate puede escribir programas en archivos \.program.ts. Un archivo de programa tiene algunas cosas: su nombre (así es como Slate sabe qué es), una descripción JSDoc y luego el cuerpo del programa real*. Una declaración de programa se ve así:

typescript
1program(async (ctx) => {
2 // modelo barato para la búsqueda — solo necesita encontrar archivos
3 const findings = await run("search", {
4 type: "read",
5 prompt: "Encuentra todos los archivos relacionados con autenticación",
6 model: "codex/gpt-4.1-mini", // usa tu clave codex integrada
7 })
8})

Los programas siguen el mismo modelo de ejecución asíncrona, lo que nos permite ejecutar un programa tanto en primer plano como en segundo plano, e interactuar con él mientras se ejecuta.

typescript
1// agente en segundo plano
2const h = await spawn("worker", { type: "general", prompt: "Tarea larga" })
3await h.notify("concéntrate en el analizador sintáctico primero") // mensaje de dirección al agente en ejecución
4const result = await h.result() // esperar la finalización después
5
6// abrirse en abanico, luego recopilar
7const a = await spawn({ prompt: "tarea A" })
8const b = await spawn({ prompt: "tarea B" })
9const [ra, rb] = [await a.result(), await b.result()]
10
11// programa en segundo plano
12import Audit from "deep-audit"
13const ah = await spawn(Audit, { input: { pr: 42 } })
14const auditResult = await ah.result()

Salida estructurada y estado

Esta es una limitación principal de todos los demás sistemas hasta la fecha. El estado, en todos los demás sistemas, está mal externalizado y no está aislado de forma segura. Si es un archivo en el sistema, no puedes garantizar que no haya corrupción. Si puedes, aún no puedes garantizar la analizabilidad. No puedes suscribirte a los cambios de estado para impulsar operaciones, y no puedes garantizar la adherencia al tipo.

¿Recuerdas cómo queríamos un estado persistente que también estuviera estructurado y pudiera ser referenciado?

El estado, en Onyx, es diferente. Los espacios de nombres de estado se declaran, se nombran directamente y se persisten a lo largo del tiempo. Esto significa que un almacén de estado se puede reutilizar una y otra vez, lo que te permite construir sistemas de agentes de larga duración con datos reales.

Tanto los agentes como el código leen el estado, y el determinismo que queríamos de un runtime surge de esto. Los agentes leen el estado a través de una herramienta dedicada que les permite interactuar siempre con él de una manera segura y estructurada. Tanto los agentes como los programas son consumidores que pueden ser dirigidos para modificar el estado, lo que permite que el runtime confíe en el objeto de estado para impulsar la orquestación.

El estado y la adherencia al esquema son la puerta de entrada para la finalización del subagente. Debido a esto, el estado proporciona una superficie unificada para dirigir todo el programa.

Los objetos de estado también se pueden pasar como variables de runtime a las sesiones hijas compartidas con el agente principal. Este acceso por referencia a través de la jerarquía de agentes (que es el primero de su tipo) permite la comunicación entre agentes a través de un canal de estado compartido.

akira - inline image

Bucles de larga duración

Algunos programas necesitan funcionar más como sistemas en ejecución. Toma openclaw como ejemplo. En realidad, puedes representar openclaw como un programa dadas las primitivas correctas. Para esto, usamos dos primitivas: sleep y checkpoint.

Sleep hace lo que esperarías: duerme (pausa).

Ahora, la cuestión es la siguiente: digamos que quieres alguna gestión de tareas de larga duración en segundo plano. Un gráfico de ejecución predefinido podría atascarse o romperse, por lo que es importante que el agente principal esté al tanto del estado del programa.

Para soportar esto, introducimos la primitiva checkpoint.

Un checkpoint puede ser cualquier cosa, pero la razón por la que se llama checkpoint es porque notifica al agente principal con un objeto de forma fija. Esto permite que el agente principal rastree cosas como el progreso de la tarea y sea notificado directamente sobre los cambios en el estado del programa. A su vez, el agente principal puede entonces gestionar un programa en ejecución de manera más efectiva.

Onyx soporta hacer que un agente se comporte como un bucle tipo Openclaw, es decir, un agente persistente con un latido (heartbeat).

Esto es realmente genial, puedes componer las primitivas en un tipo de agente completamente diferente simplemente usando un bucle while, un sleep y un checkpoint.

¡Openclaw puede representarse simplemente como un archivo de programa!

typescript
1// Un programa para ejecutar un bucle de auto-investigación de larga duración
2for (let i = 0; i < maxExperiments; i++) {
3 const idea = await run("propose", { ... })
4 const result = await run("train", { ... })
5 checkpoint({ message: `experimento ${i}`, data: { idea, result } })
6 await sleep(30_000) // tiempo de espera entre experimentos
7}
8
9// Un programa para ejecutar un agente persistente estilo openclaw
10while(true) {
11 const status = await run("status_check", { ...insertar modelo económico aquí... })if(status.pending_tasks) {checkpoint({ tasks: status.pending_tasks }) // devolver el estado importante y despertar al agente principal}
12 await sleep(30_000) // tiempo de espera entre experimentos
13}

Composición

Con Onyx, Slate puede escribir un *.program.ts por ti. Esto persiste y se puede (y debe) tratar como código normal. Viene con tipos incorporados, se ejecuta en un entorno de ejecución sin variables globales, y es simplemente TypeScript, por lo que su modelo de composición es simplemente importar y llamar a otro programa.

Como es solo TypeScript, obtienes cosas como paralelismo (Promise.all) y bucles de forma gratuita.

Así es como importarías un programa y lo usarías en otro:

typescript
1import Audit from "deep-audit"program (() => {const ah = await spawn(Audit, { input: { pr: 42 } })
2 const auditResult = await ah.result()
3 const fixer = await run("fixer", ... salida de auditoría) // esto ejecutaría y corregiría la salida del programa de auditoría.
4})

Semántica de errores

Los errores, en la VM ideal, se lanzan de forma ruidosa. Estos deben lanzarse en problemas de sintaxis en tiempo de ejecución, fallos de agentes, bloqueos, etc.

Específicamente, definimos los errores de orquestación como:

  • Un agente está bloqueado en una tarea
  • Un agente no pudo completar una tarea
  • Un agente se quedó sin pasos o presupuesto para una tarea
  • Un programa se quedó sin presupuesto para una ejecución
  • El modelo de orquestación no pudo escribir código sintácticamente correcto
  • Una modificación de estado ilegal

Todos estos casos de error específicos definen la semántica del tiempo de ejecución. Dicen: "Puedes esperar que este tiempo de ejecución lance un error, porque vemos un fallo en la ejecución del agente de la misma manera que vemos un error en el código". Puede parecer molesto al principio, pero este mecanismo de fallo ruidoso te da algo a cambio: una forma explícita de prepararte y programar en torno a los fallos. Así que, en realidad, te da más control, no menos.

typescript
1// los errores son try/catch — igual que cualquier programa TypeScript
2program(async (ctx) => {
3 try {
4 const result = await run("risky-refactor", {
5 type: "general",
6 prompt: "Refactorizar el módulo de autenticación",
7 model: "claude-sonnet",
8 maxSteps: 20,
9 })
10 } catch (err) {
11 // el agente falló — pero sabemos exactamente por qué.
12 // el trace tiene cada llamada a herramienta, cada solicitud al modelo,
13 // cada escritura de estado que llevó hasta aquí.
14
15 // reintentar con un modelo diferente
16 const result = await run("risky-refactor-retry", {
17 type: "general",
18 prompt: `El intento anterior falló: ${err.message}. Prueba un enfoque diferente.`,
19 model: "claude-opus",
20 maxSteps: 30,
21 })
22 }
23})

Selección de modelo, control de presupuesto y BYOK

La selección de modelo incorporada te permite tener un control aún más preciso. La habilidad /models le da a Slate acceso completo a la lista de modelos disponibles, permitiendo que Slate cree programas con múltiples modelos diferentes realizando diferentes trabajos. ¿Quieres que Fable sea el planificador, pero GLM 5.2 implemente dentro de un entorno determinista? Claro. ¿Quieres distribuir una pregunta entre Gemini, GPT 5.5 y DeepSeek? Eso también funciona.

Además, el tiempo de ejecución admite dos tipos de anulaciones de configuración para programas:

  • Los modelos globales predeterminados utilizados para la ejecución del agente
  • El presupuesto para ejecutar un programa

Puedes establecer directamente un presupuesto de ejecución para limitar el gasto de un bucle determinado.

Además, el tiempo de ejecución admite el uso de tus suscripciones existentes de OpenAI y Github Copilot.

typescript
1program(async (ctx) => {
2 // modelo económico para búsqueda — solo necesita encontrar archivos
3 const findings = await run("search", {
4 type: "read",
5 prompt: "Encontrar todos los archivos relacionados con autenticación",
6 model: "codex/gpt-4.1-mini", // usa tu clave codex incorporada
7 })
8
9 // modelo de razonamiento para la parte difícil — necesita pensar
10 const plan = await run("architect", {
11 type: "general",
12 prompt: `Diseñar una corrección basada en: ${findings.output}`,
13 model: "openai/o3", // Termina usando créditos de API
14 output: z.object({
15 approach: z.string(),
16 files: z.array(z.string()),
17 risk: z.enum(["low", "medium", "high"]),
18 }),
19 })
20
21 // modelo de nivel medio para implementación — solo necesita editar
22 const handles = await Promise.all(
23 plan.files.map(f => spawn("fix-" + f, {
24 type: "general",
25 prompt: `Aplicar esta corrección a ${f}: ${plan.approach}`,
26 model: "anthropic/claude-sonnet-5",
27 maxSteps: 15,
28 }))
29 )
30 await Promise.all(handles.map(h => h.result()))
31})

Definiendo la superficie de autoría

Hubo dos factores principales en el diseño de la superficie de autoría para los programas: lo fácil que es para un agente entenderlo y lo fácil que es para un humano leerlo. Elegimos verbos relativamente simples que se leen como inglés, y decidimos explícitamente que queríamos modelar la orquestación de forma procedimental en lugar de declarativa.

La selección de TypeScript como lenguaje también fue importante. Hay tanto código TypeScript procedimental en el mundo que un modelo entenderá implícitamente la semántica de TypeScript, incluso sin entrenamiento posterior.

akira - inline image

Piezas de ingeniería de nuestra fábrica de software

La siguiente pregunta a responder es: ¿qué te compra todo esto?

Te compra la capacidad de escribir software real para la orquestación de tus agentes. Ahora puedes diseñar tu propia orquestación de agentes de principio a fin.

Puedes diseñar la fábrica.

Por ejemplo, puedes hacer un programa que monitoree Github en un bucle, y un programa separado que ejecute un agente de implementación con un agente de QA para revisión. Ambos patrones individualmente útiles que podrías encontrar en la naturaleza. Luego puedes juntarlos para hacer un sistema que escuche comentarios en un PR, genere un implementador para abordar esos comentarios, y luego genere un agente de QA para asegurarse de que la corrección sea válida.

Luego puedes usar este programa conectado a una cola de tareas para delegar y monitorear el trabajo en tu código base, y hacer que responda automáticamente a los comentarios de los PR.

Y puedes hacer todo esto usando modelos de pesos abiertos rápidos. Porque es solo código, no necesitas un LLM potente para pensar en la orquestación después de que se haya creado la primera vez.

Ahora viene la parte divertida, es hora de compartir algunos de los programas que hemos utilizado para aumentos masivos de producción.

Investigación Profunda del Código Base

Usamos este programa para ayudar a definir el alcance de las tareas. Hace una investigación profunda sobre el estado de nuestro monorepo y prepara un paquete de investigación para que un implementador lo consulte. Lo usamos todo el tiempo. Suena caro, pero en realidad no lo es. Puedes ejecutar este programa en Slate con DeepSeek V4 Flash y el proceso de investigación es exhaustivo pero muy económico.

akira - inline image

Objetivo-Revisión-PR

Este es uno que usamos para implementar una tarea una vez que la investigación está hecha. Afortunadamente, para cuando la investigación llega al programa de objetivos, la mayor parte de la ambigüedad de la tarea se ha resuelto, lo que hace que ejecutar la tarea sea aún más rápido. Cargar la investigación por adelantado con un modelo OSS ligero facilita el uso de un modelo costoso como Opus para lo que importa: escribir código realmente bueno y verificar el estado del sistema. Incluso podrías modificar el programa para usar GPT 5.5 y revisar de forma adversaria el trabajo de Opus 4.8.

akira - inline image

Autoinvestigación como Programa

Autoinvestigación[[9]](http://localhost:5173/blog/onyx#ref-9) originalmente estaba completamente impulsada por LLM. Dirige un agente al prompt program.md y él decide qué probar y cómo progresar.

Sin sorpresas, Autoinvestigación es en realidad solo un programa.

Los programas de agente te permiten invertir eso y poner el flujo de control en el tiempo de ejecución. El programa posee el flujo de control mientras los agentes hacen el trabajo de efectos secundarios (editar código, ejecutar git, hacer SSH a la GPU remota, entrenar). Para el programa de autoinvestigación, la decisión de mantener/revertir es código determinista:

typescript
1kept = status === "ok" && valBpb != null && valBpb < best

En nuestro caso, el programa ejecuta un agente de configuración para preparar un repositorio nuevo y verificar que la A100 remota sea accesible. Si la configuración falla, regresa temprano con una salida limpia basada en un valor tipado. De lo contrario, entra en el bucle de experimentos.

Cada experimento obtiene un agente nuevo. Al agente se le da la mejor configuración actual y el historial de ideas y resultados anteriores, para que no se repita y pueda basarse en lo que se mantuvo. Propone un cambio, edita train.py, hace commit, sincroniza con rsync a la máquina remota, entrena y clasifica el resultado.

El agente y el programa comparten estado. El agente escribe datos en el estado, y el programa evalúa el estado para el flujo de control. Según el resultado, un agente registrador actualiza results.tsv y, opcionalmente, reinicia la ejecución si el programa decidió descartar el experimento. Esto deja que el HEAD de git apunte siempre a la mejor rama actual del árbol de experimentos.

Hay dos diferencias clave que vale la pena notar: 1) esto se ejecuta en un programa, por lo que podemos generar un agente nuevo por experimento y 2) podemos decidir qué tarea debe hacer el agente según el estado del programa en vivo.

akira - inline image
akira - inline image

Y se ve así en código:

typescript
1// ---------- Programa ----------
2
3program(async (ctx) => {
4 const c = cfg(ctx.input)
5 const total = ctx.input?.maxExperiments ?? 20
6
7 const setup = await run("ar-setup", {
8 prompt: setupPrompt(c),
9 type: "general",
10 maxSteps: 40,
11 output: SetupResult,
12 })
13 if (!setup.ready) {
14 return { aborted: true, reason: `configuración fallida: ${setup.note}`, setup }
15 }
16
17 let best = c.baselineValBpb
18 let bestCommit = setup.baselineCommit
19 const history = []
20
21 for (let i = 1; i <= total; i++) {
22 let exp
23 try {
24 exp = await run(`ar-exp-${i}`, {
25 prompt: experimentPrompt(c, i, total, best, historyText(history)),
26 type: "general",
27 maxSteps: 80,
28 output: ExperimentResult,
29 })
30 } catch (err) {
31 // Agente con error/bloqueado — tratar como bloqueo, restaurar repo al mejor, continuar.
32 exp = {
33 description: `experimento ${i} error de agente`,
34 commit: "error",
35 status: "crash",
36 valBpb: null,
37 peakVramMb: null,
38 numSteps: null,
39 exitCode: -1,
40 retries: 0,
41 note: String(err?.message ?? err).slice(0, 200),
42 }
43 }
44
45 const kept = exp.status === "ok" && exp.valBpb != null && exp.valBpb < best
46
47 await run(`ar-record-${i}`, {
48 prompt: recordPrompt(c, exp, kept, bestCommit),
49 type: "general",
50 maxSteps: 20,
51 output: RecordResult,
52 })
53
54 if (kept) {
55 best = exp.valBpb
56 bestCommit = exp.commit
57 }
58
59 history.push({
60 idx: i,
61 description: exp.description,
62 status: exp.status,
63 valBpb: exp.valBpb,
64 kept,
65 commit: exp.commit,
66 retries: exp.retries,
67 })
68
69 await checkpoint({
70 name: `experimento-${i}`,
71 message: `exp ${i}/${total}: ${exp.status}${kept ? " MANTENIDO" : ""} val_bpb=${exp.valBpb ?? "n/a"} (mejor=${best})`,
72 data: { i, total, status: exp.status, valBpb: exp.valBpb, kept, best, bestCommit },
73 })
74 }
75
76 const kepts = history.filter((h) => h.kept)
77 return {
78 baselineValBpb: c.baselineValBpb,
79 bestValBpb: best,
80 bestCommit,
81 improvement: c.baselineValBpb - best,
82 experimentsRun: history.length,
83 kept: kepts.length,
84 crashes: history.filter((h) => h.status === "crash").length,
85 infraFails: history.filter((h) => h.status === "infra_fail").length,
86 localRepo: c.localRepo,
87 branch: c.branch,
88 history,
89 }
90})

Trabajo futuro

El único requisito restante de la VM que no hemos definido aún es el modelo de durabilidad para los programas. No está claro cuál es el modelo correcto para reanudar y manejar el ciclo de vida de un programa, y qué nivel de control debería exponerse en el tiempo de ejecución.

Más allá de eso, hay tantas cosas emocionantes que agregaremos para soportar diferentes cargas de trabajo y formas de tareas, para que podamos escribir software real para orquestar agentes de manera más efectiva. Estamos seguros de que muchos de los patrones surgirán cuando las personas usen los programas de formas creativas por su cuenta.

Realmente no podemos esperar a ver lo que construyes.

  • Equipo RL

Referencias

  1. Yao et al., "ReAct: Sinergizando Razonamiento y Acción en Modelos de Lenguaje," 2022
  2. Geoffrey Huntley, "El Bucle Ralph"
  3. Geoffrey Huntley, "todo es un bucle ralph," Enero 2026
  4. Zhang, Kraska, Khattab, "Modelos de Lenguaje Recursivos," Diciembre 2025
  5. Wang et al., "Acciones de Código Ejecutable Mejoran Agentes LLM," ICML 2024
  6. OpenAI, "Introduciendo Investigación Profunda," Febrero 2025
  7. Cursor, "Escalando Agentes," Enero 2026
  8. OpenAI, "Usando Objetivos en Codex"
  9. Andrej Karpathy, "autoresearch"
  10. Anthropic, "Introduciendo Flujos de Trabajo Dinámicos en Claude Code"
  11. OpenAI, "Aprendiendo a Razonar con LLMs," Septiembre 2024
  12. Anthropic, "Un arnés para cada tarea: flujos de trabajo dinámicos en Claude Code"
  13. Random Labs, "Encadenamiento de Habilidades"

Turn one viral article into a full content workflow

Collect the source, decode the pattern, create assets, draft the story, and distribute from one AI workspace.

Explore YouMind
Para creadores

Convierte tu Markdown en un artículo de 𝕏 impecable

Cuando publicas tus propios textos largos, dar formato en 𝕏 a imágenes, tablas y bloques de código es un fastidio. YouMind convierte un borrador completo en Markdown en un artículo de 𝕏 impecable y listo para publicar.

Prueba Markdown a 𝕏

Más patrones por descifrar

Artículos virales recientes

Explorar más artículos virales