Sandboxes
A sandbox is where a harness runs. A run file sets it in [sandbox], and --sandbox overrides it. The default is Docker.
kind | Where | Notes |
|---|---|---|
docker | A container on this machine | The default. No network, no capabilities, your own uid, bounded memory, CPU and processes |
local | A process on this host | Recorded as such. Nothing is isolated beyond the cleared environment |
modal | A Modal Sandbox | [sandbox] image from a registry, pinned by digest |
fly | A Fly.io Machine | Always has a network, so it needs network = "open" |
sprites | A Fly Sprite | Fly's own base image (no image); the network can be closed except for egress domains |
hemra doctor lists every sandbox, whether it is available, and the capabilities it enforces as configured.
Docker
Program harnesses need an image:
[sandbox]
image = "alpine:3.20"
mounts = [".:/harness"] # read-only unless :rw; relative to the run file
# network = "bridge" # to let the harness reach a model API
# memory = "4g"
# cpus = 2
# pids = 512
# read_only = true
A driver harness (cli) on a Harbor task runs in the task's own image instead, built from its environment/Dockerfile, with the driver and the agent mounted read-only. After the attempt, the task's verifier runs in a separate container on a snapshot of the agent's container, with no network, and its reward becomes the trial's score. The image is pinned to its ID in every attempt's manifest, every container is labelled as HEMRA's, and each is removed before artifacts are read.
hemra doctor --prune removes containers that a killed hemra left behind.
Managed sandboxes
Modal, Fly.io Machines and Fly Sprites are reached through a small bridge program, built from the repository with Node.js (npm ci && npm run build) and found with --sandbox-bin, its HEMRA_SANDBOX_<KIND>_BIN variable, or hemra-sandbox-<kind> on PATH. HEMRA itself never links a vendor SDK.
| Sandbox | Credentials | Run file example |
|---|---|---|
| Modal | MODAL_TOKEN_ID and MODAL_TOKEN_SECRET, or modal token new | modal-run.toml |
| Fly.io Machines | FLY_API_TOKEN, a Fly app (app = "..."), and flyctl on PATH | fly-run.toml |
| Fly Sprites | SPRITES_TOKEN | sprites-run.toml |
hemra run examples/harness-sh/modal-run.toml --harness-dir examples/harness-sh \
--sandbox-bin packages/sandboxes/modal/dist/main.js
mounts copies each host directory into the sandbox before the harness starts. A problem with the provider, a lost stream or an artifact copy that failed is an infrastructure failure, never a failed task.
Requiring protections
--require (or require = [...] in a run file) refuses the run, before anything is recorded, unless the sandbox as configured offers every capability named:
hemra run run.toml --require network_block,resource_limit
The ones sandboxes offer today are network_block, egress_allowlist, resource_limit, filesystem_mount, custom_image, linux_container, linux_vm, stream_stdio, stream_logs and graceful_cancel; hemra doctor shows which. A sandbox that cannot enforce a setting refuses it rather than ignoring it: a Fly Machine refuses network = "none", and only Sprites take egress.
Verifiers
Task verifiers get no network and no host variables, unless the run allows them. A verifier that calls a model needs both:
[verifier]
network = "bridge"
env = ["OPENAI_API_KEY"] # names the task's [verifier.env] may read
or --verifier-network bridge --verifier-env OPENAI_API_KEY. Names are recorded with the run; values never are.