Use it
Packages and longer runs
The default box has only Python's standard library and two seconds. When a job needs numpy, pandas, matplotlib or your own packages, or more time, you ask for it on that run, and the receipt shows exactly what you asked for.
Images#
stdlib (default) | data | Your own | |
|---|---|---|---|
| What's inside | Python 3.13 and its standard library | numpy 2.5.4, pandas 3.0.6, matplotlib 3.11.2 and their dependencies | Exactly the packages in your lock file |
| Built by | Pinned registry digest | pybox setup --yes --with data | pybox image build NAME --lock FILE |
| Memory · processes · CPU | 128 MiB · 32 · half a core | 512 MiB · 64 · one core | |
| Network, Mac files, user, read-only system | Identical: no network, no Mac folders, a non-root user with every special permission removed, a read-only system | ||
Every image is built from the same pinned Python image. Packages are installed once, inside the VM, by a throwaway build container that has the network. pip may install only the exact wheels named, each matched to its SHA-256 (--require-hashes --only-binary=:all:), so no package code runs during the build. Runs never have the network.
Before every run, PyBox checks that the box uses the image ID recorded when the image was built, and that this record was made from the current recipe: the one shipped with PyBox for data, or your saved lock file for your own images. If they differ, the run stops and tells you how to rebuild.
numpy, pandas and matplotlib#
$ pybox setup --yes --with data $ pybox exec report.py --image data --input sales.csv --output results/
Building takes about half a minute. matplotlib draws without a display, and its font cache lives in the box's scratch space, so plt.savefig("/tmp/output/chart.png") works and the chart comes back with --output.
Exit code 0 (ended by itself) Memory peak 73.9 MiB of 512 MiB Files out 1 file(s), 17.2 KiB → chart/
Your own packages#
Write the packages you want, then let uv pin them, with hashes, for the box's platform:
$ printf 'tabulate\nrich\npyyaml\n' > requirements.in $ uv pip compile requirements.in --generate-hashes \ --python-version 3.13 --python-platform aarch64-manylinux_2_28 -o tools.lock $ pybox vm start $ pybox image build tools --lock tools.lock $ pybox exec job.py --image tools
A lock file may contain only exact pins with hashes (name==version --hash=sha256:…), comments and line continuations. Options, index URLs, links, local paths and unpinned names are refused before the VM is touched. Names are 2–31 lowercase letters, digits or -. pybox image list shows every image and whether it still matches its recipe; pybox image rm NAME removes one.
Your packages run inside your box. They can't reach your Mac or the network, but a package can include code that Python runs at start-up, before PyBox's helper. That doesn't change what's trusted: the receipt's engine and kernel sections come from outside the box. But the receipt notes it for your own images.
Longer runs#
--timeout accepts up to 30 seconds, in any image. The default stays 2 s, so anything longer is a deliberate choice recorded on the receipt.
- The backup timer inside the box is always one second longer than your limit.
- The kernel watcher samples every 0.05 s for the first second, then spreads its remaining samples over the rest of the run (64 in all).
- A box still expires 120 seconds after it's labelled, which covers the longest run plus cleanup.
In a recorded test, a program that slept for 60 seconds with --timeout 30 was stopped by PyBox at 30.01 s and its box was removed. A program that exited with code 3 just before a 10-second deadline kept its own exit code.
For AI agents, the person configuring the server picks the ceiling: pybox mcp --max-timeout 30 --images stdlib,data,tools. An agent can only ask for less. See MCP for AI agents.
Final readings#
The kernel watcher reads live counters, and a program that ends between two readings can take its peak with it. In early testing, a program that allocated 300 MiB showed a peak of only 147 MiB. So for opt-in runs (any image other than stdlib, files, or a limit over 2 s) the box's helper waits briefly after the program finishes: 0.12 s, or one sampling gap on longer runs, and never past your deadline. That gives the watcher a reading after the program's work. The same 300 MiB program now shows 327.6 MiB.
The default box doesn't use the helper, so it keeps exactly the behaviour behind the restriction and resource evidence. Its receipt still says the final counters are unknown.
Limits. The helper runs as the program's own user, so a program can skip the wait. The receipt then shows the highest value seen, as before, and still calls the final value unknown. 30 seconds is a practical limit for this VM, not a hard real-time guarantee.