Skip to content

Phase 7 — Component hosting

The modern artifact: a WASM component, running as a supervised, addressable, killable, preemptible process — the Erlang/OTP model, applied to the WebAssembly component ecosystem.

Why this phase

Phase 6 proved a Wasm instance can be a supervised process. Phase 7 hosts the modern artifact: a WASM component compiled with cargo build --target wasm32-wasip2, the same target wasmCloud and the broader component ecosystem use. But where wasmCloud executes components in a lattice with a 30-second execution cap, RUSM runs them as long-lived, addressable processes — supervised, killable, preemptible, no timeout.

The programming model stays identical to Phase 6. The component model adds structured types, WASI interfaces, and language-agnostic ABI — RUSM just makes those components the process body.

What shipped

  1. bridges/ over a shared corerusm-wasm adds wasmtime-wasi and per-version bridges over one shared engine (epoch ticker, pooling allocator, CoW): wasip1 (core modules, Lunatic's home turf), wasip2 (components, WASI @0.2.0), and wasip3 (additive over p2 — WASI @0.3.0 async interfaces on the same component linker).
  2. The rusm:runtime WIT actor world — a component imports the actor interface and gets typed self/send/receive/list-processes/info/kill/register/whereis/unregister/set-label. Each is a thin call into rusm-otp — the Erlang Process API, callable from any language that targets WASI components.
  3. Default-deny capabilities (caps.rs) — named profiles (Sandboxed / NetworkClient / Trusted) build a WasiCtx (fs preopens, env, network) plus a StoreLimiter memory cap. A process gets nothing unless granted.
  4. Cross-process byte streams (Received::Stream, Wasm-free) — stream-open(to) hands a Tokio-backpressured StreamHandle to another process via the mailbox; the opener keeps the write end. Real back-pressure: a slow reader parks the writer's fiber — no busy-poll, no unbounded buffering.
  5. App model (rusm-cli) — rusm.toml [components.<name>], a ./wasm/ loader, and rusm build / rusm run / rusm dev. One toolchain: cargo build --target wasm32-wasip2, no jco, no cargo-component.
  6. wasip1 core-module bridge — RUSM on Lunatic's home turf: preview1 core modules run as processes too, with the same default-deny caps + StoreLimiter and the same raw rusm::* actor ABI over linear memory.

Design highlights

  • Composition is message passing, not WIT wiring. Two components communicating don't share a WIT import — they send messages. This keeps the programming model identical whether processes are on one node or across a cluster (Phase 9). There is no lattice, no link map, no runtime topology to declare.
  • Component model costs almost nothing over a raw core module. Component-storm: ~440k spawns/sec. Module-storm (wasip1 core modules — the direct Lunatic comparison): ~475k spawns/sec. The ~5× gap to bare tasks (~2.4M/sec) is the price of real Wasm memory isolation — paid once, regardless of whether you host a core module or a full component.
  • No execution cap. A component runs as long as it needs. rusm dev keeps running; residents stay alive. The epoch preemption from Phase 6 ensures a spinning guest doesn't starve others, but it never kills a guest for being slow.
  • Per-process InstancePre with precomputed export index. No per-spawn by-name export lookup. The entry function index is resolved at prepare time, so every spawn hits the same fast path.

What this unlocks

Any language that compiles to wasm32-wasip2 can now be a supervised RUSM process. Rust, TypeScript (via Phase 8's rquickjs runner), Go via TinyGo — all compile to the same artifact, run under the same actor model, message each other over the same wire.

The full OTP tree — spawn, supervise, kill, link, monitor, registry — applies to components without modification. Crash a component and the supervisor restarts it. Register it by name and any other component can find it. The app model (rusm.toml, rusm build, rusm dev) makes this declarative.

Try it

sh
cargo run --release -p rusm-bench -- run component-storm 3   # ~440k component spawns/sec
cargo run --release -p rusm-bench -- run module-storm 3      # ~475k wasip1 core-module spawns/sec
# In an app project:
rusm build && rusm dev                                       # build components, run, watch for changes

Status

Phase complete. Component-storm and module-storm are live in the dashboard. The Wasm-free invariant holds. Deferred to Phase 11: a native p3-typed stream<u8> WIT signature (the handle-based byte streams are fully functional and load-bearing; the native signature is a standards-surface refinement).


Next: Phase 8 — guest ergonomics: TypeScript, Rust, and Go SDKs, the typed client, streaming, callbacks, and rusm dev watch+reload.

MIT licensed