Computerworld

A deterministic computer world for training and evaluating computer-use agents: several machines with real filesystems, shells, applications and a browser, joined by a synthetic internet, inside one Rust engine that runs natively, from Python, and as WebAssembly. A run is a pure function of (engine, world, seed, action sequence, viewport) — replay it on any platform and get the same bytes.


Computerworld is a deterministic computer world for training and evaluating computer-use agents. Several machines with real filesystems, shells, applications and a browser, joined by a synthetic internet of services, inside one Rust engine that runs natively, from Python, and as WebAssembly in Node and the browser.

The whole design turns on one sentence: a run is a pure function of (engine, world, seed, action sequence, viewport). Everything else on this page is either a consequence of that promise or a limit the promise cost.

A synthetic github.com pull request, rendered by Computerworld's own rasterizer inside a simulated macOS desktopA synthetic github.com pull request, rendered by Computerworld's own rasterizer inside a simulated macOS desktop

That screenshot has no browser in it. There is no Chromium process, no DOM, no JavaScript engine laying out that page. A synthetic GitHub service produced a structured page, the engine's own layout and text shaping turned it into a scene, and a CPU rasterizer wrote the RGBA. The same call in Python, in Node and in Rust produces the same bytes.

And here are the machines themselves, not pictures of them. The frame below is Computerworld's own slideshow — the engine compiled to WebAssembly, running in your tab, with a world of its own per scene. The desktop in the middle is live and takes the mouse and the keyboard; the arrows and the ticks walk the rest. Nothing is fetched after it boots: every frame you see is rendered here.

The slideshow from computerworld's own front page, embedded. Click a machine to bring it to the middle; it boots there and takes your input.

Problem

Computer-use agents are trained and evaluated against real computers, and real computers are not reproducible. A VM's clock moves, its DNS answers change, a website redesigns, a font is missing, a race lands differently. So an episode is not an object you can hand to someone else: a benchmark score is a claim about a machine that no longer exists, a regression is a story rather than a rerun, and a bug report is "it did something weird on Tuesday."

The usual escape is to make the environment smaller — a DOM mock, a scripted website, a fixture. That buys reproducibility and spends the thing you were measuring. A mock whose buttons do not mutate any state trains an agent to produce plausible-looking clicks, and an evaluation on it measures whether the agent can recognise a mock.

The third problem is cost. Branching a real VM to try three futures means three VMs. Tree search, counterfactual rollouts and RL all want to fork the world cheaply, and an environment that costs hundreds of megabytes and a boot to copy quietly forbids the algorithms you would most like to run in it.

Solution

One Rust engine that owns the whole stack — filesystem, processes, shell, window manager, applications, DNS, HTTP, services, layout, text shaping and rasterization — with no host capabilities at all. No host filesystem, socket, clock, entropy or network unless the owner explicitly wires in an adapter.

Because nothing in the engine reads the outside world, the world's entire future is determined by its inputs, and three things follow immediately:

  • Verification, not vibes. An episode, a benchmark result or a bug report is a world, a seed and an action list. Anyone replays it exactly, on Linux, macOS, Windows or a browser tab, and the state hashes agree.
  • Cheap branching. Snapshots are copy-on-write. Fork a world at any step, run several futures, keep the best — tree search and RL rollouts without virtual machines.
  • Free labels. scene(w, h) returns every window, widget and text run with its bounds before rasterization, so a rendered frame arrives with its own ground truth. No annotation pass, and no annotation error.

How

The world is data

WorldDefinition is JSON. The engine has no built-in company, no default machines and no fixture imports in the kernel — those were explicitly removed from the predecessor designs, where the kernel knew about the catalog. The reference world, worlds/company-2026, is five computers on three OS profiles, 93 service instances built from 23 service kinds, and 87 seed files describing a browsable public web: search engines, webmail, a video site, social feeds, forums, shops, an encyclopedia, an AI assistant.

Every address in it sits in a range IANA reserves for documentation — 203.0.113.0/24, 198.51.100.0/24, 192.0.2.0/24 — so no simulated host resembles a real one, and names ending .internal and .example resolve only inside the declared synthetic network.

Those sites are one world, not a pile of fixtures. Bob files issue #14 on the internal Git host, asks about it on the synthetic Stack Overflow ("Why does my BFS shortest-path test fail only on Windows?", linking the issue), Priya answers, opens the pull request in the hero image ("Sort refs before iterating in BFS", closing #14), and writes it up on the forum thread further down this page. Same people, same incident, four services, one causally consistent world in which HashMap iteration order is genuinely the bug.

Machines are machines

cw-computer models an inode filesystem with identity, links, symlinks, ownership, modes and case-aware path resolution, over copy-on-write backing. Trusted internal accessors are deliberately distinct from permission-checked read_as / write_as, so "the kernel can read it" and "this synthetic user can read it" are different questions in the type system rather than in a reviewer's head.

ProcessTable models spawn, exit, signals, sleep, file descriptors and listener cleanup against logical time. The shell interprets documented subsets of POSIX and PowerShell through an explicit ShellHost capability; no command launches a host subprocess. Real Python and JavaScript run through embedded interpreters — and a program in the world can be debugged from Visual Studio Code, in the world.

Visual Studio Code paused on a breakpoint in a Python program running inside a simulated machine, with the call stack, watch expressions and an integrated terminalVisual Studio Code paused on a breakpoint in a Python program running inside a simulated machine, with the call stack, watch expressions and an integrated terminal

Git is content-addressed commits and trees with a canonical SHA-256 JSON protocol for remote transfer. That is honestly not Git: not the pack format, not smart HTTP, not SHA-1 object identity. Clone, commit, push and fetch across world machines work; pointing it at an existing .git directory does not. The documentation says so in those words, which is the pattern throughout — unsupported operations fail explicitly rather than approximately.

The network is the only way through

The path is application → URL → source-aware DNS → route and gateway checks → listener → HTTP response → browser page. A browser cannot short-circuit it by reading the backing service's state, which is the property that makes an information-transfer task meaningful: when Bob's machine sees Alice's edit, it saw it because a request travelled.

DNS cache expiry and link latency are logical microseconds; configured packet loss draws from seeded simulation state. Internet nodes sit behind an edge router and three points of presence, so a distant site costs milliseconds where the LAN costs ten microseconds — the latency structure an agent would learn from is present, without a packet-level TCP emulation pretending to be there.

Egress is denied by default, and the denial has teeth: an unknown domain is an error, not permission to consult the host's resolver. Real outbound traffic needs both a policy allowance and a separately supplied capability adapter. The owner calls begin_external with an explicit address set, an adapter performs the I/O, and complete_external returns it with generation, deadline and response-size checks. The shipped NativeHttpAdapter lives behind its own feature flag, pins authorized addresses, disables proxy discovery and automatic redirects, and refuses caller Host overrides; a redirect must be reauthorized as a new effect rather than followed. And a world holding an unresolved live effect is rejected by try_snapshot, because a checkpoint that depends on the outside world is not a checkpoint.

scripts/check-boundaries.py enforces the rule the rest of the design rests on: pure simulation crates may parse IP addresses with std::net value types, and may not open sockets, spawn processes, read host files or time, or draw host random bytes. It runs in CI. It is a development guard, not a formal capability proof, and the docs say that too.

Determinism is a mechanism, not a hope

Time is unsigned logical microseconds. Clock rejects backward movement and checks overflow. Scheduled work orders by due time, then phase, then insertion sequence. Neither executing a command nor observing a frame consults wall time.

Randomness is sha256-named-splitmix64-v1: each named stream derives from the seed and the stream name, so drawing from one subsystem cannot perturb another. That detail is what makes the world editable — adding a coin flip to the mail service does not reshuffle the renderer. ID counters are namespaced, deterministic and overflow-checked. Streams, counters and queued work are all serializable state.

Applications only do work inside steps. A video export advances by one bounded unit per step() through NativeApp::background, so how far it has got is a function of the steps taken. A playing timeline derives its position from the logical time playback started — never from the host.

Rendering without a browser

The pipeline is semantic application state → native page → layout → scene → optional RGBA. Structured-only observations stop before rasterization and cost nothing.

A Scene is stable node IDs, integer bounds, twelve primitives (Box, RoundedBox, UiText, UiTextBold, Text, AssetImage, Shadow, Image, Path, Symbol, Backdrop, Region), affine transforms in 1/1024 units, z-order, opacity, clipping, and the semantic role, label and value of each node. Hit testing runs on that structure — transformed coordinates, clipping, disabled state, z-order — without generating a single pixel.

Text is the part that usually gives determinism away, so it is the part that is most carefully owned: 35 font files embedded rather than looked up on the host, plus an on-demand font pack for CJK and emoji that native builds embed and the Wasm build fetches. rustybuzz shaping, Unicode bidirectional reordering, Arabic joining and lam-alef, Indic conjuncts, Thai and Hebrew mark attachment, emoji ZWJ and skin-tone ligatures, CJK line breaking with kinsoku. Colour emoji are painted from Noto Color Emoji's COLRv1 tables by the renderer's own deterministic COLR rasterizer — gradients, clip boxes, composites — identically native and in Wasm.

Crucially, scene metrics and the renderer share one layout implementation, so the measured width that decided a wrap or an ellipsis is exactly the width that gets drawn. A measurement path that disagrees with the drawing path is how "the label said it fit" becomes a mystery pixel diff on another platform.

The Windows 11 shell with PowerShell, a browser and Notepad open simultaneously, each drawn by the same scene and rasterizerThe Windows 11 shell with PowerShell, a browser and Notepad open simultaneously, each drawn by the same scene and rasterizer

Five shell styles — macOS, Windows 11, Ubuntu 24, iOS 18, Android 12 — are scene descriptions over the same primitives, not five renderers.

The agent's handle is smaller than the world

Three layers stay distinct on purpose:

  1. Simulation state — everything that exists, including hidden service state.
  2. An actor session — selected machines, action families and observation channels.
  3. An evaluator — private objectives and predicates, outside actor observations.

An owner holds World: topology, inspection, snapshots, device lifecycle. An agent holds Environment: its machines, its action families, its observation channels, nothing else. Actions are extensible envelopes rather than a model-specific token schema:

{"family": "terminal.v1", "op": "execute", "machine": "alice-mac",
 "payload": {"command": "cat launch.txt"}}

The families are terminal.v1, filesystem.v1, http.v1, browser.v1, application.v1, keyboard.v1 and pointer.v1; observations are semantic.v1 and pixels.v1, with rendering authorized but never automatic. step takes an ordered batch — not an atomic transaction — and returns per-action success, value or error.

Pointer input goes through real hit testing at scene coordinates. It does not accept a semantic target ID, which would have been much easier and would have quietly turned "the agent clicked the button" into "the agent named the button." Drag and resize are down/move/up sequences. A GUI terminal can run commands through keyboard input even when direct terminal access is not granted — installed applications are part of the capability surface, and pretending otherwise would be a hole in the grant model.

A simulated Android phone browsing a synthetic forum thread about the same HashMap iteration bug as the pull request aboveA simulated Android phone browsing a synthetic forum thread about the same HashMap iteration bug as the pull request above

One engine, three languages

Python (pip install computerworld, PyO3 wheels, CPython 3.9+ abi3), JavaScript (npm install computerworld, one package for Node and browsers) and Rust call the same code. Neither binding reimplements a command, a service or a rendering rule; the Python package does not start Node.

The release workflow is the enforcement: it builds every candidate, refuses to continue unless Linux, macOS, Windows and Node/Wasm agree on the state and pixel hashes, then creates the tag and release with SHA256SUMS and publishes the files it downloads back from that release and re-checks against those sums. Both registries use trusted publishers over OIDC — no tokens exist to leak — and npm publishes with provenance.

crates.io is the one place this does not reach: fifty-odd crates with path dependencies and a renderer whose embedded fonts and wallpapers are four times the per-crate limit. Rust consumers pin the Git tag, and the README says why rather than leaving a gap.

Tests

scripts/test-all.sh is the gate: cargo fmt, the pure-core boundary check, strict workspace Clippy, the native test suite, and a release wasm32-unknown-unknown build. The recorded acceptance pass is 178 passing test executions, zero failures.

The behaviour tests are the interesting half, because they test the promises rather than the functions: two-computer communication through DNS and HTTP, a mutation made on one machine becoming visible to another through its own request, blocked outbound access, filesystem and process isolation between machines, seeded initialization, replay, portable checkpoints, fork isolation, actor/evaluator separation, event and packet traces, structured observation without rasterization, hit testing, and deterministic direct rendering.

Cross-language parity is its own harness. scripts/smoke-bindings.sh makes Node and Python agree on checkpoints and hashes; native and Wasm rendering matched the golden RGBA hash 01455c4e…3595ec6. And scripts/test-browser.mjs drives a real Chromium through an episode — pointer and keyboard input, terminal execution, reset, checkpoint, fork, adding and removing a phone — and records zero host network requests during the episode once the static assets have loaded. That report is regenerated by the script, so the number in the docs and the number in the artifact cannot drift apart.

Results

The engine ships. pip install computerworld and npm install computerworld both work, across five wheel platforms and one npm package, with the parity gate in front of each release.

The world console: live topology on the left, an attached macOS desktop on the right, and the world state hash along the bottom — the whole engine running in one browser tabThe world console: live topology on the left, an attached macOS desktop on the right, and the world state hash along the bottom — the whole engine running in one browser tab

That console is the engine compiled to WebAssembly in a single browser tab: seven devices, live topology, a monitor preview per machine, snapshot / restore / fork controls, and the world state hash printed at the bottom. Host internet: blocked.

Measured on a quiet ARM64 host, in the rich reference world with full journaling:

Workloadp50
Terminal command, full actor step4.78 µs
File write + read, two actor actions7.54 µs
Virtual HTTP, full actor step82.9 µs
Structured actor observation34.4 µs
Desktop scene, no raster93.2 µs
Dirty same-seed reset4.54 µs
Fork from a snapshot265 µs

Profiling found node rasterization eating about 95% of full-frame wall time, and that identical old and new damage rectangles were painting the same area twice — which is why a 100% incremental update was slower than a full render. Caching nonzero text spans, specializing identity text and opaque rectangles, and coalescing damage moved full 1280×720 frames 3.11× and 100% patch rendering 7.21×, with the golden frames unchanged.

The prettiest result is one somebody else got. Because a frame is an exact function of its inputs, the text an agent types is also a label for the pixels that text produces: walk the scene, crop the frame at the same viewport, and (crop, string) pairs fall out at whatever volume you are willing to render, varying theme, viewport and typeface. One consumer trained an OCR model on its own agent's typed text and moved held-out-font accuracy from 0.668 to 0.794.

Lessons

The honest benchmark is the one that keeps its negative results. The performance document records that the proposed 3× predecessor capture target was not met, that the same Chromium screenshot API was slower for canvas than for DOM in the final run, and that no general renderer-only speedup over Chromium has been established. It carries a stale-data warning over its own sections after the world definition grew 26× and the shell was overhauled. A benchmark page that only contains wins is a marketing page, and everyone reading it knows.

Determinism has a shelf life, and it should be stated. Pixel output is not stable across engine versions — the shell work changed it — so a dataset has to keep the version that made it. Snapshots and state hashes name the engine that wrote them, and restoring an alpha snapshot on 0.1.0 is refused rather than silently reinterpreted. Refusing is the feature.

The predecessors' bugs were the specification. Eleven repositories were read before a line was written, pinned by commit in research/sources.json, and the provenance matrix records subsystem by subsystem what should survive and what should not: an internet stub that was three dictionaries; a host-socket HTTP façade whose allowlist checked the initial URL while redirects escaped it; mocks whose display controls never mutated state and whose counters were reported as though they had; a global Date patch; a sorted-list event cursor with a reproduced ordering defect. Reuse here is algorithms, causal behaviour and regression cases reexpressed in Rust — not files. That is also why an engine this size landed in days rather than months: the hard part had already been worked out five times, in the open, badly enough to learn from.

Bounded fidelity, declared per subsystem, beats unbounded fidelity implied. POSIX and PowerShell subsets. Synthetic Git over HTTP rather than packfile compatibility. Native pages rather than arbitrary HTML and JavaScript. Bundled deterministic fonts rather than full browser typography. Transport that is causal rather than packet-level. Registered native extensions that are trusted code in the same process — restricted handles prevent accidental state leakage, not hostile memory access, and the security document says exactly that instead of implying a sandbox. Every one of those is a smaller promise than "it's like a real computer," and every one of them is a promise that can actually be kept.

Related

ComputerworldSynthUXSynthUXComputatrumComputatrumbelief-graph-orchestratorbelief-graph-orchestr…Teaching Computers to Use ComputersTeaching Computers to…Node TreeNode Treebrowser-osbrowser-oswindows-web-nextwindows-web-nextmacos-web-nextmacos-web-nextgeneral-unified-world-modelinggeneral-unified-world…The Tensor ComputerThe Tensor ComputerA Differentiable von Neumann ComputerA Differentiable von …AI systems engineeringAI systems engineeringFull Stack Artificial IntelligenceFull Stack Artificial…👩🏽‍🌾 The Fertile Cresent👩🏽‍🌾 The Fertile C…ComputatrumComputatrumFrom Arxiv Reading to ML Systems TasteFrom Arxiv Reading …Software Engineering After AgentsSoftware Engineerin…notion-vibestartupnotion-vibestartupRecursive Omnimodal Video Action ModelRecursive Omnimodal…TensorCodeTensorCodeyt2ctxyt2ctxThe Cortical CanvasThe Cortical CanvasLooped Attention in Video Diffusion TransformersLooped Attention in…The Agent SuiteThe Agent SuiteThe APIThe APIBlock Sparse Attention With Block RetrievalBlock Sparse Attent…The Multi-Agent Network (MAN)The Multi-Agent Net…MPNetsMPNetsFull-Stack Artificial IntelligenceFull-Stack Artifici…Self Organized CriticalitySelf Organized Crit…DRAG TO ORBIT · SCROLL OR PINCH TO ZOOM