Beamlet is a minimum viable OTP over WebAssembly: supervised, capability-sandboxed processes running on wazero, in Go. It exists as the substrate for a self-rewriting LLM harness, the model generates tool code, the supervisor hot-deploys it, and crashes feed back into reflection.

    The OTP mapping

    Erlang's OTP gives you supervised processes with isolated heaps and mailboxes. Beamlet reproduces that shape on WASM:

    OTPBeamletMechanism
    processprocessone WASM instance + one goroutine
    heap isolationlinear memoryper-instance, capped via Caps.MemPages
    mailboxchan Messageguest blocks in the harness.recv host function
    exit(Pid, kill)context.CancelFunccancel interrupts even a hot loop
    supervisorSupervisorone-for-one, restart intensity window
    hot code loadingUpgrade(spec)new instance registers first, old drains and dies
    ambient authoritynonezero-value Caps is pure compute

    That last row is where Beamlet goes further than BEAM. OTP isolates faults; Caps also isolates authority. A tool the model wrote thirty seconds ago physically cannot open a socket or a file it wasn't granted, the grant is enforced by runtime linkage, not convention, because every process gets its own wazero.Runtime whose host functions are closures over that process's capability set.

    The harness that builds itself

    On top of the substrate sits an agentic loop where the model has exactly five meta-tools, create_tool, read_tool, delete_tool, call_tool, list_tools, and no other abilities whatsoever. Every real capability it wants, it must author as a Go wasip1 program, which the harness compiles, hot-deploys under supervision, and commits as self-edit(<name>): <reason>.

    Compile errors and supervisor crash events flow back into the loop, so reflection-and-repair happens in-band. The repo is the agent's durable body: git log is its changelog, git revert its undo.

    Dependencies are a permission, not a convenience

    Tool builds run with -mod=readonly and GOPROXY=off, so model-authored source can never pull a module. Availability is not authorization either, each tool's manifest.json carries an imports policy naming the non-stdlib packages that tool may use, reconciled against its parsed source on every deploy. Declaring an import doesn't grant it; operator approval does. With no terminal attached, nothing can be granted at all: an unattended run writes against the standard library or fails.

    Host content enters only where you put it. -mount <dir> exposes one directory read-only for that run; the model cannot choose it, widen it, or write to it.

    Pruning on evidence

    Every call_tool invocation is counted in a local ledger, and those figures, calls, failures, last used, come back from list_tools, so the model deletes on evidence rather than vibes. Standard-library tools can be upgraded but not deleted.

    What's deliberately missing

    Links and monitors between guests, a gen_server-shaped call/reply ABI, CPU metering (wazero has no fuel, runaway loops are killable but not budgeted), and harness self-modification. The model can grow tools; it cannot yet rewrite the loop or the supervisor.

    Architecture decisions

    Decisions are recorded as ADRs. Browse them all.