# Tau (and Pi) Pocket on smolvm — Tutorial A self-contained guide to running **Tau**, the minimalist terminal coding agent, inside a **smolvm** sandbox, and bringing in your existing `/teacher-mode` skills. > **Scope.** The substrate is smolvm. The primary agent is Tau. The door is left open for **Pi** (Tau's design inspiration) and for other smolvm-bundled agents — `smolvm hermes`, `smolvm claude`, `smolvm codex`, `smolvm openclaw` — but only one agent lives in a sandbox at a time. > > **What this replaces.** The earlier "Decision Summary" document in this folder was mostly a paraphrase of `smolvm --help` plus a hand-rolled snapshot model. The CLI already does snapshots (`smolvm sandbox snapshot ...`) and already ships first-class launchers for every popular agent. This tutorial uses those primitives directly. No custom substrate, no bespoke snapshot file format, no extra "Phase 1/2/3" rollout. --- ## 0. Prerequisites Verify the host can run smolvm: ```bash smolvm doctor ``` You want `Backend: firecracker` (or qemu / libkrun), `Result: OK`, and `kvm`/`network-permissions` to show **pass**. The four warnings shown on this machine (`nft-table:ip:smolvm` not created yet, swap enabled, thp enabled) are non-fatal — they self-resolve or are host-tuning, not blocking. Confirm the smolvm build you have actually exposes the commands used here: ```bash smolvm --help # should list: claude codex hermes pi openclaw sandbox ... smolvm sandbox --help # should list: create exec snapshot file ... smolvm pack --help # OPTIONAL — read §6 before relying on this ``` > **Caveat verified on this host:** this build of smolvm does **not** expose `smolvm machine ...` or `smolvm pack ...` (only `sandbox`, `browser`, and the agent launchers). Everything below is written for the `sandbox` family, which is what you have. The pack/machine flow is described in §6 for reference; if your build has it, use it. --- ## 1. The minimal mental model You only need four smolvm primitives and three Tau facts: | smolvm | What it does | |---|---| | `sandbox create` | Allocate a fresh Linux VM (alpine/ubuntu) with a name | | `sandbox exec` | Run a command inside it (auto-starts the VM with `--start`) | | `sandbox snapshot create / restore / list` | Save and rewind VM state — this is the "snapshot model" | | `sandbox file upload / download` | Copy files in and out (sandbox must be running) | | Tau | Where it lives | |---|---| | Sessions | `~/.tau/sessions/*.jsonl` (append-only) | | Custom providers/models | `~/.tau/catalog.toml` | | Project skills / instructions | `AGENTS.md`, `.tau/`, `.agents/` (loaded from cwd up) | | Skills shipped with the repo | `tau_coding/skills/` (read-only) | Pi keeps its config under `~/.pi/agent/` with skills at `~/.pi/agent/skills/`, sessions configurable via `PI_CODING_AGENT_SESSION_DIR` or settings.json. The two agents do not share state directories. --- ## 2. Build the base sandbox Pick a name — `tau-pocket` is the convention used in the rest of this guide. ```bash # Alpine is small and fast for a coding-agent sandbox smolvm sandbox create --name tau-pocket --os alpine --network nat smolvm sandbox list # status: created ``` Install Tau **inside the sandbox** (one `exec` call chains everything): ```bash smolvm sandbox exec --start tau-pocket -- sh -lc ' apk add --no-cache python3 py3-pip git curl bash pip install --break-system-packages uv uv tool install tau-ai uv tool dir # so you can see where tau-ai was installed mkdir -p /workspace && cd /workspace && git init -q ' ``` Now, `/.local/bin` is not in the PATH, so we have to add it by hand: ```bash smolvm sandbox exec tau-pocket ``` No, we edit `/etc/profile` with `vi /etc/profile` and add './local/bin' at the end of the PATH line: ```bash export PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/.local/bin" ``` We save the file and exit the VM with CTRL+d. Sanity check before snapshotting: ```bash smolvm sandbox exec tau-pocket -- tau --help | head -5 ``` If the Tau version banner appears, the base is good. If not, re-run the `apk` / `uv` / `uv tool install` chain and read the error. Snapshot the base so every later session can roll back to a known-good image: ```bash smolvm sandbox snapshot create tau-pocket --snapshot-id tau-base smolvm sandbox snapshot list ``` `tau-base` is now your anchor. If a later experiment breaks the sandbox, `restore` brings it back without re-installing anything. --- ## 3. Run a Tau session The simplest pattern: keep `/workspace` as the working directory and let Tau read its `AGENTS.md` (project instructions) from there. ```bash # Drop an AGENTS.md describing the project before starting smolvm sandbox file upload tau-pocket ./AGENTS.md /workspace/AGENTS.md ``` Then either run a one-shot, non-interactive prompt: ```bash smolvm sandbox exec --name tau-pocket -- sh -lc 'cd /workspace && tau -p "summarize the project"' ``` Or open a real interactive shell inside the sandbox: ```bash smolvm sandbox shell tau-pocket # you are now root in the sandbox cd /workspace tau # launches the Textual TUI ``` Stop the sandbox when you're done (state is preserved on the underlying disk): ```bash smolvm sandbox stop tau-pocket smolvm sandbox start tau-pocket # resume later ``` > **Filesystem note.** `/tmp`, `/run`, and `/dev/shm` are tmpfs and **do not survive** a stop+start. Write anything that must outlive a restart under `/workspace` or `~/.tau/`. Package installs, `/etc/` edits, and `~/.tau/` itself are on the persistent overlay. --- ## 4. Snapshotting the learning timeline Every time you reach a milestone worth keeping — "added the Git teacher skills", "wired compaction", "experimented with self-modification" — capture it: ```bash # inside the sandbox cd /workspace git add -A && git commit -m "m1: add basic Git teacher skills" -q tau -p "/exit" # back on the host smolvm sandbox snapshot create tau-pocket --snapshot-id m1-git-skills ``` The snapshot stores the full sandbox state — installed packages, `~/.tau/`, `/workspace`, the works. The git commit is *inside* the snapshot, which is the "Git overlay" the old design document described, except you don't need to design it; the snapshot mechanism already handles it. To rewind a failed experiment: ```bash smolvm sandbox snapshot restore m1-git-skills ``` `restore` takes a snapshot id, not a sandbox name, which is the part that trips people up. You can list available snapshots with `smolvm sandbox snapshot list` (optionally `--json` for a machine-readable view). A lightweight textual changelog is still useful as a human-readable index — drop it in `/workspace` so it travels with every snapshot: ```bash # inside the sandbox cat >> /workspace/CHANGELOG.md <<'EOF' ## m1-git-skills - Snapshot: m1-git-skills (2026-08-06) - Git: a1b2c3d "add basic Git teacher skills" - Notes: oriented on Tau architecture; added teacher-mode skills. EOF git add CHANGELOG.md && git commit -m "changelog: m1" -q ``` Repeat per milestone. The host-side record is `smolvm sandbox snapshot list`; the in-sandbox record is `CHANGELOG.md`. Pick the one that answers your question: "what state is on disk?" → host list. "why does this state matter?" → changelog. --- ## 5. Importing `/teacher-mode` and an existing Tau configuration The assumption is that `/teacher-mode` (and possibly a fully-configured `~/.tau`) already lives on the host you're importing from. There are two paths — pick one. ### 5a. `sandbox file upload` (one file or one directory at a time) ```bash # upload a tarball (recommended for directories; smolvm file upload # treats the source as a single file path) tar czf /tmp/teacher-mode.tgz -C /path/to/parent teacher-mode smolvm sandbox file upload tau-pocket /tmp/teacher-mode.tgz /workspace/teacher-mode.tgz # then inside the sandbox smolvm sandbox exec --name tau-pocket -- sh -lc ' cd /workspace tar xzf teacher-mode.tgz && rm teacher-mode.tgz ls teacher-mode/ # confirm structure ' ``` Place `/teacher-mode` under `/workspace/` (or under `~/.tau/` — see 5b) so it travels with every subsequent snapshot. The default place for project-scoped resources in Tau is `.tau/` and `.agents/` relative to the cwd; `/workspace/teacher-mode` is a clean convention that mirrors the source layout. For a custom `~/.tau` (a `catalog.toml` you've tuned, existing sessions you want to keep, a private skill set, etc.), archive and inject it the same way, but extract to the home directory: ```bash # from the source host tar czf /tmp/dot-tau.tgz -C ~ .tau # upload + extract smolvm sandbox file upload tau-pocket /tmp/dot-tau.tgz /tmp/dot-tau.tgz smolvm sandbox exec --name tau-pocket -- sh -lc ' rm -rf ~/.tau && mkdir -p ~/.tau && tar xzf /tmp/dot-tau.tgz -C ~ ls -la ~/.tau/ ' ``` > **Watch for:** `sandbox file upload` requires the sandbox to be running. If it isn't, the error is non-obvious — start it first or use `sandbox exec --start ...` for the extract step instead. `~/.tau` is on the persistent overlay, so once you've imported it, it survives stop/start and is captured by every snapshot. ### 5b. Mount-on-start (cleaner, when you iterate often) If you'll be editing `/teacher-mode` on the host and want the sandbox to see changes live, pass a host path when creating the sandbox (or update an existing one): ```bash # at create time smolvm sandbox create --name tau-pocket --os alpine \ --mount /home/pikaos/teacher-mode:/workspace/teacher-mode \ --writable-mounts # or update an existing one smolvm sandbox update --name tau-pocket \ -v /home/pikaos/teacher-mode:/workspace/teacher-mode \ --writable-mounts ``` With `--writable-mounts` the host directory is read-write inside the guest. Without it, the mount is read-only — that's usually what you want for a stable skills directory. **Same approach for `~/.tau`:** ```bash smolvm sandbox update --name tau-pocket \ -v /home/pikaos/.tau:/root/.tau \ --writable-mounts ``` Once mounted, restart the sandbox and verify: ```bash smolvm sandbox stop tau-pocket smolvm sandbox start tau-pocket smolvm sandbox exec --name tau-pocket -- ls /workspace/teacher-mode smolvm sandbox exec --name tau-pocket -- ls /root/.tau ``` > **Snapshot semantics with mounts.** Anything in the mounted directory is *not* part of the sandbox disk — it lives on the host. A snapshot still captures the sandbox state, but the mount is re-attached on next start, so the *effective* content is the union of the snapshot and the current host directory. Plan accordingly: if `/teacher-mode` is the source of truth and you want a particular revision frozen inside a snapshot, copy it in (`file upload` or `cp -a`) rather than mounting it. ### 5c. Verify Tau actually picks up the skills ```bash smolvm sandbox exec --name tau-pocket -- sh -lc ' tau -p "list the skills you can see and which file each came from" ' ``` Or, if Tau has a `/skills` slash command in your build: ```bash smolvm sandbox exec --name tau-pocket -- tau -p "/skills" ``` If the skills aren't visible, the most common cause is the cwd: Tau reads `.tau/` and `.agents/` from the **current working directory** upward. Launch Tau from `/workspace` (or from wherever the skills directory sits) and they will be picked up. Snapshot once `/teacher-mode` is wired in and you have a working setup: ```bash smolvm sandbox snapshot create tau-pocket --snapshot-id teacher-mode-ready ``` --- ## 6. Exporting the machine This is the part that needs a clear-eyed note. "Export the machine" can mean three different things, and the right tool depends on which you want. ### 6a. Export the **state** (cheapest, most common) This is what you usually want — the configured sandbox, with Tau installed and `/teacher-mode` in place, portable enough to bring to another host running smolvm. ```bash # locate the snapshot on disk smolvm sandbox snapshot list --json # the snapshot record includes an id you can use to find the file; # on Linux the snapshot storage lives under ~/.smolvm/sandboxes/... # copy the whole directory tree (or a single snapshot id) somewhere portable: cp -a ~/.smolvm/sandboxes/ /path/to/backup/ # or, if your build exposes `pack` (this host does NOT): smolvm pack create --from-vm tau-pocket -o tau-pocket.smolmachine ``` The `cp -a` route is verified; the `pack` route is documented for builds that have it. On any host that has the same smolvm version and the same architecture, you can recreate the sandbox from the copied tree by pointing the new host at the snapshot id, or by using `sandbox snapshot restore` once you've registered the copied state. > **Architecture caveat.** smolvm snapshots are tied to the guest architecture (the kernel, virtio layout, and the userland). A `firecracker` snapshot on `linux/amd64` does not move to `linux/arm64` or to macOS Apple Silicon. The substrate is portable across *compatible* hosts, not across architectures. ### 6b. Export the **workspace** (lighter, often enough) If what you actually want to share is the project state (code, skills, changelog, sessions), export just the persistent bits — no need to ship the full VM: ```bash # from inside the sandbox smolvm sandbox exec --name tau-pocket -- sh -lc ' tar czf /tmp/pocket-export.tgz \ /workspace /root/.tau /etc/profile.d 2>/dev/null ' smolvm sandbox download tau-pocket /tmp/pocket-export.tgz ./pocket-export.tgz # on a fresh host: create a new sandbox and import smolvm sandbox create --name tau-pocket --os alpine smolvm sandbox file upload tau-pocket ./pocket-export.tgz /tmp/pocket-export.tgz smolvm sandbox exec --start --name tau-pocket -- sh -lc ' apk add --no-cache python3 py3-pip git && pip install --break-system-packages uv && uv tool install tau-ai tar xzf /tmp/pocket-export.tgz -C / cd /workspace && tau ' ``` This is what you want when the receiving host also has smolvm but a different kernel build, or when you only care about project state, not the installed system packages. ### 6c. Export the **image** (heaviest, most portable across builds) If your smolvm build has `pack`, you can produce a single self-contained binary that boots the configured state on any compatible host: ```bash # only if `smolvm pack` is available: smolvm pack create --from-vm tau-pocket -o tau-pocket.smolmachine ./tau-pocket.smolmachine run -- tau -p "hello" ``` This host does **not** have `pack`; treat 6a/6b as the working path until your build exposes it. If you upgrade smolvm specifically for this, verify with `smolvm pack --help` first. --- ## 7. Switching the agent to Pi (or another bundled agent) The substrate is the constant; the agent is pluggable. Two ways to switch. ### 7a. Launch a different agent in a parallel sandbox (recommended) ```bash # keep tau-pocket running for Tau work smolvm sandbox create --name pi-pocket --os alpine --network nat smolvm sandbox exec --start --name pi-pocket -- sh -lc ' apk add --no-cache nodejs npm git bash npm i -g @mariozechner/pi-coding-agent mkdir -p /workspace && cd /workspace && git init -q ' smolvm sandbox exec --name pi-pocket -- pi --help | head -5 smolvm sandbox snapshot create pi-pocket --snapshot-id pi-base ``` Pi keeps its config in `~/.pi/agent/` (skills at `~/.pi/agent/skills/`), so the import dance in §5 is the same shape but with `~/.pi` as the target. Two sandboxes, two agents, one mental model. ### 7b. Use the bundled launchers for one-off sessions smolvm ships a launcher per popular agent. These are pre-baked images with the CLI already installed — useful for quick interactive sessions, not for "the pocket" (which is a configured, skill-laden workspace). ```bash smolvm hermes start --name hermes-pocket # if you want Hermes for a session smolvm claude start --name claude-pocket smolvm codex start --name codex-pocket smolvm pi start --name pi-quick ``` These are the same `sandbox` family under the hood; the launchers just pre-fill the install step. Once the launcher has created a sandbox, you can `sandbox exec` / `sandbox snapshot` it like any other. > **Why not pin one agent forever?** The substrate is the constant. Tau is the primary, Pi is the alternative. Each gets its own sandbox, its own snapshot chain, and its own import of `/teacher-mode` (the skills are written in plain Markdown and travel verbatim; the only difference is where the agent looks for them — `~/.tau` vs `~/.pi/agent`). --- ## 8. The full "do it once" recipe For a clean machine, this is the whole thing top to bottom: ```bash # 0. one-time check smolvm doctor # 1. create + install tau smolvm sandbox create --name tau-pocket --os alpine --network nat smolvm sandbox exec --start --name tau-pocket -- sh -lc ' apk add --no-cache python3 py3-pip git curl bash pip install --break-system-packages uv uv tool install tau-ai mkdir -p /workspace && cd /workspace && git init -q ' # 2. import /teacher-mode + ~/.tau smolvm sandbox file upload tau-pocket /path/to/teacher-mode.tgz /workspace/teacher-mode.tgz smolvm sandbox exec --name tau-pocket -- sh -lc ' cd /workspace && tar xzf teacher-mode.tgz && rm teacher-mode.tgz ' # 3. snapshot the base smolvm sandbox snapshot create tau-pocket --snapshot-id tau-base # 4. iterate: session -> snapshot per milestone -> restore on rollback smolvm sandbox shell tau-pocket # run tau inside smolvm sandbox snapshot create tau-pocket --snapshot-id m1 smolvm sandbox snapshot restore m1 # if m2 broke smolvm sandbox snapshot list # the timeline ``` That's the project. The earlier "Decision Summary" tried to design all of this from scratch; the CLI was already there. --- ## 9. Troubleshooting | Symptom | Likely cause | Fix | |---|---|---| | `sandbox file upload` fails with no clear reason | sandbox not running | `smolvm sandbox start tau-pocket` first, or use `sandbox exec --start` | | Tau doesn't see `/teacher-mode` skills | wrong cwd when launching | `cd /workspace` before running `tau`; Tau reads `.tau/` and `.agents/` from cwd upward | | Packages missing after `stop` + `start` | written under `/tmp`, `/run`, or `/dev/shm` | rewrite under `/workspace` or `~/.tau/` — those are persistent | | `sandbox snapshot restore` says "not found" | passed the sandbox name instead of the snapshot id | use `sandbox snapshot list` to get the id, then `restore ` | | Two sandboxes both want to mount the same host dir | one-shot conflict | unmount the host dir from one of them, or use `file upload` instead | | `smolvm pack` / `smolvm machine` not present | this build of smolvm | use §6a/§6b (snapshot tree copy + workspace tarball) | | `smolvm doctor` shows swap / thp warnings | host tuning, not blocking | `sudo swapoff -a` and `echo never > /sys/kernel/mm/transparent_hugepage/enabled` if you want clean output | --- ## 10. What this tutorial deliberately does **not** do - **No custom substrate.** smolvm is the substrate. Adding a second sandbox technology re-introduces the problem smolvm already solves. - **No custom snapshot file format.** `smolvm sandbox snapshot create` *is* the snapshot model. The earlier doc's Phase 1/2 ("Build the Base Machine", "Snapshot & Changelog Wiring") is two CLI calls. - **No hand-rolled changelog daemon.** `smolvm sandbox snapshot list` is the host-side log. `CHANGELOG.md` inside `/workspace` is the why-we-care log. Both are optional, both are trivial. - **No agent lock-in.** Tau is the primary; Pi, Hermes, Claude, Codex, and OpenClaw are all reachable from the same smolvm install. The pocket is the *workspace*, not the *agent*. - **No architecture-cross-portability claim.** smolvm snapshots are architecture-bound. A `linux/amd64` snapshot is not a `linux/arm64` or macOS binary. The substrate is portable across *compatible* hosts, not all hosts.