Abbiamo recentemente terminato la migrazione dell’agente camelAI dalle macchine virtuali. L’agente ora gira all’interno di un Cloudflare Durable Object, il suo filesystem risiede in SQLite e R2, e scrive JavaScript invece di bash. Molti team eseguono agenti di codifica in una VM Linux completa o in un contenitore sandbox, e anche noi lo facevamo.
Volevamo abbandonare le VM perché mantenere una macchina sempre accesa per ogni utente, con disco associato, era troppo costoso per scalare. La parte difficile è che gli agenti di codifica danno per scontato Linux. Sono addestrati a ricorrere a bash, e il nostro harness iniziale richiedeva una VM completa, quindi ci sono voluti tre redesign per arrivare a questo punto. Il compromesso è che ora l’agente può fare solo cose per cui abbiamo costruito un metodo esplicito, il che sembra limitante ma è stato positivo per il prodotto.
Sono Miguel, CTO di camelAI. Il nostro codice è recentemente diventato open source, quindi tutto ciò che è descritto in questo post è codice che potete leggere su github.com/qaml-ai/camelAI. Farò riferimento ai file pertinenti man mano. Ecco la progressione.
Passo zero: l’era delle VM
Abbiamo lanciato con l’harness di Claude Code, che necessita di una macchina virtuale completa per funzionare. Abbiamo provato diversi provider di VM, nessuno soddisfaceva i nostri requisiti di persistenza e prestazioni, e abbiamo finito per costruire il nostro servizio di container. Quel post è ancora online, ma non gestiamo più nessuna di quelle infrastrutture.
Il servizio di container funzionava, ma era pesante. Una VM sempre accesa per ogni utente è costosa, e lo è anche tenere i file di ogni utente su un disco veloce e collegato. Scalare significa scalare macchine reali con dischi reali, il che sarebbe stato proibitivamente costoso ai numeri di utenti che puntiamo. Quindi, invece di ingegnarsi sull’orchestrazione delle VM, abbiamo iniziato a progettare senza aver bisogno di una VM.
Passo uno: togliere l’agente dalla VM
L’harness di Claude Code è inscindibile dalla sua VM, quindi la prima mossa è stata costruire il nostro harness. Lo abbiamo costruito su pi, l’agente di codifica open source di Mario Zechner. Pi è uno stack di librerie. Il livello più alto presuppone un sistema operativo normale, ma i livelli inferiori forniscono i primitivi dell’agente, come il ciclo dell’agente e la gestione dello stato, senza preoccuparsi di dove vengono eseguiti. Non abbiamo modificato alcun codice di pi. Abbiamo importato quei livelli inferiori e costruito il nostro harness sopra di essi, eseguendolo all’interno di un Cloudflare Durable Object invece che in un ambiente Linux.
Un Durable Object è una piccola istanza di calcolo stateful che si attiva per l’esecuzione ai margini di Cloudflare, vicino all’utente che l’ha creata. Ogni thread di chat ha il suo Durable Object, il che ha ridotto la latenza da solo rispetto al routing di tutto attraverso un host VM centralizzato.
In questa fase abbiamo mantenuto le VM, ma l’agente non viveva più al loro interno. Chiamava la VM da remoto quando aveva bisogno di eseguire comandi. Anche Anthropic descrive la stessa divisione per i suoi agentei gestiti: il cervello separato dalle mani. Questo ci ha dato alcune proprietà interessanti:
- L’agente inizia a rispondere prima che la VM sia sveglia, perché non aspetta l’avvio di una macchina.
- La VM può tornare a dormire mentre l’agente continua a lavorare, o non svegliarsi mai se il turno non necessita di comandi.
- Un cervello può controllare più mani. Un singolo agente può operare su più VM contemporaneamente.
Chiamiamo queste mani progetti. Ogni progetto aveva una VM per eseguire comandi e un repository git creato programmaticamente tramite Cloudflare Artifacts, uno storage compatibile con git che può essere provisionato al volo da un Worker. L’agente non sapeva realmente di funzionare fuori dalla VM. Aveva ancora bash e lavorava come qualsiasi altro agente di codifica.
Il problema è che questo risolveva solo la latenza e nient’altro. Avevamo ancora una VM per utente, quindi avevamo ancora tutti i problemi di costo e scalabilità del design originale.
Passo due: rimuovere la VM
La versione successiva manteneva la stessa struttura dei progetti ma eliminava la VM sottostante. Ogni progetto è ora supportato da un filesystem che vive all’interno di un Durable Object, con R2 dietro per i file più grandi.
Non l’abbiamo inventato noi. Il team degli agenti di Cloudflare ha costruito Shell, un filesystem e runtime di esecuzione sperimentale per Workers, e abbiamo riutilizzato pesantemente il loro codice. I meccanismi sono semplici. Lo storage di un Durable Object è un database SQLite con un limite di 10 GB, e ogni riga ha una dimensione massima. I file piccoli vivono direttamente nelle righe di SQLite. I file oltre circa 1.5 MB vengono scritti su R2, e la riga di SQLite contiene solo un puntatore. Per l’agente, sembra un filesystem normale, ma sotto il cofano è un database e object storage, quindi la persistenza è dati memorizzati piuttosto che infrastruttura da tenere in vita.
La cronologia delle versioni funziona ancora tramite Artifacts, quindi ogni progetto mantiene una storia git senza che noi ospitiamo un server git.
Passo tre: rimuovere bash
Rimuovere bash è sembrato drastico. Gli agenti di codifica sono addestrati a rcorrere a bash, ed è per questo chhe tutti li eseconono in VM. Era anche un problema oltre al costo. Un agente con bash e accesso alla rete deve avere credenziali per fare qualcosa di utile, e i nostri tentativi di URL proxy autenticati stavano diventando complicati e difficili da far rispettare.
Quindi lo abbiamo rimoso. Invece di bash, l’agente scrive JavaScript, eseconito tramite Code Mode e i caricatori dinamici Worker di Cloudflare. Ogni esecuzione gira in un isoalto V8 fresco che si avvia in milesscondi e conusuma quachi megabte di memmoria. La sandbox viene pre-caricata con le connessoni ai dati dell’utente e con metodi per tutto ciò che la plattaforma può fare. Le credenziali non entrano mai nella sandbox. L’agente chiama i metodi di una connessione e l’autenticazione avviene dalla nostra parte.
Quando si guarda a ciò per cui gli agenti usano effettivamente bash, perderlo costa meno di quanto ci si aspetterebbe. La maggior parte sono operazioni sui file, per le quali l’agente ha strumeti nativi. Forniamo lece lettura, scritura e modifica, più le nostre imlementazioni dif grep e glob. Queto copre l’80-20. Il resto sono comandi specifci per lavori specifici, e queti sono diventati metodi espliciti:
- wrangler deploy tramite un proxy è diventato un metodo deploy_project che controllamo completamente. Da quando sappiamo esattamente quando avviene un deploy, possiamo agganciarlo e aprire un’anteprima live automaticamente. Prima, dovevamo sniffare il traffico wrangler proxyato per indovinare quale thread aveva deployato qualcosa.
- Costruire l’app dell’utente ed eseguire notebook Python sono diventati metodi propri, entrambi supportati da contenitori a breve durata.
Abbiamo mantenuto i contenitori per queti due lavori perché hanno genuinamente bisogno di Linux. La app degli utenti sono costruite con Vite, Tailwind e React Router, e aggiungere dipendenze significa eseguire bun install. Abbiamo considerato di eseconire le build all’inerno di un Worker, dato che ciò che viene costriuto è esso stesso un Worker, ma quel percoro non è ben supportato, e i Worker hanno un limte di memoria di 128 MB e una frazione di CPU. Le build sarebbero lente e molti progetti superebero il limite di memmoria. Quindi, invece, una build avvia un contenitore tramite Cloudflare Sandbox SDK, copia il progetto all’interno, esegue il lavoro, restituisce il risutato e chiuode il contenitore. I notebook funzionano allo stesso modo. Usiamo ancora Linux completo, ma solo per i secondo di lavoro che ne hanno effettivamente bisogno.
Lo svantaggio oneesto è che dobbiamo antivedere ciò di cui l’agente ha bissogno. Con bash, poteva capire le cose da solo. Ora, se una capacità manca, dobbiamo aggengela. In pratica, questa pressione è stata buona per il prodoto, perché ci force a pensare a ciò che fanno gli utenti e a costruire un persorso di prima classe per quelo, invèce di lasciare l’agente improvvisare.
C’è statto anche un beneficio imprevisto. Bash è sempre apeto, e i modelli economici faticano in ambienti aperti. Con un insieme più ristretto di metodi espliciti, si comportano notevolmente meglio, il che conta perché mantenere camelAI economico da esegue è il ponto di queta architettura.
Dove ci lascia tutto queto
Lo stack ora è: Durable Objects per l’agente e il suo filesystem, R2 per i file grandi, Artfacts per la cronologia git, pi come harne e Code Mode con Worker dinamici per l’esecuzione. Si dispiega come qualunque altra app di Cloudflare, e non ci sono servizi di contenitori esterni da gestire.
I Worker dinamici vengono faturati per esecuzione, non per secondo di uptime. Migliaia di esecuzioni costano quanto pochi minuti di tempo contenitore sui servizi che usavamo per valutare. La latenza è bassa perché tutto ese oni margine vicino all’utente, e scalare è un problema di Cloudflare, non nostro.
Gli utenti costruiscono e deployano ancora app full-stack a URL live, e l’agente legge, scrive, grepa e deploya. Dal lato dell’utente, nulla è cambiato.
TL;DR
Siamo iniziati con l’harness di Claude Code su un servizio di VM auto-costruito, che era costoso e duro da scalare. Primo, abbiamo spostato l’agente stesso in un Cloudflare Durable Object e gli abbiamo permesso di controllare le VM da remoto, il che ha fissato la latenza ma non il costo. Poi abbiamo sostituto completamene le VM con un filesystem memorizato in SQLite di Durable Object e R2, basato sul progetto Shell di Cloudflare, con cronologia git tramite Cloudflare Artifacts. Infine, abbiamo rimosso bash e dato all’agente una sandbox JavaScript tramite Code Mode e Worker dinamici, con metodi espliciti per deploy, build e notebook. Il risutato è più economico di ordini di grandezza, latenza inferiore, più semplice da operare e più facile da guidare per i modelli più piccoli. Tutto è open source su github.com/qaml-ai/camelAI.





