Skip to main content
Sandboxes run untrusted or short-lived code in an isolated microVM and capture its output, useful for agent tool-calls, REPLs, and one-off jobs. They are provided by the iii-sandbox worker:
iii worker add iii-sandbox
This page is a quick tour of the sandbox worker. For the authoritative documentation, see the iii-sandbox worker docs.
You drive sandboxes by invoking the worker’s sandbox::* triggers, the same way you call any function (see Triggering functions). Images are catalog names such as python or node, not arbitrary OCI references. The examples below capture the new sandbox’s id with jq and stop the sandbox when done so nothing keeps running.

One-shot run

sandbox::run boots a VM, runs a snippet, captures its output, and stops the VM in a single call, so there is nothing to clean up.
# run a snippet and print just its stdout (-> 4)
iii trigger sandbox::run image=python lang=python code='print(2+2)' | jq -r .stdout
lang accepts node, python, shell, or an interpreter path. Pass keep_sandbox=true to leave the VM running afterwards (then stop it yourself with sandbox::stop).

Lifecycle

For multi-step work, create a sandbox, operate on it with its id, then stop it. Most triggers take flat key=value arguments; only nested payloads need --json.
# boot a sandbox and capture its id
SB=$(iii trigger sandbox::create image=python | jq -r .sandbox_id)

# run a command and print its stdout
iii trigger sandbox::exec sandbox_id=$SB cmd='python --version' | jq -r .stdout

# list the active sandbox ids
iii trigger sandbox::list | jq -r '.[].sandbox_id'

# stop when done
iii trigger sandbox::stop sandbox_id=$SB
A whitespace-containing cmd is split into a command and its arguments. It is not a shell, so it does not expand variables or chain commands; use sandbox::run with lang=shell for that.

Catalog

sandbox::catalog::list reports the images this engine can boot (presets plus any operator-registered images). Call it when you do not already know what is available. It does not boot a sandbox, so there is nothing to stop.
# list bootable image names (e.g. python, node)
iii trigger sandbox::catalog::list | jq -r '.images[].name'

Filesystem

The sandbox::fs::* triggers manipulate files inside a running sandbox. Each takes a sandbox_id plus operation-specific fields.
# boot a sandbox and capture its id
SB=$(iii trigger sandbox::create image=python | jq -r .sandbox_id)

# reuse a directory and file path across the calls
D=/work; F=$D/main.py

# create the directory
iii trigger sandbox::fs::mkdir sandbox_id=$SB path=$D parents=true

# write a file
iii trigger sandbox::fs::write sandbox_id=$SB path=$F content='print(1)'

# list the directory
iii trigger sandbox::fs::ls sandbox_id=$SB path=$D | jq -r '.entries[].name'

# stat the file
iii trigger sandbox::fs::stat sandbox_id=$SB path=$F

# read the file contents
iii trigger sandbox::fs::read sandbox_id=$SB path=$F | jq -r .body

# change permissions
iii trigger sandbox::fs::chmod sandbox_id=$SB path=$F mode=0644

# search for a pattern
iii trigger sandbox::fs::grep sandbox_id=$SB path=$D pattern=print

# find and replace across files
iii trigger sandbox::fs::sed sandbox_id=$SB path=$D pattern=print replacement=log

# move the file
iii trigger sandbox::fs::mv sandbox_id=$SB src=$F dst=$D/app.py

# remove the file
iii trigger sandbox::fs::rm sandbox_id=$SB path=$D/app.py

# stop the sandbox
iii trigger sandbox::stop sandbox_id=$SB

Moving files in and out

To copy a file between the host and a running sandbox, use the iii worker sandbox CLI:
# boot a sandbox and capture its id
SB=$(iii trigger sandbox::create image=python | jq -r .sandbox_id)

# make a file to send
echo 'hello from host' > ./local.txt

# host -> sandbox
iii worker sandbox upload "$SB" ./local.txt /remote.txt

# remove the local copy
rm ./local.txt

# retrieve it from the sandbox
iii worker sandbox download "$SB" /remote.txt ./remote.txt

# -> hello from host
cat ./remote.txt

# stop the sandbox
iii trigger sandbox::stop sandbox_id=$SB