Verbs

agentproto sandbox

agentproto sandbox list   [--no-probe] [--reconcile] [--json]
agentproto sandbox attach <provider> <sandboxId> [--config-json <json>] [--keep-alive] [--json]
agentproto sandbox rm     <sandboxId|label|id-prefix> [--box] [--yes] [--json]
agentproto sandbox gc     [--apply] [--pause] [--json]

Browse the daemon's sandbox ledger (every box the daemon has booted, reconnected to, or paused — stored in ~/.agentproto/sandboxes.json), and connect to already-existing boxes without tearing them down.

Provider credentials are read from ~/.agentproto/sandbox-creds/<slug>.json (written by the setup_sandbox_provider MCP tool); provider API keys (e.g. BOX_API_KEY, E2B_API_KEY) must additionally be set in this process's own environment.

Subverbs

list

agentproto sandbox list
agentproto sandbox list --json
agentproto sandbox list --no-probe
agentproto sandbox list --json --reconcile

Prints the sandbox ledger — every box the daemon has booted, reconnected to, paused, or stopped — with its current state, idle-expiry, and the origin session that spawned it.

The table includes a LIVE column showing the result of a per-row provider liveness probe (yes / no / ? probe-errored / — provider can't probe). The LIVE column is the only reliable signal that a box still exists on its provider: the STATE column reflects what the daemon last did and may lag reality (a provider-reaped box still shows paused until probed). Probes are network calls; pass --no-probe to skip them.

This same probe pass is a reconcile: any row the provider confirms gone is flipped to state: "gone" in the ledger (never deleted, and the box itself is never touched — this is read-only against the provider). Table mode reconciles by default; --json does not (a plain --json is a raw, unprobed dump) unless --reconcile is also passed, which forces the same pass and adds a reconciled: {checked,alive,gone,unknown,skipped} summary to the JSON output. The daemon also runs this same reconcile once at boot (best-effort, logged to stderr) so a stale ledger — dozens of paused rows the provider actually reaped long ago — self-heals without anyone running sandbox list first.

FlagDefaultDescription
--no-probefalseSkip the per-row provider liveness probes (no network calls). The LIVE column shows — for every row. Table mode only — --json never probes unless --reconcile is passed.
--reconcilefalseForce the provider-liveness/reconcile pass even under --json (table mode already does this by default), and include the reconciled summary in JSON output.
--jsonfalsePrint the raw { sandboxes: […] } JSON instead of the human table.

rm <sandboxId|label|id-prefix>

agentproto sandbox rm my-task-label
agentproto sandbox rm bx_abc123 --box --yes

Removes the ledger entry for the given box (resolved by exact label, full sandboxId, or unique id prefix). By default the box itself is left running/paused on the provider — only the local bookkeeping row is dropped.

FlagDefaultDescription
--boxfalseALSO stop the box on its provider (destructive — the sandbox and all data inside it is torn down). Without --yes, an interactive terminal is asked to confirm.
--yesfalseSkip the interactive confirmation for --box. Required in non-TTY environments.
--jsonfalsePrint { ok, removed, boxStopped } as JSON.

attach <provider> <sandboxId>

Resumes the sandbox, ensures its agentproto daemon is healthy, and exposes it with a PERSISTENT, token-gated URL (never an ungated one — a provider that can't gate the port fails the command rather than printing an insecure URL). Prints the connection descriptor and a paste-ready .mcp.json snippet.

FlagDefaultDescription
--config-json <json>{}Provider-specific SandboxSpec.config overrides, e.g. '{"port":18790}'.
--keep-alivefalseKeep the sandbox awake indefinitely for an always-on rendezvous.
--jsonfalsePrint only {"descriptor":…,"mcpConfig":…} as JSON.

gc

agentproto sandbox gc              # dry run — print what would be torn down
agentproto sandbox gc --apply      # actually kill orphan boxes on the provider
agentproto sandbox gc --apply --pause   # pause instead of kill (stays reattachable)
agentproto sandbox gc --json       # machine-readable plan / outcomes

Reaps orphan boxes: ledger entries whose origin session ended in a failure state (error / killed / exited) — the session can never return to its box, so the box is wasted spend. Dry run by default; nothing is torn down until --apply is passed.

Requires a running daemon to look up origin-session statuses.

FlagDefaultDescription
--applyfalseActually tear down (or pause) the orphan boxes and stamp their ledger rows stopped. Without this, the command only prints the plan.
--pausefalseWith --apply: pause the boxes instead of killing them, keeping them reattachable via sandbox attach. Note: a paused e2b box still bills, so the default kill is usually preferable for orphans.
--jsonfalsePrint the plan / outcomes as JSON instead of the human summary.

The always-on model (--keep-alive)

--keep-alive is for a sandbox meant to stay reachable indefinitely rather than get reclaimed by the provider's own idle/TTL auto-stop. The documented Box mechanism for "runs until you stop it" is no-auto-stop (box extend <id> --no-auto-stop / ttlSeconds: null), and it's sticky across resume — so --keep-alive (re-)asserts it on connect(), defensively, even for a box that already defaults to it (e.g. one created before that default, or with an explicit numeric ttlSeconds). It does not start a background heartbeat process — a one-shot CLI invocation can't cleanly host one, and no-auto-stop has no deadline to begin with, so none is needed. --keep-alive is a no-op for providers with no equivalent concept (e.g. e2b).

Examples

# Browse all boxes the daemon has touched (with liveness probes)
agentproto sandbox list
agentproto sandbox list --json

# Browse without network calls (skip liveness probes)
agentproto sandbox list --no-probe

# Remove a ledger entry (box stays running/paused on the provider)
agentproto sandbox rm my-task-label
agentproto sandbox rm bx_abc123

# Remove ledger entry AND stop the box (destructive)
agentproto sandbox rm bx_abc123 --box --yes

# Dry-run GC — see which orphan boxes would be reaped
agentproto sandbox gc

# Actually reap orphan boxes (sessions that errored/were killed)
agentproto sandbox gc --apply

# Attach to a Box sandbox booted by an earlier agent_start sandbox spawn
agentproto sandbox attach box bx_abc123

# Pin it awake indefinitely for an always-on rendezvous
agentproto sandbox attach box bx_abc123 --keep-alive

# Same, machine-readable
agentproto sandbox attach e2b sbx_abc123 --json

# Override the port the daemon listens on inside the box
agentproto sandbox attach box bx_abc123 --config-json '{"port":19000}'

Non-JSON output:

sandbox attached  provider=box  sandboxId=bx_abc123
  mcpUrl      https://frazil-pneuma-rallye-18790.on.ascii.dev/mcp
  token       •••••••• (gated)
  allowOrigin https://frazil-pneuma-rallye-18790.on.ascii.dev
  keepAlive   no

Paste into .mcp.json:
{
  "mcpServers": {
    "sandbox-box-bx_abc123": {
      "type": "http",
      "url": "https://frazil-pneuma-rallye-18790.on.ascii.dev/mcp",
      "headers": { "Cookie": "_port_auth=••••••••" }
    }
  }
}

The exact auth header is provider-specific and comes straight from the descriptor's authHeaders. Box gates its private hostname on a Cookie: _port_auth=<token> (its port edge ignores Authorization: Bearer); a token-only provider falls back to Authorization: Bearer <token>.

See also

  • sessions.md — agent_start.sandbox / sessions start --sandbox boots and drives a fresh sandbox; sandbox list/attach/rm operate on already-existing boxes
  • auth.md — credential storage conventions