Git versions files.
mooversions the machine.
moo gives every git branch, worktree, or agent attempt its own
hardware-isolated Linux machine — database, ports, packages, services and
all — with the machine's state saved per commit and restored by
git checkout.
$ moo new feat/billing # a machine for this branch
$ moo run feat/billing -- npm run migrate # migration applied inside it
$ git commit -am "add billing migration"
$ moo save feat/billing # runtime snapshot, tagged with the commit
$ git checkout HEAD^ # rewind the code…
$ moo new feat/billing # …and the machine follows.
# the migration is gone; state matches code
One noun (the machine), four verbs (new, run, save, drop).
Everything else composes from those four plus the git you already know.
Status: alpha.
mooruns on macOS Apple Silicon only (Linux hosts planned). Pre-1.0: the CLI surface is stable, but the snapshot format may still change between releases.
Running parallel coding agents against one repo hits the same wall every
time: git worktree isolates files, but the database, the ports, the
.env, the installed packages, and the running services all collide. The
common workaround is a five-tool stack — worktrees + a port-offset script +
.env symlinks + a DB-per-branch tool + docker compose -p hacks.
moo replaces the stack with one motion:
$ git worktree add ../app-agent-b -b agent/b
$ moo new agent-b
Each machine is a full Linux microVM with copy-on-write state. Six machines
on a 20 GB base cost megabytes and fork in under a second. And unlike any
sandbox, the machine's state is a versioned artifact of the repo:
git bisect can boot the exact runtime — migrations, seeds, packages —
that existed at every commit it probes.
Requirements checked before anything is installed: an Apple Silicon Mac and Homebrew. No root, no daemon, nothing runs in the background except your machines.
curl -fsSL https://github.com/heyito/moo/releases/latest/download/install.sh | shThe installer brew-installs the isolation runtime and filesystem tools,
downloads the prebuilt binary for the release, signs it, installs it to
/opt/homebrew/bin/moo, and finishes with moo doctor — exit code 0
and four green checks mean you're ready. Verify end to end with one
machine (prints ok):
moo new smoke && moo run smoke -- echo ok && moo drop smoke --forceAdditionally requires Rust:
git clone https://github.com/heyito/moo && cd moo
scripts/install.shSame result: deps via Homebrew, then a local build is signed, installed,
and checked with moo doctor. Both installers are non-interactive and
safe to run from a coding agent; see AGENTS.md.
Two steps, once per repository.
1. Install the agent skills. Four skills teach a coding agent the full workflow — machine-per-branch coding, golden-image setup, browser desktops, and verifying changes. From the repository root:
mkdir -p .claude/skills && curl -fsSL https://github.com/heyito/moo/archive/main.tar.gz \
| tar -xz -C .claude/skills --strip-components=2 '*/skills'That is the Claude Code project location; for Cursor use .cursor/skills
as the destination, or the home-directory equivalents (~/.claude/skills,
~/.cursor/skills) to install them once for every project.
2. Build the golden image and baseline machine. Ask your agent to
"set up moo for this project" — the moo-golden-image skill walks it
through everything below — or do it by hand:
moo new base # first use builds the project's golden image
curl -fsSL https://github.com/heyito/moo/releases/latest/download/agent-base.sh \
| bash -s -- base # optional: compilers, Node.js, Chromium
moo run base -- '<install your runtime: packages, database, services>'
moo save base # the baseline everyone forksFrom here every branch, worktree, or agent attempt starts fully
provisioned in under a second: moo new feat/x from base.
moo new <name> [from <src>] [--detached] create or restore a machine
moo run <name> -- <cmd> [args...] execute inside the machine
moo save [<name>] snapshot state, tagged with the current commit
moo drop <name> [--force] [--snapshots] destroy the machine (snapshots survive)
newis idempotent, likegit checkout. If a snapshot exists for the commit the handle shadows, it boots that snapshot; otherwise it boots the current live state; otherwise it creates a fresh machine from the base image.<src>can be a git ref or SHA, a snapshot ID, or another machine's name (sub-second copy-on-write fork).runhasdocker execsemantics: services you start keep running between invocations. Exit codes and output round-trip faithfully.- The working tree follows you automatically.
newandrun, invoked from inside the machine's repository, sync your working tree — tracked files plus untracked-unignored files, exactly whatgit statuscalls your work — into the machine at/srv/app(configurable via[project] workdir). Unchanged trees are skipped in milliseconds. Gitignored files are never pushed and never deleted, sonode_modules, build output, and the machine's own.envsurvive every sync. The host tree is authoritative: files you delete or switch away from on the host disappear in the machine too. saveisgit commitfor the runtime. Idempotent — same commit, same content, same snapshot. Byte-identical states share storage.dropdestroys the live machine. Saved snapshots survive unless you pass--snapshots.
Admin, read-only: moo ls (machines, ports, snapshots), moo open <name> [guest-port] [/path] (print and open the host URL for a forwarded guest
port in the browser — the port is optional when the machine forwards
exactly one), moo doctor (host checks).
$ moo open feat/billing 3000 # -> http://localhost:24817/
$ moo open desktop 6901 '/vnc.html?autoconnect=true'
Machines are headless, but they are full Linux systems — a desktop is
just packages plus a port. With ports = [6901] in moo.toml:
$ scripts/desktop.sh my-machine
Installed from a release with no checkout? The provisioning scripts are attached to every release — run them from inside your project:
$ curl -fsSL https://github.com/heyito/moo/releases/latest/download/desktop.sh | bash -s -- my-machine
installs XFCE + VNC + a browser client inside the machine, starts it on
every boot, saves a snapshot, and prints a localhost URL you can click
around in. Reopen it any time with:
$ moo open my-machine 6901 '/vnc.html?autoconnect=true&resize=scale'
The desktop is part of the machine's state: forks of the
machine get their own desktop on their own port, and moo new after a
git checkout boots the desktop exactly as it was at that commit.
Similarly, scripts/agent-base.sh provisions a machine with the common
agent toolkit — compilers, developer CLIs, database clients, Node.js,
and a headless-capable Chromium — and snapshots it, so
moo new agent-1 from base forks a fully equipped machine in under a
second. Run both scripts against the same machine and the desktop gets
a working browser.
moo new <name> on an existing handle prefers the snapshot saved for the
current commit over the live overlay. That is the whole point — the
runtime follows the code — but it means unsaved runtime work is replaced
when you switch commits. The rule of thumb is the same as git's:
moo save before you git checkout, the way you git commit before
you switch branches. A shell alias makes it automatic:
gitcommit() { git commit "$@" && moo save; }Committed to the repo if used. This is the whole schema — there are no service graphs, health checks, or volumes:
[project]
base = "debian:bookworm" # any OCI image reference
workdir = "/srv/app" # where the working tree is synced in the guest
[recipe]
lockfiles = ["package-lock.json"] # participate in the base image identity
[resources]
cpus = 2
memory = "4GiB"
[network]
ports = [5432, 3000] # guest ports; each gets a stable host port (moo ls shows the map)
[quiesce]
commands = [ # run inside the guest before every save
"su postgres -c 'psql -c CHECKPOINT'",
]The base image is built automatically on first use — layers are fetched straight from the registry (no Docker daemon needed) and assembled into a bootable disk, unprivileged. Machines with the same base + lockfiles share one image.
- Time travel.
git checkout <old-sha>+moo new <name>boots the exact runtime that existed at that commit. - Runtime-dependent bisects. Bugs that only reproduce against a specific migration state become bisectable:
$ git bisect start bad-sha good-sha
$ git bisect run bash -c 'moo new probe && moo run probe -- npm test'
- Parallel agents, zero collisions. Machines isolate the runtime;
git worktreeisolates the files. Every parallel attempt gets one of each — its own checkout and its own DB, ports, packages, services:
$ for name in a b c; do
git worktree add ../app-agent-$name -b agent/$name
(cd ../app-agent-$name && moo new agent-$name from base)
done
Never point two sessions at the same checkout — the working tree is host-owned and authoritative, so they would overwrite each other's files no matter how many machines they have.
- Fork-and-promote. Fork a machine, let an agent work,
git mergethe winner,moo dropthe losers and remove their worktrees.
- Machines belong to their repository. Handles are scoped to the
repository a machine was created from:
moo new basein two different repositories creates two independent machines, and every verb resolves names against the repository you run it in. All worktrees of a repository share its machines —moo new feat/x from baseworks from a fresh worktree — and each worktree syncs its own files.moo lslists all machines on the host with their repository. Golden images are still shared across repositories by recipe content. - Durability. A live machine's disk survives machine shutdown, not host power loss. Snapshots are flushed to physical disk and survive power loss.
- Network isolation. Every machine has a fully private network stack.
localhostinside the machine is the machine's own loopback — never the host's. Host services are not reachable from the guest's loopback, and two machines never share network state. - Ports. Guest TCP services listed in
[network] portsare reachable on host localhost at the port shown bymoo ls. Like containers, a service must listen on a non-loopback address (0.0.0.0) to be reachable from the host. TCP half-close is not proxied faithfully — plain request/response protocols (HTTP etc.) are unaffected. - Platform. macOS Apple Silicon hosts, Linux guests (arm64). Linux host support is planned.
git reset --hardandgit rebasemove HEAD without any hook — the machine doesn't auto-follow; runmoo new <name>afterwards.
Three self-contained, self-verifying demos (each cleans up after itself):
$ scripts/demo-timetravel.sh # runtime follows git checkout, survives drop
$ scripts/demo-parallel.sh # 3 machines, same guest port, zero collisions
$ scripts/demo-bisect.sh # git bisect finds a bug that only exists in a
# specific migration state — unattended
moo is built for agent workflows: AGENTS.md is a terse
machine-readable reference (install, verbs, rules), and
skills/moo-code/SKILL.md is an installable
skill for Claude Code / Cursor that teaches an agent the full workflow —
isolated machines per branch, save at commit boundaries, fork-and-promote.
$ cargo build --release # builds the CLI + embedded guest agent
$ scripts/leakcheck.sh # gate: no backend names in user output
The isolation backend is an implementation detail and never appears in
user-facing output; scripts/leakcheck.sh enforces this in CI. See
CONTRIBUTING.md for the demo suite that gates PRs.
