Skip to main content
AGOAGO

There is not enough compute on Earth to give every agent a container

Cloudflare moved the agent filesystem out of the sandbox and into a Durable Object, so the container stops being kept warm and becomes a tool you call.

7 min read

Give every agent its own Linux box and you run out of Linux boxes. That is the constraint behind cloudflare/computer, which Cloudflare open sourced on 3 August 2026. Matt Carey and Aron Carroll put it in one line:

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.

That is a capacity argument, not a latency one, and capacity arguments do not get solved by waiting for hardware to get cheaper. You solve them by needing less hardware per unit.

The per unit numbers are already public. Cloudflare's documentation puts container cold starts "often in the 1 to 3 second range". In March 2026, announcing Dynamic Workers, Cloudflare wrote that "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."

The fix is not a lighter container, it is no container at all for most calls

The tempting answer is to shrink the sandbox. Cloudflare went the other way: for the overwhelming majority of calls, do not start a sandbox. Run the work in a V8 isolate, the same primitive that already serves Workers, and keep the container for the minority of tasks that genuinely need Linux. The target is stated outright: "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."

Look at what an agent does all day and the split holds up. It reads a file, searches a directory for a string, rewrites a line, reshapes some JSON, fetches a URL, checks what changed in git. Text in, text out, over files it already has. None of that needs a Linux userland.

The container earns its keep on a much shorter list: installing a dependency tree, compiling something, converting a document with pandoc, driving a headless browser. Real binaries doing work no interpreter can fake.

Splitting the two is the obvious idea. Making it work took solving three problems.

Problem 1: your files die with the sandbox, so you keep paying to keep it alive

Here is the old arrangement. Your agent's files sit on a disk inside the sandbox. To read one, you ask the sandbox. To write one, you ask the sandbox, and now the sandbox is the only place in the world where that byte exists. The sandbox is your database, and it has the lifetime of a process.

Everything painful follows from that. You keep the container warm between calls because shutting it down would delete the working directory. You cannot run a cheap grep in an isolate, because the isolate has nothing to grep. You pay for a Linux machine to sit idle, guarding a few megabytes of text.

Solution: the agent keeps the files in its own SQLite database

One definition first, because everything below rests on it. A Durable Object is Cloudflare's name for a small server that has a name, runs in exactly one place at a time, and carries a private SQLite database that survives between requests. You get one per user, or per conversation, or per agent. It is the part of Workers that is allowed to remember.

Cloudflare put the files there. They are now rows in a SQLite table inside the Durable Object that runs your agent loop, so reading one is reading your own storage, in your own process. The sandbox holds nothing of its own: it receives a copy of what the command needs, runs, hands its writes back, and can then be thrown away.

The container stopped being where your data lives. It became a place your data visits.

Where the files live

Move the files out of the sandbox and the sandbox becomes disposable

Both designs run a sandbox. Only one of them can afford to throw it away mid task.

The usual sandbox
State lives in the compute
Your agent loopStateless. Forgets everything between calls.run / read backSandbox VMReal Linux, real binaries, real network/workspaceThe only copy of your filesdies with the box
  • Kill the sandbox and the work goes with it.
  • Keep it warm to keep the state, and you rent a machine that is doing nothing.
  • Every file read is a network hop into somebody else's cloud.
Cloudflare Computer
State lives in the control plane
Durable ObjectYour agent loop, with an address and a memorySQLite: the source of truthsurvives restarts, sleeps for freepush / pullSandbox, mounted over FUSEReal Linux. Owns nothing.throw it away, start another
  • The sandbox can die mid run. The files are already home.
  • Idle costs storage, not compute. A sleeping workspace bills like rows in a database.
  • Swap the execution engine without moving a single file.

Nothing exotic is holding this up. Durable Objects have had a SQLite storage backend generally available since April 2025, with 10 GB per object. @cloudflare/computer builds a POSIX shaped virtual filesystem on those tables and exposes it as an API that deliberately looks like 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 });

No container is running in that snippet, and none needs to. Durable files with an address, sitting in the same object as your agent loop, are already useful on their own.

Problem 2: your agent can hold files now, but it still cannot run anything

A Durable Object is a JavaScript environment: it runs the code you deployed, and that code was frozen the moment you deployed it. Your agent does not work that way. It decides at runtime that it wants grep -r TODO /workspace, or sed -i, or npm test, and none of those were in your bundle. Workers also blocks eval and new Function, so you cannot cheat your way to running a string.

So exec needs something that can run a command nobody wrote down in advance, and until recently the only thing on Cloudflare that could was a container. Hence the absurd result: boot a Linux machine, wait one to three seconds, pay for an entire userland, to run grep.

Deleting the container is not an option either, because some of the work genuinely needs it. pandoc, a C compiler, a headless browser, npm install: those want real binaries and real processes, and no clever trick substitutes. You need something cheap for the common case, the container for the rest, and a way to cross between them that does not cost you the working directory.

Solution: skip the container for most commands, keep it for the rest, never move the files

Pluggable execution

One filesystem, one exec call, three ways to run

All three read and write the same files, so you keep the container only for the few commands that truly need it.

The single entry pointws.runtime.exec(source, { backend })Containercontainer-shellRunsA shell command infull Linux userland.Trade offSeconds to start.Anything is possible.Worker shellworker-shellRunsA just-bash commandin a Dynamic Worker.Trade offMilliseconds to start.Text tooling only.Worker JavaScriptworker-javascriptRunsAn ES module in afresh Dynamic Worker.Trade offMilliseconds to start.Structured in and out.One Workspace, one SQLite store, inside one Durable ObjectEvery backend reads and writes the same authoritative tree

Three backends ship today behind a single entry point. Worker shell simulates a shell inside an isolate, Worker JavaScript runs a module there, and Container is the real Linux escape hatch.

// Worker shell: milliseconds, no container.
const grep = await ws.runtime.exec("grep -r TODO /workspace");
 
// Escape hatch: full Linux userland, same files.
const build = await ws.runtime.exec("npm test", { backend: "container-shell" });

Worker shell runs inside a Dynamic Worker, which is a Worker your code creates at runtime instead of at deploy time, through a binding Cloudflare put into open beta in March 2026. It boots in milliseconds and has no filesystem of its own: every file operation goes back over RPC to the Durable Object that holds the truth, so there is no second copy and nothing to sync.

Worker JavaScript swaps the shell for a module. It evaluates that module in a fresh Dynamic Worker with node:fs/promises wired to the same Workspace, for when you want the agent to hand back structured data instead of text.

Who decides which backend runs

Nothing routes automatically. runtime.exec uses the first backend you registered unless the call names another, so inside your own code the choice is just a string you type.

For the commands the agent invents, the choice is a prompt. createAITools takes one description per backend and hands them to the model:

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," says the README, "so write them in plain language." So the 10% target is not enforced anywhere in the system. It is the outcome of two English sentences, and a model that reaches for the container out of caution will quietly cost you the whole saving.

How you run a shell where there are no processes

How Worker shell works is not obvious, because a V8 isolate has no process model: no fork, no exec, no way to launch grep as a child process. A normal shell is nothing but a program that launches other programs, so a normal shell cannot exist there.

The way around it is to stop launching programs and start pretending. just-bash is a bash interpreter written in TypeScript: it parses your command line and implements grep, sed, awk and the rest as TypeScript functions against a virtual filesystem, so nothing is ever spawned. Vercel Labs publishes it under Apache-2.0, it has been on npm since December 2025, and its README says it is "designed for AI agents". Cloudflare lists it as a hard dependency of @cloudflare/computer and swaps its in-memory filesystem for the Workspace, so Worker shell reads and writes your real durable tree.

The result is no grep toy. You get the standard text tooling, plus opt in groups for jq and yq, xan for CSV, sqlite to query a database file, python to run a script, and git to clone and commit. Its curl runs on the isolate's own fetch, so the agent makes HTTP calls with egress still governed by the Dynamic Worker's globalOutbound, a tighter leash than a container gives you. So you have an agent that reads, searches, rewrites, fetches, parses, queries and commits, without ever starting a container.

Worth noticing who wrote it. Cloudflare's containerless fast path, the thing that makes the 10% target reachable at all, runs on a package from Vercel Labs, whose Vercel Sandbox competes in this exact market.

This is also where solving problem 1 pays off. Two runtimes that share nothing at the infrastructure level look at exactly the same tree, because neither of them owns it. Switching between them means changing one option in the call. Not a single file moves.

What you can and cannot run without a container

The boundary is not obvious from the names, so here it is concretely:

Task Worker shell Worker JavaScript Container
grep -r TODO /workspace yes via node:fs yes
Reshape a JSON file with jq yes yes yes
Fetch a URL, strip it to markdown yes yes yes
Clone a repo, commit a file yes yes yes
Run a Python script yes, opt in group no yes
Render markdown to PDF with pandoc no no yes
npm install no no yes
Drive a headless browser no no yes

npm install is the clarifying case, because three separate things stop it and only the last one is about speed.

First, npm is a Node program, and there is no Node in a V8 isolate. There is no node binary sitting on a PATH, because there is no PATH and no binaries. Second, and more fundamentally, installing a package tree means spawning child processes: lifecycle scripts, node-gyp, a C compiler. An isolate has no process model, so there is nothing to spawn them with. just-bash gets around that for grep and sed by reimplementing them in TypeScript, which is a fine trick for text tools and no help at all for make. Third, even if you solved both, this is precisely the workload the sync protocol is worst at: the benchmark install writes 36,675 files.

So the rule is narrower than "use the container for the heavy jobs". The container is for work that needs a real binary, however small that work is.

Problem 3: the container still has to see your files, and moving them is not free

The isolate backends have it easy. They call back into the Durable Object over RPC, so they never need a filesystem of their own. The container cannot do that. pandoc does not know what a Durable Object is. It wants to open a path, and it wants the kernel to answer.

Solution: show the container an ordinary folder, and send only what changed

A daemon called computerd runs inside the container and projects the Durable Object's state as a real FUSE mount, so ordinary binaries see an ordinary filesystem. Keeping the two sides in agreement is what makes every exec a round trip.

The container round trip

Every container exec is a push, a run, and a pull back

That round trip is what lets you throw the container away, and it is also what makes bulk I/O slow.

Source of truthDurable ObjectSQLite virtual filesystemHoldsinodes and paths512 KiB content chunksa monotonic revisionWhen idleIt sleeps. Files stay.DisposableContainercomputerd, FUSE mountedHoldsa working copyreal binaries on PATHnothing you cannot loseWhen idleKill it. Nothing lost.01Push what changedOnly the paths touched since last sync. Hashes, not bytes.02Run the commandThe sandbox sees an ordinary POSIX filesystem over FUSE.03Pull the writes backContainer revisions land back in SQLite, now current again.04Return the resultstdout, exitCode, and a count of what moved each way.
The hashing that makes sync cheap is what makes bulk writes slow. Every 512 KiB chunk is hashed on write, which is how step 1 sends a diff instead of the whole tree, and why writing 64 MiB through the mount takes 231 ms against the disk's 17 ms. Metadata gets the opposite deal: the inode store sits in memory, so find and git init beat the container's real disk.

The push carries hashes, not bytes: the sender asks the receiver which content chunks it is missing and ships only those. Five rewrites of the same path between two execs cost one entry on the wire, not five. That is a good protocol, and it has a price, because producing those hashes means hashing every 512 KiB chunk on write.

The published benchmarks in docs/19_performance.md show exactly where that price lands. They are measured on a Containers standard-2 instance, comparing the FUSE mount against the container's own ext4 disk:

Operation Over FUSE Real disk Difference
find across a 10x10x10 tree 1814 ms 4404 ms 2.4x faster
git init and commit 100 files 459 ms 635 ms 1.4x faster
write 64 MiB 231 ms 17 ms 14x slower
npm install, 854 packages 124.7 s 63.9 s 2x slower

The split is exactly what the chunk hashing predicts: walking the tree is cheap, moving bytes through it is not.

Running most commands in an isolate lowers exposure, but it is not a security model

An agent running inside someone else's product, on real customer data, has to be capable and tightly bounded at the same time. Those pull against each other, and the usual answer is to lock it down until it cannot do much.

The isolate path helps here, almost as a side effect. It has no processes to spawn, no binaries on a PATH, and its egress stays under the Worker's globalOutbound, so the runtime handling most of the work is also the one with the least reach. The container stays available for the rest, as something you opt into per command rather than the default environment.

It is worth being clear about how narrow that is. It does nothing about prompt injection, and it will not stop an agent from writing the wrong file with entirely legitimate permissions. It shrinks the surface for the commands that never needed a machine, which is useful without being decisive. At AGO we want agents that are both capable and secure, so we will be looking at this closely. Not production ready yet, @cloudflare/sandbox is the GA option for now.

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.