For agents and programs
A native instance takes jobs on the guest's root shell, whatever its app is, and a run can be bounded, stopped from outside and recorded. For many machines at once, see Orchestration.
Commands and files: sh and cp
Pass the command to snowglobe sh as one quoted argument; its flags would otherwise be read as
snowglobe's own.
snowglobe run dist/python --name box --detach --runtime native snowglobe sh box 'python3 -c "print(6*7)"' # its output, and its exit status as ours snowglobe sh box --json 'ls /nope' snowglobe cp ./project box:/root/project # files and directories, either way snowglobe cp box:/root/project/out ./out
Each command runs with the app's environment in a fresh working directory; files elsewhere persist for
the instance's life. Out of time (300 s, or --timeout S), the command and everything it
started are killed (exit 124). What the guest sends back through cp can't write outside the
destination.
A fresh machine per command: --fork
--fork makes a detached native instance a template. It restores once, and every
snowglobe sh on it runs in a copy of that machine, forked in milliseconds and sharing its
memory. Nothing one command does is seen by the next, so sh is all a fork instance takes:
cp, exec, attach and mcp need a machine that lasts.
snowglobe run dist/python --name tpl --detach --runtime native --fork
snowglobe sh tpl 'python3 untrusted.py' # many at once, each on its own copy
snowglobe stop tpl
MCP
snowglobe mcp <site> serves a site to an agent as an MCP server over stdio: an isolated
Linux machine with run_command, write_file and read_file tools. It
starts a native instance and stops it when the agent goes (--keep leaves it).
claude mcp add sandbox -- snowglobe mcp https://filipecabaco.github.io/snowglobe/python/
snowglobe mcp --daemon serves the daemon instead: an agent lists,
builds and adds templates, runs commands on fresh forks or on machines it brings up, writes and reads their
files, submits fire-and-forget runs and reads their results, saves snapshots, puts services behind ports,
lists and checks ports, diffs and applies fleet files, and reads the daemon's status and events. SNOWGLOBE_DAEMON points it at a daemon on another host.
claude mcp add snowglobe -- snowglobe mcp --daemon
Limits, stopping and logs
| Flag | What it does | Exit |
|---|---|---|
--max-runtime 30m | ends the run after this long, whatever it is doing | 124 |
--stop-file PATH | ends the run once PATH exists: create it from anywhere | 143 |
--log-dir DIR | durable console.log, serial.log and events.jsonl |
Either way the job running in the guest is killed first, and a stop event saying why goes to
the log. Logs are appended as things happen, so they survive a crash of the run, and rotate at 8 MiB. A
named native instance logs to snowglobe/logs/<name> in your cache directory unless
--log-dir says otherwise.
console.log: the app's terminal, byte for byte.serial.log: the guest's serial line, with the kernel's own messages (an OOM kill, a panic) and the job shell.events.jsonl: one JSON object a line, each synced to disk:start,restored,job_start(the command, its environment variables' names but never their values, the timeout),job_end(status,timed_out,killed, milliseconds, the first 64 KiB of output) andstop(itsreason:stop-file,max-runtime,guest-stoppedorend, and the exit status).
Kills reach everything a job started. A job runs in a session of its own in the guest;
when it runs out of time or the instance stops, it gets TERM, then KILL after 3 s, including whatever it
put in the background. A process that makes a session of its own (a daemon that setsids) is
out of reach, as it would be anywhere.
A job's result is its own word. A job's exit status comes back on a line marked with a
one-time value the host types only to the job's launcher, so a job that merely prints a fake result isn't
believed. A job runs as root in the guest, though: one determined to can still read that value from the
launcher's memory and forge its own status, output and out/ files. That reaches only its own
result. The host still enforces the time limit, and if a job leaves the guest's shell busy, later jobs
fail with "the guest's shell is not answering" rather than run inside it.