Scopes and packs
How boxer decides which sandbox a command runs in, and how it caches built environments.
| Term | What it is | What it decides |
|---|---|---|
| Scope | The identity of one sandbox | Which sandbox a command runs in |
| Pack | A cached snapshot of a built environment | Whether 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-7e1852e4a3c3The 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:
isolation | Hashed | One VM per |
|---|---|---|
repo | the git common directory | repository, all worktrees together |
worktree (default) | the worktree's root path | worktree |
session | the worktree root and the session id | agent session |
subagent | the worktree root, the session id and the agent id | subagent |
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 | failon_missing_id | When the id is missing |
|---|---|
degrade (default) | Fall back one step (subagent to session, session to worktree) and print a warning. |
fail | Refuse 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:
| Key | Why |
|---|---|
image_setup | the commands that changed the guest |
env | the variables baked into it |
mounts | what else is attached |
start, ready | what 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 notA 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 packingBelow 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 diskUse --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 baseUse 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 oneWhen 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.
--scopeaccepts either form.