← writings

AI agents need millions of computers. Do they actually need to?

In part 1 I worked out what one training attempt costs for an AI that's learning to use a computer. The short version: a few dollars of model tokens, plus one entire computer reserved for the whole attempt, sitting idle more than 96% of the time while the model thinks. Tokens are getting cheaper on their own. The computer isn't.

If you want 10,000 attempts running at once, today's tooling wants 10,000 booted computers. Each with its own Ubuntu, its own Chrome, its own LibreOffice, all restored from the same snapshot, all started from the same screen. They are, to a first approximation, the same computer 10,000 times.

So: how much of the computer actually needs to exist once per attempt? This post walks down the stack one layer at a time and asks, at each layer, what is unique to this attempt and what is identical across a thousand of them. Along the way I'll cover what virtualization already shares (more than you'd think), what it still duplicates, and what the CUA-Sandbox paper found two weeks ago when it tried sharing the running software itself [1].

A vertical stack of eight layers from top to bottom: model, RL trainer, rollouts, computer-use environment, VM or container, operating system, applications, and memory storage CPU. A column on the right asks for each layer: unique per attempt, or identical across a thousand? Model: one copy, shared. Trainer: one, shared. Rollouts: unique, that's the point. Environment: starting state identical, what the attempt changes is unique. VM: one per attempt today. OS: identical. Apps: identical, started the same way. Hardware: paid per copy. the stack unique per attempt, or identical across 1,000? model one copy on the GPUs, shared by everything RL trainer one, shared rollouts / attempts unique. that's the whole point. computer-use environment starting state: identical what this attempt has changed: unique VM or container one per attempt, today. is that necessary? operating system identical. same Ubuntu, same kernel. applications identical binaries, started the same way. memory · storage · CPU paid for once per copy you actually make.
The layers between a model and the silicon. Everything above the rollouts is shared already. Everything below them is identical in principle, and the question is how much of it is duplicated in practice.

The top of the stack is already shared

Let's get the easy layers out of the way. The model is one set of weights on a cluster of GPUs. When a thousand rollouts each need a decision, they all send their screenshots to the same model, and it answers them in batches. Nobody runs a thousand copies of the model. The trainer, the thing that collects the scores and updates the weights, is also one program. In the CUA-Sandbox paper's own training run, one update used 128 rollouts in parallel against one model, on one host [1]. That's the normal shape.

The rollouts are, by definition, the things that must differ. Attempt 17 clicked on the wrong menu at step 3 and is now in a different place than attempt 18. The whole purpose of running many rollouts is to get many different trajectories to learn from. So the question isn't whether rollouts can be shared. It's how far below the rollout the "must be different" line actually reaches.

What's actually different between two attempts?

Picture two attempts at the same OSWorld task: "rename Sheet 1 to LARS Resources and make a backup copy." Both start from the same snapshot, the same spreadsheet open in LibreOffice Calc. Attempt A has right-clicked the sheet tab. Attempt B misclicked and opened the Format menu instead. What's different between the two computers right now?

Two side-by-side computers for attempt A and attempt B. Each is drawn as a stack: Ubuntu kernel, system services, Chrome and LibreOffice binaries loaded in memory, the Calc document, the window state, and the screen. The lower layers are shaded grey and labelled identical. Only the top two slivers, the document's edits and what's on screen, are highlighted red and labelled different. A note says: two attempts differ by a few files and some window state; they agree on gigabytes. attempt A attempt B screen: right-click menu open screen: Format menu open document: unchanged so far document: unchanged so far LibreOffice Calc, running (~hundreds of MB) LibreOffice Calc, running (~hundreds of MB) Chrome, desktop, display server Chrome, desktop, display server system services, libraries system services, libraries Ubuntu kernel + the disk image Ubuntu kernel + the disk image differs identical two attempts disagree about a few kilobytes of files and some window state. they agree about gigabytes of operating system and running applications.
Two attempts at the same task, a few steps in. The part that must differ is thin. The part that is identical is almost everything.

The CUA-Sandbox authors put this precisely. A rollout needs its own mutable state: the files it has written, the database rows it has changed, the browser session it's logged into, what its windows look like. But the initialized runtime, meaning the application code and the already-started programs, is "common across rollouts" and "can remain common" [1]. Their one-line thesis: state is the natural unit of environment independence, not the whole computer.

That's the claim. Now let's see how much of it existing tools already deliver, layer by layer.

Layer: the disk

Start at the bottom. Each VM has a disk image: a big file, tens of gigabytes, holding Ubuntu and all the apps. Does each of 1,000 VMs need its own copy?

No, and this has been solved for a long time. The technique is called copy-on-write. All 1,000 VMs read from one shared base image. When a VM writes something, the write goes to a small private file that sits on top of the base. Reads check the private layer first, then fall through to the base. Nothing is copied until it's changed, and only the changed part is copied. The base is never touched.

Copy-on-write disks. One large grey block at the bottom labelled shared base image, 30 gigabytes, read-only. Above it, four small blue blocks, one per attempt, labelled private layer, a few megabytes, holding only the files this attempt has changed. Arrows show reads falling through to the base and writes landing in the private layer. shared base image · tens of GB · read-only Ubuntu, Chrome, LibreOffice, GIMP, the lot. stored once. attempt 1 private layer, MBs attempt 2 private layer, MBs attempt 3 private layer, MBs … attempt n private layer, MBs reads: check my layer, then fall through to the base. writes: land in my layer only. resetting an attempt = throw away its small layer. cloning = start a new empty layer.
Copy-on-write, the oldest trick in the book. Disk is effectively already shared across attempts. This is why OSWorld could use one base snapshot for 369 tasks [2].

How much does this matter in practice? CUA-Sandbox measured it on a 3.6 GB shopping-site database [1]. Making a fresh copy for a new attempt without copy-on-write took 5.5 seconds and 3.67 GB of disk. With copy-on-write it took 0.07 seconds and 0.11 MB. That's 77× faster and about 30,000× smaller, for the same logical result: a database this attempt can scribble on without affecting anyone else.

Verdict for the disk layer: solved. Modern setups share it, and sharing it costs almost nothing.

Layer: the starting state

Next up: getting the computer to the task's starting screen. A snapshot freezes a VM's disk and memory at a moment, so that any number of attempts can be reset to that moment. Copy-on-write makes snapshots cheap to keep and cheap to restore from, because restoring is just "point at the base, start with an empty private layer."

OSWorld's design, which I covered in part 1, leans on this: one shared snapshot of a clean desktop, then a small recipe per task to download a couple of files and open the right app [2]. The alternative, one snapshot per task, would have cost gigabytes each because a snapshot captures the whole memory of the machine. So OSWorld already shares the common starting point and only materializes the per-task difference.

Verdict: mostly solved. Restoring the start is cheap. What's not cheap is what comes next.

Layer: the running programs

Here's where it stops being solved. Disk is what's stored. Memory is what's running. Once you boot Ubuntu, start the desktop, launch Chrome and LibreOffice, those programs occupy memory: their code, their caches, their fonts, their window buffers, their internal data structures. And every VM boots them separately, so every VM holds its own copy of all of it.

This is the number that surprised me. CUA-Sandbox measured the memory per environment for the standard Docker-based setups of three benchmarks, running eight attempts in parallel [1]:

BenchmarkWhat's inside each environmentMemory per attempt, standard setup
WebArenaA whole shopping site: web server, PHP, MySQL database2,103 MB
VisualWebArenaClassifieds / Reddit / shopping sites1,745 MB
OSWorldUbuntu desktop with real apps2,654 MB

About 2 to 2.7 GB per attempt, of which the attempt's actual private state is a few megabytes. The rest is a copy of a running program that is byte-for-byte the same as the copy next door. On the 1.5 TB host the paper used, that's a ceiling of roughly 550 OSWorld attempts in flight on memory alone, before anything else gets a share. (That last calculation is mine, not the paper's.)

Eight boxes in a row, each labelled attempt 1 through 8. Inside each box is a tall grey bar labelled running programs, 2.6 gigabytes, and a tiny red sliver on top labelled this attempt's changes, a few megabytes. A bracket underneath says: 8 copies of the same running programs, 21 gigabytes, to hold 8 slivers of difference. eight OSWorld attempts in parallel, standard setup [1] 1 2 3 4 5 6 7 8 the same running programs, ~2.6 GB each, 8 times over what this attempt changed: MBs
Memory, not disk, is where duplication lives. Eight attempts hold eight copies of the same booted desktop to protect eight tiny differences.

Why doesn't copy-on-write fix this too? Partly it does, a little. Operating systems can share identical memory pages between processes if they came from the same file on disk, so the code of Chrome might be shared across VMs on a good day. But a running program is mostly not its code. It's heaps, caches, decoded images, parsed configuration, open connections, all built up at startup, all living in memory that was allocated separately in each VM. There's no shared base for that. Each copy was constructed independently, so the operating system has no idea they're the same.

Verdict: not solved. This is where the "one computer per attempt" cost actually lives.

What CUA-Sandbox did about it

The CUA-Sandbox paper's move is to stop giving each attempt its own running programs, and instead run the programs once and give each attempt its own state capsule: a private bundle of the things that genuinely need to differ [1]. The web server runs once. The database engine runs once. But each attempt gets its own database branch (copy-on-write, as above), its own session, its own browser profile. Every action carries an environment ID, and a lookup table routes that action's reads and writes to the right capsule.

Left: conventional setup, three attempts each with their own runtime box containing the running programs plus their state. Right: CUA-Sandbox, one shared initialized runtime at the bottom, three small state capsules above it, and a routing table in the middle that directs each attempt's actions to its own capsule. Reset, clone and fork arrows act on the capsules only; the runtime keeps running. conventional running programs state 1 running programs state 2 running programs state 3 N copies of everything. reset = rebuild the box. CUA-Sandbox capsule 1 capsule 2 capsule 3 files · DB branch · session · profile · display routing table: action from env 2 → capsule 2 one shared running runtime web server, database engine, app binaries, started once N small capsules, 1 copy of the programs. reset / clone / fork act on a capsule. the programs never stop. the agent sees the exact same website or desktop
CUA-Sandbox's runtime–state separation, redrawn from the paper's Figure 1 [1]. The programs are shared; only the state is per attempt. The agent's interface doesn't change, and the official graders are unchanged.

Three things about the design that I think are the important ones:

What they measured

They compared their setup against the official Docker-based environments for WebArena, VisualWebArena, and OSWorld, running eight attempts in parallel on the same host, with the same models, prompts, step limits and graders [1].

Three groups of paired horizontal bars comparing Docker and CUA-Sandbox. Memory per attempt: WebArena 2103 vs 464 megabytes, 4.5 times smaller. VisualWebArena 1745 vs 190, 9.2 times smaller. OSWorld 2654 vs 837, 3.2 times smaller. Storage per attempt: WebArena 6757 vs 13 megabytes, 516 times smaller. VisualWebArena 6504 vs 13, 504 times. OSWorld 63 vs 29, 2.2 times. Throughput, attempts per second: WebArena 0.116 vs 0.258, 2.2 times higher. VisualWebArena 0.179 vs 1.109, 6.2 times. OSWorld 0.291 vs 0.895, 3.1 times. memory per attempt, MB (shorter is better) WebArena 2,103 464 · 4.5× smaller VisualWebArena 1,745 190 · 9.2× smaller OSWorld 2,654 837 · 3.2× smaller extra storage per attempt, MB WebArena 6,757 13 · 516× smaller OSWorld 63 29 · 2.2× smaller throughput, attempts finished per second (longer is better) VisualWebArena 0.18 1.11 · 6.2× higher OSWorld 0.29 0.90 · 3.1× higher Docker CUA-Sandbox
Eight attempts in parallel, Docker versus CUA-Sandbox, from the paper's Table 6 [1]. OSWorld throughput rose from 0.29 to 0.90 attempts per second, 3.1×. Desktop gains are real but smaller than web gains, and the reason matters.

And the thing that actually makes the paper useful rather than just impressive: task success didn't drop. Across four models and three benchmarks, twelve pairs, the shared-runtime version scored the same or slightly higher every time [1]. They also trained a small model end-to-end on both setups, supervised fine-tuning then RL, and the gains from RL were within a tenth of a point of each other (3.88 versus 3.87 points on OSWorld). The cheaper environment learned the same lessons.

The ablation that explains everything

The most clarifying table in the paper is a three-way comparison on the web benchmark [1]. They ran eight attempts three ways: the official Docker setup; Docker plus copy-on-write for the database; and copy-on-write plus one shared runtime.

Three rows, one per configuration, each with two bars: runtime memory in gigabytes and writable storage in gigabytes. Official Docker: 15.6 GB memory, 53 GB storage. Copy-on-write but still eight separate runtimes: 13.9 GB memory, 24.7 GB storage. Copy-on-write plus one shared runtime: 3.3 GB memory, 23.2 GB storage. A fourth bar, agent and browser memory, stays at about 7 GB in all three rows. Caption: copy-on-write fixes storage; sharing the runtime fixes memory; the browser is untouched by either. eight WebArena attempts, three ways [1] official Docker 8 runtimes, no CoW 15.6 GB runtime memory 53.0 GB writable storage + copy-on-write still 8 separate runtimes 13.9 GB 24.7 GB + shared runtime CoW + 1 web, 1 database 3.3 GB 23.2 GB agent + browser memory, all three rows ~7 GB, unchanged copy-on-write fixes storage. sharing the runtime fixes memory. neither touches the browser.
From the paper's Table 8 [1]. Copy-on-write alone halved storage but barely moved memory. Sharing the runtime cut memory by 4.8×. The agent's browser processes, about 7 GB for eight attempts, were untouched by both.

This is the cleanest statement I've seen of where the duplication lives. Copy-on-write is a storage trick, and it does its job: writable disk fell from 53 GB to 25 GB. But memory only dropped from 15.6 GB to 13.9 GB, because the eight web servers and eight database engines were still eight separately booted programs. Sharing the runtime is what collapsed memory, to 3.3 GB. And notice the red bar: the agent's own browser processes, the thing taking screenshots and clicking, stayed at about 7 GB no matter what. Nobody has shared those yet.

What breaks when you share more

This is the part the short version of this story usually skips, and it's the part I care about most, because it's where the real engineering is.

Problem 1: time travel

Suppose attempt 5 is being reset. Its old database branch is about to be swapped for a fresh one. Meanwhile, a request that attempt 5 sent a moment ago is still in flight inside the shared web server. If the swap happens mid-request, that request might read from the old branch and write to the new one, or the grader might inspect a half-swapped state. CUA-Sandbox calls this a mixed generation. They stress-tested it: with a naive swap, it happened in 100 out of 100 trials. With their freeze-drain-publish protocol, 0 out of 100 [1].

Two timelines. Top, naive swap: a request from attempt 5 starts, the reset swaps the state underneath it mid-flight, and the request finishes against the wrong state; labelled mixed generation, 100 of 100 trials. Bottom, transactional: freeze new requests, drain the in-flight one to completion, stage the new state, publish atomically, resume; labelled 0 of 100 trials. Old-generation stragglers are rejected. naive swap: 100 / 100 trials corrupted [1] request from attempt 5, in flight reset swaps state here first half saw old state, second half saw new freeze → drain → stage → publish: 0 / 100 trials corrupted [1] in-flight request freeze drain stage new capsule publish, atomic new requests, new generation stragglers tagged with the old generation are rejected
Why resetting shared state is harder than resetting a VM. A VM reset can't be interrupted by its own in-flight work because the whole VM stops. A shared runtime never stops, so the reset has to be a careful transaction [1].

Problem 2: the stuff that isn't a database

Databases are the easy case: they already have branches and transactions. Browser profiles, cookies, caches, search indexes, the display, the inter-process message bus that desktop apps use to talk to each other, these all need their own adapter to route per attempt, and the adapters cost time. On one read-only web task, managing the non-database state added 0.84 seconds to setup and 1.09 seconds to every reset [1]. Not huge, but not free, and the list of things that need adapters grows with the richness of the environment.

Problem 3: the desktop is the hard case

Look back at the results chart. On the web benchmarks, memory per attempt dropped 4.5× to 9.2×, and on one site (GitLab) 51×. On OSWorld, the desktop benchmark, it dropped 3.2× overall and only 1.45× on the desktop-specific measurement in the paper's Table 3 [1]. Why the gap?

Because a website is a service with a clean seam between "the server" and "this user's data." A desktop isn't. The display server, the window manager, the open LibreOffice window with its document loaded, the message bus, the clipboard: these are one tangled session, and the session is the state. CUA-Sandbox's desktop adapter keeps "filesystem, application-profile, configuration, display, message-bus, and window state" private per attempt, and anything whose in-memory state "cannot be safely rebound" stays in a private backend [1]. That's honest, and it means on a desktop most of the running session is still duplicated. The paper's own closing line says that "broader mutation workloads, longer trajectories, and concurrency sweeps are needed to characterize the remaining sharing boundary."

Two columns comparing what gets shared on a website versus a desktop. Website: server, database engine and app code shared; only database branch, session and cache per attempt. Memory 4.5 to 51 times smaller. Desktop: kernel, binaries and base files shared; but display server, window manager, open app windows, message bus, clipboard and profile all private per attempt. Memory 1.45 to 3.2 times smaller. Arrow labelled: the remaining sharing boundary. website environment shared, started once web server · database engine · app code caches rebuilt per attempt private per attempt DB branch · session · browser profile memory 4.5× to 51× smaller there is a clean seam between server and user desktop environment shared kernel · app binaries · base files private per attempt display · window manager · open app windows message bus · clipboard · profile · config the agent's own browser memory 1.45× to 3.2× smaller the session is the state. no clean seam. ← the remaining sharing boundary is here →
Why the desktop gains are smaller. On a website the per-attempt state is a thin slice. On a desktop, most of what's running is per-attempt session state that CUA-Sandbox leaves private [1].

Back to the question

Here's the accounting the paper gives, in plain words [1]. With N attempts in the conventional design, memory is N × (runtime + per-attempt execution overhead) + the sum of everyone's private state. With a shared runtime it's 1 × runtime + Q × execution overhead + the same private state, where Q is the number of attempts that are actually active at a given moment, which can be less than N because attempts that are waiting on the model don't need a live execution slot. The savings are (N − 1) runtimes plus (N − Q) idle slots.

Two formulas drawn as bars. Conventional: N times runtime plus N times slot plus sum of private state. Shared: one runtime plus Q slots plus the same private state, where Q is the number of attempts active right now. The difference, N minus one runtimes and N minus Q slots, is highlighted as the avoided cost. A note recalls that attempts are idle 96 percent of the time, so Q can be far below N. conventional, N attempts: N × running programs N × live slot N × state shared runtime, N attempts, Q active at once: 1 × Q × slot N × state avoided: (N − 1) runtimes + (N − Q) idle slots from part 1: an attempt is waiting on the model more than 96% of the time. so Q, the number that need a live slot right now, can be far smaller than N.
The paper's memory model, Appendix E, in picture form [1]. The N × state term never goes away. Everything else is a candidate for sharing.

The last point connects the two posts. Part 1 found that during a rollout, the computer is idle over 96% of the time, waiting for the model. If attempts don't each need a permanently reserved execution slot, if a slot can be handed to whichever attempt has an action to run right now, then the number of live slots you need is set by how many attempts are acting at this instant, not how many are in progress. CUA-Sandbox's design allows this (they call it reusing execution slots), though they don't push on it in the measurements.

Where I've landed

Modern virtualization already does a surprising amount of sharing. Copy-on-write disks, snapshots, deduplicated base images: these mean a training farm isn't as naive as "one complete computer per agent." Disk and starting state are effectively solved.

What isn't shared is the running software. Every attempt boots its own copy of the same programs into memory, and that memory, 2 to 2.7 GB per attempt in the measurements above, is what caps how many attempts a machine can hold. CUA-Sandbox showed that for websites you can collapse most of that by running the programs once and routing each attempt to its own state, with no loss in what the agent learns. For desktops, the win is real but smaller, because a desktop session doesn't have a clean seam between the shared part and the private part, and the paper's authors say so.

But computer-use RL is an unusual workload. It's an enormous number of independent environments, repeatedly starting from highly similar states, executing many of the same applications, and spending almost all their time waiting. That makes me wonder whether the right abstraction isn't a faster disposable VM, but a computer designed from the start around parallel rollouts: one that treats the running session itself the way copy-on-write treats a disk, so that a thousand attempts at "rename Sheet 1" share one booted LibreOffice until the moment they diverge.

I don't know yet how far that can be pushed on a real desktop, or where it breaks. That's what I'm working on with Drums, and the next post in this series will be measurements, not reading: one physical Mac, an increasing number of attempts, and a graph of where it falls over.

Vocabulary

References

  1. Yan, Jiao, et al., CUA-Sandbox: Efficient Environments for Computer-Use Agent Reinforcement Learning, 2026. arXiv:2609.32750
  2. Xie et al., OSWorld: Benchmarking Multimodal Agents for Open-Ended Tasks in Real Computer Environments, 2024. arXiv:2404.07972
  3. Abhyankar, Qi, and Zhang, OSWorld-Human: Benchmarking the Efficiency of Computer-Use Agents, MLSys 2026. arXiv:2506.16042
  4. Dai et al., OSWorld-Science: A Benchmark of Computer Use Agents for Learning and Using Scientific Software, 2026. arXiv:2609.39903

All figures are my redrawings of numbers reported in those papers, cited inline. The "550 attempts on a 1.5 TB host" figure is my arithmetic from the paper's per-attempt memory and host description, not a measurement.

← Part 1: What does it cost to give an AI one more try at using a computer?

← more writings