Recentemente concluímos a migração do agente camelAI para fora das máquinas virtuais. O agente agora roda dentro de um Cloudflare Durable Object, seu sistema de arquivos vive em SQLite e R2, e ele escreve JavaScript em vez de bash. A maioria das equipes executa agentes de codificação em uma VM Linux completa ou em um sandbox de contêiner, e nós também costumávamos fazer o mesmo.
Queríamos sair das VMs porque dar a cada usuário uma máquina sempre ligada com disco anexado era muito caro para escalar. A parte difícil é que os agentes de codificação assumem Linux. Eles são treinados para usar bash, e o harness com o qual lançamos exigia uma VM completa, então chegar até aqui exigiu três reformulações. A compensação é que o agente agora só pode fazer coisas para as quais construímos um método explícito, o que parece limitador, mas tem sido bom para o produto.
Sou Miguel, CTO da camelAI. Nosso código-fonte recentemente se tornou open source, então tudo neste post é código que você pode ler em github.com/qaml-ai/camelAI. Vou linkar para os arquivos relevantes ao longo do texto. Aqui está a progressão.
Etapa zero: a era das VMs
Lançamos com o harness Claude Code, que precisa de uma máquina virtual completa para funcionar. Tentamos vários provedores de VM, nenhum atendeu aos nossos requisitos de persistência e desempenho, e acabamos construindo nosso próprio serviço de contêineres. Esse post ainda está no ar, mas não executamos mais nenhuma dessa infraestrutura.
O serviço de contêineres funcionava, mas era pesado. Uma VM sempre ligada para cada usuário é cara, e o mesmo vale para manter os arquivos de cada usuário em um disco rápido anexado. Escalar isso significa escalar máquinas reais com discos reais, o que seria proibitivamente caro para as contagens de usuários que almejamos. Então, em vez de tentar ser inteligente com a orquestração de VMs, começamos a projetar em torno de não precisar de uma VM.
Etapa um: tirar o agente da VM
O harness Claude Code é inseparável de sua VM, então o primeiro passo foi construir nosso próprio harness. Nós o construímos sobre o pi, o agente de codificação open source de Mario Zechner. pi é uma pilha de bibliotecas. A camada mais alta assume um sistema operacional normal, mas as camadas inferiores fornecem os primitivos do agente, como o loop do agente e o gerenciamento de estado, sem se importar onde eles são executados. Não alteramos nenhum código do pi. Importamos essas camadas inferiores e construímos nosso próprio harness sobre elas, rodando dentro de um Cloudflare Durable Object em vez de um ambiente Linux.
Um Durable Object é uma pequena instância de computação com estado que é ativada na borda da Cloudflare, perto do usuário que a criou. Cada thread de chat recebe seu próprio Durable Object, o que por si só reduziu a latência em comparação com rotear tudo através de um host de VM centralizado.
Nesta fase, mantivemos as VMs, mas o agente não vivia mais dentro de uma. Ele chamava a VM remotamente quando precisava executar comandos. A Anthropic descreve essa mesma divisão para seus agentes gerenciados, o cérebro separado das mãos. Isso nos deu algumas propriedades interessantes:
- O agente começa a responder antes da VM acordar, porque não espera uma máquina inicializar.
- A VM pode voltar a dormir enquanto o agente continua trabalhando, ou nunca acordar se a rodada não precisar de comandos.
- Um cérebro pode controlar várias mãos. Um único agente pode operar várias VMs ao mesmo tempo.
Chamamos essas mãos de projetos. Cada projeto vinha com uma VM para executar comandos e um repositório git criado programaticamente através do Cloudflare Artifacts, que é um armazenamento compatível com git que você pode provisionar em tempo real a partir de um Worker. O agente não sabia que estava rodando fora da VM. Ele ainda tinha bash e funcionava como qualquer outro agente de codificação.
O problema é que isso corrigiu a latência e nada mais. Ainda tínhamos uma VM por usuário, então ainda tínhamos todos os problemas de custo e escalabilidade do design original.
Etapa dois: remover a VM
A próxima versão manteve a mesma estrutura de projeto, mas eliminou a VM por trás dele. Cada projeto agora é sustentado por um sistema de arquivos que vive dentro de um Durable Object, com R2 por trás para arquivos maiores.
Não inventamos isso. A equipe de agentes da Cloudflare construiu o Shell, um sistema de arquivos experimental e runtime de execução para Workers, e reutilizamos bastante o código deles. A mecânica é simples. O armazenamento de um Durable Object é um banco de dados SQLite com um limite de 10 GB, e cada linha tem um tamanho máximo. Arquivos pequenos vivem diretamente em linhas do SQLite. Arquivos acima de aproximadamente 1,5 MB são escritos no R2, e a linha do SQLite apenas contém um ponteiro. Para o agente, parece um sistema de arquivos normal, mas por baixo é um banco de dados e armazenamento de objetos, então a persistência são dados armazenados, e não infraestrutura que precisamos manter ativa.
O histórico de versões ainda funciona através do Artifacts, então cada projeto mantém um histórico git sem que precisemos hospedar um servidor git.
Etapa três: remover o bash
Remover o bash parecia drástico. Os agentes de codificação são treinados para usar bash, e o bash é a razão pela qual todos os executam em VMs em primeiro lugar. Também era um problema além do custo. Um agente com bash e acesso à rede precisa de credenciais para fazer algo útil, e nossas tentativas de URLs de proxy autenticadas estavam ficando complicadas e difíceis de aplicar.
Então o removemos. Em vez de bash, o agente escreve JavaScript, executado através do Code Mode e dos dynamic Worker loaders da Cloudflare. Cada execução roda em um novo isolado V8 que inicializa em milissegundos e usa alguns megabytes de memória. O sandbox vem pré-carregado com as conexões de dados do usuário e com métodos para tudo que a plataforma pode fazer. As credenciais nunca entram no sandbox. O agente chama os métodos de uma conexão, e a autenticação acontece do nosso lado.
Quando você olha para o que os agentes realmente usam bash, perder isso custa menos do que você esperaria. A maior parte são operações de arquivo, para as quais o agente tem ferramentas nativas. Damos a ele leitura, escrita e edição, além de nossas próprias implementações de grep e glob. Isso cobre os 80-20. O resto são comandos específicos para tarefas específicas, e eles se tornaram métodos explícitos:
- O deploy do wrangler através de um proxy se tornou um método deploy_project que controlamos totalmente. Como sabemos exatamente quando um deploy acontece, podemos interceptá-lo e abrir uma prévia ao vivo automaticamente. Antes, tínhamos que farejar o tráfego do wrangler proxy para adivinhar qual thread havia implantado algo.
- Construir o aplicativo do usuário e executar notebooks Python se tornaram seus próprios métodos, ambos apoiados por contêineres de curta duração.
Mantivemos contêineres para essas duas tarefas porque elas realmente precisam de Linux. Os aplicativos dos usuários são construídos com Vite, Tailwind e React Router, e adicionar dependências significa executar bun install. Consideramos executar as builds dentro de um Worker, já que o que está sendo construído é ele próprio um Worker, mas esse caminho não é bem suportado, e os Workers têm um limite de memória de 128 MB e uma fração de CPU. As builds seriam lentas e muitos projetos ultrapassariam o limite de memória. Então, em vez disso, uma build inicia um contêiner através do Cloudflare Sandbox SDK, copia o projeto para dentro, executa o trabalho, retorna o resultado e desliga o contêiner. As execuções de notebooks funcionam da mesma forma. Ainda usamos Linux completo, mas apenas pelos segundos de trabalho que realmente precisam dele.
A desvantagem honesta é que temos que antecipar o que o agente precisa. Com bash, ele poderia resolver as coisas por conta própria. Agora, se uma capacidade está faltando, temos que adicioná-la. Na prática, essa pressão tem sido boa para o produto, porque nos obriga a pensar sobre o que os usuários estão fazendo e construir um caminho de primeira classe para isso, em vez de deixar o agente improvisar.
Houve também um benefício inesperado. Bash é aberto, e modelos baratos têm dificuldade em ambientes abertos. Com um conjunto menor de métodos explícitos, eles têm um desempenho visivelmente melhor, o que importa porque manter o camelAI barato para executar é o ponto dessa arquitetura.
Onde isso nos deixa
A pilha agora é Durable Objects para o agente e seu sistema de arquivos, R2 para arquivos grandes, Artifacts para histórico git, pi como o harness, e Code Mode com Workers dinâmicos para execução. Ele é implantado como qualquer outro aplicativo Cloudflare, e não há serviços de contêiner externos para gerenciar.
Workers dinâmicos são cobrados por execução, não por segundo de atividade. Milhares de execuções custam aproximadamente o que alguns minutos de tempo de contêiner custam nos serviços que costumávamos avaliar. A latência é baixa porque tudo roda na borda perto do usuário, e escalar é problema da Cloudflare, não nosso.
Os usuários ainda constroem e implantam aplicativos full-stack em URLs ativas, e o agente ainda lê, escreve, usa grep e faz deploy. Do lado do usuário, nada mudou.
TL;DR
Começamos com o harness Claude Code em um serviço de VM próprio, que era caro e difícil de escalar. Primeiro, movemos o próprio agente para um Cloudflare Durable Object e deixamos ele controlar VMs remotamente, o que corrigiu a latência, mas não o custo. Em seguida, substituímos as VMs inteiramente por um sistema de arquivos armazenado em Durable Object SQLite e R2, baseado no projeto Shell da Cloudflare, com histórico git através do Cloudflare Artifacts. Finalmente, removemos o bash e demos ao agente um sandbox JavaScript via Code Mode e Workers dinâmicos, com métodos explícitos para deploys, builds e notebooks. O resultado é ordens de magnitude mais barato, menor latência, mais simples de operar e mais fácil para modelos menores conduzirem. Tudo é open source em github.com/qaml-ai/camelAI.





