On this page
Planned These runtimes are part of the function runtime proposal (platform part 5). Loam does not run user code on any of them yet.
A platform that runs other people's functions has to answer two questions for every request: how is this code kept away from everyone else's, and how much CPU did it use? Loam's proposal answers them three ways, one per tier, because no single technology is cheap, compatible and strongly isolated at once.
| workerd | wasmtime | gVisor | |
|---|---|---|---|
| What it is | Cloudflare Workers' JavaScript and Wasm runtime | The Bytecode Alliance's WebAssembly runtime | Google's user-space application kernel |
| License | Apache-2.0 | Apache-2.0 WITH LLVM-exception | Apache-2.0 |
| Version checked | v1.20260929.1 (it releases daily) | v49.0.1 | release-20260921.0 |
| Loam tier | T0: JavaScript and TypeScript | T1: Wasm components | T2: any Linux binary; also the outer wall around T0 |
| Isolation unit | V8 isolate (inside a process) | Wasm instance (inside a process) | Sandbox (its own kernel, per pod) |
| Start cost | Milliseconds for an isolate | Microseconds to milliseconds | Hundreds of milliseconds |
| CPU meter | The process's cgroup | Fuel or epochs, per invocation | The sandbox's cgroup |
workerd: V8 isolates with the web platform
workerd is the open-source runtime at the heart of Cloudflare Workers. It runs JavaScript (TypeScript once compiled to it) and WebAssembly in V8 isolates: separate heaps inside one process, each with its own globals, created in milliseconds and costing a few megabytes. Around them it implements the web platform APIs (fetch, Request and Response, streams, Web Crypto, URL), a Node.js compatibility layer, and capability-based bindings: a worker can only reach what its configuration gives it, such as a named service, a KV namespace or an R2 bucket. Configuration is a Cap'n Proto file describing services, sockets and bindings.
Why Loam would use it. Framework adapters for Cloudflare already produce workerd-compatible output: Hono, Astro, SvelteKit, Nuxt through Nitro, React Router, and Next.js through OpenNext. Running workerd means those builds run without a Loam-specific port, and Loam does not have to write and maintain the web platform on a bare JavaScript engine. Its KV and R2 bindings become HTTP requests to a named service, which Loam's node supervisor can implement over Loam storage.
The caveat, in its own words. workerd's README says: "workerd on its own does not contain suitable defense-in-depth against the possibility of implementation bugs. When using workerd to run possibly-malicious code, you must run it inside an appropriate secure sandbox, such as a virtual machine." Cloudflare's hosted service adds defenses that are not all in the open-source runtime. So Loam's design never shares a workerd process between tenants: each tenant gets its own process, inside gVisor where the node supports it (or seccomp, a user namespace and a cgroup where it does not), and isolates separate only one tenant's functions and versions from each other.
Metering. workerd's open-source configuration has no per-request CPU limit. CPU is measured per process, from the tenant's cgroup (cpu.stat), cross-checked with getrusage. That is exact per tenant but only an estimate per invocation when one process serves many.
wasmtime: WebAssembly components
wasmtime runs WebAssembly, a portable bytecode with a simple safety model: a module can only touch its own linear memory, bounds-checked (often for free, with guard pages), and can only call the functions its host imports into it. It compiles modules ahead of time or just in time with its Cranelift code generator. It implements the component model and WASI 0.2, so a component can declare that it exports wasi:http/incoming-handler and be served as an HTTP function whatever language it was written in: Rust, Go through TinyGo, Python through componentize-py.
Metering and limits are where wasmtime shines for a CPU-billed platform:
- Fuel. The compiled code decrements a counter as it executes; when fuel runs out, execution traps. It is exact and deterministic, at some runtime cost.
- Epoch interruption. A cheaper, coarser alternative: a timer increments an epoch, and code checks it at loop back-edges and function entries.
- The pooling allocator pre-reserves memory slots for instances, so instantiation per request takes microseconds.
Why wasmtime first. It is the reference implementation of the component model, it is Rust, and fuel gives a deterministic per-invocation count of Wasm work even when many tenants share one host process. Before writing our own host, the plan evaluates Spin and wasmCloud, both Apache-2.0 and both built on wasmtime.
gVisor: a kernel in user space
gVisor runs ordinary Linux programs (Node, Bun, Python, Go binaries, anything) with a much smaller attack surface than a container. Its core, the Sentry, is an application kernel written in Go that implements Linux system calls in user space. The sandboxed program's system calls are intercepted (with the systrap platform, using seccomp, or with KVM where available) and served by the Sentry, which itself uses only a small, filtered set of host system calls. File access goes through a separate proxy. On Kubernetes it runs as an OCI runtime, runsc, selected with a RuntimeClass.
The cost is overhead on system-call-heavy work and slower starts than an isolate: hundreds of milliseconds rather than milliseconds. Some kernel features are limited; io_uring, for example, is restricted, which is why Loam's design keeps io_uring to its own data plane and never hands it to tenant sandboxes.
In Loam's design, gVisor is the T2 tier for apps that listen on a port (Express, Fastify, Django, Rails), and the outer sandbox around each tenant's workerd process. Metering reads the sandbox pod's cgroup.
Why three, and not one
- Only isolates are cheap enough to keep thousands resident while they wait on models, which is the whole point of CPU billing. But isolates alone are not a security boundary between tenants.
- Only Wasm gives a deterministic per-invocation work count inside a shared process, and polyglot components. But most web code today is JavaScript written for Node or Workers, not Wasm.
- Only a sandbox like gVisor runs arbitrary binaries. But it is too heavy to start per request.
Firecracker microVMs restored from snapshots would add a fourth tier with microsecond resumes; that is deferred, because Kata Containers' Firecracker driver does not actually restore snapshots and the alternative needs more orchestration than a first phase should carry.
Limits we plan around
- workerd releases daily and its compatibility dates move. Loam would pin a release per Loam version and set each function's compatibility date from its manifest.
- Hosted Workers features are not all in open-source workerd. Which adapter outputs break is a spike item, answered with a published support matrix.
- Per-tenant workerd processes cost memory at high tenant counts; idle tenants are evicted from the resident pool.
- Whether Resonate's TypeScript SDK runs inside workerd unchanged is unverified; the fallback is a thin shim over
fetchto the Resonate protocol.
The next post covers the authorization service that would decide what every one of these functions, and every user, may touch: OpenFGA.