boxer

Scopes and packs

How boxer decides which sandbox a command runs in, and how it caches built environments.

TermWhat it isWhat it decides
ScopeThe identity of one sandboxWhich sandbox a command runs in
PackA cached snapshot of a built environmentWhether a new sandbox has to be built

An unexpected new sandbox, a missing sandbox, or a setup step that ran again usually comes from one of these two.

Scope

A scope is a name derived from the working directory. For finer isolation, boxer also uses the agent's session or subagent id.

sb-7e1852e4a3c3

The name is sb- plus twelve hex characters of a SHA-256. It is also the sandbox's name on the backend. isolation sets what goes into the hash:

isolationHashedOne VM per
repothe git common directoryrepository, all worktrees together
worktree (default)the worktree's root pathworktree
sessionthe worktree root and the session idagent session
subagentthe worktree root, the session id and the agent idsubagent

worktree is the default because orchestrators already create one worktree per task. Parallel agents get separate sandboxes with no extra setup. boxer gc reclaims a sandbox once its worktree is gone.

Degradation

session and subagent need an id from the harness. Not every harness supplies one, and several harnesses may read one configuration.

isolation     = "session"
on_missing_id = "degrade"    # degrade | fail
on_missing_idWhen the id is missing
degrade (default)Fall back one step (subagent to session, session to worktree) and print a warning.
failRefuse to run.

boxer doctor shows whether resolution degraded.

Placement is part of the key

Running the harness inside the guest (inside mode) and outside it produces two different scopes for the same worktree. One sandbox carries the harness and the other does not.

Packs

A pack is a snapshot of a guest after image_setup has run, taken with smolvm pack create. The next sandbox with the same environment starts from the snapshot and skips the rebuild.

Packs exist on smolvm only. On other backends, image_setup runs once per sandbox.

The key

A pack is keyed on the image plus a hash of everything that shaped it:

KeyWhy
image_setupthe commands that changed the guest
envthe variables baked into it
mountswhat else is attached
start, readywhat a VM from this pack is expected to be running

Change any of them and the next sandbox builds a new pack.

setup is not in the key. It prepares the worktree, and no pack carries the worktree.

image_setup versus setup

Put a command that changes the guest in image_setup. Put a command that changes the worktree in setup.

image_setup = ["apt-get install -y libpq-dev"]   # changes the image, so it is packed
setup       = ["npm ci"]                          # changes the worktree, so it is not

A pack carries the guest's filesystem. The worktree is mounted from the host, so it is not in the snapshot.

Do not put npm install in image_setup. The second worktree starts from a pack that marks the install done, with no node_modules in its worktree. Put dependency installs in setup.

See Environment for both lists and [prep].

Cache management

Packs are hundreds of megabytes each. An environment that changes often leaves one pack per version. Two settings limit the cache:

packs_keep_last = 5      # bound by count, not only by age
min_free_gb     = 5      # below this much free disk, skip packing

Below min_free_gb, boxer does not write a pack and says so. The next worktree runs its setup again.

boxer up --rebuild     # drop this environment's pack and build it again
boxer gc               # idle sandboxes, sandboxes whose worktree is gone, unused packs
boxer gc --all         # also every stopped sandbox and every unreferenced pack
boxer doctor           # what boxer currently costs on disk

Use --rebuild when something outside the key changed, such as a mutable base image tag or a dependency resolved at install time.

Packs you name

boxer manages the packs above as a cache. You manage a named pack yourself:

boxer pack save base     # snapshot this sandbox as it stands
boxer pack ls
boxer pack use base      # recreate this scope's sandbox from it
boxer pack rm base

Use one for a guest that took a long time to prepare, such as a toolchain assembled by hand. Or use one as a base that several worktrees start from.

gc never removes a named pack. It clears the automatic cache by age and by packs_keep_last.

A named pack is a filesystem snapshot. It keeps installed files. It does not keep running processes. smolvm cannot checkpoint RAM on a machine with a host mount, and boxer always mounts the worktree. For a live copy, use a fork.

Read the current state

boxer status     # this scope: state, image, ports, mount
boxer ls         # every sandbox, with the worktree each is keyed to
boxer doctor     # the resolved scope, and why it is that one

When a sandbox is not the one you expected, run boxer doctor. It prints the isolation in force, the resolved scope, and whether resolution degraded.

Names

A scope key is a hash, which makes it a legal machine name whatever the worktree path contains. boxer also derives two words from the same hash and prints them next to it:

NAME               SCOPE            STATE     ISOLATION  WORKTREE
swift-crab         sb-7e1852e4a3c3  running   worktree   /Users/me/src/demo
  • The name is computed from the key and never stored. The same worktree always gets the same words.
  • Two keys can share a name. The key is always printed next to it.
  • --scope accepts either form.

On this page