Capabilities & limits
A black-box probe proves every capability below. Each one drives a real
session over POST /new and WebSocket, and an HTTP 200 alone is not a pass.
Run the suite against any live deployment:
BASE=https://nimbus-os.dev bun test:behavioral- ShellReal shell, 60+ Unix commands, persistent 10 GB filesystemworks
- Runtimesnode, bun via Cloudflare workerd nodejs_compat (V8 + Node-API shim, not the upstream binaries)works
- Runtimespython / python3 — CPython 3.13 on wasm32-wasi (script + -c + -m + stdlib)works
- Runtimesruby / ruby3 — ruby.wasm-based Ruby 3.3 (script + -e + -r + stdlib)works
- Runtimesclang — LLVM 8 → wasm32-wasi-nimbus, modern wasi-libc sysroot default, multi-TU + user headers + fopenworks
- Runtimestsc — tsrs 0.18.0, the experimental Rust port of TypeScript 7.1 built to wasm, when the project installs no typescript (check + emit, no --watch); a project's own typescript runs as installedalpha
- RuntimesInteractive REPLs — python, ruby, node, bun (see REPL for state semantics)works
- Packagesnpm install against the live registry, with cross-session L2 cacheworks
- Packagesnpm alias dependencies such as alias: "npm:<pkg>@<version>"works
- Gitgit clone over HTTPS — chunked checkout engine; facebook/react (7,300 files) in ~28 s; 84,000-file worktrees materialize via bounded continuationworks
- NetworkIn-session loopback networking — curl http://127.0.0.1:<port> reaches servers in other isolates; node server.js auto-promotes to a routeable resident processworks
- NetworkDurable app URLs — a private preview URL per port that restarts the server on the next request after an eviction; nimbus expose <port> --public for a shareable link bound to the program; nimbus app list / url / rotate / remove; the same verbs in the SDK as box.appsworks
- NetworkStreaming HTTP through the fabric — SSE / chunked bodies flow live (per-chunk) across the isolate boundary, loopback and external preview alikeworks
- NetworkLarge files and HTTP Range through port previews — Range, If-Range, If-None-Match, If-Match, If-Modified-Since, If-Unmodified-Since and User-Agent reach the server whole; 206 and 304 come back with Content-Range, Content-Length, Accept-Ranges and ETag; a 210 MiB VFS file served by a Node static server downloads byte-exact, whole and by range, with the session's memory boundedworks
- NetworkCross-origin isolated previews — an app that sends COEP (Emscripten -pthread: SharedArrayBuffer + Workers + Atomics) is isolated in its own tab, and in the preview pane after an isolated workspace reload when its CORP admits the workspace origin (path previews always; host previews with CORP cross-origin/same-site)works
- ShellUnix permissions — real uid/gid ownership with EACCES enforced on read/write (including inside bash), durable st_mode, persisted chmod (octal + symbolic) + chown, exec-bit enforcement: ./binary runs only if executable (Permission denied, exit 126 otherwise), generic #! shebang dispatchworks
- ProcessesMulti-isolate processes — client/server apps span facets (opencode runs as a serve + attach pair, each in its own isolate)alpha
- Dev serversVite SPA dev server — full HMR to the preview iframeworks
- Dev serverswrangler dev for single-file Workers; Workers + Static Assetsworks
- SDKProgrammatic sandbox SDK — exec/files/runtimes/processes/ports/Proteus-style toolsworks
- ProcessesJS agent CLI primitives — env/home, npm/npx, child_process.spawn/exec/execFile, piped stdio, streams, logsworks
- ProcessesForeground attached npm-bin TTY tabs — stdin, resize, ANSI output, clean exit; Pi official installer and npm path probedalpha
- PackagesPython package workflows — pip, PyPI pure wheels, requirements/constraints, curated pure source artifacts, numpy and MarkupSafe linked into the interpreter, Flask, python -m flask run, python -m http.server previewalpha
- PackagesRuby package workflows — gem, bundle, pure gems, Rack/WEBrick previewalpha
- AgentSession Agent — editor-workspace chat with Cloudflare OAuth / Workers AI and sandbox toolsworks
- Packagesnpx <pkg> — first-class shebang + auto-install fallbackworks
- Packagesnode_modules/.bin/* resolves and executesworks
- ShellBinary file round-trip via fs.writeFileSync / readFileSyncworks
- ShellSession recovery — WebSocket drop → reconnect preserves cwd, env, filesworks
- RuntimesWASI preview1 — all 46 spec functions; outbound TCP via path_open('/dev/tcp/<host>/<port>') (JSPI); full poll_oneoff (fd / clock / socket subscriptions)works
- Runtimeswasi-threads / pthreads — mutex, condvar, pthread_join, TLS, barriers, semaphores; cooperative, never parallelworks
No capabilities match that filter.
Shares its capability catalog with the README capability table, covered by the behavioral suite intests/behavioral/.
The “no” list
Section titled “The “no” list”Nimbus is built on Workers, Durable Objects, and WASM, not as a Linux VM. A workload that needs one of these missing parts belongs on a microVM or container sandbox (where Nimbus fits).
| Not supported | |
|---|---|
| Docker, apt, custom Linux images | no container substrate exists |
| Native Linux ELF execution | there is no ELF loader |
| Native platform CLI shards, manylinux wheels, native gems | fail before import with an exact ABI diagnostic |
| Raw public TCP listeners | inbound is HTTP(S) through session routes |
Next.js dev server, wrangler pages dev |
explicit limits today |
| GPUs | — |
wasi-threads / -pthread |
refused at link time (design note) |
Known constraints
Section titled “Known constraints”| Constraint | State |
|---|---|
| One active terminal owner per session | sequential reconnect/share preserves state |
node/bun REPL declarations don’t persist |
workerd CSP; scripts unaffected; Python/Ruby REPLs fully stateful |
util.inspect and console output of what V8 alone can read |
util.inspect and console run Node’s own inspect.js. A promise’s result, an iterator’s or a WeakMap/WeakSet’s entries, and a proxy’s target and handler are read as values through workerd’s inspect and formatted by inspect.js. A proxy among them is shown by its target and handler, but a program’s code is never handed a rebuilt one: without showProxy, one whose target has a util.inspect.custom shows as <unknown> (Node calls that with the proxy as this), and a proxy inside it is shown by its innermost target without running its traps. A promise, iterator or collection whose own Symbol.toStringTag is a getter, whose class has its own Symbol.hasInstance or a name getter, or that has a proxy on its prototype chain, shows what it holds as <unknown> or <items unknown>: reading it would run that code again |
| Where an uncaught error was thrown | node’s report of an uncaught error is Node’s: the file, line and caret, the error as util.inspect shows it, Node’s version, the exit code. The place is read off the error’s stack, since a Worker cannot read V8’s message: an error a builtin throws, which Node places at a line of its own library, is reported without one; a thrown value that is not an error, an error thrown away from where it was made (throw e), or one whose stack was replaced, has no place, or the place it was made at |
ES modules under node |
run compiled to CommonJS, their imports live, every line and column where Node has them but in a module nested past about a thousand levels, which esbuild compiles. An import read before its module has run (the temporal dead zone) and a cycle’s exact evaluation order are not Node’s: workerd evaluates an ES module outside the request, where the process’s synchronous file system cannot be read |
.tsx and .jsx under node |
compiled and run; Node refuses them |
TypeScript under node |
stripped (or with --experimental-transform-types, transformed) by amaro as Node does, then run in Node’s format for it. Where Node refuses a file an ES module imports (syntax it does not strip, a file under node_modules), the imports before it have run first. A .js specifier naming a .ts file resolves, where Node finds no module |
Argument errors of the builtins workerd provides (zlib, crypto, buffer, url, path, …) |
they have Node’s shape (its class, code and stack header), but in 416 measured calls their wording or code differs from Node’s, they do not throw, or the function is missing (tests/fixtures/builtin-error-gaps.json). The methods of a Buffer, an EventEmitter or an AsyncLocalStorage, hot paths, keep workerd’s shape |
process.emitWarning |
--disable-warning and --redirect-warnings are not read; NODE_NO_WARNINGS=1 and --no-warnings in NODE_OPTIONS are |
| 75,000+-object clones | index-pack CPU headroom in progress |
| git transport | HTTPS only, no SSH |
npm dependencies from git (github:u/r, npm init u/r) |
npm installs from the registry only; npm init with a git repository as its initializer is refused with EUNSUPPORTED |
npm init |
npm 10.9.8’s package.json, questions and configuration; it reads a directory’s entries in sorted order (npm takes the first .js file for main and bin in the OS’s order) and does not run an init-module |
npm configuration (.npmrc, npm_config_*, flags) |
read as npm 10.9.8 reads it, except that a relative path value (userconfig=./x) resolves against the Worker’s own directory rather than the shell’s, and a workspace root’s .npmrc above the nearest package is not looked for |
| A native wasm binary’s linear memory | 128 MiB. Past it malloc returns NULL and the program reports its own allocation failure, instead of the session dying without one |
| opencode TUI | serve half boots and streams SSE; attach half renders frames from a facet with its own memory envelope, still hardening |
Stopping an app’s server (box.apps, nimbus app) soon after it starts, then starting it again |
the session can reset: sockets close and that start is lost; files and the app’s storage are kept. A Cloudflare facet-storage defect: measured 1 in 3 restarts when stopped as soon as it answered, 1 in 36 after 1.5 s idle |
| Hosted demo | evaluation only; no SLA, may be reset, not for secrets |
Verified live: Vite + React, the Cloudflare Vite Plugin, single-file Workers, Workers with Static Assets, npm and git workflows, Python and Ruby scripts, and clang. Astro, SvelteKit, and Remix use the Vite path. Nuxt has Nitro caveats.
A capability joins this list only after a behavioral probe proves it live. Until then it stays on the research page.