Este artigo apresenta o Onyx, nossa VM para orquestração programável de agentes. E, por extensão, um runtime que transforma orquestração em engenharia de software. Ao final deste artigo, você entenderá as restrições e decisões de design que envolveram a construção da VM, bem como como criar seus próprios programas e arquitetar seus sistemas de agentes.
Introdução
Agentes são inerentemente não-determinísticos. Esse é o ponto principal. Se você quisesse determinismo, estaria escrevendo software.
Mas em algum momento, todos que usam agentes quiseram ir além. Aprendemos que dividir a execução em etapas estruturadas ajuda no desempenho: Planejar, Implementar, Revisar, QA, etc. Então, aparentemente, concordamos em escrever scripts, ferramentas e habilidades para direcionar cada agente, compartilhar contexto entre eles e estabelecer barreiras de segurança. Em seguida, juntamos esses scripts, canalizando texto entre os agentes, e, como estamos apenas passando texto, isso meio que funciona.
Se você dedicasse tempo suficiente ao problema e fosse particularmente engenhoso, teria descoberto como obter garantias do seu sistema para ter execução condicional com base em um determinado estado. E você provavelmente armazenaria esse estado em um arquivo de marcação analisável ou em um conjunto deles para direcionar seus scripts bash. Você pode até ter construído uma CLI personalizada para seus agentes usarem.
Como engenheiros, isso é familiar; usamos scripts enquanto fazemos engenharia de software. No entanto, o software moderno não é construído encadeando scripts bash e ferramentas CLI. Em vez disso, temos linguagens de programação, runtimes e cadeias de ferramentas para nos ajudar a projetar nossos sistemas. Escrevemos software com linguagens de programação porque elas vêm com uma biblioteca padrão, semântica clara e um modelo de execução no qual podemos confiar. Elas possuem ecossistemas ricos com cadeias de ferramentas para todas as nossas necessidades.
As garantias que elas nos dão sobre nossos sistemas nos permitem raciocinar em níveis mais altos de abstração.
Mas não há equivalente para a engenharia de sistemas de agentes. Para construir sistemas, a orquestração de agentes precisa ser programável exatamente da mesma forma que o software moderno.
Hoje, estamos apresentando a especificação para PROGRAMS (*.program.ts), e o Onyx, nossa VM construída para orquestração determinística de agentes. Este artigo explora um histórico da orquestração de agentes, a semântica estática e de runtime de uma VM que pode executar um programa, e suas implicações para onde o campo está caminhando.
Parece caro, mas na verdade não é. Explico isso mais adiante no artigo.
Para os curiosos, é assim que o Autoresearch de Andrej Karpathy se parece como um programa:

Problemas Não Resolvidos na Orquestração de Agentes
Para entender o que um runtime para orquestração de agentes deve incluir, precisamos entender as limitações dos agentes.
Um agente LLM pode ser pensado como um gerador de fluxo JSON, alimentado em um parser, que então despacha chamadas de ferramenta para um ambiente em um loop.
Cada chamada de ferramenta tem exatamente a mesma forma de esquema externo, mas o conteúdo desse fluxo de saída não é determinístico.
A combinação de determinismo e não-determinismo é o que tornou os agentes tão valiosos. Eles são flexíveis o suficiente para encadear sequências de ações de maneiras únicas, mas determinísticos o suficiente para interagir com um computador por meio de chamadas de ferramenta.
A composabilidade é quase gratuita se você estiver disposto a abrir mão do requisito de que o conteúdo desse fluxo seja tipado. Os modelos são bons o suficiente para canalizar texto para dentro e para fora dentro dos trilhos que fornecemos a eles: prompts, mensagens e chamadas de ferramenta.
Isso expõe uma interface muito componível: texto
Texto é uma interface universal. Tudo em um computador pode ser serializado em texto, mesmo que seja apenas código de máquina. Se você puder ter um LLM que recebe e produz texto através dessa interface universal, você obtém componibilidade sobre fluxos de texto.
Isso significa que a confiabilidade do comportamento do seu agente está diretamente relacionada à consistência da saída do modelo. Alta variabilidade de saída significa comportamento de agente mais errático.
Depois de ter uma interface para compor peças, a próxima restrição com a qual você se preocupa é a dirigibilidade:
o que você quer que o agente faça e como você o faz fazer o que você quer de forma consistente
Dirigimos agentes deslocando a distribuição da qual eles amostram, em outras palavras, prompting.
Em 2022, o ReAct surgiu e essencialmente pioneirizou a dirigibilidade de agentes. Na verdade, podemos ir tão longe a ponto de dizer que ele tornou os agentes como os conhecemos uma realidade. Pensar e raciocinar sobre a saída de uma ferramenta antes de dar o próximo passo é o que mantém o loop coerente.

Ainda precisávamos que os agentes fossem mais inteligentes. O uso de scaling de compute em tempo de teste, industrializado pela série O de modelos da @OpenAI, deu aos laboratórios de modelos a capacidade de incorporar melhor comportamento de agente [[11]](http://localhost:5173/blog/onyx#ref-11). Produzir mais tokens antes de chamar uma ferramenta permite que o modelo escape da distribuição de saída na qual teria ficado preso se tivesse sido restringido no comprimento do raciocínio de saída. Você pode escolher treinar como o modelo percorre sua paisagem de distribuição de saída e, portanto, ter liberdade para treinar um comportamento de agente mais claro nas tarefas que lhe interessam.
À medida que o comprimento do contexto cresce ilimitadamente, dirigir o agente se torna difícil e a conclusão da tarefa se torna menos provável. Mesmo com um modelo de raciocínio, não há garantia de recuperação, e o agente morre ali mesmo. O agente pode atingir seu limite de contexto, declarar uma conclusão precoce, ficar preso em um loop, etc.
Extraindo Garantias de um Sistema Não-Determinístico
As soluções para isso foram variadas, mas uma se destaca: O Loop Ralph, feito por @GeoffreyHuntley. [[3]](http://localhost:5173/blog/onyx#ref-3
Ele introduziu a ideia de que você poderia limitar a execução do agente e, em seguida, usar esses limites para raciocinar sobre a conclusão da tarefa. Isso permite que o Loop Ralph faça algo mágico: ele fornece algo em que você pode confiar em um sistema não-determinístico.
Uma centelha de determinismo.
É melhor garantir a falha e progredir gradualmente em direção a algo correto do que puxar a alavanca da máquina caça-níqueis mais uma vez. Esse limite definido lhe dá algo concreto para raciocinar e, uma vez que você pode raciocinar sobre os limites de algo, você pode criar um sistema a partir disso.
Lutando contra os limites do comprimento do contexto
Há um problema, no entanto: um agente novo perde a coerência entre execuções, mas um único agente fica sem contexto com tempo suficiente.
Surge então o RLM, por @lateinteraction @a1zhang. O RLM nos deu um conceito de como interagir com contexto longo (ou seja, uma execução de agente) de forma estruturada [[4]](http://localhost:5173/blog/onyx#ref-4). O RLM foi inspirado pelo CodeAct, um artigo de 2024 que demonstrou o uso de código para orquestrar operações [[5]](http://localhost:5173/blog/onyx#ref-5). O agente escreve scripts que orquestram operações dentro de um REPL para então recuperar uma saída. O RLM opera da mesma forma, com a ressalva adicional de que usa variáveis para armazenar contexto e realizar operações nesse contexto. Ele também permite chamadas LLM recursivas no REPL. Você pode perder alguma reatividade que outros loops têm, mas ganha a capacidade de trabalhar programaticamente com o contexto. O ponto chave aqui é que os scripts no REPL são efêmeros. Você obtém um runtime de script e gerenciamento de contexto, mas não há reutilização ou componibilidade. Apenas escreva o script, execute-o e ele desaparece. Em termos de construção de sistemas, isso é estritamente pior do que apenas encadear agentes e arquivos markdown com scripts bash, porque você perde a persistência e a execução limitada.
Movendo-se de loops individuais para orquestração em escala
O Deep research da OpenAI[[6]](http://localhost:5173/blog/onyx#ref-6) foi um dos primeiros exemplos de um fluxo de trabalho determinístico que tinha uma forma ou esquema de execução geral com pequena variabilidade em cada execução. A maneira como funciona é planejando um lote de consultas, executando-as na web, revisando os resultados e planejando o próximo lote de consultas. Cada lote investiga mais profundamente o espaço do problema.

O Cursor levou a ideia de determinismo muito mais longe quando @wilsonzlin demonstrou um harness que orquestrava agentes para construir um navegador. Ele construiu um harness personalizado para coordenar grandes quantidades de trabalho usando agentes planejadores paralelos e agentes de tarefa [[7]](http://localhost:5173/blog/onyx#ref-7). O que é relevante aqui é que a relação entre cada parte do harness é fixa. Existem planejadores, que exploram o estado atual do sistema e geram tarefas, e executores, que pegam as tarefas e as implementam em paralelo. Existem barreiras de segurança fixas entre agentes e canais fixos para comunicar informações. Para fazer a coordenação bem, você precisa de garantias nas interfaces.
Usando condições de término para execução limitada
Em maio, a Codex introduziu a ideia de uma meta que usa um loop de verificador para escalar em direção a algum estado final desejado até que uma tarefa seja concluída. Você pode pensar nisso como uma versão pronta para produção do loop Ralph, embutida no Codex. Ele permite que você especifique um objetivo e tem um loop automatizado que executa e revisa, embutido.

O autoresearch de Karpathy[[9]](http://localhost:5173/blog/onyx#ref-9) é semelhante ao /goal da Codex e ao loop Ralph. Ele combina a condição de término verificável da meta com a limitação de execução de um loop Ralph ao longo das iterações, permitindo que ele se mova continuamente em direção a um objetivo. Ele progride pesquisando o espaço de ideias, melhorando iterativamente ao longo do tempo.

Até este ponto, todas as soluções que externalizam a orquestração para fora do agente são fixas na forma de seu grafo de execução. Elas são executadas usando um padrão escrito à mão e têm uma espécie de esquema para as formas permitidas nas quais podem operar. Elas não se adaptam por tarefa ou não têm fortes garantias sobre a forma do grafo de execução.
Tornando a orquestração flexível
Em março deste ano, apresentamos o Slate, o primeiro agente de codificação a usar código para orquestração ao vivo de subagentes no estilo do RLM. Ainda é o único agente de codificação bem utilizado que usa código para fazer orquestração ao vivo de agentes. No Slate, threads podem ser geradas, pausadas, retomadas e direcionadas em tempo real. O agente principal entende profundamente como orquestrar todos os subagentes em execução para que você não precise. No entanto, semelhante ao RLM, ainda enfrentávamos o desafio de compartilhar estado entre subagentes e scripts efêmeros, o que não é algo que você encontraria usando um script bash e um arquivo markdown.
Ainda assim, se o modelo é quem faz a orquestração, como você o direciona? Você diz a ele para escrever seu código de orquestração de uma maneira específica? O que você faz?
Nossa solução inicial (como um patch antes de lançarmos o runtime Onyx) foi chamada de habilidades de orquestração [[13]](http://localhost:5173/blog/onyx#ref-13). A ideia era simples: permitir que o usuário fornecesse uma habilidade para direcionar como o agente aborda sua orquestração. Só isso. Funcionou razoavelmente bem, mas tinha muitos problemas.
Principalmente, uma habilidade não é um contrato de comportamento vinculante. Você não pode obter uma garantia a partir de texto.
Isso significa que o orquestrador não precisava seguir o padrão de execução desejado porque não havia uma maneira real de impô-lo. Um dos maiores benefícios do runtime Onyx é que resolvemos esse problema.
Nenhum dos sistemas mencionados possui contratos de comportamento vinculantes.
Bem, então, e se o agente pudesse escrever seu código de orquestração em um script por tarefa para que o grafo de execução fosse fixo? É isso que os fluxos de trabalho dinâmicos do Claude são.[[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) Da mesma forma que o RLM e o Slate, ao escrever código para orquestrar subagentes, os fluxos de trabalho dinâmicos permitem que o Claude escreva e salve formas de fluxo de trabalho. Isso se combina com /loop para poder iterar sobre padrões específicos. Ele fornece um contrato declarativo para o comportamento de um conjunto de agentes. Ainda não é o mesmo que escrever software, pois carece de coisas como composição funcional, mas você obtém persistência e uma forte garantia sobre como a tarefa será executada. Eles são scripts de fluxo de trabalho escritos dinamicamente para uma determinada tarefa ad hoc.[[12]](http://localhost:5173/blog/onyx#ref-12) E como são persistidos em disco, eles têm um benefício adicional: podem ser reexecutados e envolvidos com cola de orquestração como /loop.

Se você notar, todas as soluções acima estão buscando a mesma coisa: uma maneira determinística de controlar como os agentes executam ao longo do tempo.
Esta é uma história que já vimos se desenrolar na engenharia de software como um campo. Começamos juntando sistemas díspares e scripts de jobs, e então nossas linguagens se tornaram mais flexíveis e poderosas. Ganhamos cada vez mais alavancagem sobre o processo de engenharia com ecossistemas mais fortes, permitindo-nos construir sistemas mais confiáveis em níveis de abstração mais altos.
Agora, os agentes estão na mesma trajetória, e hoje estamos lançando o próximo passo nessa trajetória para permitir que você projete os sistemas que executam seus agentes. Linguagens de programação frequentemente usam interpretadores ou VMs para agendar recursos automaticamente. Isso é o que lhe dá alavancagem como engenheiro usando a linguagem.
Se uma VM fizesse sentido para orquestração de agentes, você precisaria de algumas coisas:
- Gerenciamento de estado persistente: devemos ser capazes de definir estado, referenciá-lo pelo nome, persistí-lo e manipulá-lo programaticamente.
- Garantias de tipo. Devemos respeitar as formas de entrada e saída definidas e segui-las, e ser capazes de confiar nelas.
- Primitivas de fluxo de controle, de preferência bem conhecidas que um LLM entenderia.
- Estrutura clara para tratamento de erros (ex: try-catch).
- Gerenciamento de recursos: controles definidos sobre recursos como paralelismo de agentes, custo, quais modelos estão sendo executados, etc.
- Isolamento de execução: Um determinado agente ou programa em execução deve ser isolado de outro, a menos que o estado seja explicitamente compartilhado.
- Controle de ciclo de vida: como um programa de agente se parece e semântica para executar, cancelar e direcionar. Sem isso, você não tem um caminho claro para limpeza e não pode controlar o gerenciamento do ciclo de vida.
- Componibilidade: Programas devem se compor uns nos outros e devem ser chamáveis com tipos de entrada e saída definidos.
- Visibilidade: Devemos ser capazes de saber o que foi executado, quando, e ser capazes de rastrear uma falha de execução na fonte.
- Durabilidade: Devemos ter um modelo claro de como podemos nos recuperar de falhas e retomar.
Cada um desses é um problema que já foi resolvido por linguagens de programação décadas atrás. A orquestração de agentes está apenas encontrando todos eles novamente pela primeira vez.
Para ser verdadeiramente capaz de escrever software para isso, um programa "program.ts" deve ser criado em um runtime que suporte tudo o que foi dito acima, para que possamos raciocinar sobre o que acontecerá quando um programa não funcionar e projetar em torno da falha.
É por isso que construímos o Onyx. É uma VM de orquestração de agentes projetada precisamente para suportar tanto programas persistentes e componíveis quanto uma camada de script interpretada. Aqui está como funciona e o que um runtime compatível com "program.ts" precisa suportar.
Projetando o runtime
Quando projetamos uma linguagem e um runtime para essa linguagem, precisamos pensar sobre as restrições sobre as quais queremos ser capazes de raciocinar e o que nos importa que seja facilmente expressável. Então, podemos dividir a semântica resultante em duas categorias: semântica estática e semântica de runtime.
A semântica estática são todas as coisas que podem ser inferidas sobre um programa apenas olhando para ele. As coisas que um compilador ou verificador de tipos sabe sobre um determinado programa.
A semântica de runtime define o que o código realmente significa e como o programa realmente é executado. Isso inclui a alocação de recursos subjacentes e a mecânica de agendamento.
Nosso objetivo com um runtime para agentes é transformar o fluxo de controle da orquestração em código, e queremos tornar o estado de execução persistente e tipado para que possamos usá-lo de forma confiável para direcionar a orquestração.
Alguns requisitos da VM
Existem 3 coisas específicas da VM com as quais nos importamos além de algo como a execução normal do TypeScript.
- Como um runtime de orquestração de agentes, ele precisa ser capaz de orquestrar agentes. Isso significa criá-los, rastrear seus ciclos de vida, etc. Queremos que o runtime seja capaz de executá-los de forma bloqueante ou não bloqueante e agendá-los corretamente.
- Queremos controle sobre as formas de saída dos agentes e queremos uma aplicação estrita do contrato de saída.
- Queremos ter controle em tempo de execução sobre recursos externos como modelos e custo.
Executando Agentes e Programas
Para executar um agente, selecionamos dois verbos básicos: run e spawn. Run executa um agente bloqueante em primeiro plano. Spawn executa um agente em segundo plano. Isso está alinhado com os entendimentos comuns de spawn, como posix_spawn, facilitando para um modelo entender nossos novos verbos, já que eles estão conceitualmente nos dados de treinamento. Spawn e run permitem que você invoque diretamente agentes e programas lidos do disco, retornando informações suficientes para um handle de execução.
Run também suporta algumas coisas. Ele suporta tipos de saída diretamente aplicados através do zod @colinhacks, e suporta substituições diretas de modelo, facilitando a escrita e execução de programas onde faz sentido distribuir para vários modelos diferentes para diferentes soluções ou diferentes etapas de uma tarefa.
1function run<S extends z.ZodType>(2 name: string,3 options: ...4): Promise<z.infer<S>>
Run permite que você encadeie diretamente subagentes inline.
1// execução de agente simples2const out = await run({ type: "read", prompt: () => "Responda com: ok" })3// execução nomeada (string = workflowId filho)4const review = await run("reviewer", {5 type: "general",6 prompt: () => "Revise o diff",7})8// saída estruturada (resultado tipado)9const Verdict = z.object({ risk: z.enum(["low", "high"]), why: z.string() })10const v = await run({11 type: "general",12 prompt: () => "Avalie o risco",13 output: Verdict,14})
Spawn é semelhante ao run, mas cria um agente em segundo plano. Subagentes gerados com spawn não são aguardados e o fluxo de controle segue em frente. Spawn é muito útil para iniciar vários agentes de execução não bloqueante.
1// agente em segundo plano2const h = await spawn("worker", { type: "general", prompt: "Tarefa longa" })
Interagindo com agentes em execução
Queremos ser capazes de realizar dois tipos de operações em agentes em execução: direcionamento e parada.
Uma mensagem de direcionamento é uma mensagem enviada ao agente que o LLM receberá enquanto estiver em execução para empurrá-lo em uma direção. Isso é útil para atualizar o contexto da tarefa do agente sem precisar derrubar o worker.
O cancelamento também é importante; queremos ser capazes de derrubar ativamente um subagente se ele não deveria estar em execução.
Ser capaz de executar essas operações tanto do REPL ao vivo quanto de um programa pré-criado dá ao Slate sua capacidade de orquestrar tudo em tempo real. Ele pode definir dinamicamente a forma da orquestração em tempo de execução, ou pode criar e iterar sobre software real para fazer a orquestração.
O Slate é capaz de escrever programas em arquivos \.program.ts. Um arquivo de programa tem algumas coisas: seu nome (é assim que o Slate sabe o que é), uma descrição JSDoc e, em seguida, o corpo real do programa*. Uma declaração de programa se parece com isso:
1program(async (ctx) => {2 // modelo barato para busca — ele só precisa encontrar arquivos3 const findings = await run("search", {4 type: "read",5 prompt: "Encontre todos os arquivos relacionados à autenticação",6 model: "codex/gpt-4.1-mini", // usa sua chave codex embutida7 })8})
Os programas seguem o mesmo modelo de execução assíncrona, o que nos permite executar um programa tanto em primeiro quanto em segundo plano, e interagir com ele enquanto está em execução.
1// agente em segundo plano2const h = await spawn("worker", { type: "general", prompt: "Tarefa longa" })3await h.notify("foco no parser primeiro") // mensagem de direcionamento para o agente em execução4const result = await h.result() // aguarda a conclusão depois// distribuir e depois reunir5const a = await spawn({ prompt: "tarefa A" })6const b = await spawn({ prompt: "tarefa B" })7const [ra, rb] = [await a.result(), await b.result()]// programa em segundo plano8import Audit from "deep-audit"9const ah = await spawn(Audit, { input: { pr: 42 } })10const auditResult = await ah.result()
Saída estruturada e estado
Esta é uma limitação primária de todos os outros sistemas até hoje. O estado, em todos os outros sistemas, é mal externalizado e não é isolado com segurança. Se for um arquivo no sistema, você não pode garantir que não haverá corrupção. Se puder, ainda não pode garantir a analisabilidade. Você não pode se inscrever em mudanças de estado para conduzir operações e não pode garantir a adesão ao tipo.
Lembra como queríamos estado persistente que também fosse estruturado e pudesse ser referenciado?
O estado, no Onyx, é diferente. Namespaces de estado são declarados, nomeados diretamente e persistidos ao longo do tempo. Isso significa que um armazenamento de estado pode ser reutilizado repetidamente, permitindo que você construa sistemas de agentes de longa duração com dados reais.
Tanto agentes quanto código leem o estado, e o determinismo que queríamos de um runtime decorre disso. Os agentes leem o estado através de uma ferramenta dedicada que lhes permite sempre interagir com ele de forma segura e estruturada. Agentes e programas são ambos consumidores que podem ser direcionados para modificar o estado, o que permite que o runtime confie no objeto de estado para conduzir a orquestração.
O estado e a adesão ao esquema controlam a conclusão do subagente. Por causa disso, o estado fornece uma superfície unificada para direcionar todo o programa.
Objetos de estado também podem ser passados como variáveis de runtime para sessões filhas compartilhadas com o agente principal. Esse acesso por referência em toda a hierarquia de agentes (que é uma novidade) permite a comunicação entre agentes através de um canal de estado compartilhado.

Loops de longa duração
Alguns programas precisam funcionar mais como sistemas em execução. Considere o openclaw, por exemplo. Você pode realmente representar o openclaw como um programa, dadas as primitivas certas. Para isso, usamos duas primitivas: sleep e checkpoint.
Sleep faz o que você espera: ele dorme.
Agora, aqui está a questão: digamos que você queira algum gerenciamento de tarefas de longa duração em segundo plano. Um grafo de execução predefinido pode travar ou quebrar, e por isso é importante que o agente principal esteja ciente do status do programa.
Para suportar isso, introduzimos a primitiva checkpoint.
Um checkpoint pode ser qualquer coisa, mas a razão pela qual é chamado de checkpoint é porque ele notifica o agente principal com um objeto de forma fixa. Isso permite que o agente principal rastreie coisas como o progresso da tarefa e seja notificado diretamente sobre mudanças no estado do programa. Por sua vez, o agente principal pode então gerenciar mais efetivamente um programa em execução.
O Onyx suporta fazer um loop de agente como o Openclaw, ou seja, um agente persistente com um batimento cardíaco.
Isso é realmente muito legal: você pode compor os primitivos em um tipo completamente diferente de agente apenas usando um loop while, um sleep e um checkpoint.
O Openclaw pode ser simplesmente representado como um arquivo de programa!
1// Um programa para executar um loop de auto-pesquisa de longa duração2for (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) // pausa entre experimentos7}89// Um programa para executar um agente persistente no estilo Openclaw10while(true) {11 const status = await run("status_check", { ...insira modelo barato aqui... })if(status.pending_tasks) {checkpoint({ tasks: status.pending_tasks }) // retorna o estado importante e acorda o agente principal}12 await sleep(30_000) // pausa entre experimentos13}
Composição
Com o Onyx, o Slate pode escrever um *.program.ts para você. Isso persiste e pode (e deve) ser tratado como código normal. Ele vem com tipos prontos para uso, é executado em um runtime sem globais de runtime, e é apenas TypeScript, então seu modelo de composição é simplesmente importar e chamar outro programa.
Por ser apenas TypeScript, você obtém coisas como paralelismo (Promise.all) e loops de graça.
Veja como você importaria um programa e o usaria em outro:
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", ... saída da auditoria) // isso executaria e corrigiria a saída do programa de auditoria.4})
Semântica de erros
Erros, na VM ideal, são lançados de forma ruidosa. Eles devem ser lançados em problemas de sintaxe de runtime, falhas de agente, crashes, etc.
Especificamente, definimos erros de orquestração como:
- Um agente está bloqueado em uma tarefa
- Um agente falhou ao concluir uma tarefa
- Um agente ficou sem etapas ou orçamento para uma tarefa
- Um programa ficou sem orçamento para uma execução
- O modelo de orquestração falhou ao escrever código sintaticamente correto
- Uma modificação de estado ilegal sendo feita
Todos esses casos específicos de erro definem a semântica de runtime. Eles dizem: "Você pode esperar que este runtime lance um erro, porque vemos uma falha de execução do agente da mesma forma que vemos um erro no código". Pode parecer irritante no início, mas esse mecanismo de falha ruidosa lhe dá algo em troca: uma maneira explícita de se preparar e programar em torno de falhas. Então, na realidade, isso lhe dá mais controle, não menos.
1// erros são try/catch — igual a qualquer programa TypeScript2program(async (ctx) => {3 try {4 const result = await run("risky-refactor", {5 type: "general",6 prompt: "Refatore o módulo de autenticação",7 model: "claude-sonnet",8 maxSteps: 20,9 })10 } catch (err) {11 // o agente falhou — mas sabemos exatamente o porquê.12 // o trace tem cada chamada de ferramenta, cada requisição de modelo,13 // cada escrita de estado que levou até aqui.1415 // tente novamente com um modelo diferente16 const result = await run("risky-refactor-retry", {17 type: "general",18 prompt: `A tentativa anterior falhou: ${err.message}. Tente uma abordagem diferente.`,19 model: "claude-opus",20 maxSteps: 30,21 })22 }23})
Seleção de modelo, controle de orçamento e BYOK
A seleção de modelo integrada permite que você tenha um controle ainda mais preciso. A habilidade /models dá ao Slate acesso total à lista de modelos disponíveis, permitindo que o Slate crie programas com vários modelos diferentes realizando tarefas distintas. Quer que o Fable seja o planejador, mas o GLM 5.2 implemente dentro de um harness determinístico? Claro. Quer distribuir uma pergunta entre o Gemini, GPT 5.5 e DeepSeek? Isso também funciona.
Além disso, o runtime suporta dois tipos de substituições de configuração para programas:
- Os modelos globais padrão usados para execução do agente
- O orçamento para executar um programa
Você pode definir diretamente um orçamento de execução para limitar os gastos de um determinado loop.
Além disso, o runtime suporta o uso de suas assinaturas existentes do OpenAI e do Github Copilot.
1program(async (ctx) => {2 // modelo barato para busca — ele só precisa encontrar arquivos3 const findings = await run("search", {4 type: "read",5 prompt: "Encontre todos os arquivos relacionados à autenticação",6 model: "codex/gpt-4.1-mini", // usa sua chave codex integrada7 })89 // modelo de raciocínio para a parte difícil — ele precisa pensar10 const plan = await run("architect", {11 type: "general",12 prompt: `Projete uma correção com base em: ${findings.output}`,13 model: "openai/o3", // Acaba usando créditos da API14 output: z.object({15 approach: z.string(),16 files: z.array(z.string()),17 risk: z.enum(["low", "medium", "high"]),18 }),19 })2021 // modelo de nível médio para implementação — ele só precisa editar22 const handles = await Promise.all(23 plan.files.map(f => spawn("fix-" + f, {24 type: "general",25 prompt: `Aplique esta correção em ${f}: ${plan.approach}`,26 model: "anthropic/claude-sonnet-5",27 maxSteps: 15,28 }))29 )30 await Promise.all(handles.map(h => h.result()))31})
Definindo a superfície de autoria
Houve dois fatores principais no design da superfície de autoria para programas: o quão fácil é para um agente entendê-la e o quão fácil é para um humano lê-la. Escolhemos verbos relativamente simples que soam como inglês e decidimos explicitamente que queríamos modelar a orquestração proceduralmente, em vez de declarativamente.
A seleção de TypeScript como linguagem também foi importante. Há tanto código TypeScript procedural por aí que um modelo entenderá implicitamente a semântica do TypeScript, mesmo sem pós-treinamento.

Peças de engenharia da nossa fábrica de software
A próxima pergunta a ser respondida é: o que tudo isso te proporciona?
Isso te proporciona a capacidade de escrever software real para sua orquestração de agentes. Agora você pode projetar sua própria orquestração de agentes do início ao fim.
Você pode projetar a fábrica.
Por exemplo, você pode criar um programa que monitora o Github em um loop e um programa separado que executa um agente de implementação com um agente de QA para revisão. Ambos são padrões individualmente úteis que você pode encontrar por aí. Então você pode juntá-los para criar um sistema que ouve comentários em um PR, gera um implementador para abordar esses comentários e, em seguida, gera um agente de QA para garantir que a correção seja válida.
Você pode então usar este programa conectado a uma fila de tarefas para delegar e monitorar o trabalho em sua base de código e fazê-lo responder automaticamente a comentários de PR.
E você pode fazer tudo isso usando modelos de pesos abertos rápidos. Porque é apenas código, você não precisa de um LLM poderoso para pensar na orquestração depois que ela é criada pela primeira vez.
Agora, a parte divertida: é hora de compartilhar alguns dos programas que usamos para aumentos massivos de produção.
Pesquisa Profunda de Base de Código
Usamos este programa para ajudar a definir o escopo das tarefas. Ele faz uma pesquisa profunda sobre o estado do nosso monorepo e prepara um pacote de pesquisa para um implementador consultar. Nós o usamos o tempo todo. Parece caro, mas na verdade não é. Você pode executar este programa no Slate com DeepSeek V4 Flash e o processo de pesquisa é completo, mas extremamente barato.

Goal-Review-PR
Este é um que usamos para implementar uma tarefa assim que a pesquisa é concluída. Felizmente, quando a pesquisa chega ao programa de meta, a maior parte da ambiguidade da tarefa já foi resolvida, o que torna a execução da tarefa ainda mais rápida. Carregar a pesquisa com um modelo OSS leve facilita o uso de um modelo caro como o Opus para o que importa: escrever código realmente bom e verificar o estado do sistema. Você pode até modificar o programa para usar o GPT 5.5 para revisar adversariamente o trabalho do Opus 4.8.

Autopesquisa como um Programa
A Autopesquisa[[9]](http://localhost:5173/blog/onyx#ref-9) era originalmente totalmente orientada por LLM. Direcione um agente para o prompt program.md e ele decide o que tentar e como progredir.
Não é surpresa que a Autopesquisa é, na verdade, apenas um programa.
Os programas de agente permitem que você inverta isso e coloque o fluxo de controle no runtime. O programa possui o fluxo de controle enquanto os agentes fazem o trabalho de efeito colateral (editar código, executar git, fazer SSH para a GPU remota, treinar). Para o programa de autopesquisa, a decisão de manter/reverter é código determinístico:
1kept = status === "ok" && valBpb != null && valBpb < best
No nosso caso, o programa executa um agente de configuração para preparar um repositório novo e verificar se a A100 remota está acessível. Se a configuração falhar, ele retorna antecipadamente com uma saída limpa com base em um valor tipado. Caso contrário, ele entra no loop de experimentos.
Cada experimento recebe um novo agente. O agente recebe a melhor configuração atual e o histórico de ideias e resultados anteriores, para que não se repita e possa construir sobre o que foi mantido. Ele propõe uma alteração, edita train.py, faz commit, rsync para a máquina remota, treina e classifica o resultado.
O agente e o programa compartilham o estado. O agente escreve dados no estado, e o programa avalia o estado para o fluxo de controle. Com base no resultado, um agente gravador atualiza results.tsv e, opcionalmente, redefine a execução se o programa decidiu descartar o experimento. Isso deixa o HEAD do git sempre apontando para o melhor branch atual da árvore de experimentos.
Existem duas diferenças principais que valem a pena notar: 1) isso é executado em um programa, então podemos gerar um novo agente por experimento e 2) podemos decidir qual tarefa o agente deve realizar com base no estado ativo do programa.


E fica assim no código:
1// ---------- Programa ----------23program(async (ctx) => {4 const c = cfg(ctx.input)5 const total = ctx.input?.maxExperiments ?? 2067 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: `configuração falhou: ${setup.note}`, setup }15 }1617 let best = c.baselineValBpb18 let bestCommit = setup.baselineCommit19 const history = []2021 for (let i = 1; i <= total; i++) {22 let exp23 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 com erro/bloqueado — trata como crash, restaura o repo para o melhor, continua.32 exp = {33 description: `experimento ${i} erro 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 }4445 const kept = exp.status === "ok" && exp.valBpb != null && exp.valBpb < best4647 await run(`ar-record-${i}`, {48 prompt: recordPrompt(c, exp, kept, bestCommit),49 type: "general",50 maxSteps: 20,51 output: RecordResult,52 })5354 if (kept) {55 best = exp.valBpb56 bestCommit = exp.commit57 }5859 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 })6869 await checkpoint({70 name: `experimento-${i}`,71 message: `exp ${i}/${total}: ${exp.status}${kept ? " MANTIDO" : ""} val_bpb=${exp.valBpb ?? "n/a"} (melhor=${best})`,72 data: { i, total, status: exp.status, valBpb: exp.valBpb, kept, best, bestCommit },73 })74 }7576 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})
Trabalho futuro
O único requisito restante da VM que ainda não definimos é o modelo de durabilidade para programas. Não está claro qual é o modelo correto para retomar e gerenciar o ciclo de vida de um programa, e qual nível de controle deve ser exposto no runtime.
Além disso, há tantas coisas empolgantes que adicionaremos para suportar diferentes cargas de trabalho e formatos de tarefas, para que possamos escrever software real para orquestrar agentes de forma mais eficaz. Temos certeza de que muitos dos padrões surgirão de pessoas usando programas de maneiras criativas por conta própria.
Estamos realmente ansiosos para ver o que você vai construir.
- Equipe RL
Referências
- Yao et al., "ReAct: Synergizing Reasoning and Acting in Language Models," 2022
- Geoffrey Huntley, "The Ralph Loop"
- Geoffrey Huntley, "everything is a ralph loop," January 2026
- Zhang, Kraska, Khattab, "Recursive Language Models," December 2025
- Wang et al., "Executable Code Actions Elicit Better LLM Agents," ICML 2024
- OpenAI, "Introducing Deep Research," February 2025
- Cursor, "Scaling Agents," January 2026
- OpenAI, "Using Goals in Codex"
- Andrej Karpathy, "autoresearch"
- Anthropic, "Introducing Dynamic Workflows in Claude Code"
- OpenAI, "Learning to Reason with LLMs," September 2024
- Anthropic, "A harness for every task: dynamic workflows in Claude Code"
- Random Labs, "Skill Chaining"





