$worker

iii-sandbox

v0.20.0

Spawn ephemeral microVMs and expose 14 sandbox::* triggers (lifecycle + filesystem) for isolated command execution and file ops.

engine module
baked into the iii engine; no separate install required.
agent-ready brief for v0.20.0
install + config + dependencies + readme + api reference, all in one place. fetch as agent-context.md for an llm to consume.
the same content rendered as discrete blocks below is exposed as a single markdown document at /workers/iii-sandbox.md. paste it into an llm prompt or pipe it through curl from a worker.

install

install
$iii worker add iii-sandbox@0.20.0

configuration

iii-config.yaml
- auto_install: true
  default_cpus: 1
  default_idle_timeout_secs: 300
  default_memory_mb: 512
  image_allowlist:
    - python
    - node
  max_concurrent_sandboxes: 32

dependencies

no dependencies for v0.20.0

readme

README.md

iii-sandbox

Spawn ephemeral microVMs from worker code or the terminal. The daemon registers 16 sandbox::* triggers — 4 lifecycle ops, 10 filesystem ops, the one-shot sandbox::run, and sandbox::catalog::list — every one called via iii.trigger(). Each sandbox boots in a few hundred milliseconds, runs commands isolated from the host, and is reaped when idle. The overlay filesystem is discarded on stop.

Implementation: crates/iii-worker/src/sandbox_daemon/. Ships inside the iii-worker binary; the engine starts the daemon as iii-worker sandbox-daemon when iii-sandbox appears in config.yaml.

Use it for: running untrusted code, AI-agent tool calls, one-shot scripts, per-request isolation.

Don't use it for: long-lived services (use a regular worker), durable stateful tasks (overlay is wiped on stop).

Agent Quickstart

If you are an AI agent calling sandbox::*, start here. Two paths:

Boots a VM, drops your code in /tmp/run.{ext}, runs the interpreter, captures stdout/stderr, and stops the VM. One call, one response.

{
  "trigger": "sandbox::run",
  "payload": {
    "image": "node",
    "code": "console.log('hello from sandbox')",
    "lang": "node"
  }
}

lang accepts "node", "python", "shell", or a custom interpreter binary path. Pass keep_sandbox: true if you want to keep the VM alive to inspect afterwards.

Surgical workflow: sandbox::create -> sandbox::fs::write -> sandbox::exec -> sandbox::stop

Use this when you need fine-grained control over multiple operations on one sandbox.

// 1. boot
{ "trigger": "sandbox::create", "payload": { "image": "node" } }
// 2. write a file (content accepts a UTF-8 string directly)
{ "trigger": "sandbox::fs::write", "payload": {
    "sandbox_id": "<uuid>", "path": "/home/app/main.js",
    "content": "console.log('hi')\n"
} }
// 3. run it (3 valid cmd shapes — pick whichever you like)
{ "trigger": "sandbox::exec", "payload": {
    "sandbox_id": "<uuid>", "cmd": "node", "args": ["/home/app/main.js"]
} }
// 4. tear down
{ "trigger": "sandbox::stop", "payload": { "sandbox_id": "<uuid>" } }

Three accepted cmd shapes for sandbox::exec

{ "cmd": "node /home/app/main.js" }                 // shell-line, shlex-split
{ "cmd": "node", "args": ["/home/app/main.js"] }   // classic POSIX argv
{ "argv": ["node", "/home/app/main.js"] }          // single argv array

Shlex is not bash — cmd: "echo $HOME && pwd" won't expand variables or chain commands. Use sandbox::run with lang: "shell" for bash semantics.

Two accepted env shapes for sandbox::create, sandbox::exec, sandbox::run

{ "env": ["FOO=bar", "PATH=/usr/bin"] }   // wire shape (legacy)
{ "env": { "FOO": "bar", "PATH": "/usr/bin" } }   // agent-natural shape

Error responses carry a self-healing payload

Every error returns JSON encoded inside error.message (see SandboxErrorWire docs). Parse it once:

const detail = JSON.parse(err.message);
// detail.code, detail.type, detail.message, detail.docs_url, detail.retryable
// detail.fix       — ready-to-send next-call payload, null if not auto-fixable
// detail.fix_note  — one-liner explaining why fix is null

If detail.fix is non-null, merge its fields into your original request and resubmit with await fn(mergedRequest) — keep your original fields and let detail.fix override only what it names; do not call fn(detail.fix) on its own. For example FsParentNotFound returns fix: { "parents": true }, which you merge into the original sandbox::fs::write / sandbox::fs::mkdir request rather than replacing it. detail.fix_note spells out the merge. The error IS the recovery path.

Error codes (anchors below)

Host requirements

Sandboxes run as libkrun microVMs and need hardware virtualization on the host:

  • macOS: Apple Silicon (M-series). Intel Macs can't boot sandboxes.
  • Linux: /dev/kvm readable by the engine process.
  • Windows: unsupported.

Hosts without hardware virtualization will fail sandbox::create with error S300 and a stderr tail from the failed VM process. See S300 in docs/api-reference/sandbox.mdx for the full diagnostic flow.

Sample Configuration

- name: iii-sandbox
  config:
    auto_install: true
    image_allowlist:
      - python
      - node
    default_idle_timeout_secs: 300
    max_concurrent_sandboxes: 32
    default_cpus: 1
    default_memory_mb: 512

iii worker add iii-sandbox appends this block to your config.yaml. Trim or extend image_allowlist and custom_images to control what callers can boot.

Configuration

Field Type Default Description
auto_install boolean true Pull the image from its OCI ref on first use when the rootfs isn't cached. Set false in air-gapped or pre-provisioned deployments — callers get S101 and operators pre-pull with iii worker add iiidev/.
image_allowlist string[] [] Fail-closed list of image names that may be booted. Entries must be preset names (python, node) or keys from custom_images. Empty list denies everything — sandbox::create returns S100 for every request.
default_idle_timeout_secs number 300 Reap a sandbox when now - last_exec_at exceeds this. The reaper runs every 10 s. Per-request idle_timeout_secs on sandbox::create overrides.
max_concurrent_sandboxes number 32 Hard cap on live sandboxes. The 33rd concurrent sandbox::create returns S400. Size by host RAM (default RAM per sandbox × cap ≤ available RAM).
default_cpus number 1 vCPUs per sandbox when the request omits cpus.
default_memory_mb number 512 RAM ceiling per sandbox when the request omits memory_mb.
per_image_caps map {} Per-image hard caps. Each value is { max_cpus: N, max_memory_mb: N }. Requests exceeding a cap return S400.
custom_images map {} Deployment-specific images beyond the built-in presets. Map key is the name used in image_allowlist and the image field on sandbox::create; value is a fully-qualified OCI reference (e.g. ghcr.io/acme/my-app:1.2.3). Preset names (python, node) are reserved. See docs/api-reference/sandbox.mdx.

Triggers

All 16 triggers are dispatched via iii.trigger({ function_id, payload, timeoutMs }). Recommended timeoutMs is in each table; lifecycle ops have meaningful timeout pressure, filesystem ops generally don't (the daemon is local).

Lifecycle (4)

sandbox::create

Boot a microVM and return a sandbox_id. Recommended timeoutMs: 300_000 (cold pull can take 5-30 s).

Field Type Default Description
image string required Preset (python, node) or custom_images key.
cpus number default_cpus vCPUs. Capped by per_image_caps.
memory_mb number default_memory_mb RAM ceiling. Capped by per_image_caps.
name string none Human label for sandbox::list.
network boolean false Enable guest networking.
idle_timeout_secs number default_idle_timeout_secs Override the per-sandbox idle reaper.
env string[] [] K=V entries injected into the guest.

Returns: { sandbox_id, image }.

sandbox::exec

Run a command inside a live sandbox. Recommended timeoutMs: 35_000 (daemon's 30 s default + 5 s margin).

Field Type Default Description
sandbox_id string (UUID) required From sandbox::create.
cmd string required Executable name.
args string[] [] argv tail.
stdin string (base64) none Bytes piped to the process's stdin.
env string[] [] K=V entries merged on top of the boot env.
timeout_ms number 300_000 Per-exec deadline enforced inside the daemon. Sized for cold npm install / pip install / cargo build; pass a smaller value for probes and version checks.
workdir string guest home Working directory.

Returns: { stdout, stderr, exit_code, timed_out, duration_ms, success }.

sandbox::list

Enumerate active sandboxes. Empty payload ({}). Returns an array of { sandbox_id, image, name, status, created_at, last_exec_at }.

sandbox::stop

Tear down a sandbox and reclaim resources.

Field Type Default Description
sandbox_id string (UUID) required Sandbox to stop.
wait boolean false Block until the VM process has exited.

Returns: { sandbox_id, stopped }.

One-shot (1)

sandbox::run

Boot a VM, run a code snippet, capture output, and auto-stop — in a single call. The fast path for agents; see the Agent Quickstart above for the full walkthrough. Recommended timeoutMs: 300_000.

Field Type Default Description
image string required Preset (python, node) or custom_images key.
code string required Source written to /tmp/run.{ext} and executed.
lang string required node, python, shell, or a literal interpreter binary path. No default.
files object[] [] Extra files dropped in first, each { path, content } (UTF-8).
env string[] | map [] Injected into the interpreter (both env shapes accepted).
stdin string (base64) none Bytes piped to the interpreter's stdin.
timeout_ms number 300_000 Per-run deadline.
keep_sandbox boolean false Keep the VM alive after the run and return its sandbox_id.

Returns: { stdout, stderr, exit_code, timed_out, duration_ms, success, sandbox_id? }. sandbox_id is present only when keep_sandbox: true; otherwise the VM is stopped on success and on failure.

Catalog (1)

sandbox::catalog::list

List the images this engine can boot — bundled presets plus operator-registered custom_images. Call it before sandbox::create / sandbox::run when you don't already know what's available (closes the S100 "image not in catalog" loop). Empty payload ({}).

Returns: { images: [ { name, oci_ref, kind } ] } where name is the value you pass to image, oci_ref is the pulled reference, and kind is "preset" or "custom". Presets come first; custom entries follow, sorted by name.

Filesystem (10)

All filesystem triggers take sandbox_id (UUID) plus operation-specific fields. Bumping the sandbox's idle clock is automatic — fs activity counts as liveness. Errors return S2xx.

sandbox::fs::ls

Field Type Description
sandbox_id string Sandbox to operate in.
path string Directory to list.

Returns: { entries: FsEntry[] } where each entry has { name, is_dir, size, mode, mtime, is_symlink }.

sandbox::fs::stat

Field Type Description
sandbox_id string Sandbox to operate in.
path string Path to inspect.

Returns: { name, is_dir, size, mode, mtime, is_symlink }.

sandbox::fs::mkdir

Field Type Default Description
sandbox_id string required Sandbox to operate in.
path string required Directory to create.
mode string "0755" Octal permissions.
parents boolean false Create intermediate directories (mkdir -p).

Returns: { created: boolean }.

sandbox::fs::write

Write a file into the sandbox. The body is given by exactly one of content or content_b64:

  • content: "" — a bare JSON string written verbatim. The agent-natural form for source/text.
  • content_b64: "" — inline binary (decoded before write).
  • content: — an object channel handle for large/streaming uploads from a programmatic caller (channel-paced, no envelope timeout cap).
Field Type Default Description
sandbox_id string required Sandbox to operate in.
path string required Destination path inside the guest.
mode string "0644" Octal permissions for the new file.
parents boolean false Create missing parent directories.
content string | StreamChannelRef one of content/content_b64 UTF-8 string (inline) or a channel handle (streaming).
content_b64 string one of content/content_b64 Base64-encoded inline binary body.

Returns: { bytes_written, path }.

sandbox::fs::read

Read a file out of the sandbox. Always returns a content: StreamChannelRef the caller can subscribe to for the full file bytes. For UTF-8 text files under 1 MiB, the response also includes an inline body: string so callers can short-circuit the channel subscription and use the body directly.

Field Type Description
sandbox_id string Sandbox to operate in.
path string Path to read.

Returns: { content: StreamChannelRef, body?: string, size, mode, mtime }.

  • content is always populated. The same bytes are delivered through it whether or not body is also set, so peers that statically type content as StreamChannelRef keep working unchanged.
  • body is present (Some) for files that fit in the 1 MiB inline cap and decode cleanly as UTF-8. Absent (None) for large or binary files — read those through content instead.

sandbox::fs::rm

Field Type Default Description
sandbox_id string required Sandbox to operate in.
path string required Path to remove.
recursive boolean false Recurse into directories (rm -r).

Returns: { removed: boolean }.

sandbox::fs::chmod

Field Type Default Description
sandbox_id string required Sandbox to operate in.
path string required Target path.
mode string required Octal permissions (e.g. "0644").
uid number unchanged New owner UID.
gid number unchanged New group GID.
recursive boolean false Apply recursively to a directory tree.

Returns: { updated: number } — count of paths changed.

sandbox::fs::mv

Field Type Default Description
sandbox_id string required Sandbox to operate in.
src string required Source path.
dst string required Destination path.
overwrite boolean false Allow overwriting an existing destination.

Returns: { moved: boolean }.

sandbox::fs::grep

Recursive regex search.

Field Type Default Description
sandbox_id string required Sandbox to operate in.
path string required Root path.
pattern string required Regex (RE2 syntax).
recursive boolean true Walk subdirectories.
ignore_case boolean false Case-insensitive match.
include_glob string[] [] Gitignore-style include filter.
exclude_glob string[] [] Gitignore-style exclude filter.
max_matches number 10000 Stop after N matches.
max_line_bytes number 4096 Truncate any single line longer than this.

Returns: { matches: FsMatch[], truncated } where each match has { path, line_no, byte_offset, line }.

sandbox::fs::sed

Find-and-replace across files. Pass either files (explicit list) or path (walk like grep) — exactly one. Passing both, or neither, returns S210.

Field Type Default Description
sandbox_id string required Sandbox to operate in.
files string[] [] Explicit list of paths. Mutually exclusive with path.
path string none Root path to walk. Mutually exclusive with files.
recursive boolean true Walk subdirectories. Only meaningful with path.
include_glob string[] [] Include filter (only with path).
exclude_glob string[] [] Exclude filter (only with path).
pattern string required Regex or literal (see regex flag).
replacement string required Replacement string.
regex boolean true Treat pattern as a regex; false for literal.
first_only boolean false Replace only the first match in each file.
ignore_case boolean false Case-insensitive match.

Returns: { results: FsSedFileResult[], total_replacements }.

Example: create → exec → stop

import { registerWorker } from 'iii-sdk'

const iii = registerWorker('ws://127.0.0.1:49134')

const { sandbox_id } = await iii.trigger({
  function_id: 'sandbox::create',
  payload: { image: 'python', cpus: 1, memory_mb: 512 },
  timeoutMs: 300_000,
})

const out = await iii.trigger({
  function_id: 'sandbox::exec',
  payload: { sandbox_id, cmd: 'python3', args: ['-c', 'print(2 + 2)'] },
  timeoutMs: 35_000,
})
console.log(out.stdout) // "4\n"

await iii.trigger({
  function_id: 'sandbox::stop',
  payload: { sandbox_id, wait: true },
})
from iii import register_worker

iii = register_worker("ws://127.0.0.1:49134")

result = await iii.trigger({
    "function_id": "sandbox::create",
    "payload": {"image": "python", "cpus": 1, "memory_mb": 512},
    "timeout_ms": 300_000,
})
sandbox_id = result["sandbox_id"]

out = await iii.trigger({
    "function_id": "sandbox::exec",
    "payload": {"sandbox_id": sandbox_id, "cmd": "python3", "args": ["-c", "print(2 + 2)"]},
    "timeout_ms": 35_000,
})
print(out["stdout"])  # "4\n"

await iii.trigger({
    "function_id": "sandbox::stop",
    "payload": {"sandbox_id": sandbox_id, "wait": True},
})

CLI

The iii sandbox subcommands wrap a curated subset of the lifecycle and fs surface:

iii sandbox run <image> -- <cmd> [args...]    # one-shot create+exec+stop
iii sandbox create <image> [--idle-timeout N] # boot, print sandbox_id
iii sandbox exec <sandbox_id> -- <cmd> ...    # exec into a live sandbox
iii sandbox list
iii sandbox stop <sandbox_id>
iii sandbox upload <sandbox_id> <local> <remote>
iii sandbox download <sandbox_id> <remote> <local>

Anything outside this set (e.g. fs::grep, fs::sed, fs::chmod) is reachable only via iii.trigger() from worker code.

Errors

The daemon returns typed SandboxErrors with S-codes embedded in the wire payload ({type, code, message, docs_url, fix, fix_note, retryable}).

error.docs_url resolves to one of the anchors below. The anchor IDs are case-sensitive HTML anchors so a regression test (sandbox_docs_anchor_stability) can verify each SandboxErrorCode has a documented home.

Request validation

S001 — invalid request

Bad UUID, missing required field, ambiguous cmd/args/argv combo, invalid env var name. Check error.message for the specific cause.

S002 — sandbox not found

The sandbox_id is well-formed but no sandbox with that id exists. Call sandbox::create first.

S003 — concurrent exec

Exec is serialized one-at-a-time per sandbox; another exec is already in flight. If that exec is a long-running or FOREGROUND process (a server, npm install, a build/watch), waiting will NOT free the slot — it holds until the process exits or hits its timeout_ms (default 300s). Detach servers with nohup > /tmp/out.log 2>&1 & and read progress via sandbox::fs::read, or sandbox::stop + sandbox::create to reset. Retry-after-wait only helps for a short in-flight command.

S004 — sandbox already stopped

The sandbox was reaped or explicitly stopped. Create a new one.

Image catalog

S100 — image not in catalog

The image value isn't a built-in preset (python, node) and isn't a key in sandbox.custom_images of iii.config.yaml. Either pick a known preset or add a custom_images entry.

S101 — rootfs missing

The image is in the catalog but the rootfs isn't on disk. Operator action: run iii worker add on the host.

S102 — auto-install failed (transient)

Pull or extract of the image bundle failed. Often transient — retry.

Exec runtime

S200 — exec timed out

The timeout_ms window elapsed before the binary exited. Raise the timeout or break the work into smaller steps.

Filesystem

S210 — fs invalid request

Mutually exclusive fields, missing required field, unsupported operation combination. Check error.message.

S211 — file not found

S212 — wrong file type (expected file, found directory, or vice versa)

S213 — already exists

S214 — directory not empty

S215 — permission denied

S216 — fs i/o error

S217 — invalid regex pattern

S218 — fs channel aborted (transient)

The streaming channel closed before the operation completed. Retry.

S219 — fs operation unsupported

The sandbox supervisor inside the VM is too old to implement this fs op. Upgrade iii-worker.

Platform

S300 — VM boot failed

The microVM couldn't boot. Almost always missing virtualization on the host (no /dev/kvm, Intel Mac, Windows). Check the stderr tail in error.message.

S400 — resource limit exceeded

Capacity bound (max_concurrent_sandboxes, per-image caps) reached.

See also

api reference (json)

agent-api-reference.json
{
  "functions": [
    {
      "description": "List bootable images: bundled presets plus operator-registered custom_images. Call this before sandbox::create when you don't know what's available.",
      "metadata": {},
      "name": "sandbox::catalog::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "`sandbox::catalog::list` request. No fields — kept as a struct (not `()`) so adding optional filters (e.g. `include_oci_refs: bool`) later is a non-breaking serde change.",
        "title": "CatalogListRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "CatalogEntry": {
            "description": "One row in the catalog response. `name` is the catalog key that callers pass to `sandbox::create` (or `sandbox::run`); `oci_ref` is the fully-qualified OCI reference the daemon will pull. `kind` lets agents distinguish bundled presets from operator-registered custom images without having to maintain their own preset allowlist.",
            "properties": {
              "kind": {
                "$ref": "#/definitions/CatalogEntryKind"
              },
              "name": {
                "type": "string"
              },
              "oci_ref": {
                "type": "string"
              }
            },
            "required": [
              "kind",
              "name",
              "oci_ref"
            ],
            "type": "object"
          },
          "CatalogEntryKind": {
            "oneOf": [
              {
                "description": "Bundled with the daemon binary. Stable identifier across releases.",
                "enum": [
                  "preset"
                ],
                "type": "string"
              },
              {
                "description": "Operator-registered under `sandbox.custom_images` in `iii.config.yaml`. Specific to this deployment.",
                "enum": [
                  "custom"
                ],
                "type": "string"
              }
            ]
          }
        },
        "properties": {
          "images": {
            "description": "Every image the daemon will accept on `sandbox::create.image`. Presets come first in stable order; custom entries follow, sorted by `name` for determinism so an agent that diffs two `catalog::list` responses sees stable output.",
            "items": {
              "$ref": "#/definitions/CatalogEntry"
            },
            "type": "array"
          }
        },
        "required": [
          "images"
        ],
        "title": "CatalogListResponse",
        "type": "object"
      }
    },
    {
      "description": "Create an ephemeral sandbox VM. `image` must be a preset (`\"python\"`, `\"node\"`) or a `custom_images` key from iii.config.yaml; OCI refs are NOT accepted unless they match a catalog key. `env` accepts both `Vec<\"K=V\">` and `{ K: V }` map shapes.",
      "metadata": {},
      "name": "sandbox::create",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "EnvShape": {
            "anyOf": [
              {
                "description": "Original wire shape: `[\"FOO=bar\", \"PATH=/usr/bin\"]`.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "additionalProperties": {
                  "type": "string"
                },
                "description": "Agent-natural shape: `{ \"FOO\": \"bar\", \"PATH\": \"/usr/bin\" }`. Iteration is sorted by key (BTreeMap) so two callers passing the same map get the same env-var ordering.",
                "type": "object"
              }
            ],
            "description": "Environment-variable input. Agents naturally pass `{ FOO: \"bar\" }` (matching Docker/npm/k8s mental models); the original wire shape was `Vec<\"K=V\">`. The untagged enum accepts both; `into_kv_vec()` normalises to the canonical `Vec<String>` the runner expects.\n\n`Default` is the empty vec form (the historical wire shape)."
          }
        },
        "examples": [
          {
            "env": {
              "NODE_ENV": "production"
            },
            "idle_timeout_secs": 600,
            "image": "node",
            "memory_mb": 512
          }
        ],
        "properties": {
          "cpus": {
            "default": null,
            "description": "vCPU count; daemon/image default applies when omitted.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "env": {
            "allOf": [
              {
                "$ref": "#/definitions/EnvShape"
              }
            ],
            "description": "Environment entries injected into the VM. Accepts either a `Vec<\"K=V\">` (original wire shape) or a `{ K: V }` map. `handle_create` normalises to the `Vec<String>` shape the boot path expects before invoking the launcher."
          },
          "idle_timeout_secs": {
            "default": null,
            "description": "Auto-stop the VM after this many seconds of inactivity; daemon default applies when omitted.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "image": {
            "description": "Catalog name of the image to boot. Bundled presets are `\"python\"` and `\"node\"`; pass either string verbatim. The only other accepted values are the literal keys of `sandbox.custom_images` in `iii.config.yaml` — set by the operator. Do NOT pass an OCI ref like `\"ghcr.io/iii-hq/node:latest\"` or `\"docker.io/library/node:20\"` unless that exact string is the catalog key. Unknown values return S100 with the allowed set in the error message.",
            "type": "string"
          },
          "memory_mb": {
            "default": null,
            "description": "Memory cap in MiB; daemon/image default applies when omitted.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "name": {
            "default": null,
            "description": "Human label surfaced by `sandbox::list`; not an identifier.",
            "type": [
              "string",
              "null"
            ]
          },
          "network": {
            "default": null,
            "description": "Whether the VM gets outbound networking; daemon default when omitted.",
            "type": [
              "boolean",
              "null"
            ]
          }
        },
        "required": [
          "image"
        ],
        "title": "CreateRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "image": {
            "type": "string"
          },
          "sandbox_id": {
            "type": "string"
          }
        },
        "required": [
          "image",
          "sandbox_id"
        ],
        "title": "CreateResponse",
        "type": "object"
      }
    },
    {
      "description": "Execute a command inside a live sandbox. `cmd` accepts three shapes: (1) a shell-style line that gets shlex-split (`cmd: \"node -v\"`), (2) `cmd` + `args` (the POSIX shape, `cmd: \"node\", args: [\"-v\"]`), (3) an `argv` array (`argv: [\"node\", \"-v\"]`). Shell metacharacters in the shell-line shape are NOT interpreted; use `sandbox::run` with `lang: \"shell\"` for bash semantics. `env` accepts both `Vec<\"K=V\">` and `{ K: V }` map shapes.",
      "metadata": {},
      "name": "sandbox::exec",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "EnvShape": {
            "anyOf": [
              {
                "description": "Original wire shape: `[\"FOO=bar\", \"PATH=/usr/bin\"]`.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "additionalProperties": {
                  "type": "string"
                },
                "description": "Agent-natural shape: `{ \"FOO\": \"bar\", \"PATH\": \"/usr/bin\" }`. Iteration is sorted by key (BTreeMap) so two callers passing the same map get the same env-var ordering.",
                "type": "object"
              }
            ],
            "description": "Environment-variable input. Agents naturally pass `{ FOO: \"bar\" }` (matching Docker/npm/k8s mental models); the original wire shape was `Vec<\"K=V\">`. The untagged enum accepts both; `into_kv_vec()` normalises to the canonical `Vec<String>` the runner expects.\n\n`Default` is the empty vec form (the historical wire shape)."
          }
        },
        "examples": [
          {
            "args": [
              "/home/app/index.js"
            ],
            "cmd": "node",
            "env": {
              "NODE_ENV": "production"
            },
            "sandbox_id": "00000000-0000-0000-0000-000000000000",
            "timeout_ms": 300000
          }
        ],
        "properties": {
          "args": {
            "default": [],
            "description": "Argv tail passed to `cmd` (each entry is one argv slot).",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "argv": {
            "default": [],
            "description": "Alternative input shape: a single argv array where the first element is the binary and the rest are arguments. Mutually exclusive with `cmd` having whitespace OR `args` being set.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "cmd": {
            "default": "",
            "description": "The binary to execute. Accepts three shapes (`handle_exec` picks one and normalises `cmd`/`args` before passing to the runner):\n\n1. **Shell line**: `cmd: \"node /home/app/index.js\"`. If `cmd` contains whitespace AND `args` is empty AND `argv` is empty, `cmd` is shlex-split into `(head, tail)` and `head` becomes the binary while `tail` becomes the argv. 2. **cmd + args**: `cmd: \"node\", args: [\"-v\"]`. The classic POSIX shape; unchanged from earlier versions. 3. **argv array**: `argv: [\"node\", \"/home/app/index.js\"]`. Wins over `cmd`/`args` if non-empty; the first element is the binary, the rest are arguments.\n\nShlex is NOT bash. Shell metacharacters (`;`, `|`, `&&`, `>`, pipes, redirects, variable expansion) inside the shell-line shape are split as text, not interpreted. Use `sandbox::run` with `lang: \"shell\"` if you need bash semantics.",
            "type": "string"
          },
          "env": {
            "allOf": [
              {
                "$ref": "#/definitions/EnvShape"
              }
            ],
            "description": "Environment entries added to the child. Accepts either a `Vec<\"K=V\">` (the original wire shape) or a `{ K: V }` map. `handle_exec` normalises to `Vec<String>` before invoking the runner."
          },
          "sandbox_id": {
            "description": "UUID returned by `sandbox::create`.",
            "type": "string"
          },
          "stdin": {
            "default": null,
            "description": "Base64-encoded bytes piped to the child's stdin.",
            "type": [
              "string",
              "null"
            ]
          },
          "timeout_ms": {
            "default": null,
            "description": "Kill-after window in ms. Defaults to 300_000 (5 minutes) — sized for cold `npm install` / `pip install` / `cargo build`. Pass a smaller value (e.g. 10_000) for probes and version checks.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "workdir": {
            "default": null,
            "description": "Working directory inside the sandbox; image default when omitted.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "sandbox_id"
        ],
        "title": "ExecRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "duration_ms": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "exit_code": {
            "format": "int32",
            "type": [
              "integer",
              "null"
            ]
          },
          "stderr": {
            "type": "string"
          },
          "stdout": {
            "type": "string"
          },
          "success": {
            "type": "boolean"
          },
          "timed_out": {
            "type": "boolean"
          }
        },
        "required": [
          "duration_ms",
          "stderr",
          "stdout",
          "success",
          "timed_out"
        ],
        "title": "ExecResponse",
        "type": "object"
      }
    },
    {
      "description": "Change file permissions (and optionally owner) inside a sandbox. Example: { sandbox_id: \"...\", path: \"/home/app/script.sh\", mode: \"0755\" }",
      "metadata": {},
      "name": "sandbox::fs::chmod",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "mode": "0755",
            "path": "/home/app/script.sh",
            "sandbox_id": "00000000-0000-0000-0000-000000000000"
          }
        ],
        "properties": {
          "gid": {
            "default": null,
            "description": "Optional GID to chown to. Pair with `uid` for a full chown.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "mode": {
            "description": "Octal permissions (e.g. `\"0644\"`, `\"0755\"`).",
            "type": "string"
          },
          "path": {
            "description": "Absolute path to modify inside the sandbox guest.",
            "type": "string"
          },
          "recursive": {
            "default": false,
            "description": "Apply recursively to all entries under `path`.",
            "type": "boolean"
          },
          "sandbox_id": {
            "description": "UUID returned by `sandbox::create`.",
            "type": "string"
          },
          "uid": {
            "default": null,
            "description": "Optional UID to chown to. Pair with `gid` for a full chown.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "mode",
          "path",
          "sandbox_id"
        ],
        "title": "ChmodRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "updated": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "updated"
        ],
        "title": "ChmodResponse",
        "type": "object"
      }
    },
    {
      "description": "Search for a regex pattern in files inside a sandbox. Walks `path` recursively by default. Example: { sandbox_id: \"...\", path: \"/home/app/src\", pattern: \"TODO\" }",
      "metadata": {},
      "name": "sandbox::fs::grep",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "include_glob": [
              "**/*.js",
              "**/*.ts"
            ],
            "path": "/home/app/src",
            "pattern": "TODO|FIXME",
            "recursive": true,
            "sandbox_id": "00000000-0000-0000-0000-000000000000"
          }
        ],
        "properties": {
          "exclude_glob": {
            "default": [],
            "description": "Gitignore-style exclude filter applied relative to `path`.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "ignore_case": {
            "default": false,
            "description": "Case-insensitive match.",
            "type": "boolean"
          },
          "include_glob": {
            "default": [],
            "description": "Gitignore-style include filter applied relative to `path`.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "max_line_bytes": {
            "default": 4096,
            "description": "Maximum bytes per matched line before content is truncated with `…`. Default 4096.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "max_matches": {
            "default": 10000,
            "description": "Maximum number of matches before truncation. Default 10_000.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "path": {
            "description": "Root path to search inside the sandbox guest. Treated as a directory when `recursive: true`, else as a single file.",
            "type": "string"
          },
          "pattern": {
            "description": "Regex pattern (Rust regex syntax, anchored fragments allowed).",
            "type": "string"
          },
          "recursive": {
            "default": true,
            "description": "Descend into subdirectories under `path`. Defaults to true.",
            "type": "boolean"
          },
          "sandbox_id": {
            "description": "UUID returned by `sandbox::create`.",
            "type": "string"
          }
        },
        "required": [
          "path",
          "pattern",
          "sandbox_id"
        ],
        "title": "GrepRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "FsMatch": {
            "description": "Single grep hit. `line` is 1-based. `content` is already truncated to `max_line_bytes` (with a trailing `…`) if the original line was longer.",
            "properties": {
              "content": {
                "type": "string"
              },
              "line": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "path": {
                "type": "string"
              }
            },
            "required": [
              "content",
              "line",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "matches": {
            "items": {
              "$ref": "#/definitions/FsMatch"
            },
            "type": "array"
          },
          "truncated": {
            "type": "boolean"
          }
        },
        "required": [
          "matches",
          "truncated"
        ],
        "title": "GrepResponse",
        "type": "object"
      }
    },
    {
      "description": "List directory contents inside a sandbox. Example: { sandbox_id: \"...\", path: \"/home/app\" }",
      "metadata": {},
      "name": "sandbox::fs::ls",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "path": "/home/app",
            "sandbox_id": "00000000-0000-0000-0000-000000000000"
          }
        ],
        "properties": {
          "path": {
            "description": "Absolute path of the directory to list inside the sandbox guest.",
            "type": "string"
          },
          "sandbox_id": {
            "description": "UUID returned by `sandbox::create`.",
            "type": "string"
          }
        },
        "required": [
          "path",
          "sandbox_id"
        ],
        "title": "LsRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "FsEntry": {
            "description": "Single directory entry returned by `FsOp::Ls` / `FsOp::Stat`. `mode` is the octal permission string (e.g. `\"0644\"`); `mtime` is Unix seconds. `is_symlink` reflects the entry itself — FS handlers never follow symlinks unless the op doc-comment says otherwise.",
            "properties": {
              "is_dir": {
                "type": "boolean"
              },
              "is_symlink": {
                "type": "boolean"
              },
              "mode": {
                "type": "string"
              },
              "mtime": {
                "format": "int64",
                "type": "integer"
              },
              "name": {
                "type": "string"
              },
              "size": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "is_dir",
              "is_symlink",
              "mode",
              "mtime",
              "name",
              "size"
            ],
            "type": "object"
          }
        },
        "properties": {
          "entries": {
            "items": {
              "$ref": "#/definitions/FsEntry"
            },
            "type": "array"
          }
        },
        "required": [
          "entries"
        ],
        "title": "LsResponse",
        "type": "object"
      }
    },
    {
      "description": "Create a directory inside a sandbox. Pass `parents:true` to make missing parents like `mkdir -p`. Example: { sandbox_id: \"...\", path: \"/home/app/cache\", mode: \"0755\", parents: true }",
      "metadata": {},
      "name": "sandbox::fs::mkdir",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "mode": "0755",
            "parents": true,
            "path": "/home/app/cache",
            "sandbox_id": "00000000-0000-0000-0000-000000000000"
          }
        ],
        "properties": {
          "mode": {
            "default": "0755",
            "description": "Octal permissions for the new directory (e.g. `\"0755\"`).",
            "type": "string"
          },
          "parents": {
            "default": false,
            "description": "Create intermediate parent directories like `mkdir -p`.",
            "type": "boolean"
          },
          "path": {
            "description": "Absolute path of the directory to create inside the sandbox guest.",
            "type": "string"
          },
          "sandbox_id": {
            "description": "UUID returned by `sandbox::create`.",
            "type": "string"
          }
        },
        "required": [
          "path",
          "sandbox_id"
        ],
        "title": "MkdirRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "created": {
            "type": "boolean"
          }
        },
        "required": [
          "created"
        ],
        "title": "MkdirResponse",
        "type": "object"
      }
    },
    {
      "description": "Move or rename a path inside a sandbox. Pass `overwrite:true` to clobber an existing destination. Example: { sandbox_id: \"...\", src: \"/home/app/old.js\", dst: \"/home/app/new.js\" }",
      "metadata": {},
      "name": "sandbox::fs::mv",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "dst": "/home/app/new.js",
            "overwrite": false,
            "sandbox_id": "00000000-0000-0000-0000-000000000000",
            "src": "/home/app/old.js"
          }
        ],
        "properties": {
          "dst": {
            "description": "Destination absolute path inside the sandbox guest.",
            "type": "string"
          },
          "overwrite": {
            "default": false,
            "description": "Allow overwriting `dst` if it already exists.",
            "type": "boolean"
          },
          "sandbox_id": {
            "description": "UUID returned by `sandbox::create`.",
            "type": "string"
          },
          "src": {
            "description": "Source absolute path inside the sandbox guest.",
            "type": "string"
          }
        },
        "required": [
          "dst",
          "sandbox_id",
          "src"
        ],
        "title": "MvRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "moved": {
            "type": "boolean"
          }
        },
        "required": [
          "moved"
        ],
        "title": "MvResponse",
        "type": "object"
      }
    },
    {
      "description": "Read a file from a sandbox. Always returns `content`: a StreamChannelRef callers can subscribe to for the full file bytes. For UTF-8 text files under 1 MiB, the response also carries an inline `body` string so callers can short-circuit the subscription. Example: { sandbox_id: \"...\", path: \"/home/app/index.js\" }",
      "metadata": {},
      "name": "sandbox::fs::read",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "path": "/home/app/index.js",
            "sandbox_id": "00000000-0000-0000-0000-000000000000"
          }
        ],
        "properties": {
          "path": {
            "description": "Absolute path to read inside the sandbox guest.",
            "type": "string"
          },
          "sandbox_id": {
            "description": "UUID returned by `sandbox::create`.",
            "type": "string"
          }
        },
        "required": [
          "path",
          "sandbox_id"
        ],
        "title": "ReadRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ChannelDirection": {
            "enum": [
              "read",
              "write"
            ],
            "type": "string"
          },
          "StreamChannelRef": {
            "properties": {
              "access_key": {
                "type": "string"
              },
              "channel_id": {
                "type": "string"
              },
              "direction": {
                "$ref": "#/definitions/ChannelDirection"
              }
            },
            "required": [
              "access_key",
              "channel_id",
              "direction"
            ],
            "type": "object"
          }
        },
        "properties": {
          "body": {
            "description": "Inline UTF-8 body. Populated for text files under [`INLINE_BUFFER_CAP`] (1 MiB) that decode cleanly as UTF-8. `None` for large or binary files; subscribe to `content` instead. When `Some`, the same bytes are also delivered through `content` so legacy callers keep working — new callers can use `body` directly and skip the channel subscription.",
            "type": [
              "string",
              "null"
            ]
          },
          "content": {
            "allOf": [
              {
                "$ref": "#/definitions/StreamChannelRef"
              }
            ],
            "description": "Channel ref for the file body. Always set; callers can subscribe to receive the full file contents as bytes. Preserved for wire compatibility with peers that statically type this field as `StreamChannelRef`."
          },
          "mode": {
            "type": "string"
          },
          "mtime": {
            "format": "int64",
            "type": "integer"
          },
          "size": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "content",
          "mode",
          "mtime",
          "size"
        ],
        "title": "ReadResponse",
        "type": "object"
      }
    },
    {
      "description": "Remove a file or directory inside a sandbox. Pass `recursive:true` to remove directories with contents. Example: { sandbox_id: \"...\", path: \"/home/app/temp\", recursive: true }",
      "metadata": {},
      "name": "sandbox::fs::rm",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "path": "/home/app/temp",
            "recursive": true,
            "sandbox_id": "00000000-0000-0000-0000-000000000000"
          }
        ],
        "properties": {
          "path": {
            "description": "Absolute path to remove inside the sandbox guest.",
            "type": "string"
          },
          "recursive": {
            "default": false,
            "description": "Remove directories and their contents recursively (like `rm -rf`).",
            "type": "boolean"
          },
          "sandbox_id": {
            "description": "UUID returned by `sandbox::create`.",
            "type": "string"
          }
        },
        "required": [
          "path",
          "sandbox_id"
        ],
        "title": "RmRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "removed": {
            "type": "boolean"
          }
        },
        "required": [
          "removed"
        ],
        "title": "RmResponse",
        "type": "object"
      }
    },
    {
      "description": "Find-and-replace in files inside a sandbox. Pass either `path` (walked like grep) OR `files` (explicit list), not both. Example: { sandbox_id: \"...\", path: \"/home/app/src\", pattern: \"foo\", replacement: \"bar\" }",
      "metadata": {},
      "name": "sandbox::fs::sed",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Find-and-replace request, accepting either an explicit `files` list or a `path` walked like grep does. Exactly one of those two must be provided — `handle_sed` returns S210 otherwise.",
        "examples": [
          {
            "path": "/home/app/src",
            "pattern": "foo",
            "recursive": true,
            "replacement": "bar",
            "sandbox_id": "00000000-0000-0000-0000-000000000000"
          }
        ],
        "properties": {
          "exclude_glob": {
            "default": [],
            "description": "Gitignore-style exclude filter applied to paths relative to `path`. Only meaningful with `path`.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "files": {
            "default": [],
            "description": "Legacy form: explicit list of paths to rewrite.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "first_only": {
            "default": false,
            "type": "boolean"
          },
          "ignore_case": {
            "default": false,
            "type": "boolean"
          },
          "include_glob": {
            "default": [],
            "description": "Gitignore-style include filter applied to paths relative to `path`. Only meaningful with `path`.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "path": {
            "default": null,
            "description": "New form: walk `path` like grep does. May be a directory or a single file. Mutually exclusive with `files`.",
            "type": [
              "string",
              "null"
            ]
          },
          "pattern": {
            "type": "string"
          },
          "recursive": {
            "default": true,
            "description": "Whether to descend into subdirectories. Only meaningful with `path`. Defaults to `true` so the path-form behaves like grep.",
            "type": "boolean"
          },
          "regex": {
            "default": true,
            "type": "boolean"
          },
          "replacement": {
            "type": "string"
          },
          "sandbox_id": {
            "description": "UUID returned by `sandbox::create`.",
            "type": "string"
          }
        },
        "required": [
          "pattern",
          "replacement",
          "sandbox_id"
        ],
        "title": "SedRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "FsSedFileResult": {
            "description": "One `sed` file-level outcome. `success=false` carries the human failure message in `error`; otherwise `error` is absent.",
            "properties": {
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "path": {
                "type": "string"
              },
              "replacements": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "success": {
                "type": "boolean"
              }
            },
            "required": [
              "path",
              "replacements",
              "success"
            ],
            "type": "object"
          }
        },
        "properties": {
          "results": {
            "items": {
              "$ref": "#/definitions/FsSedFileResult"
            },
            "type": "array"
          },
          "total_replacements": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "results",
          "total_replacements"
        ],
        "title": "SedResponse",
        "type": "object"
      }
    },
    {
      "description": "Stat a path inside a sandbox. Example: { sandbox_id: \"...\", path: \"/home/app/index.js\" }",
      "metadata": {},
      "name": "sandbox::fs::stat",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "path": "/home/app/index.js",
            "sandbox_id": "00000000-0000-0000-0000-000000000000"
          }
        ],
        "properties": {
          "path": {
            "description": "Absolute path to inspect inside the sandbox guest.",
            "type": "string"
          },
          "sandbox_id": {
            "description": "UUID returned by `sandbox::create`.",
            "type": "string"
          }
        },
        "required": [
          "path",
          "sandbox_id"
        ],
        "title": "StatRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Mirrors `FsEntry`. Note: the shell protocol does not carry uid/gid; those fields are absent at this trigger level.",
        "properties": {
          "is_dir": {
            "type": "boolean"
          },
          "is_symlink": {
            "type": "boolean"
          },
          "mode": {
            "type": "string"
          },
          "mtime": {
            "format": "int64",
            "type": "integer"
          },
          "name": {
            "type": "string"
          },
          "size": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "is_dir",
          "is_symlink",
          "mode",
          "mtime",
          "name",
          "size"
        ],
        "title": "StatResponse",
        "type": "object"
      }
    },
    {
      "description": "Write a file into a sandbox. `content` accepts a UTF-8 string (recommended for source/text), a StreamChannelRef object (for large uploads), or use `content_b64` for small binary. Example: { sandbox_id: \"...\", path: \"/home/app/index.js\", content: \"console.log('hi')\\n\" }",
      "metadata": {},
      "name": "sandbox::fs::write",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ChannelDirection": {
            "enum": [
              "read",
              "write"
            ],
            "type": "string"
          },
          "StreamChannelRef": {
            "properties": {
              "access_key": {
                "type": "string"
              },
              "channel_id": {
                "type": "string"
              },
              "direction": {
                "$ref": "#/definitions/ChannelDirection"
              }
            },
            "required": [
              "access_key",
              "channel_id",
              "direction"
            ],
            "type": "object"
          },
          "WriteContent": {
            "anyOf": [
              {
                "description": "Inline UTF-8 string. Written to the file verbatim.",
                "type": "string"
              },
              {
                "allOf": [
                  {
                    "$ref": "#/definitions/StreamChannelRef"
                  }
                ],
                "description": "Streaming channel for large / binary uploads."
              }
            ],
            "description": "File body for `sandbox::fs::write`. Untagged so the JSON shape decides which variant runs:\n\n- A bare JSON string (`\"console.log('hi')\"`) → [`WriteContent::Utf8`]. This is what LLM agents naturally pass and the recommended form for source files / configs / small text. - An object that matches [`StreamChannelRef`] → [`WriteContent::Stream`]. The existing channel-streaming path for large or binary payloads coming from a programmatic caller that can construct a channel.\n\nSerde tries variants in declaration order; `Utf8` matches first because every JSON string deserialises into `String`, and `StreamChannelRef` requires an object. Binary inline data uses the separate `content_b64` field on [`WriteRequest`] (not a variant here, so a caller can't accidentally pass base64 expecting it to be decoded — they have to opt in by name)."
          }
        },
        "examples": [
          {
            "content": "console.log('hello world')\n",
            "mode": "0644",
            "parents": true,
            "path": "/home/app/index.js",
            "sandbox_id": "00000000-0000-0000-0000-000000000000"
          }
        ],
        "properties": {
          "content": {
            "anyOf": [
              {
                "$ref": "#/definitions/WriteContent"
              },
              {
                "type": "null"
              }
            ],
            "description": "File body. Pass a UTF-8 string for source/text (the agent-natural form), or a `StreamChannelRef` object for streaming large/binary uploads. Mutually exclusive with `content_b64`; exactly one of the two must be set."
          },
          "content_b64": {
            "default": null,
            "description": "Base64-encoded inline body for small binary payloads. Mutually exclusive with `content`.",
            "type": [
              "string",
              "null"
            ]
          },
          "mode": {
            "default": "0644",
            "description": "Octal permissions for the new file (default `\"0644\"`).",
            "type": "string"
          },
          "parents": {
            "default": false,
            "description": "Create missing parent directories before writing.",
            "type": "boolean"
          },
          "path": {
            "description": "Absolute destination path inside the sandbox guest.",
            "type": "string"
          },
          "sandbox_id": {
            "description": "UUID returned by `sandbox::create`.",
            "type": "string"
          }
        },
        "required": [
          "path",
          "sandbox_id"
        ],
        "title": "WriteRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "bytes_written": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "path": {
            "type": "string"
          }
        },
        "required": [
          "bytes_written",
          "path"
        ],
        "title": "WriteResponse",
        "type": "object"
      }
    },
    {
      "description": "List active sandboxes",
      "metadata": {},
      "name": "sandbox::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "ListRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "SandboxSummary": {
            "properties": {
              "age_secs": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "exec_in_progress": {
                "type": "boolean"
              },
              "image": {
                "type": "string"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "sandbox_id": {
                "type": "string"
              },
              "stopped": {
                "type": "boolean"
              }
            },
            "required": [
              "age_secs",
              "exec_in_progress",
              "image",
              "sandbox_id",
              "stopped"
            ],
            "type": "object"
          }
        },
        "properties": {
          "sandboxes": {
            "items": {
              "$ref": "#/definitions/SandboxSummary"
            },
            "type": "array"
          }
        },
        "required": [
          "sandboxes"
        ],
        "title": "ListResponse",
        "type": "object"
      }
    },
    {
      "description": "Run code in an ephemeral sandbox in ONE call. Composes create + fs::write + exec + stop. `lang` selects the interpreter (`node`, `python`, `shell`, or a custom binary path). Sandbox auto-stops on success and on failure unless `keep_sandbox: true`. Example: { image: \"node\", code: \"console.log('hi')\", lang: \"node\" }",
      "metadata": {},
      "name": "sandbox::run",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "EnvShape": {
            "anyOf": [
              {
                "description": "Original wire shape: `[\"FOO=bar\", \"PATH=/usr/bin\"]`.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              {
                "additionalProperties": {
                  "type": "string"
                },
                "description": "Agent-natural shape: `{ \"FOO\": \"bar\", \"PATH\": \"/usr/bin\" }`. Iteration is sorted by key (BTreeMap) so two callers passing the same map get the same env-var ordering.",
                "type": "object"
              }
            ],
            "description": "Environment-variable input. Agents naturally pass `{ FOO: \"bar\" }` (matching Docker/npm/k8s mental models); the original wire shape was `Vec<\"K=V\">`. The untagged enum accepts both; `into_kv_vec()` normalises to the canonical `Vec<String>` the runner expects.\n\n`Default` is the empty vec form (the historical wire shape)."
          },
          "RunFile": {
            "description": "Optional sibling file to drop into the sandbox before the main code runs.",
            "properties": {
              "content": {
                "description": "UTF-8 file body. Streaming and base64 are not supported here; callers needing those should use `sandbox::create` + repeated `sandbox::fs::write` calls instead.",
                "type": "string"
              },
              "path": {
                "description": "Absolute path inside the sandbox guest where the file lands.",
                "type": "string"
              }
            },
            "required": [
              "content",
              "path"
            ],
            "type": "object"
          }
        },
        "examples": [
          {
            "code": "console.log('hello world')",
            "image": "node",
            "lang": "node",
            "timeout_ms": 300000
          }
        ],
        "properties": {
          "code": {
            "description": "The actual code to run. Written to a `/tmp/run.{ext}` file inside the sandbox; the chosen interpreter runs that file.",
            "type": "string"
          },
          "env": {
            "allOf": [
              {
                "$ref": "#/definitions/EnvShape"
              }
            ],
            "description": "Env vars exposed to the interpreter. Accepts both `Vec<\"K=V\">` and `{ K: V }` map shapes (same as `sandbox::exec.env`)."
          },
          "files": {
            "description": "Optional sibling files (extra source modules, config, fixtures).",
            "items": {
              "$ref": "#/definitions/RunFile"
            },
            "type": "array"
          },
          "image": {
            "description": "Catalog name of the image to boot (preset or `custom_images` key). Same value space as `sandbox::create`.",
            "type": "string"
          },
          "keep_sandbox": {
            "default": false,
            "description": "If true, the sandbox is NOT stopped after the run completes; `sandbox_id` is returned so the caller can poke around. Default false (the sandbox is torn down on either success or failure).",
            "type": "boolean"
          },
          "lang": {
            "description": "Selects the interpreter and file extension. Required. Built-in values: `\"node\"`, `\"python\"`, `\"shell\"`. Any other string is treated as a literal interpreter binary path inside the VM and the file is written to `/tmp/run.txt`. There is no default — there's no honest universal answer to \"what language is this code\", and silently defaulting to shell makes Python or JS code produce confusing line-by-line failures.",
            "type": "string"
          },
          "stdin": {
            "default": null,
            "description": "Base64-encoded bytes piped to the interpreter's stdin.",
            "type": [
              "string",
              "null"
            ]
          },
          "timeout_ms": {
            "default": null,
            "description": "Kill-after window for the interpreter, in ms. Defaults to 300_000 (5 minutes); pass a smaller value to fail fast on quick probes.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "code",
          "image",
          "lang"
        ],
        "title": "RunRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "duration_ms": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "exit_code": {
            "format": "int32",
            "type": [
              "integer",
              "null"
            ]
          },
          "sandbox_id": {
            "description": "Present only if `keep_sandbox: true` was set on the request. Otherwise null (sandbox auto-stopped).",
            "type": [
              "string",
              "null"
            ]
          },
          "stderr": {
            "type": "string"
          },
          "stdout": {
            "type": "string"
          },
          "success": {
            "type": "boolean"
          },
          "timed_out": {
            "type": "boolean"
          }
        },
        "required": [
          "duration_ms",
          "stderr",
          "stdout",
          "success",
          "timed_out"
        ],
        "title": "RunResponse",
        "type": "object"
      }
    },
    {
      "description": "Stop and remove a running sandbox. Set `wait: true` to block until the VM process exits and resources are reclaimed.",
      "metadata": {},
      "name": "sandbox::stop",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "sandbox_id": {
            "type": "string"
          },
          "wait": {
            "default": false,
            "description": "Block until the VM is fully reaped before returning.",
            "type": "boolean"
          }
        },
        "required": [
          "sandbox_id"
        ],
        "title": "StopRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "sandbox_id": {
            "type": "string"
          },
          "stopped": {
            "type": "boolean"
          }
        },
        "required": [
          "sandbox_id",
          "stopped"
        ],
        "title": "StopResponse",
        "type": "object"
      }
    }
  ],
  "triggers": []
}