← Blog
Engineering

Giving AI an undo button

You can undo a typo, a deleted email or a bad crop. An AI agent working on a real computer can’t undo anything. When it drops the wrong table or spends forty steps going the wrong way, the only fix is to throw the machine away and lose everything it built. smol machines gives the agent’s whole computer an undo button.

Why agents can’t undo today

Undo in a text editor works because the editor holds everything you changed. An agent’s work is spread across a whole computer: a half-built repo, a running dev server, a database, a logged-in browser, files in /tmp. The usual tools each cover only part of that.

ToolWhat it can undoWhat it loses
gitFiles in one repoRunning programs, databases, installed packages, everything outside the repo
Restart the containerEverything, by going back to the imageAll the work done since the image was built
VM memory snapshotMemoryDisks, unless you save them some other way
smol machines checkpointMemory, running programs and disks, togetherNothing on the machine

Memory and disk have to be saved at the same moment. If you roll back one without the other, a running program comes back expecting files that are no longer there, or no longer the same.

Save, then undo

Start the machine with --branchable and save a checkpoint before anything risky. The machine only stops for a moment while the save begins, then keeps running while its memory is written out. On a MacBook, saving a small Alpine machine took 2.7 seconds, and the machine was paused for 71 milliseconds of that.

smolvm machine start --name agent --branchable

# save before anything risky
smolvm machine checkpoint --name agent --store ./history -o ./before-migrate.checkpoint

To undo, create a machine from that checkpoint. Its programs carry on from exactly where they were, and its files match what they expect. The broken machine stays around until you delete it, so you can still look at what went wrong.

# the migration went wrong: undo it
smolvm machine stop   --name agent
smolvm machine create --name agent-undo --from ./before-migrate.checkpoint
smolvm machine start  --name agent-undo

Restoring is fast enough to do every time something fails. On our Linux test host, restoring a machine with 2 GiB of memory took about a quarter of a second, because it reads its disk from the checkpoint instead of copying it, and the machine was running again about two seconds later.

In an agent loop, that means saving before each step and undoing any step that fails, instead of starting over:

while read -r step; do
  smolvm machine checkpoint --name agent --store ./history -o "./before-$step.checkpoint"

  if ! smolvm machine exec --name agent -- ./do-step.sh "$step"; then
    # undo: back to the moment before this step, processes and all
    smolvm machine stop   --name agent
    smolvm machine delete --name agent -f
    smolvm machine create --name agent --from "./before-$step.checkpoint"
    smolvm machine start  --name agent --branchable
  fi
done < plan.txt

Going back further

One step of undo is rarely enough, because the mistake is often ten steps back. So every checkpoint remembers the one before it. Save a few times and you have a history. Go back to an old point and keep working, and the history branches instead of being overwritten, the way checking out an old git commit and committing on top of it does.

Checkpoint lineage · click a checkpoint, then restore it
9e37793c6ef3daa66d
current position of worker
last command
smolvm machine checkpoint --name worker --store ./ckpt -o ./ckpt-3.checkpoint
smolvm machine checkpoint-log ./ckpt-3.checkpoint
~0   daa66d13daa5  worker  (this checkpoint)
~1   3c6ef3623c6e  worker
~2   9e3779b19e37  worker
smolvm machine checkpoint-log ./ckpt-3.checkpoint
# ~0   c4c23a7c6d3a  2026-09-28T03:01:05Z  worker  401 MiB data  (this checkpoint)
# ~1   6b3e4f1fde48  2026-09-28T03:00:55Z  worker  401 MiB data
# ~2   58a6bb95baca  2026-09-28T03:00:45Z  worker  401 MiB data

# go back two steps (quote ~2 so zsh doesn't expand it)
smolvm machine create --name rewind --from ./ckpt-3.checkpoint --at '~2'

A history is only useful if cleaning up old checkpoints doesn’t break it. With most snapshot tools, each save depends on the one before it, so deleting one breaks every later save. A smol machines checkpoint saved with --store doesn’t depend on older checkpoint files. The newest one can restore its whole history on its own, and data that didn’t change between saves is stored only once. Three saves of the same machine took 98 MB, against 52 MB for one.

Delete files on either side below and see what still restores:

Three points in time · delete the older files and see what still restores

Diff chain snapshot per point

  • base.mem
    ABCDEFGH
    full snapshot
  • diff-1.mem
    C'G'
    pages dirtied since base
  • diff-2.mem
    E'H'
    pages dirtied since diff-1
  • rootfs.ext4
    disk state: yours to copy and keep consistent with each point
  1. ✓ point 1 base.mem
  2. ✓ point 2 rebase base.mem → diff-1.mem
  3. ✓ point 3 rebase base.mem → diff-1.mem → diff-2.mem

Stored checkpoints RAM + disk, retained history

  • ./ckpt-1.checkpoint/
    ABCDEFGH
    index + links
  • ./ckpt-2.checkpoint/
    ABC'DEFG'H
    index + links · retains #1
  • ./ckpt-3.checkpoint/
    ABC'DE'FG'H'
    index + links · retains #1, #2
objects/
ABCDEFGHC'G'E'H'
12 unique chunks stored once
  1. ✓ point 1 ./ckpt-3.checkpoint --at '~2'
  2. ✓ point 2 ./ckpt-3.checkpoint --at '~1'
  3. ✓ point 3 ./ckpt-3.checkpoint
Try deleting base.mem, then ./ckpt-1.checkpoint/ and ./ckpt-2.checkpoint/.

Trying two paths at once

Sometimes you don’t want to undo. You want to try two next steps from the same moment and keep whichever works. That’s a branch. The machine pauses for a moment, and each new copy starts from exactly that point and only stores what it changes afterwards. On a MacBook the pause was about 80 milliseconds for a machine with 1 GiB of memory.

smolvm machine branch --from agent --name try-a
smolvm machine branch --from agent --name try-b
# run a different next step in each, keep the one that works

Try it: branch the machine while it runs, then write from each side.

Live branch · copy-on-write memory
source counter 0

The source machine is running. Branch it while it works.

machines
1
pages held
32
as full copies
32
still shared
0

private page (written after the branch) read through the shared base

Each copy is a new machine with its own name, hostname, machine id and ports, so the copies don’t clash, and it records which machine it came from.

How this compares with Firecracker

Most microVM platforms are built on Firecracker. Firecracker can pause a VM and save its memory, and leaves disks, history and copies for you to build. In practice every team on Firecracker ends up writing its own undo on top.

Firecrackersmol machines
SavesMemory and devicesMemory, devices and disks
Go back to an older pointReplay the base and every change after it, in order--at ~N on any later checkpoint
Delete older savesLater saves stop restoringThe newest save still restores everything
Copy a running VMSave it, then restore it N timesmachine branch, and the original keeps running
Copies get their own identityUp to you, inside the guestNew name, hostname, machine id and ports
HostsLinuxLinux and macOS

Firecracker is cheaper when you save very often. It writes only the memory that changed since the last save, while smol machines still reads all of memory and skips writing the parts that didn’t change. If you save every few seconds, Firecracker’s approach costs less.

A few limits to know about. A checkpoint restores on a host with the same CPU architecture, and a checkpoint from an Apple Silicon Mac also restores on arm64 Linux (smolvm 1.23.0 and later). History keeps 32 points by default (--history N changes that), and copies of copies can go 32 levels deep. Windows doesn’t support checkpoints or branching yet. Everything else here works in smolvm 1.19.1 and later.


Get the runtime on GitHub, or read how checkpoint history works.

RSS feed