9.3 KiB
Blue/Green VMs — a self-hosting, agent-managed runtime
Partially superseded (2026-08-03): the deployment subsystem (§5–§6 here) is now specified in
docs/superpowers/specs/2026-08-03-blue-green-vm-design.md— developer +woCLI as the management client (agent/MCP becomes a later wrapper), schema migration folded into the approval step (additive-only auto-diff in v1), fixed slots with alternating activity, HTTP+JSON+SSE. §1–§4 (transports, recipe box, fibers, source-in-binary) remain current thinking feeding plans 3/4/6.
Thought-process capture (2026-08-02). Not a phase plan yet — the vision that shapes how the wovm runtime grows past milestone 1, recorded before the details harden. Related: the OOP spec (
../../../superpowers/specs/2026-08-01-oop-compiler-vm-design.md), plan 4 (shard-actor runtime), plan 15 (MCP streamable HTTP), and the single-binary trailer already shipped bywoc build.
The idea, in five sentences
The writeonce executable is a systemd service that never stops. It embeds its own source code, not just its bytecode. An external Claude agent reads and edits that source through a managed channel; an approved change is compiled inside the runtime and loaded into the idle VM slot. The runtime holds exactly two VMs — Blue (active) and Green (previous version) — and deployment is an atomic switch between them. Rollback is the same switch in reverse, because the previous version never left memory.
1. A runtime is not a port
The runtime core is the VM pair + engine + scheduler — it must run with zero listeners. Ports are transports, attached at boot like modules: an HTTP listener, a unix socket, an MCP endpoint, stdio. Consequences:
- The same binary serves as web app, CLI batch runner, or agent-managed service depending on which transports the deployment attaches — one of the recipes a custom web framework builds from (§2).
- systemd socket activation fits exactly: the unit owns the socket
(
LISTEN_FDS), the runtime accepts on whatever fds it inherits. The "always running" property (§6) and the "no port of its own" property come from the same mechanism.
2. The runtime is a recipe box for web frameworks
Everything a custom web framework needs in later phases must exist as a
separable runtime capability, not a monolith: transports (§1), fibers (§3),
routing surface (plan 6), the subscription registry (plan 7), the DB engine
(plan 5), and the deploy/rollback machinery (§5). A "framework" in a later
phase is a .wo library that composes these recipes — the runtime itself
stays framework-agnostic.
3. Fibers (green threads)
Concurrency inside a shard is cooperative fibers scheduled by the VM, not OS threads — the Erlang shape on the wovm substrate:
- A fiber is exactly the execution state
wo_vmalready isolates: a register window stack + frame stack + a current pc. Making that state per-fiber instead of per-VM turns the interpreter into a fiber scheduler almost for free. - Preemption by reduction budget: the dispatch loop decrements a counter per instruction (or per call/back-edge); at zero, the fiber parks and the scheduler picks the next runnable one. No signals, no stack switching tricks, deterministic and debuggable.
- Fibers park on I/O: a blocked read hands the fd to the shard's event
loop (
wo-rt.c's epoll/io_uring machinery) and the fiber resumes when the completion arrives. One OS thread per core (plan 4's shard), thousands of fibers per shard. - Fits the ownership model: a fiber is an actor mailbox owner; cross-fiber sends follow the same ownership-move rule as cross-shard sends.
4. The binary contains its source
woc build already appends the .wob image to a copy of wovm with an
offset trailer. The trailer grows one more section: the .wo source tree
(paths + contents, compressed). Why:
- The deployed artifact is self-describing — no "which commit is prod
running?" class of question.
wovm --dump-sourcecan always reproduce exactly what is executing. - The agent workflow (§5) needs a source of truth that travels with the binary, not a checkout that can drift from it.
- After a deployment, the runtime rewrites its own source section (write to temp, fsync, rename) so the artifact on disk always matches the Blue VM.
5. Agent-managed source — how Claude fits
The runtime exposes a management transport (MCP over streamable HTTP — plan 15's machinery, localhost + bearer token, the log-watcher posture). Claude Code connects as an MCP client. Tools the runtime serves:
| Tool | What it does |
|---|---|
source_list / source_read |
browse the embedded source tree of the running (Blue) version |
source_propose |
submit a changed file set as a proposal — staged, never applied |
proposal_diff |
render the pending proposal against Blue's source |
proposal_check |
run woc check on the proposal inside the runtime — diagnostics come back to the agent |
proposal_approve |
human-only gate (separate credential or out-of-band confirmation) — approval triggers compile + green-slot load |
deploy_switch |
atomic Blue↔Green switch after health checks |
deploy_rollback |
the same switch back — Green still holds the previous version |
deploy_status |
which version is Blue, which is Green, in-flight drain state |
Properties worth pinning now:
- The agent proposes; a human approves.
proposal_approveis not reachable with the agent's token. Approval is the compile trigger, not the edit. - Every step is WAL-logged — proposals, diagnostics, approvals, switches, rollbacks form an audit trail that survives crashes like any other commit.
- The compiler lives with the runtime for this loop to work: either
wocembedded in the binary (adds OCaml runtime weight) or shipped beside it in the service directory (lighter; the systemd unit owns both files). Open question in §8 — start with "beside it".
6. Blue/Green VM lifecycle
Exactly two VM slots per runtime, never more:
- Blue — the active VM: all new requests/fibers dispatch into it.
- Green — the previous version, loaded and warm: the instant-rollback target. After a successful deploy the roles swap; the old Blue becomes the new Green.
The critical separation: VMs own code, the engine owns data. Tables, WAL, subscriptions, and the arena slabs live in the engine layer beneath both VMs; a switch swaps which bytecode handles requests, never the data. That is what makes the switch cheap and rollback safe — no state migration on the happy path (and schema changes are exactly the hard part, §8).
Deploy sequence:
- Approved proposal compiles (
woc emit) — failure ends the deploy, Blue untouched. - New image loads + validates into the idle slot (loader is the same validation battery as always — a bad image cannot boot).
- Health gate: entry smoke / conformance subset runs against the idle VM.
- Switch at the dispatch boundary: new work enters the new Blue; in-flight fibers on the old VM drain to completion (bounded timeout).
- Old Blue becomes Green (rollback target); the binary's source section is rewritten to match (§4).
deploy_rollbackat any later point is step 4 in reverse — no compile, no load, the code is already resident.
7. Always running
The executable maps to a systemd service: Restart=always, socket
activation for the transports (§1), the hardening posture proven in the
log-watcher units (unprivileged user, read-only system, StateDirectory
for WAL/data). Deployment never restarts the unit — that is the whole point
of the VM pair. The unit restarting (crash, host reboot) boots Blue from the
binary's current source/bytecode section and reloads Green only when the
next deploy happens.
8. Open questions (deliberately unresolved here)
- Schema migrations. Code switches atomically; data does not. A proposal that changes a class's fields needs a migration story between Green-shaped and Blue-shaped rows — the wo-seg migration doc's dual-write thinking applies inside one process. Hardest problem in this vision; needs its own exploration.
- Live subscriptions across a switch. Do WebSocket subscribers survive a deploy (registry lives in the engine layer → yes, by design), and what do they see mid-drain?
wocplacement — beside the binary vs embedded (§5).- Fiber preemption granularity — per-instruction counter vs call/back-edge only (cheaper, coarser).
- Does Green count against the heap budget (two arenas resident) or does Green hibernate (bytecode resident, heap lazily rebuilt on rollback)?
9. Where this lands in the plan sequence
- Fibers (§3): extends plan 4 (shard-actor runtime) — same scheduler work, one more scheduling unit.
- Transports-not-ports (§1): shapes plan 6 (HTTP/service layer) — the listener becomes one attachable transport among several.
- Management MCP (§5): builds on plan 15's streamable-HTTP machinery.
- Source-in-binary (§4): extends plan 3's
woc buildtrailer. - Blue/Green switch (§6) + agent loop (§5): a new phase after those land — needs spec + plan of its own once this vision stabilizes.