Sandboxes#
A sandbox is an isolated container that runs an agent under policy. It has its own filesystem, its own locked-down network, its own identity in the platform, and its own line in the audit trail.
This is where agents actually run.
Create a sandbox#
curl -fsS -X POST \
-H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
-H "Content-Type: application/json" \
https://agents.example.com/v1/projects/$PROJECT_ID/sandboxes \
-d '{
"image": "ghcr.io/acme/agent:1.2",
"command": "python agent.py",
"cpu": "500m",
"memory": "2Gi",
"policyIds": ["'"$POLICY_ID"'"]
}'
Create a sandbox in the production project from the image
ghcr.io/acme/agent:1.2. Run "python agent.py" with 500m CPU and
2Gi memory, and attach the agent-base policy.
Requires an AI tool connected to the MCP endpoint.
nexusctl sandbox create --project production \
--image ghcr.io/acme/agent:1.2 \
--command "python agent.py" \
--cpu 500m \
--memory 2Gi \
--policy agent-base
image, command, cpu, and memory are required. The command runs through sh -c inside the container.
cpu and memory are Kubernetes quantities and are capped by the install's ceilings (sandbox.k8s.cpu and sandbox.k8s.memory). A request above the ceiling is rejected rather than silently clipped.
The sandbox starts as soon as it is created. It gets a slug — an immutable, URL-safe label — which is what you use to refer to it everywhere else, and what its exposed ports are published under.
Anything with nested structure — the embedded policy, embedded credentials — has no flag form and comes from a spec file instead:
nexusctl sandbox create --project production -f sandbox.yaml
Attach policies#
A sandbox is its own principal. It does not inherit a person's permissions, and there is no binding to set up: attach policies directly.
Create that sandbox again in production, but attach both the
agent-base and github-readonly policies.
nexusctl sandbox create --project production \
--image ghcr.io/acme/agent:1.2 \
--command "python agent.py" \
--cpu 500m --memory 2Gi \
--policy agent-base \
--policy github-readonly
--policy is repeatable and takes a name or a UUID. Each policy must be an org policy or one that lives in this project; the server enforces that.
With no policy attached, the sandbox runs with the network denied by default and no credentials — it can execute code, and reach nothing.
See the effective policy the platform resolved for a running sandbox:
SB=https://agents.example.com/v1/projects/$PROJECT_ID/sandboxes/$SANDBOX_ID
curl -fsS -H "Authorization: Bearer $LENS_AGENTS_TOKEN" $SB/effective-policy
Show me the effective policy for the nightly-refactor sandbox in
the production project. What domains can it actually reach?
List and inspect#
curl -fsS -H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
https://agents.example.com/v1/projects/$PROJECT_ID/sandboxes
List the sandboxes in the production project. Which ones are not
running, and why?
nexusctl sandbox list --project production
nexusctl sandbox get nightly-refactor --project production
nexusctl sandbox describe nightly-refactor --project production
describe is the one to reach for when something is wrong: it joins the sandbox's configuration, state, health snapshot, exposed ports, attached policies, and revisions into a single view.
Lifecycle#
stateDiagram-v2
[*] --> creating
creating --> starting
starting --> started
started --> stopping: stop
stopping --> stopped
stopped --> starting: start
started --> destroying: delete
stopped --> destroying: delete
destroying --> [*]
creating --> error
starting --> error
error --> destroying: delete
The platform tracks two fields: state is what the sandbox is observed to be doing, and desiredState is what an API caller last asked for. They differ while a transition is in flight, and errorReason explains a sandbox that landed in error.
| State | Meaning |
|---|---|
creating |
Resources are being provisioned. |
starting |
The pod exists; the supervisor has not reported ready. |
started |
Running, and reachable by exec. |
stopping |
A stop was requested. |
stopped |
Not running. Volumes and configuration survive. |
destroying |
Deletion is in progress. |
error |
Provisioning or startup failed. errorReason carries the detail. |
Stop and start#
curl -fsS -X POST -H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
https://agents.example.com/v1/projects/$PROJECT_ID/sandboxes/$SANDBOX_ID/stop
curl -fsS -X POST -H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
https://agents.example.com/v1/projects/$PROJECT_ID/sandboxes/$SANDBOX_ID/start
Stop the nightly-refactor sandbox in the production project.
nexusctl sandbox stop nightly-refactor --project production
nexusctl sandbox start nightly-refactor --project production
Both are idempotent. Stopping releases compute but keeps the sandbox's identity, configuration, and persistent volume — starting it again resumes on the same disk.
Delete#
curl -fsS -X DELETE -H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
https://agents.example.com/v1/projects/$PROJECT_ID/sandboxes/$SANDBOX_ID
Delete the nightly-refactor sandbox in the production project.
nexusctl sandbox delete nightly-refactor --project production
Deletion is asynchronous: the call returns immediately and cleanup follows. Persistent volumes are deleted with the sandbox.
Restart#
curl -fsS -X POST -H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
https://agents.example.com/v1/projects/$PROJECT_ID/sandboxes/$SANDBOX_ID/restart
Restart the nightly-refactor sandbox in the production project and
tell me when it is back up.
A restart tears down the runtime and provisions the active revision again, even on a running sandbox. On a stopped sandbox it is the same as a start, and it recovers a sandbox in error. It needs project admin. While the sandbox is in transition the restart is refused, and a restart that races another restart gets 409.
The call returns before teardown begins, so the state it returns is the old one. Keep the health.startedAt from the response, then poll the sandbox until state is started and health.startedAt has a new value. state error with an errorReason means the restart failed.
Stop and delete idle sandboxes#
curl -fsS -X POST \
-H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
-H "Content-Type: application/json" \
https://agents.example.com/v1/projects/$PROJECT_ID/sandboxes \
-d '{
"image": "ghcr.io/acme/agent:1.2",
"command": "python agent.py",
"cpu": "500m",
"memory": "2Gi",
"autoStopMinutes": 30,
"autoDeleteMinutes": 1440,
"autoStart": true
}'
Create a sandbox in production from ghcr.io/acme/agent:1.2 running
"python agent.py" with 500m CPU and 2Gi memory. Stop it after 30
idle minutes, start it again on demand, and delete it a day after
it stops.
nexusctl sandbox create --project production \
--image ghcr.io/acme/agent:1.2 \
--command "python agent.py" \
--cpu 500m --memory 2Gi \
--auto-stop 30m \
--auto-delete 1d \
--auto-start
Timers are off unless you set them. The CLI and spec files take durations in m, h, or d; the API takes whole minutes. Both timers go up to 30 days.
- Auto-stop stops the sandbox after that long with no activity. Activity is an open exec or terminal session, or a request to a
privateexposed port. Egress and requests to apublicport do not count. - Auto-delete deletes the sandbox, with its volume, that long after it was asked to stop, whoever stopped it.
0deletes it as soon as it has stopped. A sandbox inerroris kept, so you can see what went wrong. - Start on demand (
--auto-start) starts a sandbox that auto-stop stopped when an exec, terminal, or private-port request reaches it. The request waits for the start, up to a minute; after that an exec or private-port request gets 503 withRetry-Afterand a terminal shows an error, and the start continues. A sandbox that a person stopped stays stopped.
Each timer acts within about a minute of its due time. sandbox describe shows when, as Auto-stop due and Auto-delete due, and the API returns autoStopAt and autoDeleteAt. Each timed stop, delete, and on-demand start is written to the audit trail as sandbox.auto_stop, sandbox.auto_delete, or sandbox.auto_start.
In the web UI, set the timers under Auto-stop, Auto-delete, and Start on demand when you create or edit a sandbox.
Change or clear the timers on an existing sandbox. This does not restart it:
curl -fsS -X PATCH \
-H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
-H "Content-Type: application/json" \
https://agents.example.com/v1/projects/$PROJECT_ID/sandboxes/$SANDBOX_ID \
-d '{"autoStopMinutes": 120, "autoDeleteMinutes": null}'
Make the nightly-refactor sandbox in production stop after two idle
hours, and turn off its auto-delete.
nexusctl sandbox update nightly-refactor --project production \
--auto-stop 2h --clear-auto-delete
null in the API, or --clear-auto-stop, --clear-auto-delete, and --clear-auto-start in the CLI, turn a setting off. A new auto-delete on a sandbox that is already stopped counts from the change.
Update#
nexusctl sandbox update changes image, command, resources, environment, health check, or exposed ports. Each change creates a new revision and restarts the sandbox on it. A change to the timers does not restart it.
curl -fsS -X PATCH \
-H "Authorization: Bearer $LENS_AGENTS_TOKEN" \
-H "Content-Type: application/json" \
https://agents.example.com/v1/projects/$PROJECT_ID/sandboxes/$SANDBOX_ID \
-d '{"image": "ghcr.io/acme/agent:1.3"}'
Upgrade the nightly-refactor sandbox in production to image
ghcr.io/acme/agent:1.3.
nexusctl sandbox update nightly-refactor --project production \
--image ghcr.io/acme/agent:1.3
Related#
-
Declare the whole sandbox in a file, including the embedded policy and credentials that have no flag form.
-
Run commands inside a sandbox, open an interactive shell, run a one-shot command in a new sandbox, and copy files in and out.
-
Publish an agent's web UI or API at a stable URL, with or without platform authentication.
-
Persistent volumes, HTTP health probes, revisions, and rollback.
-
What the boundary is made of, and why an agent cannot talk its way past it.
-
Cap what a sandbox can spend on inference, and see where the money went.