# sandbox-libkrun

> libkrun sandbox adapter - serves the iii sandbox router's sandbox::* calls as sandbox-libkrun::* in libkrun microVMs, and boots Compose's worker VMs.

| field | value |
|-------|-------|
| version | 0.2.2 |
| type | binary |
| license | Elastic-2.0 |
| repo | https://github.com/iii-hq/workers |
| supported_targets | aarch64-apple-darwin, x86_64-unknown-linux-gnu, aarch64-unknown-linux-gnu |
| author | iii |

## installation

```sh
iii trigger compose::add worker=sandbox-libkrun@0.2.2
```

## configuration

```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

- `configuration` @ `latest`

## readme

# sandbox-libkrun

The libkrun sandbox adapter: it runs sandboxes and worker VMs as libkrun
microVMs on the engine's host. The engine's `sandbox::*` router forwards each
sandbox call to it as `sandbox-libkrun::<operation>`, and Compose boots
`package://` and `path://` VM workers through its `sandbox-libkrun::worker::*`
functions. You rarely call it directly: callers use `sandbox::*`, and Compose
installs this adapter when a project needs it.

## Install

Compose adds it on its own when a project needs sandboxes or VM workers. To
add it by hand on iii 0.26 or later, use the `iii-sandbox` alias, which pins
the adapter to the sandbox contract major the engine supports:

```bash
iii trigger compose::add worker=iii-sandbox
```

Older Compose has no such alias, so pin the range yourself (0.2.x implements
sandbox contract 1):

```bash
iii trigger compose::add worker=sandbox-libkrun@^0.2
```

Calling `sandbox::*` through the router needs iii 0.26 or later; the
`sandbox-libkrun::*` ids work on iii 0.24 and later.

## Quickstart

Run code in a fresh VM through the router, which forwards to this adapter:

```bash
iii trigger sandbox::run --timeout-ms 300000 \
  --json '{"image":"python","code":"print(6*7)","lang":"python"}'
```

The reply carries `stdout` (`42`), `stderr` and `exit_code`; the VM stops
when the call returns. The first call pulls the image, so it is slower. `sandbox-libkrun::run` takes the same payload directly.

## What it serves

| Functions | Purpose |
|---|---|
| `create`, `exec`, `stop`, `list`, `catalog::list`, `run` | Sandbox lifecycle and one-call runs |
| `fs::ls`, `fs::stat`, `fs::mkdir`, `fs::rm`, `fs::chmod`, `fs::mv`, `fs::grep`, `fs::sed`, `fs::write`, `fs::read` | Files inside a live sandbox |
| `adapter::info` | Adapter name, version and the sandbox contract version it implements |
| `worker::prepare`, `worker::start`, `worker::status`, `worker::logs`, `worker::stop`, `worker::destroy` | Compose's worker VMs (see below) |

Every id is prefixed with `sandbox-libkrun::`.

## Configuration

The adapter reads its configuration entry once at startup with
`configuration::get` (`III_CONFIG_NAME`, else `sandbox-libkrun`). A change
applies when the adapter restarts. A config that does not parse, or a failed
read, stops the adapter with exit code 2 instead of falling back to defaults.

```yaml
auto_install: true              # pull a missing image on first create
image_allowlist: [python, node] # bootable images; empty denies every create
default_idle_timeout_secs: 300  # idle sandboxes are reaped after this
max_concurrent_sandboxes: 32
max_concurrent_exec_per_sandbox: 4  # 0 means 1, not unlimited
max_exec_timeout_ms: 300000     # ceiling on any exec's timeout_ms
default_cpus: 1
default_memory_mb: 512
per_image_caps:                 # optional per-image ceilings
  python: { max_cpus: 4, max_memory_mb: 2048 }
custom_images:                  # extra images: name -> OCI ref
  my-app: ghcr.io/acme/my-app:1.2.3   # must also be in image_allowlist
```

A `custom_images` key may not reuse a preset name (`python`, `node`), and the
old wrapped shape (`sandbox: {...}`) is rejected.

VM tuning stays in four environment variables the adapter passes to each
worker VM (sandboxes ignore them):

| Variable | Effect |
|---|---|
| `III_VM_HYPERTHREADING=1` | Enable SMT (hyperthreading) in the guest |
| `III_VM_NESTED_VIRT=1` | Enable nested virtualization |
| `III_VM_VIRTIOFS_SHM_SIZE_MIB=<n>` | virtiofs shared-memory window; `0` skips it |
| `III_VM_NOFILE_LIMIT=<n>` | Guest open-file limit; `0` leaves it to the guest init |

## Host prerequisites

- **macOS, Apple Silicon only.** The released binary is ad-hoc signed with
  the `com.apple.security.hypervisor` and
  `com.apple.security.cs.disable-library-validation` entitlements. An
  unsigned dev build signs itself in place on first run.
- **Linux, glibc.** The Compose user needs read/write access to `/dev/kvm`.
- Any other host gets a clear `UnsupportedPlatform` error from Compose when a
  project needs the adapter.

## Worker VMs

Compose runs VM workers through `sandbox-libkrun::worker::*`. Because those
requests name host directories (`host_path`) and carry the guest's
environment, two guards apply:

- **Token.** Compose generates a per-project token and passes it to the
  adapter as `III_SANDBOX_WORKER_TOKEN`. Every `worker::*` request carries it
  in `auth`; a mismatch gets S505.
- **Host roots.** `host_path` is accepted only under the project's Compose
  directory and the Compose package cache. Anything else gets S501 naming the
  two roots.

Compose also sets `III_DISABLE_TRACE_PAYLOADS=1` on the adapter, so the token
and guest env never land in the engine's trace store.

## Known limits

The guards assume bus clients are trusted. A client that registers the
adapter's function ids or its worker name first, while the adapter is down,
receives the `auth` token and the guest `env` that Compose sends. The router
has the same limit for `sandbox::*` forwards.

## Errors

Errors carry an S-code with a `docs_url`. The full list is at
[iii.dev/docs/creating-workers/sandboxes](https://iii.dev/docs/creating-workers/sandboxes).

## api reference

```json
{
  "functions": [
    {
      "description": "Name and version of this sandbox adapter, and the sandbox contract version it implements.",
      "metadata": {},
      "name": "sandbox-libkrun::adapter::info",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Request of `<adapter>::adapter::info`; carries no fields.",
        "title": "AdapterInfoRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ContractVersion": {
            "description": "Contract version. A different major is incompatible (S302); a minor only adds fields.",
            "properties": {
              "major": {
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "minor": {
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "major",
              "minor"
            ],
            "type": "object"
          }
        },
        "description": "Reply of `<adapter>::adapter::info`.",
        "properties": {
          "adapter": {
            "description": "The adapter's name, e.g. `\"sandbox-libkrun\"`.",
            "type": "string"
          },
          "adapter_version": {
            "type": "string"
          },
          "contract": {
            "allOf": [
              {
                "$ref": "#/definitions/ContractVersion"
              }
            ],
            "description": "The contract version the adapter implements."
          }
        },
        "required": [
          "adapter",
          "adapter_version",
          "contract"
        ],
        "title": "AdapterInfo",
        "type": "object"
      }
    },
    {
      "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-libkrun::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 in the adapter's `custom_images` config. 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 key from the adapter's `custom_images` config; 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-libkrun::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, at least 1; 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 the adapter's `custom_images` config — 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, at least 64; 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-libkrun::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; `/` 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"
          },
          "stderr_truncated": {
            "description": "`stderr` was cut at the 1 MiB output cap. Since contract 1.1.",
            "type": "boolean"
          },
          "stdout": {
            "type": "string"
          },
          "stdout_truncated": {
            "description": "`stdout` was cut at the 1 MiB output cap. Since contract 1.1.",
            "type": "boolean"
          },
          "success": {
            "type": "boolean"
          },
          "timed_out": {
            "type": "boolean"
          }
        },
        "required": [
          "duration_ms",
          "stderr",
          "stderr_truncated",
          "stdout",
          "stdout_truncated",
          "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-libkrun::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-libkrun::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-libkrun::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-libkrun::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-libkrun::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-libkrun::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-libkrun::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-libkrun::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-libkrun::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-libkrun::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-libkrun::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_flight": {
                "description": "Execs running right now.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "exec_in_progress": {
                "description": "Any exec running. NOTE: this no longer implies the next exec will be refused — a sandbox admits `max_concurrent_exec_per_sandbox` at once. Use `exec_in_flight` / `exec_slots_free` for admission decisions; a caller that polls this waiting for `false` will wait forever against a resident process while slots sit idle.",
                "type": "boolean"
              },
              "exec_slots_free": {
                "description": "Execs this sandbox would admit before returning S003.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "image": {
                "type": "string"
              },
              "name": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "sandbox_id": {
                "type": "string"
              },
              "stopped": {
                "type": "boolean"
              }
            },
            "required": [
              "age_secs",
              "exec_in_flight",
              "exec_in_progress",
              "exec_slots_free",
              "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-libkrun::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"
          },
          "stderr_truncated": {
            "description": "As `ExecResponse::stderr_truncated`.",
            "type": "boolean"
          },
          "stdout": {
            "type": "string"
          },
          "stdout_truncated": {
            "description": "As `ExecResponse::stdout_truncated`.",
            "type": "boolean"
          },
          "success": {
            "type": "boolean"
          },
          "timed_out": {
            "type": "boolean"
          }
        },
        "required": [
          "duration_ms",
          "stderr",
          "stderr_truncated",
          "stdout",
          "stdout_truncated",
          "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-libkrun::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"
      }
    },
    {
      "description": "Remove the state of `state_key`'s worker VM. Needs the project token; S502 while that VM is preparing or running.",
      "metadata": {},
      "name": "sandbox-libkrun::worker::destroy",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Refused while the VM is running.",
        "properties": {
          "auth": {
            "type": "string"
          },
          "state_key": {
            "type": "string"
          }
        },
        "required": [
          "auth",
          "state_key"
        ],
        "title": "WorkerDestroyRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "destroyed": {
            "type": "boolean"
          }
        },
        "required": [
          "destroyed"
        ],
        "title": "WorkerDestroyResponse",
        "type": "object"
      }
    },
    {
      "description": "Stream a worker VM's console log into `channel`, one `LogRecord` JSON per text frame, from `cursor` on; with `follow`, until the instance exits. Needs the project token.",
      "metadata": {},
      "name": "sandbox-libkrun::worker::logs",
      "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"
          }
        },
        "properties": {
          "auth": {
            "type": "string"
          },
          "channel": {
            "allOf": [
              {
                "$ref": "#/definitions/StreamChannelRef"
              }
            ],
            "description": "Writer end of a channel Compose created; one `LogRecord` JSON per text frame."
          },
          "cursor": {
            "description": "`LogRecord.cursor` of the last record the caller has; reading resumes right after it. Opaque to Compose: an offset into all the instance logged, valid across a log rotation. A cursor past the end (another instance's) reads from the oldest record kept.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "follow": {
            "description": "Keep the channel open while the instance runs.",
            "type": "boolean"
          },
          "vm_id": {
            "type": "string"
          }
        },
        "required": [
          "auth",
          "channel",
          "follow",
          "vm_id"
        ],
        "title": "WorkerLogsRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Returned once the log pump starts; the records travel over the channel.",
        "title": "WorkerLogsResponse",
        "type": "object"
      }
    },
    {
      "description": "Validate a worker VM's source and pull its base image; idempotent. Needs the project token in `auth`, and `host_path` under the host roots.",
      "metadata": {},
      "name": "sandbox-libkrun::worker::prepare",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "WorkerSource": {
            "description": "Where the worker's code comes from.",
            "oneOf": [
              {
                "properties": {
                  "host_path": {
                    "description": "The bundle in Compose's package cache.",
                    "type": "string"
                  },
                  "kind": {
                    "enum": [
                      "bundle"
                    ],
                    "type": "string"
                  },
                  "package": {
                    "description": "Manifest name, not the container key.",
                    "type": "string"
                  }
                },
                "required": [
                  "host_path",
                  "kind",
                  "package"
                ],
                "type": "object"
              },
              {
                "description": "Local sources always sync into the VM.",
                "properties": {
                  "host_path": {
                    "type": "string"
                  },
                  "kind": {
                    "enum": [
                      "local"
                    ],
                    "type": "string"
                  },
                  "run_override": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "host_path",
                  "kind"
                ],
                "type": "object"
              }
            ]
          }
        },
        "description": "Idempotent image and base-layer pull; long-running.",
        "properties": {
          "auth": {
            "description": "Per-project token.",
            "type": "string"
          },
          "source": {
            "$ref": "#/definitions/WorkerSource"
          }
        },
        "required": [
          "auth",
          "source"
        ],
        "title": "WorkerPrepareRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "WorkerPrepareResponse",
        "type": "object"
      }
    },
    {
      "description": "Boot the worker VM of `state_key` as its next instance. Needs the project token; S502 while that VM is preparing or running.",
      "metadata": {},
      "name": "sandbox-libkrun::worker::start",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "WorkerSource": {
            "description": "Where the worker's code comes from.",
            "oneOf": [
              {
                "properties": {
                  "host_path": {
                    "description": "The bundle in Compose's package cache.",
                    "type": "string"
                  },
                  "kind": {
                    "enum": [
                      "bundle"
                    ],
                    "type": "string"
                  },
                  "package": {
                    "description": "Manifest name, not the container key.",
                    "type": "string"
                  }
                },
                "required": [
                  "host_path",
                  "kind",
                  "package"
                ],
                "type": "object"
              },
              {
                "description": "Local sources always sync into the VM.",
                "properties": {
                  "host_path": {
                    "type": "string"
                  },
                  "kind": {
                    "enum": [
                      "local"
                    ],
                    "type": "string"
                  },
                  "run_override": {
                    "type": [
                      "string",
                      "null"
                    ]
                  }
                },
                "required": [
                  "host_path",
                  "kind"
                ],
                "type": "object"
              }
            ]
          }
        },
        "description": "Boots the VM. The adapter reads resources from the worker's manifest.",
        "properties": {
          "auth": {
            "description": "Per-project token.",
            "type": "string"
          },
          "engine_url": {
            "description": "The engine's URL as seen from the host.",
            "type": "string"
          },
          "env": {
            "additionalProperties": {
              "type": "string"
            },
            "description": "Complete guest env. Never on the `__vm-boot` argv: the adapter writes it to `__vm-boot`'s stdin, which sends it to the guest init over the `iii.env` virtio-console port, not the kernel cmdline (only a build without an embedded init still uses the cmdline).",
            "type": "object"
          },
          "source": {
            "$ref": "#/definitions/WorkerSource"
          },
          "state_key": {
            "description": "Hash of the compose state dir and container key; the same key gets the same persistent upper.",
            "type": "string"
          },
          "worker_name": {
            "description": "Container key; becomes `III_WORKER_NAME`.",
            "type": "string"
          }
        },
        "required": [
          "auth",
          "engine_url",
          "env",
          "source",
          "state_key",
          "worker_name"
        ],
        "title": "WorkerStartRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "instance": {
            "description": "Increments on every boot of this `vm_id`.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "vm_id": {
            "description": "Opaque; stable per `state_key`.",
            "type": "string"
          }
        },
        "required": [
          "instance",
          "vm_id"
        ],
        "title": "WorkerStartResponse",
        "type": "object"
      }
    },
    {
      "description": "State of one worker VM instance. Needs the project token.",
      "metadata": {},
      "name": "sandbox-libkrun::worker::status",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "auth": {
            "type": "string"
          },
          "instance": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "vm_id": {
            "type": "string"
          }
        },
        "required": [
          "auth",
          "instance",
          "vm_id"
        ],
        "title": "WorkerStatusRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "State": {
            "enum": [
              "preparing",
              "running",
              "exited",
              "stopped",
              "unknown"
            ],
            "type": "string"
          }
        },
        "description": "Reply of `worker::status` and `worker::stop`.",
        "properties": {
          "ended_at_ms": {
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "exit_code": {
            "format": "int32",
            "type": [
              "integer",
              "null"
            ]
          },
          "instance": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "signal": {
            "description": "The signal that ended the VM, if one did.",
            "format": "int32",
            "type": [
              "integer",
              "null"
            ]
          },
          "started_at_ms": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "state": {
            "$ref": "#/definitions/State"
          },
          "vm_id": {
            "type": "string"
          }
        },
        "required": [
          "instance",
          "started_at_ms",
          "state",
          "vm_id"
        ],
        "title": "WorkerStatus",
        "type": "object"
      }
    },
    {
      "description": "SIGTERM a worker VM instance, then SIGKILL after `grace_ms` (0 kills at once); returns its final status. Needs the project token.",
      "metadata": {},
      "name": "sandbox-libkrun::worker::stop",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Idempotent; `grace_ms: 0` kills. Replies with the final `WorkerStatus`.",
        "properties": {
          "auth": {
            "type": "string"
          },
          "grace_ms": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "instance": {
            "description": "Names the boot to stop, so a stale stop cannot hit a newer one.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "vm_id": {
            "type": "string"
          }
        },
        "required": [
          "auth",
          "grace_ms",
          "instance",
          "vm_id"
        ],
        "title": "WorkerStopRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "State": {
            "enum": [
              "preparing",
              "running",
              "exited",
              "stopped",
              "unknown"
            ],
            "type": "string"
          }
        },
        "description": "Reply of `worker::status` and `worker::stop`.",
        "properties": {
          "ended_at_ms": {
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "exit_code": {
            "format": "int32",
            "type": [
              "integer",
              "null"
            ]
          },
          "instance": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "signal": {
            "description": "The signal that ended the VM, if one did.",
            "format": "int32",
            "type": [
              "integer",
              "null"
            ]
          },
          "started_at_ms": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "state": {
            "$ref": "#/definitions/State"
          },
          "vm_id": {
            "type": "string"
          }
        },
        "required": [
          "instance",
          "started_at_ms",
          "state",
          "vm_id"
        ],
        "title": "WorkerStatus",
        "type": "object"
      }
    }
  ],
  "triggers": []
}
```
