A box is one isolated Linux desktop. It has one or more screens, a browser, a filesystem, and a shell. An agent drives it, and a person can watch it or take control.
In the Rust library, a box is a Computer. In the CLI, REST API, and MCP tools, a box has an ID such as box_9cf78792…, and each command or tool takes that ID.
Spec and placement
The server describes a box with two parts:
| Part | Describes | Examples |
|---|---|---|
| Spec | The desktop | Screen size, number of screens, applications, packages, features, network policy, viewer access |
| Placement | Where the box runs, and for how long | Runtime, memory, CPUs, lifetime, idle timeout, browser profile |
They are separate because two desktops that differ only in a memory limit are the same desktop. Each spec has a spec_digest, and holm box shows it.
{
"spec": {
"desktop": { "width": 1280, "height": 800, "packages": ["jq"] },
"policy": { "network": true }
},
"placement": { "runtime": "docker", "memory": "2g", "expires_after_secs": 3600 }
}The CLI flags of holm new and the parameters of launch_box fill in the same two parts. holm new --spec box.json reads a spec from a file. The server refuses unknown keys in a spec.
Screens
A box has one screen unless the spec asks for more, up to 8. Each screen has:
- Its own display, at the size of the spec.
- Its own Chromium, with a separate browser profile.
- Its own viewer URL.
Coordinates are device pixels on one screen. (0, 0) is the top-left corner.
States
| State | Meaning |
|---|---|
Ready |
The box accepts actions. |
Paused |
The box is frozen. It keeps its memory, ports, and open windows, and uses no CPU. Calls to it wait until it resumes. They do not fail. |
Stopped |
All processes are stopped. The files stay. |
Unreachable |
The runtime of the box is missing or unavailable. The server keeps the record, and the box comes back when the runtime does. The reason field says why. |
Gone |
The box was removed, expired, or is no longer on its runtime. |
pause stop
Ready ──────────► Paused Ready ──────────► Stopped
▲ │ ▲ │
└──── resume ─────┘ └───── resume ─────┘
(new desktop, new viewer URL)| Operation | CLI | MCP | Rust |
|---|---|---|---|
| Pause | holm pause |
pause_box |
pause() |
| Stop | holm stop |
stop_box |
stop() |
| Resume | holm resume |
resume_box |
resume(), or start() after a stop |
| Remove | holm rm |
remove_box |
shutdown() |
Use pause when you will come back soon: the desktop comes back as it was. Use stop to release the memory: a resumed box starts a new desktop with nothing open. Remove deletes the files.
Not all runtimes support pause and stop. See Runtimes.
Lifetime
Each box has a deadline. A box lives one hour unless it asks for a different time or its runtime has a different default.
| Limit | CLI | REST placement | MCP |
|---|---|---|---|
| Fixed lifetime | --ttl MINUTES |
expires_after_secs |
ttl_minutes |
| Idle timeout | --idle MINUTES |
idle_timeout_secs |
idle_minutes |
| Keep files when stopped | --persistent |
persistent |
persistent |
The server refuses values under 60 seconds. The clock starts when the server creates the box, not when the box is ready.
holmd removes expired boxes every 30 seconds (HOLM_SERVER_REAP_SECS). It also forgets boxes that the runtime no longer has. The trace records each removal as gone, with the reason.
In the Rust library with no server, a box is removed when its Computer is dropped, unless you set keep_on_drop(true).
History
holmd records a trace for each box: actions, frames, commands, lifecycle changes, file transfers, and changes of control between the agent and a person.
holm trace "$BOX"
curl -s "localhost:8080/v1/boxes/$BOX/trace?after=12&limit=100"A trace records that a file write or clipboard change occurred, but not the bytes.
With the default memory store, traces stop when the server stops. Use a durable storage backend to keep them across a restart.
Forks
A fork makes a new box from the same spec, on the same runtime, and does the recorded actions again.
NEW_BOX=$(holm fork "$BOX")
holm fork "$BOX" --up-to 40A fork does not copy memory or disk. The result can be different from the original, because:
- The page can change between the two runs.
- Timing and network speed can change.
- File writes and clipboard changes are skipped, because the trace does not keep their bytes. The result lists them under
skipped.
Actions that the original box refused are also skipped. A fork stops at the first failure and names the step that failed. It stops after three minutes.
Restart
A box runs in its runtime, not in the server process. When holmd starts, it takes back each box that it recorded. Then it scans each runtime for boxes labeled by an earlier server, so it can also find a box that the store lost. A box that it takes back after a restart starts a new trace, marked adopted, unless the store is durable.
What is inside a box
The default X11 image is based on debian:bookworm-slim:
| Component | Purpose |
|---|---|
| Xvfb, fluxbox | The virtual display and window manager |
| Chromium | The browser, with a separate profile for each screen |
| x11vnc, websockify, noVNC | The viewer in a browser |
| xdotool, wmctrl | Pointer, keyboard, and window control |
| ImageMagick | Screenshots |
| xclip | Clipboard and primary selection |
socat, holm-devtools-bridge |
The Chrome DevTools bridge on port 9223. Host runtimes use socat. On E2B and Vercel, a bridge that accepts only requests with the box's secret. |
| xterm | A terminal |
| Input guard | Blocks agent input while a person has control |
The Wayland image uses headless sway, wayvnc, and grim instead. See Runtimes.
Features and applications add more packages. A box gets the default features unless its spec lists its own:
| Feature | Adds | Default |
|---|---|---|
wide_fonts |
Noto CJK and color emoji fonts | Yes |
video |
ffmpeg | Yes |
dock |
tint2 | Yes |
accessibility |
AT-SPI, for reading native widgets | Yes |
x11_apps |
XWayland | On Wayland |
audio |
PulseAudio | No |
A spec that lists features gets exactly that list, and "features": [] is the bare desktop. The default set and the same features listed by name are one spec, so they use one image.
The image tag contains a hash of the image source and the added packages, and the CPU architecture. A change to the source or the packages makes a new image, so a box never uses an old image by mistake.