Skip to main content
AGOAGO

Il n'y a pas assez de compute sur Terre pour donner un container à chaque agent

Cloudflare a sorti le filesystem de l'agent du sandbox pour le mettre dans un Durable Object. Le container n'est plus gardé chaud, il devient un outil appelé.

7 min read

Donnez à chaque agent sa propre machine Linux et vous tombez à court de machines Linux. C'est la contrainte derrière cloudflare/computer, que Cloudflare a publié en open source le 3 août 2026. Matt Carey et Aron Carroll la posent en une ligne :

there's nowhere near enough compute in the world for every company to give each of their users' agents their own containerized compute environment. This will not scale to hundreds of millions, then billions, of concurrent agents.

En clair : il n'y a nulle part assez de compute dans le monde pour que chaque entreprise donne à chaque agent de chaque utilisateur son propre environnement conteneurisé.

C'est un argument de capacité, pas de latence, et les arguments de capacité ne se résolvent pas en attendant que le matériel devienne moins cher. Ils se résolvent en ayant besoin de moins de matériel par unité.

Les chiffres à l'unité sont déjà publics. La documentation de Cloudflare situe les démarrages à froid de container « often in the 1 to 3 second range ». En mars 2026, en annonçant les Dynamic Workers, Cloudflare écrivait : « an isolate takes a few milliseconds to start and uses a few megabytes of memory. That's around 100x faster and 10x to 100x more memory efficient than a typical container. »

La solution n'est pas un container plus léger, c'est zéro container pour la plupart des appels

La réponse tentante consiste à faire maigrir le sandbox. Cloudflare a pris l'autre chemin : pour l'écrasante majorité des appels, ne démarrez pas de sandbox du tout. Faites tourner le travail dans un isolat V8, la primitive qui sert déjà les Workers, et gardez le container pour la minorité de tâches qui ont vraiment besoin de Linux. L'objectif est annoncé sans détour : « our goal with @cloudflare/computer is to provide an agent with a runtime where a container is required for less than 10% of its work. »

Regardez ce qu'un agent fait de sa journée et le découpage tient. Il lit un fichier, cherche une chaîne dans un répertoire, réécrit une ligne, retravaille du JSON, récupère une URL, regarde ce qui a changé dans git. Du texte en entrée, du texte en sortie, sur des fichiers qu'il a déjà. Rien de tout ça n'a besoin d'un userland Linux.

Le container gagne sa place sur une liste bien plus courte : installer un arbre de dépendances, compiler quelque chose, convertir un document avec pandoc, piloter un navigateur headless. De vrais binaires qui font un travail qu'aucun interpréteur ne sait imiter.

Séparer les deux est l'idée évidente. La faire fonctionner a demandé de résoudre trois problèmes.

Problème 1 : vos fichiers meurent avec le sandbox, donc vous payez pour le garder en vie

Voici l'ancien montage. Les fichiers de votre agent sont sur un disque à l'intérieur du sandbox. Pour en lire un, vous demandez au sandbox. Pour en écrire un, vous demandez au sandbox, et le sandbox devient le seul endroit au monde où cet octet existe. Le sandbox est votre base de données, et il a la durée de vie d'un processus.

Tout ce qui fait mal découle de là. Vous gardez le container chaud entre deux appels parce que l'éteindre effacerait le répertoire de travail. Vous ne pouvez pas exécuter un grep pas cher dans un isolat, parce que l'isolat n'a rien à grepper. Vous payez une machine Linux à ne rien faire, pour garder quelques mégaoctets de texte.

Solution : l'agent garde les fichiers dans sa propre base SQLite

Une définition d'abord, parce que tout ce qui suit repose dessus. Un Durable Object, c'est le nom que donne Cloudflare à un petit serveur qui porte un nom, tourne à un seul endroit à la fois, et transporte une base SQLite privée qui survit d'une requête à l'autre. Vous en avez un par utilisateur, ou par conversation, ou par agent. C'est la partie de Workers qui a le droit de se souvenir.

Cloudflare y a mis les fichiers. Ce sont maintenant des lignes dans une table SQLite, à l'intérieur du Durable Object qui fait tourner votre boucle d'agent, donc en lire un revient à lire votre propre stockage, dans votre propre processus. Le sandbox ne détient plus rien : il reçoit une copie de ce dont la commande a besoin, s'exécute, rend ses écritures, et peut ensuite être jeté.

Le container a cessé d'être l'endroit où vivent vos données. Il est devenu un endroit que vos données visitent.

Où vivent les fichiers

Sortez les fichiers du sandbox et le sandbox devient jetable

Les deux designs font tourner un sandbox. Un seul peut se permettre de le jeter en plein travail.

Le sandbox habituel
L'état vit dans le calcul
Votre boucle d'agentSans état. Elle oublie tout entre deux appels.exécuter / relireVM sandboxVrai Linux, vrais binaires, vrai réseau/workspaceLa seule copie de vos fichiersmeurt avec la machine
  • Vous tuez le sandbox, le travail part avec lui.
  • Le garder chaud pour garder l'état, c'est louer une machine qui ne fait rien.
  • Chaque lecture de fichier est un aller retour vers le cloud d'un autre.
Cloudflare Computer
L'état vit dans le plan de contrôle
Durable ObjectVotre boucle d'agent, avec une adresse et une mémoireSQLite : la source de véritésurvit aux redémarrages, dort gratuitementpush / pullSandbox, monté en FUSEVrai Linux. Ne possède rien.jetez le, relancez en un autre
  • Le sandbox peut mourir en plein run. Les fichiers sont déjà rentrés.
  • L'inactivité coûte du stockage, pas du calcul. Un workspace endormi se facture comme des lignes en base.
  • Changez de moteur d'exécution sans déplacer un seul fichier.

Rien d'exotique ne soutient tout ça. Les Durable Objects disposent d'un backend de stockage SQLite en disponibilité générale depuis avril 2025, avec 10 Go par objet. @cloudflare/computer construit sur ces tables un filesystem virtuel de forme POSIX, exposé via une API qui ressemble volontairement à node:fs/promises :

using ws = await getWorkspace(env.Agent.get(id));
 
await ws.fs.writeFile("/notes/todo.md", "- [ ] ship it\n");
await ws.fs.mkdir("/notes/daily", { recursive: true });
const hits = await ws.fs.grep("TODO", "/", { ignoreCase: true });

Aucun container ne tourne dans cet extrait, et aucun n'a besoin de tourner. Des fichiers durables avec une adresse, posés dans le même objet que votre boucle d'agent, sont déjà utiles en soi.

Problème 2 : votre agent sait détenir des fichiers, il ne sait toujours rien exécuter

Un Durable Object est un environnement JavaScript : il exécute le code que vous avez déployé, et ce code a été figé au moment du déploiement. Votre agent ne fonctionne pas comme ça. Il décide à l'exécution qu'il veut grep -r TODO /workspace, ou sed -i, ou npm test, et rien de tout ça n'était dans votre bundle. Workers interdit par ailleurs eval et new Function, donc vous ne pouvez pas tricher en exécutant une chaîne de caractères.

Il faut donc à exec quelque chose capable de lancer une commande que personne n'a écrite à l'avance, et jusqu'à récemment la seule chose qui savait faire ça chez Cloudflare était un container. D'où le résultat absurde : démarrer une machine Linux, attendre une à trois secondes, payer un userland entier, pour lancer un grep.

Supprimer le container n'est pas une option non plus, parce qu'une partie du travail en a réellement besoin. pandoc, un compilateur C, un navigateur headless, npm install : tout ça exige de vrais binaires et de vrais processus, et aucune astuce ne les remplace. Il vous faut quelque chose de bon marché pour le cas courant, le container pour le reste, et un moyen de passer de l'un à l'autre qui ne vous coûte pas le répertoire de travail.

Solution : évitez le container pour la plupart des commandes, gardez le pour le reste, sans jamais déplacer les fichiers

Backends d'exécution

Un filesystem, un appel exec, trois façons d'exécuter

Les trois lisent et écrivent les mêmes fichiers, donc vous gardez le container seulement pour les rares commandes qui l'exigent.

Le point d'entrée uniquews.runtime.exec(source, { backend })Containercontainer-shellExécuteUne commande shell dansun vrai userland Linux.ContrepartieDémarre en secondes.Tout devient possible.Worker shellworker-shellExécuteUne commande just-bashdans un Dynamic Worker.ContrepartieDémarre en millisecondes.Outils texte uniquement.Worker JavaScriptworker-javascriptExécuteUn module ES dans unDynamic Worker neuf.ContrepartieDémarre en millisecondes.Entrée et sortie typées.Un Workspace, un store SQLite, dans un seul Durable ObjectChaque backend lit et écrit dans le même arbre de référence

Trois backends existent aujourd'hui derrière un point d'entrée unique. Worker shell simule un shell dans un isolat, Worker JavaScript y exécute un module, et Container est la vraie issue de secours Linux.

// Worker shell : des millisecondes, zero container.
const grep = await ws.runtime.exec("grep -r TODO /workspace");
 
// Issue de secours : userland Linux complet, mêmes fichiers.
const build = await ws.runtime.exec("npm test", { backend: "container-shell" });

Worker shell tourne dans un Dynamic Worker, c'est à dire un Worker que votre code crée à l'exécution et non au déploiement, via un binding que Cloudflare a mis en bêta ouverte en mars 2026. Il démarre en millisecondes et n'a aucun filesystem à lui : chaque opération de fichier repart en RPC vers le Durable Object qui détient la vérité, donc pas de seconde copie et rien à synchroniser.

Worker JavaScript remplace le shell par un module. Il évalue ce module dans un Dynamic Worker neuf, avec node:fs/promises branché sur le même Workspace, pour quand vous voulez que l'agent rende des données structurées plutôt que du texte.

Qui décide du backend qui tourne

Rien ne route automatiquement. runtime.exec prend le premier backend que vous avez enregistré, sauf si l'appel en nomme un autre : dans votre propre code, le choix est une chaîne de caractères que vous tapez.

Pour les commandes que l'agent invente, le choix est un prompt. createAITools prend une description par backend et les donne au modèle :

shell: {
  defaultBackend: "worker-shell",
  backends: {
    "worker-shell": { description: "Fast shell with built-in text commands. No container." },
    "container-shell": { description: "Full Linux userland. Slower to start." },
  },
}

« The model reads each backend's description when deciding where a command should run », dit le README, « so write them in plain language ». L'objectif des 10 % n'est donc garanti nulle part dans le système. Il est le résultat de deux phrases en anglais, et un modèle qui choisit le container par prudence vous coûte discrètement toute l'économie.

Comment faire tourner un shell là où il n'y a pas de processus

Le fonctionnement de Worker shell n'a rien d'évident, parce qu'un isolat V8 n'a pas de modèle de processus : pas de fork, pas de exec, aucun moyen de lancer grep comme processus fils. Or un shell n'est rien d'autre qu'un programme qui lance d'autres programmes, donc un vrai shell ne peut pas exister là dedans.

La parade consiste à arrêter de lancer des programmes et à faire semblant. just-bash est un interpréteur bash écrit en TypeScript : il analyse votre ligne de commande et implémente grep, sed, awk et les autres sous forme de fonctions TypeScript, par dessus un filesystem virtuel, sans jamais rien lancer. Vercel Labs le publie sous licence Apache-2.0, il est sur npm depuis décembre 2025, et son README dit qu'il est « designed for AI agents ». Cloudflare le déclare en dépendance dure de @cloudflare/computer et remplace son filesystem en mémoire par le Workspace, pour que Worker shell lise et écrive dans votre vrai arbre durable.

Le résultat n'est pas un jouet à grep. Vous avez l'outillage texte standard, plus des groupes optionnels pour jq et yq, xan pour le CSV, sqlite pour interroger un fichier de base, python pour exécuter un script, et git pour cloner et commiter. Son curl tourne sur le fetch de l'isolat, donc l'agent fait des appels HTTP avec une sortie réseau toujours gouvernée par le globalOutbound du Dynamic Worker, une laisse plus courte que ce que donne un container. Vous avez donc un agent qui lit, cherche, réécrit, récupère, parse, interroge et commite, sans jamais démarrer de container.

Notez au passage qui l'a écrit. La voie rapide sans container de Cloudflare, celle qui rend l'objectif des 10 % atteignable, tourne sur un paquet de Vercel Labs, dont le Vercel Sandbox est un concurrent direct sur ce marché précis.

C'est aussi là que la résolution du problème 1 se rembourse. Deux runtimes qui ne partagent rien au niveau infrastructure regardent exactement le même arbre, parce qu'aucun des deux ne le possède. Passer de l'un à l'autre revient à changer une option dans l'appel. Pas un fichier ne bouge.

Ce que vous pouvez et ne pouvez pas exécuter sans container

La frontière ne saute pas aux yeux depuis les noms, alors la voici concrètement :

Tâche Worker shell Worker JavaScript Container
grep -r TODO /workspace oui via node:fs oui
Retravailler un JSON avec jq oui oui oui
Récupérer une URL, la passer en markdown oui oui oui
Cloner un dépôt, commiter un fichier oui oui oui
Exécuter un script Python oui, groupe optionnel non oui
Rendre du markdown en PDF avec pandoc non non oui
npm install non non oui
Piloter un navigateur headless non non oui

npm install est le cas qui éclaire tout, parce que trois choses distinctes l'empêchent de tourner dans l'isolat, et une seule des trois est une question de vitesse.

D'abord, npm est un programme Node, et il n'y a pas de Node dans un isolat V8. Pas de binaire node posé sur un PATH, parce qu'il n'y a ni PATH ni binaires. Ensuite, et c'est plus fondamental, installer un arbre de paquets veut dire lancer des processus fils : scripts de cycle de vie, node-gyp, un compilateur C. Un isolat n'a pas de modèle de processus, donc il n'y a rien pour les lancer. just-bash contourne le problème pour grep et sed en les réimplémentant en TypeScript, ce qui est une belle astuce pour les outils texte et ne sert à rien pour make. Enfin, même en réglant les deux premiers points, c'est exactement la charge que le protocole de synchronisation encaisse le plus mal : l'install de référence écrit 36 675 fichiers.

La règle est donc plus étroite que « le container pour les gros travaux ». Le container est fait pour le travail qui exige un vrai binaire, aussi petit soit il.

Problème 3 : le container doit quand même voir vos fichiers, et les déplacer n'est pas gratuit

Les backends isolate ont la vie facile. Ils rappellent le Durable Object en RPC, donc ils n'ont jamais besoin d'un filesystem à eux. Le container ne peut pas faire ça. pandoc ne sait pas ce qu'est un Durable Object. Il veut ouvrir un chemin, et il veut que le noyau réponde.

Solution : montrez au container un dossier ordinaire, et n'envoyez que ce qui a changé

Un démon appelé computerd tourne dans le container et projette l'état du Durable Object sous forme de vrai mount FUSE, pour que les binaires ordinaires voient un filesystem ordinaire. C'est le maintien de l'accord entre les deux côtés qui fait de chaque exec un aller retour.

L'aller retour container

Chaque exec container est un push, un run et un pull retour

Cet aller retour est ce qui permet de jeter le container, et aussi ce qui rend les grosses I/O lentes.

Source de véritéDurable ObjectFilesystem virtuel SQLiteContientles inodes et les cheminsdes chunks de 512 Kioune révision monotoneAu reposIl dort, tout est gardé.JetableContainercomputerd, monté en FUSEContientune copie de travaildes binaires dans le PATHrien d'irremplaçableAu reposTuez le. Rien de perdu.01Pousser les changementsSeuls les chemins modifiés. Des hashes, pas des octets.02Exécuter la commandeLe sandbox voit un filesystem POSIX ordinaire via FUSE.03Récupérer les écrituresLes révisions du container reviennent dans SQLite.04Renvoyer le résultatstdout, exitCode, et le compte de ce qui a bougé.
Le hashing qui rend la sync bon marché rend les grosses écritures lentes. Chaque chunk de 512 Kio est hashé à l'écriture, c'est ainsi que l'étape 1 envoie un diff et pas tout l'arbre, et pourquoi écrire 64 Mio via le mount prend 231 ms contre 17 ms sur le disque. Les métadonnées obtiennent l'inverse : le store d'inodes tient en mémoire, donc find et git init battent le vrai disque.

Le push transporte des hashes, pas des octets : l'émetteur demande au récepteur quels chunks de contenu lui manquent et n'envoie que ceux là. Cinq réécritures du même chemin entre deux execs coûtent une entrée sur le fil, pas cinq. C'est un bon protocole, et il a un prix, parce que produire ces hashes signifie hasher chaque chunk de 512 Kio à l'écriture.

Les benchmarks publiés dans docs/19_performance.md montrent exactement où tombe ce prix. Ils sont mesurés sur une instance Containers standard-2, en comparant le mount FUSE au disque ext4 du container :

Opération Via FUSE Vrai disque Écart
find sur un arbre 10x10x10 1814 ms 4404 ms 2,4x plus rapide
git init et commit de 100 fichiers 459 ms 635 ms 1,4x plus rapide
écriture de 64 Mio 231 ms 17 ms 14x plus lent
npm install, 854 paquets 124,7 s 63,9 s 2x plus lent

Le partage est exactement ce que prédit le hashing des chunks : parcourir l'arbre est bon marché, y faire transiter des octets ne l'est pas.

Exécuter la plupart des commandes dans un isolat réduit l'exposition, mais ce n'est pas un modèle de sécurité

Un agent qui tourne à l'intérieur du produit de quelqu'un d'autre, sur de vraies données clients, doit être à la fois capable et étroitement borné. Les deux se tirent dessus, et la réponse habituelle consiste à le verrouiller jusqu'à ce qu'il ne puisse plus faire grand chose.

La voie isolat aide ici, presque par effet de bord. Elle n'a pas de processus à lancer, pas de binaires dans un PATH, et sa sortie réseau reste sous le globalOutbound du Worker : le runtime qui fait l'essentiel du travail est donc aussi celui qui atteint le moins de choses. Le container reste disponible pour le reste, comme quelque chose qu'on choisit commande par commande plutôt que comme environnement par défaut.

Il faut être clair sur l'étroitesse de tout ça. Ça ne fait rien contre l'injection de prompt, et ça n'empêchera pas un agent d'écrire dans le mauvais fichier avec des permissions parfaitement légitimes. Ça réduit la surface pour les commandes qui n'avaient jamais eu besoin d'une machine, ce qui est utile sans être décisif. Chez AGO, on veut des agents à la fois capables et sécurisés, donc on va regarder ça de près. Pas encore prêt pour la production, @cloudflare/sandbox est l'option en disponibilité générale en attendant.

Share this article
Maxime Thoonsen

Maxime Thoonsen

Co-founder

Expert in AI and customer operations with over 10 years of experience in building scalable solutions.