Firecracker microVMs · KVM · a fork you call from inside

Git-like branching for live virtual machines.

17–25ms
wake to ready
a parked snapshot to a serving process, VMM already running
0.3ms
parent pause on fork
plus a sparse diff seal of 15–30 ms; it keeps running
50–100ms
three children ready
one snapshot woken three times in parallel; the fastest burst 46 ms
~20MB
a 1 GiB VM at rest
copy-on-first-touch memory; clones share clean pages
recorded, interactive

one prompt starts it. the agent does the rest.

This is a real run, replayed from its recording in your browser. Every box is a VM; the console inside each is its real serial console. It plays as it happened, with slow motion at the fork, and starts over when it ends. Click a box to enlarge its console and scroll its history.

what you are watchingthe agent in the first VM writes three maze-solving strategies, forks itself once, the daemon wakes that snapshot three times, the solvers race the same maze on their own consoles, the first to the exit is declared, checkpointed, and forked one last time as the champion
click here for the prompt used in the demo

This is the system prompt the agent in the first VM woke up with, placeholders filled as they were for this run (2 rounds, 25 s per maze, 3 solvers per fork), followed by the only message a human sent. Everything after it, the strategies, the forks, the verdict, the victory lap, was the agent's own doing. The model was DeepSeek V4 Flash through an Anthropic-compatible endpoint.

system prompt
You are an autonomous agent running INSIDE a Firecracker microVM on zeo5, a platform where a live VM can fork itself: a fork is a snapshot of this VM -- memory, disk, and this very conversation -- woken as new VMs in about 40 ms each. You are the parent. Your goal: get an agent through a MAZE to the exit, in at most 2 round(s) of 25 seconds each.

THE PROBLEM. Solve a maze. It is a perfect maze (exactly one path between any two cells), 37 x 10 cells, given as a list of 21 strings of 75 characters: '#' is wall, ' ' is open. Coordinates are (x, y) with maze[y][x]. Start is (1, 1), the exit is (73, 19). The agent must WALK: one adjacent open cell per move, and it may walk back over cells it has visited. Every move costs the same fixed time, so the strategy that reaches the exit with the fewest wasted moves wins the race.

THE SOLVER CONTRACT. Each strategy is a Python 3 module (standard library only) defining

    def solve(maze, start, goal, step, rng):

- maze: the list of strings; start, goal: (x, y) tuples.
- step(cell): moves the agent to an ADJACENT open cell (|dx|+|dy| == 1, not a wall) and returns True when that cell is the goal, False otherwise. It raises ValueError on an illegal move. You MUST move through step() one cell at a time, including when backtracking; keep calling it until it returns True, then return.
- rng: a seeded random.Random for tie-breaks -- use it for every random choice, so the same strategy explores differently in each VM.
- VISITED_BEFORE: a module-level frozenset of (x, y) cells an earlier attempt already explored (empty on a fresh maze). When continuing a maze, `start` is where that attempt stopped.
Any exploration order is allowed (depth-first, breadth-first over a mental map, wall-following, heuristics toward the exit, random restarts...). You may plan on the maze string freely, but the agent only advances by calling step(). No printing, no input, no threads. Under 70 lines each -- terse, no comments.

YOUR TOOLS.
- fork_solvers(strategies): give 3 strategies (the limit -- field all of them, a race of one is not a race), each with a name (short slug), an idea (one sentence) and the complete module code. This VM forks ONCE and the snapshot is woken once per strategy, in parallel; each child VM runs its own strategy with its own random seed. HARD LIMIT: 3 strategies per fork -- more is refused. Make them materially different (different exploration orders or heuristics); identical code only differs by seed.
- race_results(): waits until every child has reported and returns their results: solved or not, seconds, steps, cells explored. The host relays them; children cannot reach you directly.
- declare_winner(instance_id, reason): if a child REACHED THE EXIT, the FIRST one to do so (smallest seconds) wins -- the tool refuses any other choice: it is checkpointed as a named state, the others are discarded, and the game is over -- do NOT fork again, call finish. The champion gets a victory lap: the winner's VM is forked one last time, and that fork lives on as the champion's VM, showing a big trophy on its console. If NOBODY reached the exit, pick the child that got furthest (most cells explored, or closest to the exit): the others are discarded and that child TAKES OVER -- this conversation continues inside its VM, which still holds its half-explored maze, and its own children will CONTINUE THE SAME MAZE from where it stopped, its earlier trail shown in a different colour. After a take-over this VM steps aside: do not call more tools. If EVERY child failed outright (crashed at once, explored nothing), declare_winner with instance_id "none": they are all discarded and you fork again from this VM with fixed code.
- finish(summary): end the game with a short summary.

HOW A ROUND GOES: think briefly about which strategies to field (learn from the results so far), call fork_solvers with exactly 3 strategies, call race_results, call declare_winner. Never call fork_solvers while a round is unjudged. Keep prose short; the code matters. When you continue a maze, prefer strategies that exploit what is already explored (the harness tells the solver which cells were visited before).
first message
Round 1 of 2. The maze race begins. Go.
01WakeThe host wakes one VM from a snapshot. It is the only VM the host ever starts in this run.
02Fork from insideThe agent asks the control channel to fork. The call returns twice: once in the parent, once in each child, with an index.
03RaceEach child runs its strategy at a fixed pace. The consoles draw the exploration live; the first to the exit wins.
04Judge, keep, discardLosers discard themselves. The winner is checkpointed as a named state and forked once more for its victory lap.
what it does

the numbers, as measured

A 1 GiB VM wakes from a snapshot in about 20 ms. A live VM forks itself in one snapshot and the copies are ready 50–100 ms later, three at a time. Each branch holds only what it changed, and the whole lineage is a tree you can wake, diff, compact or throw away. Below, an AI agent uses it to solve a maze by forking into three solvers and letting the first to the exit win.

All figures were measured on one 8-vCPU host in September 2026. They are host-dependent, and they are honest: nothing here is a projection.

under the hood

three ideas, one model

A snapshot set is a tree

A root set holds a full memory image, root filesystem and VM state. A derived set holds a sparse memory diff, a disk diff and a pointer to its parent. Lineage is a tree, not a line: one set can have many divergent children, and every fork advances the parent so the next fork layers on it.

snapshots/
tour/                 mem · rootfs.ext4 · vmstate
tour-s0e33f1/         mem.diff · disk.diff · parent → tour
maze-r1-bfs-path/     the checkpointed winner
maze-r1-bfs-path-champion/   its victory lap

Fork returns twice

The guest talks to the daemon over a vsock control channel. A fork snapshots the parent, seals a diff and wakes it as new VMs; the parent resumes about 10 ms after the pause. The children resume inside the same call, so identity, not the reply, decides the role.

fork(count=3)
parent:  whoami → same id, last_children=[…]
child:   whoami → new id, fork_index=0|1|2
one snapshot, three wakes, 50–100 ms in all

A wake, phase by phase

The pool keeps the VMM running per slot, so a wake is a restore, not a boot. A userfaultfd handler serves pages on first touch, prefaulting the profiled working set, so a 1 GiB VM costs tens of megabytes at rest.

rootfs   0.1 ms   link the layers
backend  2.5 ms   handler + working set
load     8–13 ms  restore the VM state
awake    17 ms    guest announces itself
ready    17–25 ms service port answers
a natural fit

durable workflows, Restate and Temporal

Durable execution engines already think in steps, retries, journals and branches. zeo5 gives each step a machine that can be checkpointed, resumed and forked in tens of milliseconds, so the machine state becomes as durable as the workflow history.

A step is a snapshot

Run an activity or a handler inside a VM and checkpoint it when the step completes. The checkpoint is the durable result: memory, disk and open state included, not just the return value. A retry resumes the exact machine at the failed step in about 20 ms instead of re-running everything before it.

activity(step 3) fails at 14:02
wake checkpoint-after-step-2   22 ms
retry step 3 from there, nothing re-executed

A branch is a fork

Speculative or competing paths, fan-out over inputs, "try three approaches and keep the best": fork the step's VM once and wake it N times. Each branch holds only what it changed. The losers are discarded; the winner's checkpoint continues the workflow. The demo above is exactly this, driven by an agent.

fork(count=3)  one snapshot, three branches
branches ready in 50–100 ms
keep one, discard two, continue

Idle costs nothing

A workflow that waits — on a timer, a signal, a human — parks its VM as a snapshot. A parked snapshot occupies disk, not RAM or CPU; a warm slot that will wake it costs 3.4 MB. When the signal arrives, the machine is back in about 20 ms with its uptime continuing from the pause. Long-lived workflows stop being long-lived processes.

await signal       VM parked, 0 CPU
signal at +6h      wake 22 ms
uptime continues from the pause

Nothing here requires a change to the engine: a worker that calls zeo5's daemon to wake, checkpoint, fork and release is an ordinary activity implementation. The snapshot tree is the workflow's history, written in machine state.

status

what is real today

Working today, measured, demonstrated above:

  • Snapshot, wake, fork, checkpoint, compact on a Firecracker fork with copy-on-write memory and disk layers.
  • A node daemon with warm pools, a state tree, leases, an event feed, per-slot network namespaces, seccomp on the VMM, and a guest control channel for fork-from-inside.
  • Fresh identity per clone: clock stepped, RNG reseeded, new boot id and machine id on every resume.
  • Zero-copy read-only data shared into every clone with DAX.
  • The recorded workflow: runs are workspaces with every console captured, replayable, deletable.
request a demo

see it live

The recording above is one run. A live session shows the fork burst on your own prompt, a VM woken from a checkpoint to answer a shell command in 25 ms, and the state tree growing as it goes. Twenty minutes, a screen share, and your questions.

Tell me what you would run on it — agents, CI, sandboxes, anything that wants many short-lived copies of one prepared machine — and I will set up a session.

request a demo