WASM Modules
WASM sandboxes are created from a module reference (module_ref). The daemon resolves the ref to a content-addressed file under SB_WASM_MODULES_DIR and records it in the wasm_modules catalogue.
Module references
Section titled “Module references”A module_ref is resolved in this precedence order:
- Standard-module keyword — a short alias your operator staged on every
node (e.g.
python,javascript). Recommended: no host paths, identical fleet-wide. See Standard modules. oci://host/repo:tag— a registry artifact, pulled under your own credentials and cached content-addressed. See Bring your own module.- Catalogue id — a digest id returned by
createWasmModule. file:///path/to/module.wasm/ bare filename underSB_WASM_MODULES_DIR— for self-managed hosts.
Whatever the form, the daemon validates the artifact is a core wasip1
module (WASI components are rejected), records the resolved digest, and pins it
to the sandbox so a restart or failover boots the exact same bytes. Remote
oci:// pulls are restricted to the hosts in SB_WASM_REGISTRY_ALLOWLIST.
Standard modules
Section titled “Standard modules”Operators pre-stage a curated set of language modules on every node (via the
stage-wasm-modules playbook) and expose them as reserved keywords through
SB_WASM_STANDARD_MODULES. You reference one by name — no upload, no path:
await client.create({ module_ref: 'python', runtime: 'wasm' })client.create({'module_ref': 'python', 'runtime': 'wasm'})client.Create(ctx, types.CreateSandboxRequest{ModuleRef: "python", Runtime: types.RuntimeWasm})client.create(CreateSandboxRequest { module_ref: Some("python".into()), runtime: Some("wasm".into()), ..Default::default()}).await?;CreateSandboxRequest req = new CreateSandboxRequest();req.setModuleRef("python");req.setRuntime("wasm");client.create(req);A standard-module keyword always wins over a same-named file in the modules dir, so the curated runtime can never be shadowed.
Bring your own module
Section titled “Bring your own module”Compile your program to a core wasip1 module (GOOS=wasip1 for Go,
--target wasm32-wasip1 for Rust), then push it to the registry under your own
credentials. The daemon validates and forwards the bytes — it never stores
them or acts as a registry — and returns the oci:// ref to use on create.
import { readFile } from 'node:fs/promises'
const wasm = await readFile('app.wasm')const pushed = await client.pushWasmModule({ name: 'tenant/app', tag: 'v1', module: new Uint8Array(wasm), registryUsername: process.env.AOCR_USER, registryToken: process.env.AOCR_TOKEN!,})await client.create({ module_ref: pushed.moduleRef, runtime: 'wasm' })with open('app.wasm', 'rb') as f: module = f.read()pushed = client.push_wasm_module({ 'name': 'tenant/app', 'tag': 'v1', 'module': module, 'registryUsername': os.environ['AOCR_USER'], 'registryToken': os.environ['AOCR_TOKEN'],})client.create({'module_ref': pushed['moduleRef'], 'runtime': 'wasm'})wasm, _ := os.ReadFile("app.wasm")pushed, err := client.PushWasmModule(ctx, types.PushWasmModuleOptions{ Name: "tenant/app", Tag: "v1", Module: wasm, RegistryUsername: os.Getenv("AOCR_USER"), RegistryToken: os.Getenv("AOCR_TOKEN"),})if err != nil { log.Fatal(err)}client.Create(ctx, types.CreateSandboxRequest{ModuleRef: pushed.ModuleRef, Runtime: types.RuntimeWasm})let module = std::fs::read("app.wasm")?;let pushed = client.push_wasm_module(PushWasmModuleOptions { name: "tenant/app".into(), tag: "v1".into(), module, registry_username: std::env::var("AOCR_USER")?, registry_token: std::env::var("AOCR_TOKEN")?,})?;client.create(CreateSandboxRequest { module_ref: Some(pushed.module_ref), runtime: Some("wasm".into()), ..Default::default()}).await?;byte[] module = Files.readAllBytes(Path.of("app.wasm"));PushWasmModuleResponse pushed = client.pushWasmModule(new PushWasmModuleOptions() .setName("tenant/app") .setTag("v1") .setModule(module) .setRegistryUsername(System.getenv("AOCR_USER")) .setRegistryToken(System.getenv("AOCR_TOKEN")));CreateSandboxRequest req = new CreateSandboxRequest();req.setModuleRef(pushed.moduleRef);req.setRuntime("wasm");client.create(req);You can also oras push your module to the registry directly and skip the
daemon — then create with the oci:// ref plus your registry credentials.
Either way the registry is the source of truth and the daemon only pulls to run.
Catalogue API
Section titled “Catalogue API”Register modules on the host before create, or let create resolve inline. Catalogue rows are per-node (not cluster-forwarded), like Firecracker templates.
const mod = await client.createWasmModule({ moduleRef: 'file:///opt/agent.wasm', entrypoint: '_start',})const rows = await client.listWasmModules()await client.deleteWasmModule(mod.id)mod = client.create_wasm_module({ 'moduleRef': 'file:///opt/agent.wasm', 'entrypoint': '_start',})rows = client.list_wasm_modules()client.delete_wasm_module(mod.id)mod, err := client.CreateWasmModule(ctx, types.CreateWasmModuleOptions{ ModuleRef: "file:///opt/agent.wasm", Entrypoint: "_start",})rows, err := client.ListWasmModules(ctx)err = client.DeleteWasmModule(ctx, mod.ID)let module = client.create_wasm_module(CreateWasmModuleOptions { module_ref: "file:///opt/agent.wasm".into(), entrypoint: Some("_start".into()), id: None,})?;let rows = client.list_wasm_modules()?;client.delete_wasm_module(&module.id)?;WasmModule mod = client.createWasmModule(new CreateWasmModuleOptions() .setModuleRef("file:///opt/agent.wasm") .setEntrypoint("_start"));List<WasmModule> rows = client.listWasmModules();client.deleteWasmModule(mod.id);Warm pool
Section titled “Warm pool”When SB_WASM_POOL_ENABLED=true, the daemon keeps pre-spawned workers per module digest. Creates against a warm module skip cold worker boot.
const sandbox = await client.create({ module_ref: 'https://example.com/builds/agent.wasm', runtime: 'wasm', memoryMB: 512,})sandbox = client.create({ 'module_ref': 'https://example.com/builds/agent.wasm', 'runtime': 'wasm', 'memoryMB': 512,})sb, err := client.Create(ctx, types.CreateSandboxRequest{ ModuleRef: "https://example.com/builds/agent.wasm", Runtime: types.RuntimeWasm, MemoryMB: 512,})client.create(CreateSandboxRequest { module_ref: Some("https://example.com/builds/agent.wasm".into()), runtime: Some("wasm".into()), memory_mb: Some(512), ..Default::default()}).await?;CreateSandboxRequest req = new CreateSandboxRequest();req.setModuleRef("https://example.com/builds/agent.wasm");req.setRuntime("wasm");req.setMemoryMB(512);client.create(req);Cluster placement
Section titled “Cluster placement”Peers gossip local_wasm_module_ids via /v1/capacity. Placement prefers nodes that already cache the requested module_ref.
Garbage collection
Section titled “Garbage collection”When SB_WASM_MODULE_GC_ENABLED=true, the wasm module janitor runs two sweeps
each SB_WASM_MODULE_GC_INTERVAL:
- Catalogue rows. Unreferenced rows older than
SB_WASM_MODULE_GC_TTLare removed. oci://cache bytes. Content-addressed files underSB_WASM_CACHE_DIR(<digest>.wasm) are evicted when older thanSB_WASM_CACHE_GC_TTL(default 24h), or oldest-first when the cache exceedsSB_WASM_CACHE_MAX_BYTES(default unlimited). A digest still referenced by a sandbox or a catalogue row is never evicted, regardless of age or disk pressure — eviction only reclaims pull-on-create modules that nothing depends on.