skip to content
$worker

shell

v0.5.4

Unix shell + filesystem worker — exec with denylist/timeout/output caps and background jobs; fs::ls|stat|mkdir|rm|chmod|mv|grep|sed|read|write with host jail, denylist, size caps, and sandbox-target forwarding

iiiverified
406 installs112 in 7d29 today
install
$iii worker add shell@0.5.4
  • macOS: arm64 · x64
  • Linux: arm64 · armv7 · x64

exact versions are immutable; binary and bundle artifacts are digest-pinned.

agent-ready brief for v0.5.4
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/shell.md?version=0.5.4. paste it into an llm prompt or pipe it through curl from a worker.

install

install
$iii worker add shell@0.5.4

configuration

iii-config.yaml
- allowed_env:
    - PATH
    - HOME
    - LANG
    - LC_ALL
    - TERM
  allowlist:

  default_timeout_ms: 10000
  denylist_patterns:
    - rm\s+-rf\s+/
    - :\(\)\s*\{\s*:\|
    - mkfs
    - dd\s+if=
    - shutdown
    - reboot
    - /etc/shadow
  fs:
    allow_unjailed: false
    denylist_paths:
      - /etc/passwd
      - /etc/shadow
    host_root: /tmp
    max_read_bytes: 16777216
    max_write_bytes: 16777216
  inherit_env: true
  job_retention_secs: 3600
  max_bg_timeout_ms: 0
  max_concurrent_jobs: 16
  max_output_bytes: 1048576
  max_timeout_ms: 120000
  sandbox:
    enabled: true
  working_dir: null

dependencies

no dependencies for v0.5.4

readme

README.md

shell

Run allowlisted Unix commands, background jobs, and structured filesystem operations from the iii engine, on the host or forwarded into a sandbox microVM.

Install

iii worker add shell

Sandbox-targeted execution and shell::fs::* forwarding need the iii-sandbox worker; iii worker add shell does not pull it in. To surface shell::* to LLM agents, pair with iii-directory:

iii worker add iii-sandbox
iii worker add iii-directory

Skills

Install the shell agent skill for Claude Code, Cursor, and 30+ other agents:

npx skills add iii-hq/workers --skill shell

Browse or install every worker skill at once:

npx skills add iii-hq/workers --list
npx skills add iii-hq/workers --all

Configure

Settings are managed through the central configuration worker. On boot, the shell worker registers its schema (id shell) and fetches the live value over RPC — that live value is the authoritative config, not a local file. The optional --config flag (default ./config.yaml) provides the initial_value sent on first registration only; once registered, subsequent boots pull the stored value from the configuration worker. When the config changes, the worker hot-reloads the security policy and fs backend automatically (see Hot-reload).

The worker refuses to start unless fs.host_root is set, or fs.allow_unjailed: true is explicitly opted in, because an unset root exposes the whole host filesystem behind only the advisory denylist.

By default, mkdir/chmod/write reject modes carrying setuid/setgid/sticky bits (the top octal digit, e.g. 4755) with S210, since they are a privilege-escalation primitive when the worker runs as root inside the jail. Set fs.allow_special_bits: true only if your workload genuinely needs them.

max_timeout_ms: 120000       # foreground exec hard cap; per-call timeout_ms is clamped to this
max_bg_timeout_ms: 0         # host bg job hard cap in ms; 0 = unbounded (foreground uses max_timeout_ms)
default_timeout_ms: 10000    # applied when the caller omits timeout_ms
max_output_bytes: 1048576    # 1 MiB; stdout/stderr past this set *_truncated
inherit_env: true            # forward the worker's env to children; per-call dangerous keys still blocked
allowed_env: [PATH, HOME, LANG, LC_ALL, TERM]  # gates per-call `env` (dangerous keys never settable)

# exec gate. argv[0] is matched by basename or exact path; an empty
# allowlist means OPEN — the shipped default, so any command runs.
# denylist_patterns are advisory regex over argv.join(" "), a tripwire for
# catastrophic mistakes only, NOT a security boundary.
allowlist: []
denylist_patterns:
  - "rm\\s+-rf\\s+/"
  - "mkfs"
  - "dd\\s+if="

max_concurrent_jobs: 16      # exec_bg past this is rejected
job_retention_secs: 3600     # finished jobs pruned after this

fs:
  host_root: /tmp            # jail root for shell::fs::*; required (see above)
  allow_unjailed: false      # opt-in to running with host_root unset
  max_read_bytes: 16777216   # 0 = unlimited
  max_write_bytes: 16777216  # 0 = unlimited
  denylist_paths: [/etc/passwd, /etc/shadow]
  allow_special_bits: false  # permit setuid/setgid/sticky bits in mode (default false)

sandbox:
  enabled: true              # false -> every target: sandbox call returns S210

Zero-config default

With no --config file and no value stored in the configuration worker, the worker seeds a built-in default on first registration — so it boots with nothing configured (database-style). That built-in default is the shipped config.yaml: jailed to /tmp, env forwarded, open exec with a catastrophic-only denylist (kept in sync by a unit test). If the stored value is later nulled, the worker does not silently fall back to this seed: boot fails closed and a hot-reload keeps the last-good config. A config that is present but leaves fs.host_root unset (without fs.allow_unjailed: true) also fails closed.

Host shell::exec is not a security boundary: any allowlisted interpreter (sh, node, python3) can construct a denylisted token at runtime and bypass the regex. Run untrusted input with target: { kind: "sandbox", sandbox_id }, which forwards through the iii-sandbox microVM. The allowlist and denylist still apply on top of either backend.

Per-call cwd, env, and stdin (host target)

shell::exec and shell::exec_bg each accept optional fields so an agent can scope a single command to a directory, set specific env values, and feed it standard input without wrapping everything in sh -lc (which would defeat the argv allowlist):

  • cwd (string): the working directory for this one call. It is confined to the fs jail exactly like shell::fs::* paths — jail-relative when fs.host_root is set (else absolute), canonicalized, and required to resolve inside host_root and miss denylist_paths. A cwd that escapes the jail returns S215; one that doesn't exist or isn't a directory returns S211/S210. Omit it to use the configured working_dir (unchanged default).
  • env (object of string→string): per-call environment values. A key may be set only if the operator already listed it in allowed_env, and never for an exec-hijacking key — PATH, IFS, HOME, every LD_*/DYLD_* variant, and other loader/lookup-path and interpreter startup-file keys (GCONV_PATH, BASH_ENV, ENV, PYTHONSTARTUP, PERL5OPT, RUBYOPT, NODE_OPTIONS, …) are on a hardcoded denylist that wins over allowed_env. Note that HOME ships in the default allowed_env for the worker's own forwarded env but is not settable per-call. Supplying a key that is not in allowed_env, or any dangerous key, rejects the whole call with S210 (the offending key is named and the permitted keys are listed); the env is never silently dropped. A permitted per-call value overrides the value that would otherwise be forwarded for that key. So an agent can do NODE_ENV=test only if the operator put NODE_ENV in allowed_env, and can never inject PATH, HOME, or LD_PRELOAD.
  • stdin (string): written to the program's standard input, which is then closed (EOF). Use it to feed tee, patch, cat, or any stdin filter instead of a shell heredoc. Omit it and stdin is /dev/null.

All three fields are host-only. The sandbox::exec protocol does not forward cwd/env/stdin, so a sandbox-targeted call that supplies any of them is rejected with S210 rather than silently ignoring it. Omit them and behaviour is identical to prior versions.

Quick start

import { registerWorker } from 'iii-sdk'

const iii = registerWorker(process.env.III_URL ?? 'ws://127.0.0.1:49134')

const result = await iii.trigger({
  function_id: 'shell::exec',
  payload: { command: 'echo', args: ['hello'] },
})

console.log(result)

The example runs on the host. The same payload retargets at a microVM with target: { kind: 'sandbox', sandbox_id: '' }. The other entry points are shell::exec_bg, shell::status, shell::kill, shell::list, shell::config-status, plus the shell::fs::* family (ls, stat, read, write, grep, sed, mkdir, rm, chmod, mv).

Functions

Function Purpose
shell::exec Run an allowlisted command in the foreground; returns stdout, stderr, exit code, and timing. Blocks until exit or timeout. Accepts optional host-only cwd (jail-confined), env (gated by allowed_env + a dangerous-key denylist), and stdin (string piped to the program's stdin, then EOF) — see Per-call cwd, env, and stdin.
shell::exec_bg Spawn an allowlisted command as a background job; returns { job_id, argv } immediately. Host-targeted jobs run until they exit or shell::kill terminates them — unbounded by default, and capped only when the operator sets a positive max_bg_timeout_ms (default 0 = unbounded), after which a runaway job is killed and its status becomes killed. Sandbox jobs honor timeout_ms. Same optional host-only cwd/env/stdin as shell::exec.
shell::status Fetch one job's full record: state, exit code, and captured stdout/stderr. A missing id — one that never existed or aged out past job_retention_secs — returns an S211 ("no such job") error.
shell::list Enumerate current jobs as lightweight summaries; argv, stdout, and stderr are redacted.
shell::kill Terminate a running background job by job_id. Sandbox jobs cannot be hard-killed: the record flips to killed but the in-VM process runs until its timeout_ms (or sandbox::stop).
shell::config-status Report the last hot-reload outcome: last_outcome (applied/rejected), last_error, and rejected_reloads (count since boot). A rejected outcome or non-zero count means a stored config was refused and shell is enforcing an older policy than the central store. Takes no arguments.
shell::fs::ls List a directory's entries with structured metadata.
shell::fs::stat Read one path's metadata (size, mode, symlink flag).
shell::fs::mkdir Create a directory, optionally with missing parents. Returns { created, path, already_existed }.
shell::fs::rm Remove a file or directory, optionally recursive. Returns { removed, path, was_present }.
shell::fs::chmod Change a path's mode, and optionally its uid/gid. Returns { entries_changed, path, recursive }. (Breaking: field renamed from updated to entries_changed.)
shell::fs::mv Rename or move one path within the jail. Returns { moved, src, dst, overwrote }.
shell::fs::grep Recursive regex search across a tree; returns structured matches. Keys are singular include_glob/exclude_glob; the case flag is ignore_case.
shell::fs::sed Regex find-and-replace across one file or many.
shell::fs::write Write a file. Simplest form passes inline string content (host target only): { path, content: "file text" }, with mode (octal, default "0644") and parents: true to create parents. A ContentRef object in content instead streams large/staged payloads through an SDK channel (temp file + atomic rename) and is required for sandbox targets — an inline string on a sandbox target returns S210. Batch form: pass files: [{ path, content, mode?, parents? }, ...] (host, inline per file) to write several files in one call; the response then carries per-file files: [{ path, bytes_written }]. A single-file write leaves files empty and returns { bytes_written, path }. Supplying both single path/content and files returns S210.
shell::fs::read Stream a file's bytes out through an SDK channel. For an inline read on the web surface, use the harness::fs::read_inline wrapper instead.

Every shell::fs::* call accepts the same optional target as exec, so host and sandbox share one wire shape.

Hot-reload

When the configuration worker pushes an updated config, the shell worker swaps in the new security policy and fs backend atomically. A few things to know:

  • Each call executes against one consistent runtime snapshot; there is no mid-call config change.
  • Already-running background jobs are not retroactively re-checked when the policy tightens — they continue under the policy that was active when they were spawned.
  • A reload that widens the jail (for example, clearing host_root) succeeds but is logged as a privilege change.
  • If the incoming config is invalid or unsafe, the worker keeps the last-good runtime and logs an error. The rejection is also surfaced through shell::config-status (a rejected outcome with a non-zero rejected_reloads count), so the divergence between the central store and the policy shell is actually enforcing is detectable instead of silent. Rejections are kept last-good and not retried (re-fetching returns the same bad value), so they will not retry-storm.
  • At boot the reconcile against the configuration worker is fail-closed: the worker refuses to start (and exposes no functions) if it cannot confirm the authoritative config, so it never serves a possibly stale security policy.

Errors

Returned error bodies carry a stable code field. Allowlist and denylist rejections come back as a plain message (command '' not in allowlist, command matches denylist: ) rather than an S-code.

Code Meaning
S200 In-VM execution failure on a sandbox target.
S210 Invalid request: non-absolute path, empty command or pattern, bad octal mode, malformed payload, sandbox.enabled: false on a sandbox-targeted call, a cwd that is not a directory, an env key outside allowed_env or in the dangerous-key denylist, cwd/env/stdin supplied on a sandbox target (host-only), an inline string content on a sandbox-targeted shell::fs::write, or both single path/content and files on shell::fs::write.
S211 Path not found (including a cwd that does not exist).
S212 Wrong file type for the operation (for example, a file where a directory was expected).
S213 Path already exists.
S214 Directory not empty (non-recursive rm).
S215 Path (or a per-call cwd) escapes host_root, hits fs.denylist_paths, or permission denied.
S216 Generic shell-internal failure: host spawn error, channel error, or a bad engine response.
S217 Invalid regex passed to grep/sed.
S218 fs.max_read_bytes / fs.max_write_bytes cap exceeded.
S300 Sandbox VM boot failed (needs a virtualization host: Apple Silicon or /dev/kvm).

Sandbox-forwarded fs::*/exec errors can also surface engine codes verbatim instead of collapsing to S216: S001S004 (sandbox lifecycle), S100S102 (image/VM/resource), S300, and S400. Branch on the specific code where relevant; only an unrecognized engine code falls back to S216.

Upgrading to 0.4.0

0.4.0 is a breaking release. Migrating from 0.3.x:

  • shell::fs::chmod response field renamed updatedentries_changed; read entries_changed. Inbound legacy { updated } from an older engine still deserializes (serde alias), but new consumers should read entries_changed.
  • mkdir/rm/mv gained structured fields (path/already_existed, path/was_present, src/dst/overwrote) alongside the original boolean. Additive: existing readers of created/removed/moved keep working.
  • Configuration moved to the central configuration worker (database-style) with hot-reload. --config is now a first-boot seed only; the live value from the configuration worker wins once an entry exists. --manifest was removed (the interface is collected live).
  • New read-only shell::config-status reports the last hot-reload outcome. Operator/automation only; it is denied to agents.
  • Error envelope: the S-code is now the top-level wire code. Every S-coded failure returns { "code": "S211", "message": "no such job: ..." } with the S-code as the top-level code. Previously the code was buried — handlers returned the {code,message} JSON stringified into message under the generic invocation_failed code. Consumers that branched on code == "invocation_failed" or parsed the embedded JSON out of the message must update to read error.code directly.
  • Host background jobs are unbounded by default. A host-targeted shell::exec_bg job runs until it exits or shell::kill terminates it — long installs, builds, dev-servers, and tails are not killed. To force-kill a runaway host bg job, set a positive max_bg_timeout_ms (new operator config field, default 0 = unbounded; separate from max_timeout_ms, which bounds foreground shell::exec); a job that exceeds it is killed and its status becomes killed. Sandbox jobs still honor timeout_ms.
  • Per-call env denylist broadened. The always-rejected key set now includes HOME and loader/lookup-path keys (LD_*, DYLD_*, GCONV_PATH, …) and interpreter/shell startup-file keys (BASH_ENV, ENV, PYTHONSTARTUP, PERL5OPT, RUBYOPT, NODE_OPTIONS). These are rejected even if listed in allowed_env. LD_*/DYLD_* are matched by family prefix, so a loader variable not in the explicit list is still rejected. Note that HOME is in the default allowed_env for the worker's own forwarded env but is not settable per-call.
  • Sandbox-forwarded fs/exec errors can now surface engine S-codes that 0.3.7 collapsed to S216 — e.g. S001S004 (lifecycle), S100S102 (image/VM), S300, and S400. Branch on the specific code where relevant.

Troubleshooting

  • fs.host_root is unset ... refusing to start unjailed: set fs.host_root to a directory, or set fs.allow_unjailed: true.
  • command '' not in allowlist: the basename of argv[0] is not in a non-empty allowlist. Add it, or empty the list to allow anything.
  • S215 path escapes host_root on a path inside the jail: a symlink in the path resolves outside the jail. Resolve it yourself, or move the target inside host_root.
  • S300 on a sandbox target: the host cannot boot microVMs. Sandbox execution requires Apple Silicon or /dev/kvm.
  • Worker never connects: the engine is not running or not bound on the configured --url. Start the engine first; the default WebSocket port is 49134.

For the threat model, streaming wire shapes, and contributor build steps, see ARCHITECTURE.md.

License

Apache 2.0 — see LICENSE.

api reference (json)

agent-api-reference.json
{
  "functions": [
    {
      "description": "Report the last configuration hot-reload outcome: last_outcome (applied|rejected), last_error, and rejected_reloads (count since boot). A rejected outcome or non-zero count means a stored config was refused and shell is enforcing an older policy than the central store. Takes no arguments.",
      "metadata": {},
      "name": "shell::config-status",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "`shell::config-status` takes no arguments; this empty struct publishes an accurate (empty-object) request schema, mirroring [`ListRequest`]. The response is `configuration::ReloadStatus`. Like [`ListRequest`], it is NOT `deny_unknown_fields` — the engine-injected `_caller_worker_id` would otherwise be rejected; the handler ignores the payload.",
        "title": "ConfigStatusRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ReloadOutcome": {
            "description": "Outcome of the most recent hot-reload attempt, exposed via `shell::config-status`.",
            "oneOf": [
              {
                "description": "The live runtime reflects the most recent configuration the worker loaded.",
                "enum": [
                  "applied"
                ],
                "type": "string"
              },
              {
                "description": "The most recent configuration:updated delivered a value the worker could NOT build; the previous valid policy is still active and the central store has DIVERGED from what shell is enforcing.",
                "enum": [
                  "rejected"
                ],
                "type": "string"
              }
            ]
          }
        },
        "description": "Operator-visible hot-reload status. `rejected_reloads > 0` (or `last_outcome == Rejected`) means a stored config was refused and shell is enforcing an older policy than the central store — actionable divergence.",
        "properties": {
          "last_error": {
            "description": "Build error from the most recent rejected reload (why it was refused).",
            "type": [
              "string",
              "null"
            ]
          },
          "last_outcome": {
            "$ref": "#/definitions/ReloadOutcome"
          },
          "rejected_reloads": {
            "description": "Cumulative count of rejected reloads since boot (never reset).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "last_outcome",
          "rejected_reloads"
        ],
        "title": "ReloadStatus",
        "type": "object"
      }
    },
    {
      "description": "Run an allowlisted command in the foreground and return its full output. Put the program name in `command` (string) and arguments in `args` (string[]) — do NOT pass argv as an array in `command`. `timeout_ms` is capped at the configured max; negative/fractional values fall back to the default. `target` defaults to the host; pass { kind: \"sandbox\", sandbox_id } to run in a microVM. Optional host-only `cwd` scopes this call to a directory (jail-confined exactly like shell::fs::* paths; escaping it is S215), optional `env` (object) sets per-call values — but only for keys already in allowed_env and never for PATH/IFS/HOME/LD_*/DYLD_* or other loader/lookup and interpreter-startup keys (those reject S210) — and optional host-only `stdin` (string) is written to the program's standard input (use it for `tee`, `patch`, or any stdin filter instead of a shell heredoc). cwd/env/stdin on a sandbox target reject S210. Backend errors return { code, message }; common: S216 host exec error, S300 VM boot failed, S200 in-VM failure. argv-parse and allowlist/denylist rejections are plain-string messages naming the violation.",
      "metadata": {},
      "name": "shell::exec",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "description": "Wire request for `shell::exec`. The schema is published to the engine's tool listing so callers see field types up front instead of guessing from the description.",
        "properties": {
          "args": {
            "default": null,
            "description": "Arguments passed to the program, in order. Every element must be a string; non-string elements are rejected by index. `None` (or `args: null` / absent) means \"tokenize `command` via shell-words\"; `Some(_)` (including the empty vec) means \"use args verbatim, no shell-words.\" See `parse_argv` in `crate::exec::host`.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "command": {
            "description": "Program name (matched against the allowlist by basename or exact path). Must be a string — split arguments into `args`, do not pass argv as an array here.",
            "type": "string"
          },
          "cwd": {
            "default": null,
            "description": "Optional working directory for this call (host target only). Confined to the fs jail exactly like `shell::fs::*` paths: jail-relative when `fs.host_root` is set (else absolute), canonicalized, and must resolve inside `host_root` and miss the denylist — a path that escapes returns S215. Must already exist and be a directory. Omit to use the configured `working_dir` (unchanged default). Rejected (S210) on a sandbox target.",
            "type": [
              "string",
              "null"
            ]
          },
          "env": {
            "additionalProperties": {
              "type": "string"
            },
            "default": null,
            "description": "Optional per-call environment values (host target only). A key may be set ONLY if the operator listed it in `allowed_env`, and NEVER for an exec-hijacking key (PATH, IFS, HOME, LD_*/DYLD_*, and other loader/lookup and interpreter-startup keys — see DANGEROUS_ENV_KEYS) — those are rejected even if allowlisted. Supplying a key that is not in `allowed_env`, or any dangerous key, rejects the WHOLE call (S210) naming the offending key; the env is never silently dropped. Permitted values override what would otherwise be forwarded for that key. Rejected (S210) on a sandbox target.",
            "type": [
              "object",
              "null"
            ]
          },
          "stdin": {
            "default": null,
            "description": "Optional bytes written to the program's standard input, followed by EOF. Use this to pipe content to a command that reads stdin (`tee`, `patch`, `cat`, a filter) instead of wrapping it in a shell heredoc. Omit to leave stdin closed (`/dev/null`, the default). HOST target only — rejected (S210) on a sandbox target, like cwd/env.",
            "type": [
              "string",
              "null"
            ]
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "Where to run the command. Defaults to the host worker; pass `{ kind: \"sandbox\", sandbox_id }` to forward the call to a microVM."
          },
          "timeout_ms": {
            "default": null,
            "description": "Per-call timeout override, milliseconds. Capped at `cfg.max_timeout_ms`. Negative or fractional values silently fall back to `cfg.default_timeout_ms` (loose wire semantic, preserved on purpose).",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "command"
        ],
        "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": {
            "type": "boolean"
          },
          "stdout": {
            "type": "string"
          },
          "stdout_truncated": {
            "type": "boolean"
          },
          "timed_out": {
            "type": "boolean"
          }
        },
        "required": [
          "duration_ms",
          "stderr",
          "stderr_truncated",
          "stdout",
          "stdout_truncated",
          "timed_out"
        ],
        "title": "ExecResponse",
        "type": "object"
      }
    },
    {
      "description": "Spawn an allowlisted command as a background job; returns { job_id, argv } immediately. Same payload as shell::exec (command + args, do NOT pass argv as an array), including the optional host-only `cwd` (jail-confined; escape is S215), `env` (only allowed_env keys, never PATH/IFS/HOME/LD_*/DYLD_* or other loader/lookup and interpreter-startup keys), and `stdin` (string written to the job's stdin); violations and cwd/env/stdin on a sandbox target reject with an S210 message. Poll with shell::status, terminate with shell::kill, list with shell::list. Host background jobs ignore the per-call timeout_ms and run until they exit or shell::kill terminates them; set the operator config `max_bg_timeout_ms` (0 = unbounded, the default) to force-kill a runaway job after that long. Spawn-time failures (argv-parse, allowlist/denylist, cwd/env gating, spawn, concurrency cap) are plain-string messages naming the violation; once spawned, per-job failures surface through shell::status (the job record's status/stderr), not this call's return.",
      "metadata": {},
      "name": "shell::exec_bg",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "description": "Wire request for `shell::exec_bg`. Same shape as [`ExecRequest`]; documented separately so the engine publishes a distinct schema per function.",
        "properties": {
          "args": {
            "default": null,
            "description": "Arguments passed to the program. See [`ExecRequest::args`]. `None` (or `args: null` / absent) means \"tokenize `command` via shell-words\"; `Some(_)` (including the empty vec) means \"use args verbatim, no shell-words.\" See `parse_argv` in `crate::exec::host`.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "command": {
            "description": "Program name. See [`ExecRequest::command`].",
            "type": "string"
          },
          "cwd": {
            "default": null,
            "description": "Optional working directory for this job (host target only). Same jail confinement and rules as [`ExecRequest::cwd`]: canonicalized, must resolve inside `host_root` (S215 on escape) and be an existing directory. Rejected (S210) on a sandbox target.",
            "type": [
              "string",
              "null"
            ]
          },
          "env": {
            "additionalProperties": {
              "type": "string"
            },
            "default": null,
            "description": "Optional per-call environment values (host target only). Same gating as [`ExecRequest::env`]: a key must be in `allowed_env` and must not be an exec-hijacking key (PATH, IFS, HOME, LD_*/DYLD_*, and other loader/lookup and interpreter-startup keys — see DANGEROUS_ENV_KEYS); any violation rejects the whole call (S210). Rejected (S210) on a sandbox target.",
            "type": [
              "object",
              "null"
            ]
          },
          "stdin": {
            "default": null,
            "description": "Optional bytes written to the job's standard input, then EOF. See [`ExecRequest::stdin`]. HOST target only — rejected (S210) on a sandbox target.",
            "type": [
              "string",
              "null"
            ]
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "Where to run. See [`ExecRequest::target`]."
          },
          "timeout_ms": {
            "default": null,
            "description": "Per-call timeout. Host-targeted background jobs IGNORE `timeout_ms`; sandbox-targeted ones forward it through `cfg.resolve_timeout`.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "command"
        ],
        "title": "ExecBgRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "argv": {
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "job_id": {
            "type": "string"
          }
        },
        "required": [
          "argv",
          "job_id"
        ],
        "title": "ExecBgResponse",
        "type": "object"
      }
    },
    {
      "description": "Change permissions. `mode` is an octal string like \"0644\". `uid`/`gid` optionally chown. `recursive: true` walks the tree (symlinks skipped). Returns { entries_changed, path, recursive }. Errors return { code, message }; common: S210 bad mode, S211 not found, S215 jail/denylist.",
      "metadata": {},
      "name": "shell::fs::chmod",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "gid": {
            "default": null,
            "description": "Optional chown to this numeric gid.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "mode": {
            "description": "Octal permission string, e.g. \"0755\".",
            "type": "string"
          },
          "path": {
            "description": "Jail-relative when fs.host_root is set, else absolute.",
            "type": "string"
          },
          "recursive": {
            "default": false,
            "description": "Apply mode/owner change to all files under the path recursively.",
            "type": "boolean"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          },
          "uid": {
            "default": null,
            "description": "Optional chown to this numeric uid.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "required": [
          "mode",
          "path"
        ],
        "title": "ChmodRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "entries_changed": {
            "description": "Number of filesystem entries whose mode/owner changed.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "path": {
            "default": "",
            "description": "The path that was targeted. Empty for sandbox targets.",
            "type": "string"
          },
          "recursive": {
            "default": false,
            "description": "Whether the change was applied recursively. Host only.",
            "type": "boolean"
          }
        },
        "required": [
          "entries_changed"
        ],
        "title": "ChmodResponse",
        "type": "object"
      }
    },
    {
      "description": "Search file contents. `pattern` is a Rust regex (RE2-like). `recursive` defaults true. `include_glob`/`exclude_glob` filter paths. Returns { matches, truncated }. Errors return { code, message }; common: S217 bad regex, S215 jail/denylist.",
      "metadata": {},
      "name": "shell::fs::grep",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "exclude_glob": {
            "default": [],
            "description": "Glob filters excluding file paths from the search.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "ignore_case": {
            "default": false,
            "description": "Match pattern case-insensitively.",
            "type": "boolean"
          },
          "include_glob": {
            "default": [],
            "description": "Glob filters restricting which file paths are searched.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "max_line_bytes": {
            "default": 4096,
            "description": "Skip lines longer than this many bytes (default 4 096).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "max_matches": {
            "default": 10000,
            "description": "Stop collecting matches after this many results (default 10 000).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "path": {
            "description": "Jail-relative when fs.host_root is set, else absolute.",
            "type": "string"
          },
          "pattern": {
            "description": "Rust regex (RE2-like) matched against each line.",
            "type": "string"
          },
          "recursive": {
            "default": true,
            "description": "Descend into subdirectories (default true).",
            "type": "boolean"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "path",
          "pattern"
        ],
        "title": "GrepRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "FsMatch": {
            "properties": {
              "content": {
                "type": "string"
              },
              "line": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "path": {
                "type": "string"
              }
            },
            "required": [
              "content",
              "line",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "matches": {
            "description": "All collected match locations up to `max_matches`.",
            "items": {
              "$ref": "#/definitions/FsMatch"
            },
            "type": "array"
          },
          "truncated": {
            "description": "True when the result was capped by `max_matches` or `max_line_bytes`.",
            "type": "boolean"
          }
        },
        "required": [
          "matches",
          "truncated"
        ],
        "title": "GrepResponse",
        "type": "object"
      }
    },
    {
      "description": "List directory contents. `path` is relative to the configured fs jail root (fs.host_root) when set, otherwise absolute. `target` defaults to host; pass { kind: \"sandbox\", sandbox_id } to run in a microVM. Errors return { code, message }; common: S210 bad path, S211 not found, S212 not a directory, S215 jail/denylist.",
      "metadata": {},
      "name": "shell::fs::ls",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "path": {
            "description": "Jail-relative when fs.host_root is set, else absolute.",
            "type": "string"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "path"
        ],
        "title": "LsRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "FsEntry": {
            "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": {
            "description": "Metadata for each entry in the directory.",
            "items": {
              "$ref": "#/definitions/FsEntry"
            },
            "type": "array"
          }
        },
        "required": [
          "entries"
        ],
        "title": "LsResponse",
        "type": "object"
      }
    },
    {
      "description": "Create a directory. `mode` is an octal string like \"0755\". `parents: true` creates missing parents and is idempotent. Returns { created, path, already_existed }. Errors return { code, message }; common: S210 bad mode, S213 exists, S215 jail/denylist.",
      "metadata": {},
      "name": "shell::fs::mkdir",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "mode": {
            "default": "0755",
            "description": "Octal permission string, e.g. \"0755\".",
            "type": "string"
          },
          "parents": {
            "default": false,
            "description": "Create missing parent directories.",
            "type": "boolean"
          },
          "path": {
            "description": "Jail-relative when fs.host_root is set, else absolute.",
            "type": "string"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "path"
        ],
        "title": "MkdirRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "already_existed": {
            "default": false,
            "description": "True when the path already existed and `parents` was set. Host only; sandbox targets default this to false (not a signal there).",
            "type": "boolean"
          },
          "created": {
            "description": "True when a new directory was created; false when it already existed (only possible with `parents: true`, which is idempotent).",
            "type": "boolean"
          },
          "path": {
            "default": "",
            "description": "The directory path that was targeted. Empty for sandbox targets.",
            "type": "string"
          }
        },
        "required": [
          "created"
        ],
        "title": "MkdirResponse",
        "type": "object"
      }
    },
    {
      "description": "Move/rename a path. `overwrite: true` allows replacing an existing dst. Returns { moved, src, dst, overwrote }. Errors return { code, message }; common: S211 src not found, S213 dst exists, S215 jail/denylist.",
      "metadata": {},
      "name": "shell::fs::mv",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "dst": {
            "description": "Destination path; jail-relative when fs.host_root is set, else absolute.",
            "type": "string"
          },
          "overwrite": {
            "default": false,
            "description": "Replace an existing destination instead of returning an error.",
            "type": "boolean"
          },
          "src": {
            "description": "Source path; jail-relative when fs.host_root is set, else absolute.",
            "type": "string"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "dst",
          "src"
        ],
        "title": "MvRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "dst": {
            "default": "",
            "description": "Destination path. Empty for sandbox targets.",
            "type": "string"
          },
          "moved": {
            "description": "True when the move/rename succeeded.",
            "type": "boolean"
          },
          "overwrote": {
            "default": false,
            "description": "True when an existing destination was overwritten. Host only (sandbox targets default this to false, not a signal there). Best-effort: derived from a pre-rename existence check, so under a concurrent writer racing the destination it may under-report (and an `overwrite:false` move can still replace a file created in that window).",
            "type": "boolean"
          },
          "src": {
            "default": "",
            "description": "Source path. Empty for sandbox targets.",
            "type": "string"
          }
        },
        "required": [
          "moved"
        ],
        "title": "MvResponse",
        "type": "object"
      }
    },
    {
      "description": "Stream a file from a path. Returns a ContentRef the caller reads from, plus size/mode/mtime. Errors return { code, message }; common: S211 not found, S212 path is a directory, S215 jail/denylist, S218 file exceeds max_read_bytes, S216 channel/IO error.",
      "metadata": {},
      "name": "shell::fs::read",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "path": {
            "description": "Jail-relative when fs.host_root is set, else absolute.",
            "type": "string"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "path"
        ],
        "title": "ReadRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ContentDirection": {
            "enum": [
              "read",
              "write"
            ],
            "type": "string"
          },
          "ContentRef": {
            "description": "Wire-identical mirror of `iii_sdk::channels::StreamChannelRef`. The SDK type lacks `JsonSchema` in 0.11.3, which would block typed registration of `shell::fs::write`/`read`.",
            "properties": {
              "access_key": {
                "description": "Secret key that authorises access to this channel.",
                "type": "string"
              },
              "channel_id": {
                "description": "Opaque identifier for the open stream channel.",
                "type": "string"
              },
              "direction": {
                "allOf": [
                  {
                    "$ref": "#/definitions/ContentDirection"
                  }
                ],
                "default": "read",
                "description": "Direction of data flow: \"read\" (consume) or \"write\" (produce)."
              }
            },
            "required": [
              "access_key",
              "channel_id"
            ],
            "type": "object"
          }
        },
        "properties": {
          "content": {
            "allOf": [
              {
                "$ref": "#/definitions/ContentRef"
              }
            ],
            "description": "Channel reference for streaming the file content back to the caller."
          },
          "mode": {
            "description": "Octal permission string of the file, e.g. \"0644\".",
            "type": "string"
          },
          "mtime": {
            "description": "Last-modified time as a Unix timestamp (seconds).",
            "format": "int64",
            "type": "integer"
          },
          "size": {
            "description": "File size in bytes at the time of the read.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "content",
          "mode",
          "mtime",
          "size"
        ],
        "title": "ReadResponseWire",
        "type": "object"
      }
    },
    {
      "description": "Remove a path. `recursive: true` is required to delete a non-empty directory. Returns { removed, path, was_present }. Errors return { code, message }; common: S211 not found, S214 dir not empty (pass recursive), S215 jail/denylist.",
      "metadata": {},
      "name": "shell::fs::rm",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "path": {
            "description": "Jail-relative when fs.host_root is set, else absolute.",
            "type": "string"
          },
          "recursive": {
            "default": false,
            "description": "Required to delete a non-empty directory.",
            "type": "boolean"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "path"
        ],
        "title": "RmRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "default": "",
            "description": "The path that was targeted. Empty for sandbox targets.",
            "type": "string"
          },
          "removed": {
            "description": "True when the path was removed.",
            "type": "boolean"
          },
          "was_present": {
            "default": false,
            "description": "True when the path existed before removal. Host only; sandbox targets default this to false, which does NOT mean the path was absent.",
            "type": "boolean"
          }
        },
        "required": [
          "removed"
        ],
        "title": "RmResponse",
        "type": "object"
      }
    },
    {
      "description": "Find-and-replace across files. `pattern` is a Rust regex by default (set regex:false for a literal). Provide either `files` (explicit list) or `path` (+ recursive). Returns { results, total_replacements }. Errors return { code, message }; common: S217 bad regex, S211 not found, S215 jail/denylist.",
      "metadata": {},
      "name": "shell::fs::sed",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "exclude_glob": {
            "default": [],
            "description": "Glob filters excluding file paths from editing.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "files": {
            "default": [],
            "description": "Explicit list of file paths to edit; provide either this or `path`, not both.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "first_only": {
            "default": false,
            "description": "Replace only the first match per file instead of all matches.",
            "type": "boolean"
          },
          "ignore_case": {
            "default": false,
            "description": "Match pattern case-insensitively.",
            "type": "boolean"
          },
          "include_glob": {
            "default": [],
            "description": "Glob filters restricting which file paths are edited.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "path": {
            "default": null,
            "description": "Root path to walk for files; used with `recursive`, `include_glob`, `exclude_glob`.",
            "type": [
              "string",
              "null"
            ]
          },
          "pattern": {
            "description": "Rust regex by default; set regex:false for a literal string.",
            "type": "string"
          },
          "recursive": {
            "default": true,
            "description": "Descend into subdirectories when `path` is set (default true).",
            "type": "boolean"
          },
          "regex": {
            "default": true,
            "description": "Treat pattern as a regex (default true) or a literal string (false).",
            "type": "boolean"
          },
          "replacement": {
            "description": "String to substitute for each match.",
            "type": "string"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "pattern",
          "replacement"
        ],
        "title": "SedRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "FsSedFileResult": {
            "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": {
            "description": "Per-file replacement details.",
            "items": {
              "$ref": "#/definitions/FsSedFileResult"
            },
            "type": "array"
          },
          "total_replacements": {
            "description": "Sum of replacements made across all files.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "results",
          "total_replacements"
        ],
        "title": "SedResponse",
        "type": "object"
      }
    },
    {
      "description": "Stat a single path (jail-relative when fs.host_root is set). Returns the entry's type, size, mode, and mtime. Errors return { code, message }; common: S211 not found, S215 jail/denylist.",
      "metadata": {},
      "name": "shell::fs::stat",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "path": {
            "description": "Jail-relative when fs.host_root is set, else absolute.",
            "type": "string"
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "required": [
          "path"
        ],
        "title": "StatRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "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": "FsEntry",
        "type": "object"
      }
    },
    {
      "description": "Write a file. Simplest form: { path, content: \"text\" } — `content` as a plain STRING is written inline (host target only), no streaming channel needed. For large/streamed payloads or a sandbox target, pass `content` as a ContentRef { channel_id, access_key, direction } from a write stream channel you opened through the engine's streaming layer (inline strings reject S210 on a sandbox target). To write several files at once, pass `files: [{ path, content, mode?, parents? }, ...]` instead of the single-file fields (host target, inline content) — the response then carries per-file results in `files`. `mode` is octal (default \"0644\"); `parents: true` creates missing parents. Errors return { code, message }; common: S210 bad mode/payload or inline-on-sandbox, S215 jail/denylist, S218 payload exceeds max_write_bytes, S216 channel/IO error.",
      "metadata": {},
      "name": "shell::fs::write",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ContentDirection": {
            "enum": [
              "read",
              "write"
            ],
            "type": "string"
          },
          "ContentRef": {
            "description": "Wire-identical mirror of `iii_sdk::channels::StreamChannelRef`. The SDK type lacks `JsonSchema` in 0.11.3, which would block typed registration of `shell::fs::write`/`read`.",
            "properties": {
              "access_key": {
                "description": "Secret key that authorises access to this channel.",
                "type": "string"
              },
              "channel_id": {
                "description": "Opaque identifier for the open stream channel.",
                "type": "string"
              },
              "direction": {
                "allOf": [
                  {
                    "$ref": "#/definitions/ContentDirection"
                  }
                ],
                "default": "read",
                "description": "Direction of data flow: \"read\" (consume) or \"write\" (produce)."
              }
            },
            "required": [
              "access_key",
              "channel_id"
            ],
            "type": "object"
          },
          "Target": {
            "oneOf": [
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "host"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "kind"
                ],
                "type": "object"
              },
              {
                "properties": {
                  "kind": {
                    "enum": [
                      "sandbox"
                    ],
                    "type": "string"
                  },
                  "sandbox_id": {
                    "format": "uuid",
                    "type": "string"
                  }
                },
                "required": [
                  "kind",
                  "sandbox_id"
                ],
                "type": "object"
              }
            ]
          },
          "WriteContentWire": {
            "anyOf": [
              {
                "description": "Inline UTF-8 text, written verbatim. HOST target only.",
                "type": "string"
              },
              {
                "allOf": [
                  {
                    "$ref": "#/definitions/ContentRef"
                  }
                ],
                "description": "Open write-stream channel ref. Required for sandbox targets and large or streamed payloads."
              }
            ],
            "description": "Wire form of write content. A plain JSON **string** is written inline (host target only); a JSON **object** is a streaming `ContentRef` (host or sandbox, for large/streamed payloads). Untagged, so callers just pass `\"content\": \"text\"` or `\"content\": { channel_id, access_key, direction }`."
          },
          "WriteFileSpec": {
            "description": "One file in a batch `shell::fs::write` (`files: [...]`).",
            "properties": {
              "content": {
                "allOf": [
                  {
                    "$ref": "#/definitions/WriteContentWire"
                  }
                ],
                "description": "Inline string (recommended) or a streaming ContentRef."
              },
              "mode": {
                "default": "0644",
                "description": "Octal permission string, e.g. \"0644\".",
                "type": "string"
              },
              "parents": {
                "default": false,
                "description": "Create missing parent directories.",
                "type": "boolean"
              },
              "path": {
                "description": "Jail-relative when fs.host_root is set, else absolute.",
                "type": "string"
              }
            },
            "required": [
              "content",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "content": {
            "anyOf": [
              {
                "$ref": "#/definitions/WriteContentWire"
              },
              {
                "type": "null"
              }
            ],
            "description": "Single-file content: a plain string written inline (host target only), OR a ContentRef { channel_id, access_key, direction } for an open write stream channel. Omit when using `files`."
          },
          "files": {
            "description": "Batch form: write several files in one call. When present, the single-file fields (`path`/`content`/`mode`/`parents`) must be omitted.",
            "items": {
              "$ref": "#/definitions/WriteFileSpec"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "mode": {
            "default": null,
            "description": "Octal permission string for the single-file form, e.g. \"0644\". `Option` (not a defaulted `String`) so the batch-form check can tell \"caller omitted mode\" from \"caller sent a mode\" and reject the latter instead of silently dropping it. Defaults to \"0644\" in the single-file path. Omit when using `files` (each entry carries its own `mode`).",
            "type": [
              "string",
              "null"
            ]
          },
          "parents": {
            "default": null,
            "description": "Create missing parent directories (single-file form). `Option` for the same reason as `mode` — so a top-level `parents` sent alongside `files` is rejected, not ignored. Defaults to `false`. Omit when using `files`.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "path": {
            "default": null,
            "description": "Single-file form: the path to write. Jail-relative when fs.host_root is set, else absolute. Omit when using `files`.",
            "type": [
              "string",
              "null"
            ]
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "host (default) or { kind: \"sandbox\", sandbox_id }."
          }
        },
        "title": "WriteRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "WriteFileResult": {
            "properties": {
              "bytes_written": {
                "description": "Bytes written to this file.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "path": {
                "description": "Path of the written file.",
                "type": "string"
              }
            },
            "required": [
              "bytes_written",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "bytes_written": {
            "description": "Bytes written: this file for a single write; the sum across `files` for a batch write.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "files": {
            "default": [],
            "description": "Per-file results for a batch (`files: [...]`) write; empty for a single-file write.",
            "items": {
              "$ref": "#/definitions/WriteFileResult"
            },
            "type": "array"
          },
          "path": {
            "description": "Path written for a single write; empty for a batch (see `files`).",
            "type": "string"
          }
        },
        "required": [
          "bytes_written",
          "path"
        ],
        "title": "WriteResponse",
        "type": "object"
      }
    },
    {
      "description": "Terminate a running background job by job_id (the UUID from shell::exec_bg). Errors return { code, message }; common: S211 no such job, S216 kill/signal delivery failure.",
      "metadata": {},
      "name": "shell::kill",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "job_id": {
            "type": "string"
          }
        },
        "required": [
          "job_id"
        ],
        "title": "KillRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "JobStatus": {
            "enum": [
              "running",
              "finished",
              "killed",
              "failed"
            ],
            "type": "string"
          }
        },
        "properties": {
          "job_id": {
            "type": "string"
          },
          "killed": {
            "type": "boolean"
          },
          "reason": {
            "type": [
              "string",
              "null"
            ]
          },
          "status": {
            "$ref": "#/definitions/JobStatus"
          }
        },
        "required": [
          "job_id",
          "killed",
          "status"
        ],
        "title": "KillResponse",
        "type": "object"
      }
    },
    {
      "description": "List background jobs (running + recently completed). Takes no arguments.",
      "metadata": {},
      "name": "shell::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "`shell::list` takes no arguments; this empty struct publishes an accurate (empty-object) request schema.\n\nNOTE: deliberately NOT `#[serde(deny_unknown_fields)]`. The engine injects a `_caller_worker_id` field into every call's payload, so a strict no-arg struct would reject EVERY call. The handler ignores the payload entirely (`move |_req: Value|` in main.rs) for the same reason.",
        "title": "ListRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "JobStatus": {
            "enum": [
              "running",
              "finished",
              "killed",
              "failed"
            ],
            "type": "string"
          },
          "JobSummary": {
            "description": "`shell::list` returns one `JobSummary` per record. argv, stdout, and stderr are deliberately omitted: the global JOBS map is process-wide and has no per-caller scope, so any caller could otherwise read every other caller's command line and captured output (which may embed credentials). Full records remain reachable via `shell::status <job_id>` — the random UUID acts as an unguessable capability for that record.",
            "properties": {
              "exit_code": {
                "format": "int32",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "finished_at_ms": {
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "started_at_ms": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "status": {
                "$ref": "#/definitions/JobStatus"
              },
              "stderr_truncated": {
                "type": "boolean"
              },
              "stdout_truncated": {
                "type": "boolean"
              }
            },
            "required": [
              "id",
              "started_at_ms",
              "status",
              "stderr_truncated",
              "stdout_truncated"
            ],
            "type": "object"
          }
        },
        "properties": {
          "count": {
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          },
          "jobs": {
            "items": {
              "$ref": "#/definitions/JobSummary"
            },
            "type": "array"
          }
        },
        "required": [
          "count",
          "jobs"
        ],
        "title": "ListResponse",
        "type": "object"
      }
    },
    {
      "description": "Internal: reload the security policy + fs backend on configuration change.",
      "metadata": {},
      "name": "shell::on-config-change",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Event delivered to the internal `shell::on-config-change` handler. A struct (not `Value`) keeps the request schema concrete; the handler re-fetches the configuration id; unknown fields are ignored.",
        "properties": {
          "id": {
            "default": null,
            "description": "Configuration id that changed (advisory; the handler re-fetches the value). Schema-only: kept to publish a typed request schema; the handler ignores it.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "OnConfigChangeEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Ack returned by the internal `shell::on-config-change` handler.",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "OnConfigChangeResponse",
        "type": "object"
      }
    },
    {
      "description": "Fetch the full record (status, exit_code, timing) of a background job by job_id. Errors return { code, message }; common: S211 no such job.",
      "metadata": {},
      "name": "shell::status",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "job_id": {
            "type": "string"
          }
        },
        "required": [
          "job_id"
        ],
        "title": "StatusRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "JobRecord": {
            "properties": {
              "argv": {
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "exit_code": {
                "format": "int32",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "finished_at_ms": {
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "id": {
                "type": "string"
              },
              "started_at_ms": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "status": {
                "$ref": "#/definitions/JobStatus"
              },
              "stderr": {
                "type": "string"
              },
              "stderr_truncated": {
                "type": "boolean"
              },
              "stdout": {
                "type": "string"
              },
              "stdout_truncated": {
                "type": "boolean"
              }
            },
            "required": [
              "argv",
              "id",
              "started_at_ms",
              "status",
              "stderr",
              "stderr_truncated",
              "stdout",
              "stdout_truncated"
            ],
            "type": "object"
          },
          "JobStatus": {
            "enum": [
              "running",
              "finished",
              "killed",
              "failed"
            ],
            "type": "string"
          }
        },
        "properties": {
          "job": {
            "$ref": "#/definitions/JobRecord"
          }
        },
        "required": [
          "job"
        ],
        "title": "StatusResponse",
        "type": "object"
      }
    }
  ],
  "triggers": []
}