skip to content
$worker

ide

(alias: shell)v0.12.13

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
1,951 installs0 in 7d0 today
install
$iii trigger compose::add worker=ide@0.12.13
binarylicense: Apache-2.0bashexecfilesystemideshellterminal
  • macOS: arm64
  • Linux: arm64 · armv7 · x64

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

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

install

install
$iii trigger compose::add worker=ide@0.12.13

configuration

iii-config.yaml
- code:
    default_exclude_globs:
      - **/.git/**
      - **/node_modules/**
      - **/target/**
      - **/dist/**
      - **/.venv/**
      - **/__pycache__/**
    non_accessible_globs:
      - **/.env
      - **/.env.*
      - **/*.pem
      - **/*.key
      - **/secrets/**
  default_timeout_ms: 30000
  denylist_patterns:
    - rm\s+-rf\s+/
    - :\(\)\s*\{\s*:\|
    - (?i)^(?:(?:\S*/)?env(?:\s+\S+=\S*)*\s+|(?:\S*/)?(?:sudo|doas|nohup)\s+|(?:\S*/)?timeout(?:\s+\S+)?\s+)*(\S*/)?mkfs
    - (?i)^(?:(?:\S*/)?env(?:\s+\S+=\S*)*\s+|(?:\S*/)?(?:sudo|doas|nohup)\s+|(?:\S*/)?timeout(?:\s+\S+)?\s+)*(\S*/)?dd\s+if=
    - (?i)^(?:(?:\S*/)?env(?:\s+\S+=\S*)*\s+|(?:\S*/)?(?:sudo|doas|nohup)\s+|(?:\S*/)?timeout(?:\s+\S+)?\s+)*(\S*/)?shutdown\b
    - (?i)^(?:(?:\S*/)?env(?:\s+\S+=\S*)*\s+|(?:\S*/)?(?:sudo|doas|nohup)\s+|(?:\S*/)?timeout(?:\s+\S+)?\s+)*(\S*/)?reboot\b
    - /etc/shadow
  env:
    allow:
      - PATH
      - HOME
      - LANG
      - LC_ALL
      - TERM
    inherit: true
  fs:
    allow_unjailed: true
    denylist_paths:
      - /etc/passwd
      - /etc/shadow
    host_roots:

    max_read_bytes: 16777216
    max_write_bytes: 16777216
  job_retention_secs: 3600
  max_bg_timeout_ms: 0
  max_concurrent_jobs: 16
  max_output_bytes: 1048576
  max_timeout_ms: 120000
  sandbox:
    enabled: true
  turns:
    data_dir: data/shell/turns
    max_blob_bytes: 268435456
  working_dir: null

dependencies

no dependencies for v0.12.13

readme

README.md

ide

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

Notice: renamed from shell

This worker used to be called shell. Only its identity changed, so nothing that talks to it needs to be rewritten:

  • worker name, binary, Cargo package and registry entry: shellide (iii trigger compose::add worker=ide, npx skills add iii-hq/workers --skill ide)
  • source directory: shell/ide/; dependents pin ide in their iii.worker.yaml
  • unchanged on purpose: the shell::* function ids (shell::exec, shell::fs::*, shell::pty::*, …), coder::*, the shell::changed trigger type, the shell configuration entry, the data/shell/ data directory and the #/ext/shell console page

Install

iii trigger compose::add worker=ide

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

iii trigger compose::add worker=iii-sandbox worker=iii-directory

Skills

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

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

Browse or install every worker skill at once:

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

Running

The binary needs no required environment variables — it boots against a local engine with pure defaults. The full operator surface:

Knob Default What it does
--url / III_URL env var ws://127.0.0.1:49134 WebSocket URL of the iii engine. The CLI flag wins over the env var.
--config ./config.yaml Seed config sent as initial_value on FIRST registration only; the stored value wins afterwards (see Configure).
--version Print the worker version (also registered with the engine as worker metadata).
RUST_LOG env var info Log filter (tracing EnvFilter syntax, e.g. RUST_LOG=ide=debug,info).

If the engine is unreachable at boot, a pre-connect probe (run detached, so it never delays startup) logs one ERROR ("engine unreachable at : — is the iii engine running? Set --url or the III_URL env var...", logging the resolved host/port rather than the raw URL) and the worker keeps retrying in the background every 2s — it never exits, so supervised deployments recover as soon as the engine is up.

A --config seed file that exists but fails to parse (for example, one still carrying 0.6.x keys) aborts boot rather than silently falling back to the permissive built-in default — see Upgrading to 0.7.0. A genuinely missing file still falls through gracefully to the stored value or the built-in zero-config default.

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_roots 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. 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: 30000    # applied when the caller omits timeout_ms (code default 10000)
max_output_bytes: 1048576    # 1 MiB; stdout/stderr past this set *_truncated
env:
  inherit: true              # forward the worker's env to children; per-call dangerous keys still blocked
  allow: [PATH, HOME, LANG, LC_ALL, TERM]  # forwarded when inherit is false; has no effect on per-call `env` (deny-only — dangerous keys never settable)

# Command policy is deny-only: allow/ask policy lives in the approval-gate.
# denylist_patterns are advisory regex over argv.join(" "), a tripwire for
# catastrophic mistakes only, NOT a security boundary. Command-shaped
# patterns are anchored to argv[0] (tolerating a sudo/doas/nohup/env/timeout
# wrapper) so `grep -rn shutdown src/` is not rejected but `sudo shutdown -h
# now` still is; argument-shaped ones (rm -rf /) stay unanchored. See the
# shipped config.yaml for the full patterns.
denylist_patterns:
  - "rm\\s+-rf\\s+/"
  - "^(\\S*/)?mkfs"
  - "^(\\S*/)?dd\\s+if="

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

fs:
  host_roots: []             # jail roots for shell::fs::*/cwd; empty = unjailed (the shipped default); first entry = primary
  allow_unjailed: true       # explicit opt-in to running with no jail root (required whenever host_roots is empty)
  max_read_bytes: 16777216   # 0 = unlimited (reads stream; cap bounds caller cost)
  max_write_bytes: 16777216  # 0 = unlimited
  denylist_paths: [/etc/passwd, /etc/shadow]  # primary protection while unjailed; defense in depth once you set host_roots
  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: unjailed (fs.allow_unjailed: true, empty host_roots), env forwarded, deny-only exec with a catastrophic-only denylist (kept in sync by a unit test). Unjailed means shell::fs::* and shell::exec's per-call cwd operate against the real filesystem, confined only by fs.denylist_paths — matching shell::exec itself, which has never been confinement-based. coder::* is the one exception: it falls back to its own default roots (engine workspace cwd + /tmp) whenever fs.host_roots is empty, so it stays reasonably scoped even under the zero-config default. Set fs.host_roots if you want shell::fs::*/cwd jailed too. 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_roots unset (without fs.allow_unjailed: true) also fails closed — the failure mode only disappears once you explicitly opt in, one way or another.

Host shell::exec is not a security boundary: any 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 denylist still applies 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 blur what the argv actually was):

  • 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_roots is set (else absolute), canonicalized, and required to resolve inside a jail 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. Deny-only, like the exec/fs command policy: a key may be set to any value except 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, …), which are rejected unconditionally. env.allow plays no role in this gate at all — that list's only job is which vars get forwarded from the worker's own environment when env.inherit is false (see Configure); it has nothing to do with what a per-call override may set. Supplying a dangerous key rejects the whole call with S210 (the offending key is named); the env is never silently dropped. A per-call value overrides the value that would otherwise be forwarded for that key. So an agent can do NODE_ENV=test freely, but 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

npm install iii-sdk
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 a command in the foreground; returns stdout, stderr, exit code, and timing. Blocks until exit or timeout. Accepts optional host-only cwd (confined to fs.host_roots when set, else absolute), env (deny-only, gated by 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 a command as a background job; returns { job_id, argv } immediately. Accepts an optional caller-chosen job_id on host and sandbox targets so you can subscribe before starting the job. 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 (operator/automation only — not agent-callable) 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, sorted by name and paginated: page (1-based, default 1), page_size (default 500, max 2000); the response carries total, page, page_size, has_more.
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.

Code surface (coder::*)

The shell worker also serves the coder::* code-file functions (the former standalone coder worker, folded in). They are agent-ergonomic, structured file operations that share shell::fs::*'s jail when fs.host_roots is set. When it's empty (the shipped default — see Zero-config default), the two surfaces diverge: shell::fs::* becomes fully unjailed, but coder::* falls back to its own default roots (engine workspace cwd + /tmp) instead — it never runs fully unjailed, regardless of fs.allow_unjailed.

Function Purpose
coder::info Discover the jail: roots, caps, response budgets, exclude/non-accessible globs. Call first.
coder::read-file Windowed reads (line_from/line_to), stat probe, byte-budgeted full reads, and multi-file batch reads.
coder::search Literal/regex content + path search with context lines, bounded by match/byte budgets. respect_gitignore: true skips what .gitignore hides; fuzzy_paths: true ranks path matches quick-open style, best first.
coder::list-folder Paginated single-folder listing.
coder::tree Recursive depth- and per-folder-bounded directory snapshot.
coder::create-file / coder::update-file / coder::delete-file / coder::move Batched create, line/regex edits, delete, and atomic rename/move.

Roots come from fs.host_roots (with the cwd+/tmp fallback noted above); protection globs come from code.non_accessible_globs in the shipped config.yaml's code: block — the same list shell::fs::* enforces. coder::* returns its own C2xx codes. Since 0.10.0, shared C2xx and S2xx numbers mean the same failure class; coder-only failures are called out explicitly below:

Code Meaning fs twin
C210 Malformed input: bad payload, illegal line numbers, overlapping ops. S210
C211 Path not found, permission denied, matched non_accessible_globs, or under fs.denylist_paths — deliberately ONE code and wording for all four, so a caller can't probe for a denied path's existence. S211
C213 create-file/move saw an existing target and overwrite=false. S213
C215 Path escapes every allowed root, lexically or through a symlink (jailed mode only). S215
C216 Underlying I/O error. S216
C218 File exceeds max_read_bytes/max_write_bytes. S218
C220 Path resolves inside a configured root but outside the per-call scope_root the session is scoped to. S220
C221 Optimistic whole-file save conflict: the file no longer matches expected_revision; no bytes were written. n/a

No separate install: iii trigger compose::add worker=ide brings the whole surface.

Terminal sessions (shell::pty::*)

The console's terminal runs on this worker: shell::pty::open starts a persistent host PTY, write sends base64 keystrokes, resize follows the pane, attach/detach survive a reload (buffered output replays from a sequence number, out of a 2 MiB ring buffer), and close terminates the session. These are console plumbing — flagged internal and trace_hidden, so their spans stay out of the default trace view; one span per keystroke buries the work worth reading.

A session runs the user's login shell by default, or the program a caller names:

Field on open Meaning
program Program to run instead of the login shell, resolved on PATH. A caller that can open a login shell can already run any program by typing it, so this is reach, not privilege — it is what lets a session BE one program, with no shell around it.
args argv for program; ignored without it.
env Extra environment for the session. Deny-only exactly like shell::exec's per-call env: an exec-hijacking key (PATH, LD_*, DYLD_*, BASH_ENV, ...) refuses the whole call, and so does a key that is not a variable name ([A-Za-z_][A-Za-z0-9_]*) — a key like PATH=/tmp/evil would otherwise carry its own assignment past the PATH rule.

cwd resolves through the same jail as shell::fs::*, so what it can reach is whatever that config says:

  • fs.host_roots set: a session can only start inside a configured root.
  • fs.allow_unjailed: true with fs.host_roots empty (the shipped default): there is no root to be inside, and cwd reaches the real filesystem, held back only by fs.denylist_paths.

Either way this bounds where a session STARTS. A login shell can then cd anywhere its user can, which is the point of a terminal.

Output goes to a browser-registered console handler and nowhere else: iii::-ui::pty-output::console-. The -ui segment is not pinned to this worker's own page — a worker that runs its own program in a session serves its own console page, and therefore its own handler — but the shape is enforced, so a session can never be pointed at an arbitrary function on the bus.

Each pane carries a font-size stepper (8–40 px, 14 by default; Ctrl or ⌘ + scroll does the same). The size is a browser preference rather than a worker setting — the same person reading the same engine from a laptop and from a wall display wants two different answers — so it is stored per browser and read by every terminal the console renders, this worker's panes and the agent-CLI pages alike. A change refits the pane, which resizes the PTY through the ordinary shell::pty::resize path.

Taking a session back (shell::pty::adopt)

The reconnect token is what proves a caller opened a session, and a browser that loses its storage loses the token while the program keeps running — an agent still working in a workspace nobody can reach. shell::pty::adopt is the way back, under two rules that keep it from being a way in:

  • The session must be unattached. A terminal someone is watching is never taken from them; only one nobody holds.
  • The new output handler must name the same console page as the old one. The browser id may differ — that is the point — but iii::claude-ui::… cannot adopt a session whose output went to iii::pi-ui::….

Credentials rotate on adoption, so whatever the previous owner still held stops working, and an in-flight retry of the old attach cannot resurrect it. shell::pty::sessions is how a page finds its orphan: every session reports ui, the console page it belongs to (a page, never a browser), whether or not anyone is attached.

shell::pty::sessions lists live sessions with their program, cwd, status, last sequence number, replayable frames and bytes, and current output target. No credentials: it exists to separate a terminal that shows nothing because no output arrived from one whose frames the browser dropped.

Two surfaces, one contract

shell::fs::* and coder::* are two views of the same filesystem, served by this one worker under ONE policy: the same jail (fs.host_roots), the same unjailed opt-in (fs.allow_unjailed), the same protected globs (code.non_accessible_globs), the same operator denylist (fs.denylist_paths), and — since 0.10.0 — the same error-code semantics (equal digits, equal meaning) and the same existence redaction (a denied path reads exactly like a missing one on both surfaces).

They differ in ergonomics, and each operation has a twin:

Task coder::* (agent-ergonomic) shell::fs::* (byte-level)
read coder::read-file — inline text, windowed, batched shell::fs::read — streams bytes via channel
create coder::create-file — batched inline text shell::fs::write — inline or streamed, modes
edit coder::update-file — line ops + regex, post-apply echoes shell::fs::sed — regex replace across files
delete coder::delete-file — batched, per-entry errors shell::fs::rm — single path
list coder::list-folder / coder::tree — paginated, noise-filtered shell::fs::ls — single directory, paginated
move coder::move — batched, cross-root copy+delete shell::fs::mv — single path
search coder::search — budgeted, context lines shell::fs::grep — raw matches
introspect coder::info — mode, roots, caps, globs shell::fs::stat — one path's metadata

Conventions (hold these when adding functions to either surface):

  • Batching: coder::* operations are batched with per-entry error isolation. shell::fs::* point operations are single-path (write and sed are the historical exceptions).
  • Naming: coder::* uses verb-noun kebab (read-file); shell::fs::* uses the unix tool name (read, ls, mv). Follow the surface you are extending.
  • Errors: one { code, message } envelope; C/S codes with equal digits mean the same failure class (see Errors). Redaction: never let a denied path read differently from a missing one.
  • Discovery: coder::info is the agent-facing contract report. shell::config-status (reload health), shell::workspace::* (console working-directory picker and the explorer's chunked byte reads) and shell::turns::* (per-session change history and its revert) are operator/console control plane, not agent tools.
  • Sandbox: shell::fs::*/shell::exec accept target: sandbox; coder::* is host-only.

Custom trigger types

Trigger type Fires when Payload to subscribers
shell::job-finished A background job finishes, is killed, or fails. A binding filtered by job_id also receives a retained terminal result when registered after completion. { job_id, argv, status, exit_code, started_at_ms, finished_at_ms, duration_ms }
shell::changed A file or directory under the watched root changes. { path, kind, root, dir }

Background job completion (shell::job-finished)

Choose a job ID, register a completion subscription, then start the job with shell::exec_bg using that same ID. The subscription tells you when the job ends without polling shell::status.

shell::exec_bg accepts an optional job_id on both host and sandbox targets. It must be a string containing at least one non-whitespace character. Omitting it or passing null generates a job- ID, preserving the existing behavior. A supplied ID is reserved atomically before the command starts or the sandbox execution request is sent. If that ID already belongs to a running job or a retained finished job, the request is rejected without executing the command or changing the original job.

IDs belong to one worker process. Generate a fresh, globally unique ID for every execution, even after an older record has been pruned. This field is not an idempotency key: resubmitting an ID does not resume or return its original job.

The trigger's binding configuration accepts one field:

Field Type Default Meaning
job_id Optional string No filter Receive the named job's completion. Omit it to receive future completions of all background jobs in this worker process.

A filtered subscription also receives the result if its job has already completed, including a job that finished before shell::exec_bg returned. Registration and live completion are deduplicated for that binding. An unfiltered subscription receives future completions only; it does not replay existing records.

For a harness agent, generate a new ID, such as job- followed by a UUID. Call engine::register_trigger with that ID and wait for registration to succeed before starting the job. Save its returned subscription_id for cleanup if execution cannot start. Omitting function_id registers a wake for the calling session; sibling workers instead register the trigger with their own handler as its target:

{
  "trigger_type": "shell::job-finished",
  "config": { "job_id": "<new-unique-job-id>" },
  "once": true
}

Then call shell::exec_bg with the same ID and await its response:

{
  "job_id": "<new-unique-job-id>",
  "command": "pnpm",
  "args": ["build"],
  "cwd": "/path/to/project"
}

If shell::exec_bg fails before starting a job, no completion event will be emitted for that attempted job. Remove the subscription by calling engine::unregister_trigger with the saved harness subscription ID:

{ "id": "<returned-subscription-id>" }

A session whose policy forbids unregistration, including a leaf sub-agent, can bound the wake when registering it with "lifecycle": { "expires_in_ms": 600000 } (ten minutes in this example). An expiry notification reports that the wake ended; it is not a job completion event.

The existing flow also works: call shell::exec_bg without job_id:

{ "command": "pnpm", "args": ["build"], "cwd": "/path/to/project" }

Then call engine::register_trigger with the generated job_id from the response. A job that already completed is replayed while its record remains in memory:

{
  "trigger_type": "shell::job-finished",
  "config": { "job_id": "<returned-job-id>" },
  "once": true
}

The completion event contains:

Field Type Meaning
job_id String The ID returned by shell::exec_bg.
argv Array of strings The command and arguments as spawned.
status String finished, killed, or failed; never running.
exit_code Integer or null The process exit code, when available.
started_at_ms Non-negative integer Job start time in milliseconds since the Unix epoch.
finished_at_ms Non-negative integer Completion time in milliseconds since the Unix epoch.
duration_ms Non-negative integer Elapsed milliseconds, clamped to zero if the clock moved backwards.

The event excludes stdout and stderr. Read captured output with shell::status using { "job_id": "" }.

Replay and output retrieval depend on the job record remaining in this worker's memory. Finished records are pruned after job_retention_secs (default 3600, one hour after completion), and a worker restart loses them. Subscribe and retrieve output within that window; a pruned record cannot be replayed and shell::status returns S211. Delivery is best effort, without retries or a durable event history.

Live change feed (shell::changed)

The worker registers a custom trigger type backed by a system-level directory watch (FSEvents on macOS, inotify on Linux, via the notify crate). A subscriber names the directory in its binding config:

{ "type": "shell::changed", "config": { "path": "/some/dir" } }

Paths the root ignores — git-ignored inside a repository; data/, config/, .iii/, node_modules/ and the root .gitignore outside one — are not delivered unless the binding also sets include_ignored: true. A watch on a project root would otherwise fire on the engine's own data/observability and data/session-manager writes, i.e. on the subscriber's own transcript. The workspace UI opts in to count them.

Every fire carries the binding's registered metadata and namespace back to the bound function, the same way the engine's own trigger types do — the harness relies on it to match a wake to its __binding.

Each registration starts one recursive watcher; unregistering (or the console GC'ing a closed tab's binding) tears it down. config.path goes through the same path policy as every coder::* call — jail containment (fs.host_roots), the operator denylist, canonicalization — and must be a directory: watching a tree is a read of every filename under it, so a path you can't read is a path you can't watch.

The payload is lean — { path, kind, root, dir } with kindcreated | modified | deleted, path relative to the watched root, and dir true when the path is a directory (a subscriber that opens files must skip those; deleted paths can't be probed and report false). A subscriber that wants content asks coder::read-file; one that wants the diff asks git.

Semantics:

  • Catches every writer — an agent calling coder::*, a shell::exec command's side effects, or an editor outside the engine entirely. Nothing is coupled to the harness and no other worker is involved.
  • Coalesced: raw OS events storm, so each watcher batches per path in a 200 ms window; kinds merge toward the visible outcome (a create plus the write that fills it is created, a deletion supersedes what came before, a create-after-delete is created). .git internals are filtered — the visible outcome arrives as worktree events of its own.
  • Best-effort fan-out: events fire with no reply expected; a slow or absent subscriber never delays anything.

The console explorer page binds this on its browsed root: the tree and git panels refresh live, a clean active buffer follows the disk (a dirty one keeps the user's edits), and the last written file follows the writer into view as a diff — git baseline in a repo, empty baseline for created files, the page's last-seen content otherwise. Hidden segments, noise directories (target, node_modules, dist, build, …) at any depth, and build/temp artifact extensions never steal the view.

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_roots) 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. Denylist rejections come back as a plain message (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 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, permission denied, matched the protected globs, or under fs.denylist_paths — ONE code and wording for all four (redaction: a denied path reads exactly like a missing one). Includes 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 the fs.host_roots jail — exclusively a confinement escape; denylist and permission-denied fold into S211.
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.10.0

  • BREAKING: three coder::* error codes renumbered to align with the S2xx scheme (equal digits now mean the same failure class on both surfaces): already-exists C217C213; too-large C213C218; outside-session C218C220. Consumers branching on the old numbers must update; the approval-gate's jail-scope allowlist ships the matching change in the same release wave.
  • BREAKING: shell::fs::* existence redaction. Permission-denied, protected-glob (code.non_accessible_globs), and fs.denylist_paths rejections now return S211 with the same "not found or not accessible" wording as a missing path, instead of S215. S215 is now exclusively a jail-confinement escape. Callers must not distinguish "missing" from "denied" — that distinction was an existence-probing side channel.
  • coder::* is permissive when unjailed. With the explicit opt-in (fs.allow_unjailed: true, empty fs.host_roots) the coder surface now follows the same deny-only policy as shell::fs::*: absolute paths anywhere on the host, the cwd + /tmp fallback roots demoted to relative-path anchors, and the harness-selected working directory trusted as the anchor. fs.denylist_paths now applies to coder::* in every mode. Jailed deployments (explicit fs.host_roots) are unchanged. coder::info reports the effective mode.

Upgrading to 0.8.0

  • BREAKING: allowlist removed. The command allowlist is gone — the shell is deny-only now (see Configure); allow/ask policy lives entirely in the approval-gate. Any stored config still carrying the key, including the inert allowlist: [] written by older seeds, fails closed at boot with a migration hint. Rewrite the stored value to drop it:

    import { registerWorker } from 'iii-sdk'
    const iii = registerWorker(process.env.III_URL ?? 'ws://127.0.0.1:49134')
    const { value } = await iii.trigger({ function_id: 'configuration::get', payload: { id: 'shell' } })
    delete value.allowlist
    await iii.trigger({ function_id: 'configuration::set', payload: { id: 'shell', value } })

    If you seed from a --config file instead, just delete the allowlist: [] line.

  • BREAKING (fresh installs only): the fs jail is unjailed by default. The shipped config.yaml now sets fs.allow_unjailed: true with empty fs.host_rootsshell::fs::* and shell::exec's per-call cwd operate against the real filesystem by default (see Zero-config default). This only affects a genuinely fresh install or a config that gets nulled — an existing deployment with an explicit fs.host_roots already in its stored config is unaffected; the stored value always wins over the seed. To keep the old jailed-to-/tmp behavior, set it explicitly:

    fs:
      host_roots: [/tmp]
  • The per-call env override is deny-only. env.allow no longer gates which keys shell::exec/exec_bg's per-call env may set — only the hardcoded dangerous-key list does (see Per-call cwd, env, and stdin). This is a pure widening: nothing that worked before now fails, and no stored config needs a rewrite. env.allow keeps its other job (forwarding when env.inherit is false) unchanged.

Upgrading to 0.7.0

  • BREAKING: env config keys renamed and nested. The top-level inherit_env and allowed_env keys are replaced by one env block — no legacy aliases:

    # 0.6.x                                # 0.7.0
    inherit_env: true                      env:
    allowed_env: [PATH, HOME, LANG]          inherit: true
                                             allow: [PATH, HOME, LANG]

    The old keys are rejected at parse with a migration hint ("config keys removed in 0.7.0: inherit_env -> env.inherit ..."), because serde would otherwise ignore them silently and boot with env forwarding OFF. A stored configuration value still carrying the old keys makes the worker fail closed at boot; rewrite it via configuration::set (id shell) with the nested shape. Sequencing matters: update the binary FIRST, then the stored value — writing the new shape while 0.6.x is still running makes the old worker hot-reload it, ignore the unknown env block, and silently stop forwarding env until restart. A half-migration (nesting the OLD key names under the new block, e.g. env: { inherit_env: true }) is also rejected — env denies unknown fields — rather than silently falling back to the wider default allow list. A --config seed file carrying any of these removed keys now aborts boot rather than warning and falling back to the permissive built-in seed; only a genuinely missing seed file falls through gracefully.

  • BREAKING: fs.host_root (single-root alias) removed. The 0.6.x one-entry alias for the jail root is rejected at parse with a migration hint ("config key removed in 0.7.0: fs.host_root -> fs.host_roots (one-entry list)"). Replace it with the list form:

    # 0.6.x                                # 0.7.0
    fs:                                    fs:
      host_root: /srv/app                    host_roots: [/srv/app]

    Same fail-closed rationale as the env keys: serde would otherwise ignore the stale key and the worker would see no jail configured at all.

  • BREAKING: code.base_path/code.base_paths removed from the schema and REJECTED at parse. They were inert even before removal — the code resolver has taken its roots from fs.host_roots since the coder merge — but this is a hard migration: "never had an effect" is not an exception. A stored value still carrying either fails closed with a hint naming both keys, same as every other removed key. Set the jail once via fs.host_roots.

  • The one-shot coder→shell config migration is removed. 0.7.0 no longer folds a legacy standalone-coder configuration entry into the shell value at boot, and the hidden migrated_from_coder marker field is REJECTED at parse rather than silently tolerated. Boot also no longer probes configuration::get for the coder entry, so the "configuration 'coder' not found" WARN retries at startup are gone. Two distinct upgrade scenarios:

    • An install that ALREADY has a shell entry (it went through the fold under 0.6.x, so the entry carries migrated_from_coder: true) now fails closed at 0.7.0 boot with a migration hint, instead of silently parsing past the marker.
    • An install with ONLY a standalone coder entry and NO shell entry at all has nothing to reject — it still boots 0.7.0 with the generic permissive /tmp dev seed for shell, silently, because there is no stored shell value to fail closed on. The old coder roots and protected globs are NOT carried over. Boot 0.6.x once first (it performs the migration and writes the shell entry) before upgrading to 0.7.0 to avoid this.
  • --version added, and --url/III_URL and RUST_LOG are now documented (see Running).

  • Unreachable-engine boot is loud: one ERROR naming the host/port and the fix hint, instead of only the SDK's silent retry WARNs. The probe runs detached so it never delays boot.

  • Every config field now carries a schema description, so the console configuration UI documents each knob inline.

  • Command-shaped denylist patterns tolerate a wrapper prefix (sudo/doas/nohup/env/timeout [duration], optionally path-qualified): sudo shutdown -h now trips the tripwire again.

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_roots is empty ... refusing to start unjailed: set fs.host_roots to at least one directory, or set fs.allow_unjailed: true.
  • S215 path escapes the fs jail roots on a path inside the jail: a symlink in the path resolves outside the jail. Resolve it yourself, or move the target inside a jail 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.
  • config keys removed in 0.7.0: ... at boot or on reload: the seed file or the stored configuration value still uses the 0.6.x inherit_env/allowed_env keys (nest them under env: as inherit/allow) or the single-root fs.host_root alias (use fs.host_roots: []) — see Upgrading to 0.7.0.
  • `allowlist` (removed in 0.8.0, no replacement) at boot or on reload: the seed file or the stored configuration value still carries the removed allowlist key (even an empty allowlist: [] trips this) — see Upgrading to 0.8.0 for the fix.
  • A relative path (including ~) passed to shell::fs::*/cwd behaves unexpectedly: paths are never shell-expanded. ~ is not resolved to your home directory — when jailed, a relative path (including one starting with ~) resolves against the primary fs.host_roots entry, so ~/foo is interpreted as a path literally named ~ under that root, not your home directory; when unjailed, only absolute paths are accepted at all, so a relative path (including ~/foo) is rejected outright. Pass an absolute, already-expanded path instead.

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": "Internal console UI: retrieve an exact before/after snapshot by change id.",
      "metadata": {
        "internal": true
      },
      "name": "coder::change-diff",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "change_id": {
            "type": "string"
          }
        },
        "required": [
          "change_id"
        ],
        "title": "ChangeDiffInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "is_binary": {
            "type": "boolean"
          },
          "new_contents": {
            "description": "Exact post-mutation body. Missing when either side is not UTF-8.",
            "type": [
              "string",
              "null"
            ]
          },
          "old_contents": {
            "description": "Exact pre-mutation body. Missing when either side is not UTF-8.",
            "type": [
              "string",
              "null"
            ]
          },
          "path": {
            "type": "string"
          }
        },
        "required": [
          "is_binary",
          "path"
        ],
        "title": "ChangeDiffOutput",
        "type": "object"
      }
    },
    {
      "description": "Create one or more files atomically; per-file overwrite and parents flags. For a conflict-safe overwrite pass the revision from coder::read-file as expected_revision (stale revision: C221, nothing written). Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {
        "display": true
      },
      "name": "coder::create-file",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "CreateFileSpec": {
            "properties": {
              "content": {
                "type": "string"
              },
              "expected_revision": {
                "default": null,
                "description": "Optimistic-concurrency guard for an overwrite: the `revision` from coder::read-file; if the content changed the entry fails C221 unwritten.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "mode": {
                "default": "0644",
                "description": "Octal permission bits as a string, e.g. \"0644\". Defaults to \"0644\".",
                "type": "string"
              },
              "overwrite": {
                "default": false,
                "description": "When false (the default), refuse to write if `path` already exists.",
                "type": "boolean"
              },
              "parents": {
                "default": true,
                "description": "Create missing parent directories. Defaults to true so a single `coder::create-file` call can scaffold a fresh subtree.",
                "type": "boolean"
              },
              "path": {
                "description": "File to create.",
                "type": "string"
              }
            },
            "required": [
              "content",
              "path"
            ],
            "type": "object"
          }
        },
        "examples": [
          {
            "files": [
              {
                "content": "pub mod utils;\n",
                "overwrite": false,
                "path": "src/lib.rs"
              },
              {
                "content": "# scratch notes\n",
                "overwrite": true,
                "path": "/tmp/scratch/notes.md"
              }
            ]
          }
        ],
        "properties": {
          "files": {
            "items": {
              "$ref": "#/definitions/CreateFileSpec"
            },
            "type": "array"
          }
        },
        "required": [
          "files"
        ],
        "title": "CreateFileInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "CreateFileResult": {
            "properties": {
              "bytes_written": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "error": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/WireError"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Structured error for this entry. `code` is stable for programmatic branching (e.g. `\"C213\"` means already-exists; pass `overwrite=true` to replace). `message` carries the corrective action an LLM agent needs to make a successful second call."
              },
              "path": {
                "description": "Canonical absolute path (resolved through the jail); the caller's input verbatim when resolution failed.",
                "type": "string"
              },
              "revision": {
                "description": "Opaque revision for the exact bytes written. Supply this as `expected_revision` on a later overwrite to avoid lost updates.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "success": {
                "type": "boolean"
              }
            },
            "required": [
              "bytes_written",
              "path",
              "success"
            ],
            "type": "object"
          },
          "WireError": {
            "description": "Structured per-entry error as it appears on the wire.\n\nUse `code` for stable programmatic branching (e.g. `\"C211\"` for not-found-or-denied). `message` carries the human/LLM-readable problem description plus the corrective next call.",
            "properties": {
              "code": {
                "description": "Stable error code, e.g. \"C211\". See the README error table.",
                "type": "string"
              },
              "message": {
                "description": "Human/LLM-readable message: problem + actual values + corrective next call.",
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ],
            "type": "object"
          }
        },
        "properties": {
          "results": {
            "items": {
              "$ref": "#/definitions/CreateFileResult"
            },
            "type": "array"
          }
        },
        "required": [
          "results"
        ],
        "title": "CreateFileOutput",
        "type": "object"
      }
    },
    {
      "description": "Remove one or more paths. Directories need recursive: true; missing paths succeed; recursion refuses to descend through non-accessible entries. Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {
        "display": true
      },
      "name": "coder::delete-file",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "paths": [
              "src/old_module.rs",
              "build/artifacts"
            ],
            "recursive": true
          }
        ],
        "properties": {
          "paths": {
            "description": "Paths to remove.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "recursive": {
            "default": false,
            "description": "Required for non-empty directories. Files and empty dirs ignore it.",
            "type": "boolean"
          }
        },
        "required": [
          "paths"
        ],
        "title": "DeleteFileInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "DeleteFileResult": {
            "properties": {
              "error": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/WireError"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Structured error for this entry. `code` is stable for programmatic branching (e.g. `\"C211\"` for not-found-or-denied; `\"C210\"` for refusing to delete an allowed root). `message` carries the corrective action an LLM agent needs to make a successful second call."
              },
              "path": {
                "description": "Canonical absolute path (resolved through the jail); the caller's input verbatim when resolution failed.",
                "type": "string"
              },
              "removed": {
                "type": "boolean"
              },
              "success": {
                "type": "boolean"
              }
            },
            "required": [
              "path",
              "removed",
              "success"
            ],
            "type": "object"
          },
          "WireError": {
            "description": "Structured per-entry error as it appears on the wire.\n\nUse `code` for stable programmatic branching (e.g. `\"C211\"` for not-found-or-denied). `message` carries the human/LLM-readable problem description plus the corrective next call.",
            "properties": {
              "code": {
                "description": "Stable error code, e.g. \"C211\". See the README error table.",
                "type": "string"
              },
              "message": {
                "description": "Human/LLM-readable message: problem + actual values + corrective next call.",
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ],
            "type": "object"
          }
        },
        "properties": {
          "results": {
            "items": {
              "$ref": "#/definitions/DeleteFileResult"
            },
            "type": "array"
          }
        },
        "required": [
          "results"
        ],
        "title": "DeleteFileOutput",
        "type": "object"
      }
    },
    {
      "description": "Report the coder access contract: mode (jailed | unjailed), allowed roots (primary first), the session_root relative paths anchor against, size caps, response budgets, listing/search limits, non-accessible globs and default_exclude_globs. Call it first when a path is rejected.",
      "metadata": {},
      "name": "coder::info",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "No arguments — `coder::info` is a pure discovery call.",
        "examples": [
          {}
        ],
        "title": "InfoInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "AccessMode": {
            "description": "Effective access mode of the coder surface — the same deny-only policy switch `shell::fs::*` runs under.",
            "oneOf": [
              {
                "description": "Paths are confined to `base_paths`; anything outside is rejected.",
                "enum": [
                  "jailed"
                ],
                "type": "string"
              },
              {
                "description": "Operator opt-in (`fs.allow_unjailed: true`, empty `fs.host_roots`): absolute paths anywhere on the host are accepted, confined only by `fs.denylist_paths` and `non_accessible_globs`. `base_paths` only anchor relative wire paths.",
                "enum": [
                  "unjailed"
                ],
                "type": "string"
              }
            ]
          }
        },
        "properties": {
          "base_paths": {
            "description": "Canonical absolute paths of the allowed roots, in configuration order. The primary root (index 0) is where relative wire paths resolve; an absolute path is accepted when it canonicalises inside ANY of these (in `unjailed` mode: anywhere on the host). In `jailed` mode paths outside every root are rejected — use `shell::fs::*` instead.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "batch_read_budget_bytes": {
            "description": "Aggregate budget across a single `paths[]` batch call to `coder::read-file`, measured in bytes of returned content (after UTF-8 sanitization — invalid bytes expand to U+FFFD before being counted, so the cap bounds what the caller actually receives). Entries are collected in request order; each entry may consume up to `min(remaining_budget, max_read_bytes)`. An entry reached with zero remaining budget receives a per-entry C218 naming this key, its value, and the bytes already consumed, with recovery guidance. Budget topology: batch reads are governed by this key; single-path full reads by `max_output_bytes`; windowed reads by `max_read_bytes` applied per returned window — `max_read_bytes` is also the per-file IO ceiling for all of them.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "default_exclude_globs": {
            "description": "Noise-exclusion globs (root-relative, same matching as `non_accessible_globs`): matching paths (node_modules, .git, …) are omitted from `coder::search` results and pruned from `coder::tree` descent — the directory surfaces as a childless `truncated` stub. Hide-only — no access protection. Pass `use_default_excludes: false` on those calls to look inside.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "list_default_page_size": {
            "description": "Default `page_size` used by `coder::list-folder` when the caller omits it.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "list_max_page_size": {
            "description": "Hard cap on `page_size` accepted by `coder::list-folder`.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "max_output_bytes": {
            "description": "Context budget for single-path FULL reads in `coder::read-file`, in bytes of returned content. Full reads larger than this return C218 with the file's size/line count and window/stat recovery guidance; a per-call `max_output_bytes` override is available on `coder::read-file` (clamped to `max_read_bytes`).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "max_read_bytes": {
            "description": "Per-file IO ceiling for `coder::read-file`. Full reads of files larger than this are rejected with C218; windowed reads cap the returned window bytes instead, so larger files stay readable window by window. Also the ceiling for `coder::search` content scanning — larger files are silently skipped during search.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "max_write_bytes": {
            "description": "Maximum bytes that `coder::create-file` or `coder::update-file` will accept for a single file write. Larger writes are rejected with C218.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "mode": {
            "allOf": [
              {
                "$ref": "#/definitions/AccessMode"
              }
            ],
            "description": "`jailed`: paths must stay inside `base_paths`. `unjailed`: absolute paths anywhere on the host are accepted (deny-only, like `shell::fs::*`) and `base_paths` only anchor relative paths."
          },
          "non_accessible_globs": {
            "description": "Glob patterns matched per root (root-relative). Files whose root-relative path matches are listable but not readable/writable/deletable/creatable; they return C211.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "primary_root": {
            "description": "Convenience duplicate of `base_paths[0]` — the primary allowed root. Relative paths resolve against this directory UNLESS `session_root` is set, which takes precedence.",
            "type": "string"
          },
          "search_default_max_line_bytes": {
            "description": "Per-line byte cap in `coder::search`: matching considers at most this many bytes of each line, and matched/context lines are truncated to it.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "search_default_max_matches": {
            "description": "Default `max_matches` used by `coder::search` when the caller omits it.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "search_response_budget_bytes": {
            "description": "Aggregate byte budget for one `coder::search` response, measured in payload bytes (paths + matched text + context lines). When the budget is hit the response sets `truncated: true` — refine the query or add `include_globs`.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "session_root": {
            "description": "The session's working directory when the caller runs under a harness-stamped filesystem scope; `null` otherwise. When set, THIS is what relative wire paths (and `coder::search` glob matching) anchor against — not `primary_root`. In `unjailed` mode it may sit outside every `base_paths` entry, so a relative path that looks wrong against the allowed roots can still be correct here. Check it first when a relative path was rejected.",
            "type": [
              "string",
              "null"
            ]
          },
          "tree_default_depth": {
            "description": "Default `max_depth` used by `coder::tree` when the caller omits it.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "tree_per_folder_limit": {
            "description": "Maximum entries returned per folder node by `coder::tree`; folders that exceed this are flagged `truncated`.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "version": {
            "description": "Coder worker version (`CARGO_PKG_VERSION`).",
            "type": "string"
          }
        },
        "required": [
          "base_paths",
          "batch_read_budget_bytes",
          "default_exclude_globs",
          "list_default_page_size",
          "list_max_page_size",
          "max_output_bytes",
          "max_read_bytes",
          "max_write_bytes",
          "mode",
          "non_accessible_globs",
          "primary_root",
          "search_default_max_line_bytes",
          "search_default_max_matches",
          "search_response_budget_bytes",
          "tree_default_depth",
          "tree_per_folder_limit",
          "version"
        ],
        "title": "InfoOutput",
        "type": "object"
      }
    },
    {
      "description": "List one folder, paginated and sorted by name. Entries carry only name; entry path = response path + '/' + name. Non-accessible entries are listed with non_accessible: true. Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {},
      "name": "coder::list-folder",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "page": 1,
            "page_size": 50,
            "path": "src"
          }
        ],
        "properties": {
          "page": {
            "default": 1,
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "page_size": {
            "default": null,
            "description": "Capped at list_max_page_size; defaults to list_default_page_size (see coder::info).",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "path": {
            "default": ".",
            "description": "Folder to list (default `.`).",
            "type": "string"
          }
        },
        "title": "ListFolderInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "DirEntry": {
            "properties": {
              "kind": {
                "$ref": "#/definitions/EntryKind"
              },
              "mtime": {
                "format": "int64",
                "type": "integer"
              },
              "name": {
                "description": "Entry basename. The absolute path is derivable from the response's `path`: entry path = folder path + \"/\" + name.",
                "type": "string"
              },
              "non_accessible": {
                "description": "True if this entry matches `non_accessible_globs` — caller cannot read/write/delete it via `coder::*` even though it shows up here.",
                "type": "boolean"
              },
              "size": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "kind",
              "mtime",
              "name",
              "non_accessible",
              "size"
            ],
            "type": "object"
          },
          "EntryKind": {
            "enum": [
              "file",
              "dir",
              "symlink",
              "other"
            ],
            "type": "string"
          }
        },
        "properties": {
          "entries": {
            "items": {
              "$ref": "#/definitions/DirEntry"
            },
            "type": "array"
          },
          "has_more": {
            "type": "boolean"
          },
          "page": {
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "page_size": {
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "path": {
            "description": "Canonical absolute path of the listed folder (resolved through the jail). Entries carry only `name`; derive an entry's absolute path by joining: entry path = this path + \"/\" + name. Operations on derived paths re-validate through the jail.",
            "type": "string"
          },
          "total": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "entries",
          "has_more",
          "page",
          "page_size",
          "path",
          "total"
        ],
        "title": "ListFolderOutput",
        "type": "object"
      }
    },
    {
      "description": "Move or rename one or more paths; per-entry overwrite and parents flags. Same-root moves rename atomically; cross-root moves copy+delete files only (move directory contents individually) and remove the copy if the delete fails. Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {},
      "name": "coder::move",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "MoveFileSpec": {
            "properties": {
              "from": {
                "description": "Source path.",
                "type": "string"
              },
              "overwrite": {
                "default": false,
                "description": "When false (the default), refuse to overwrite an existing destination. Pass `overwrite: true` to replace an existing file at `to`.",
                "type": "boolean"
              },
              "parents": {
                "default": true,
                "description": "Create missing parent directories of the destination. Defaults to true.",
                "type": "boolean"
              },
              "to": {
                "description": "Destination path.",
                "type": "string"
              }
            },
            "required": [
              "from",
              "to"
            ],
            "type": "object"
          }
        },
        "examples": [
          {
            "files": [
              {
                "from": "src/old_name.rs",
                "to": "src/new_name.rs"
              },
              {
                "from": "build/output.bin",
                "overwrite": true,
                "to": "/tmp/coder-cache/output.bin"
              }
            ]
          }
        ],
        "properties": {
          "files": {
            "description": "Entries to move. Each entry is processed independently so a single failure never aborts the rest.",
            "items": {
              "$ref": "#/definitions/MoveFileSpec"
            },
            "type": "array"
          }
        },
        "required": [
          "files"
        ],
        "title": "MoveFileInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "MoveFileResult": {
            "properties": {
              "error": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/WireError"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Structured error for this entry. `code` is stable for programmatic branching (e.g. `\"C213\"` means destination exists; pass `overwrite=true` to replace; `\"C210\"` for disallowed operations such as cross-root directory moves, moving a root itself, or a destination that is a directory — the message then names the corrected target path). `message` carries the corrective action an LLM agent needs to make a successful second call."
              },
              "from": {
                "description": "Canonical absolute path of the source (resolved through the jail); the caller's input verbatim when resolution failed.",
                "type": "string"
              },
              "moved": {
                "description": "True only when the move fully completed; false for a no-op self-move (`from` and `to` resolve to the same file).",
                "type": "boolean"
              },
              "success": {
                "type": "boolean"
              },
              "to": {
                "description": "Canonical absolute path of the destination (resolved through the jail); the caller's input verbatim when resolution failed.",
                "type": "string"
              }
            },
            "required": [
              "from",
              "moved",
              "success",
              "to"
            ],
            "type": "object"
          },
          "WireError": {
            "description": "Structured per-entry error as it appears on the wire.\n\nUse `code` for stable programmatic branching (e.g. `\"C211\"` for not-found-or-denied). `message` carries the human/LLM-readable problem description plus the corrective next call.",
            "properties": {
              "code": {
                "description": "Stable error code, e.g. \"C211\". See the README error table.",
                "type": "string"
              },
              "message": {
                "description": "Human/LLM-readable message: problem + actual values + corrective next call.",
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ],
            "type": "object"
          }
        },
        "properties": {
          "results": {
            "items": {
              "$ref": "#/definitions/MoveFileResult"
            },
            "type": "array"
          }
        },
        "required": [
          "results"
        ],
        "title": "MoveFileOutput",
        "type": "object"
      }
    },
    {
      "description": "Read a file. stat: true probes size/mtime/total_lines without content; line_from/line_to (1-based, inclusive) read a window of a file of any size; numbered: true prefixes absolute line numbers; paths[] (XOR path) batch-reads. Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {},
      "name": "coder::read-file",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ReadEncoding": {
            "description": "Wire encoding for returned `content`.",
            "oneOf": [
              {
                "description": "UTF-8 text; invalid bytes replaced by U+FFFD.",
                "enum": [
                  "text"
                ],
                "type": "string"
              },
              {
                "description": "Exact file bytes, base64-encoded (standard alphabet, padded).",
                "enum": [
                  "base64"
                ],
                "type": "string"
              }
            ]
          },
          "ReadTarget": {
            "anyOf": [
              {
                "description": "Bare path string: read the whole file.",
                "type": "string"
              },
              {
                "description": "Object form: path plus optional window and flags.",
                "properties": {
                  "line_from": {
                    "default": null,
                    "description": "First line of the window, 1-based inclusive (>= 1); defaults to 1 when only `line_to` is set.",
                    "format": "uint64",
                    "minimum": 1,
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "line_to": {
                    "default": null,
                    "description": "Last line of the window, 1-based inclusive, >= `line_from`; omit to read to EOF.",
                    "format": "uint64",
                    "minimum": 1,
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "numbered": {
                    "default": false,
                    "description": "Prefix this entry's lines with their absolute 1-based line numbers (`N→`); prefix bytes count toward the batch budget.",
                    "type": "boolean"
                  },
                  "path": {
                    "description": "File to read.",
                    "type": "string"
                  },
                  "stat": {
                    "default": false,
                    "description": "Metadata probe for this entry: size/mode/mtime plus total_lines, content null, no budget consumed; C210 with a window or `numbered`.",
                    "type": "boolean"
                  }
                },
                "required": [
                  "path"
                ],
                "type": "object"
              }
            ],
            "description": "One batch entry: a bare path string (whole-file read) or an object with a per-entry window, stat or numbered flag."
          }
        },
        "examples": [
          {
            "line_from": 10,
            "line_to": 50,
            "path": "src/main.rs"
          },
          {
            "paths": [
              "src/lib.rs",
              {
                "line_from": 1,
                "line_to": 30,
                "path": "src/config.rs"
              }
            ]
          }
        ],
        "properties": {
          "encoding": {
            "allOf": [
              {
                "$ref": "#/definitions/ReadEncoding"
              }
            ],
            "description": "Content encoding for full reads; `base64` returns the exact file bytes (encoded length is budgeted). C210 with stat/window/numbered."
          },
          "line_from": {
            "default": null,
            "description": "First line (1-based, >= 1) of a windowed read; windows keep files over max_read_bytes readable slice by slice. `path` mode only.",
            "format": "uint64",
            "minimum": 1,
            "type": [
              "integer",
              "null"
            ]
          },
          "line_to": {
            "default": null,
            "description": "Last line of the window, 1-based inclusive, >= `line_from`; omit to read from `line_from` to EOF. `path` mode only.",
            "format": "uint64",
            "minimum": 1,
            "type": [
              "integer",
              "null"
            ]
          },
          "max_output_bytes": {
            "default": null,
            "description": "Full-read byte budget (returned bytes, clamped to max_read_bytes); over budget fails C218 naming size/total_lines. C210 with a window.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "numbered": {
            "default": false,
            "description": "Prefix each line with its absolute 1-based file line number (`N→`), matching coder::update-file line ops; C210 with `stat`. `path` mode only.",
            "type": "boolean"
          },
          "path": {
            "default": null,
            "description": "Single file to read; XOR with `paths` (C210 if both or neither is set).",
            "type": [
              "string",
              "null"
            ]
          },
          "paths": {
            "description": "Batch of files to read in one call (bare path strings or window objects), processed in order against batch_read_budget_bytes; XOR with `path`.",
            "items": {
              "$ref": "#/definitions/ReadTarget"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "stat": {
            "default": false,
            "description": "Metadata probe: size/mode/mtime plus total_lines/is_utf8, no content; C210 with line_from/line_to/numbered/max_output_bytes.",
            "type": "boolean"
          }
        },
        "title": "ReadFileInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ReadEntryResult": {
            "description": "Per-entry result in a batch `paths[]` response.",
            "properties": {
              "content": {
                "description": "File content as a UTF-8 string — the whole file or the requested window. Binary bytes are replaced by U+FFFD (`is_utf8: false`). `null` on failure.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "error": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/WireError"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Structured error — present only when `success: false`."
              },
              "is_utf8": {
                "description": "Whether `content` survived UTF-8 conversion without losing bytes. `null` on failure.",
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "lines_returned": {
                "description": "Number of lines returned in `content`. `null` on failure.",
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "mode": {
                "description": "Unix permission bits (lower 9 bits of `st_mode`), e.g. 0o644. `null` on failure.",
                "format": "uint32",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "more_lines": {
                "description": "`true` when the file has content beyond what `content` includes (window ended before EOF, or byte budget cut the window short). `null` on failure.",
                "type": [
                  "boolean",
                  "null"
                ]
              },
              "mtime": {
                "description": "Last-modified time as a Unix epoch in seconds. `null` on failure.",
                "format": "int64",
                "type": [
                  "integer",
                  "null"
                ]
              },
              "path": {
                "description": "Canonical absolute path of the file (resolved through the jail). If resolution failed, this echoes the caller's input verbatim.",
                "type": "string"
              },
              "size": {
                "description": "Size of the FILE in bytes (from metadata). `null` on failure or when the entry budget was exhausted before the file was opened.",
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "success": {
                "description": "`true` when the read succeeded (content/metadata fields are populated); `false` when an error occurred (only `error` is set).",
                "type": "boolean"
              },
              "total_lines": {
                "description": "Total lines in the file; present when the stream reached EOF during this entry's read. `null` when not traversed or on failure.",
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "required": [
              "path",
              "success"
            ],
            "type": "object"
          },
          "WireError": {
            "description": "Structured per-entry error as it appears on the wire.\n\nUse `code` for stable programmatic branching (e.g. `\"C211\"` for not-found-or-denied). `message` carries the human/LLM-readable problem description plus the corrective next call.",
            "properties": {
              "code": {
                "description": "Stable error code, e.g. \"C211\". See the README error table.",
                "type": "string"
              },
              "message": {
                "description": "Human/LLM-readable message: problem + actual values + corrective next call.",
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ],
            "type": "object"
          }
        },
        "properties": {
          "content": {
            "description": "File content as a UTF-8 string — the whole file, or just the requested window when `line_from`/`line_to` was given (window lines keep their newline terminators). Binary content is returned with invalid bytes replaced by U+FFFD; request `encoding: \"base64\"` when exact bytes matter (content is then the file's bytes base64-encoded). **Single-path mode only; null when the request used `paths[]`.**",
            "type": [
              "string",
              "null"
            ]
          },
          "is_utf8": {
            "description": "Whether `content` survived UTF-8 conversion without losing bytes. Reflects the RETURNED content only: a clean window inside an otherwise-binary file is still `true`. **Single-path mode only; null when the request used `paths[]`.**",
            "type": [
              "boolean",
              "null"
            ]
          },
          "lines_returned": {
            "description": "Number of lines in `content`. For full reads this equals the file's total line count. **Single-path mode only; null when the request used `paths[]`.**",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "mode": {
            "description": "Unix permission bits (lower 9 bits of `st_mode`), e.g. 0o644. **Single-path mode only; null when the request used `paths[]`.**",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "more_lines": {
            "description": "True when the file has content beyond what `content` includes: the window ended before EOF, or the byte budget cut the window short. Always false for full reads. **Single-path mode only; null when the request used `paths[]`.**",
            "type": [
              "boolean",
              "null"
            ]
          },
          "mtime": {
            "description": "Last-modified time as a Unix epoch in seconds. **Single-path mode only; null when the request used `paths[]`.**",
            "format": "int64",
            "type": [
              "integer",
              "null"
            ]
          },
          "path": {
            "description": "Canonical absolute path of the file read (resolved through the jail). **Single-path mode only; null when the request used `paths[]`.**",
            "type": [
              "string",
              "null"
            ]
          },
          "results": {
            "description": "Per-entry results for a batch `paths[]` request. **Present only when the request used `paths[]`; null in single-path mode.**",
            "items": {
              "$ref": "#/definitions/ReadEntryResult"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "revision": {
            "description": "Opaque identity of the file's exact bytes. Present for complete single-path reads only; pass it as `expected_revision` to `coder::create-file` for a conflict-safe whole-file overwrite.",
            "type": [
              "string",
              "null"
            ]
          },
          "size": {
            "description": "Size of the FILE in bytes (from metadata) — not the size of `content`; in windowed mode the two differ. **Single-path mode only; null when the request used `paths[]`.**",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "total_lines": {
            "description": "Total number of lines in the file. Present only when the read traversed the whole file: always for full reads; for windowed reads only when the stream naturally reached EOF within the byte cap. Never computed by forcing an extra full scan — absent means the file was not fully traversed. **Single-path mode only; null when the request used `paths[]`.**",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          }
        },
        "title": "ReadFileOutput",
        "type": "object"
      }
    },
    {
      "description": "Search file contents (literal or regex) and/or paths (files and dirs); first match per line only. default_exclude_globs noise is skipped unless use_default_excludes: false. truncated: true means refine the query, not paginate. Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {},
      "name": "coder::search",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "context_lines_after": 2,
            "context_lines_before": 2,
            "include_globs": [
              "**/*.rs"
            ],
            "path": "src",
            "query": "fn handle",
            "search_content": true,
            "search_paths": false
          }
        ],
        "properties": {
          "context_lines_after": {
            "default": null,
            "description": "Lines of context after each content match; same rules as `context_lines_before`.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "context_lines_before": {
            "default": null,
            "description": "Lines of context before each content match (max 10, C210 above); truncated to max_line_bytes and counted in the budget. Default 0.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "exclude_globs": {
            "default": [],
            "description": "Glob patterns (same relative-to-root matching) that exclude paths.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "fuzzy_paths": {
            "default": false,
            "description": "Rank path matches by fuzzy subsequence score (quick-open style) instead of substring/regex matching; `path_matches` then comes back best first. Content matching is unaffected.",
            "type": "boolean"
          },
          "ignore_case": {
            "default": false,
            "type": "boolean"
          },
          "include_globs": {
            "default": [],
            "description": "Root-relative glob patterns paths must match; empty = everything.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "include_hidden": {
            "default": true,
            "description": "Walk dot-files and dot-folders (`.github`, `.env`, …); default true. `false` leaves them out, the way an editor's quick open does.",
            "type": "boolean"
          },
          "max_line_bytes": {
            "default": null,
            "description": "Bytes per line to consider when scanning content; longer lines are truncated for the match snippet.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "max_matches": {
            "default": null,
            "description": "Optional explicit cap. Falls back to config when unset.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "path": {
            "default": ".",
            "description": "Folder to search (default `.`); globs match relative to its root, result paths are absolute.",
            "type": "string"
          },
          "query": {
            "description": "Pattern to search for. Treated as a regex when `regex: true`, otherwise as a literal substring. May be empty only when `search_content` is false: a path-only search with no query lists every path (with `fuzzy_paths`, shallow and short paths first).",
            "type": "string"
          },
          "regex": {
            "default": false,
            "type": "boolean"
          },
          "respect_gitignore": {
            "default": false,
            "description": "Honour `.gitignore`/`.ignore` rules (inside a Git repository) while walking, the way an editor's search does; default false keeps every non-excluded file searchable.",
            "type": "boolean"
          },
          "search_content": {
            "default": true,
            "description": "Search file contents (default true).",
            "type": "boolean"
          },
          "search_paths": {
            "default": true,
            "description": "Search file paths (default true).",
            "type": "boolean"
          },
          "use_default_excludes": {
            "default": true,
            "description": "Skip paths matching default_exclude_globs (.git, node_modules, …; see coder::info) in content and path results; false searches inside them.",
            "type": "boolean"
          }
        },
        "required": [
          "query"
        ],
        "title": "SearchInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ContentMatch": {
            "properties": {
              "after": {
                "description": "Context lines immediately after the matched line — same file only, in file order, each truncated to `max_line_bytes`. Omitted when empty (no `context_lines_after` requested, or the match is at the end of the file).",
                "items": {
                  "type": "string"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "before": {
                "description": "Context lines immediately before the matched line — same file only, in file order, each truncated to `max_line_bytes`. Omitted when empty (no `context_lines_before` requested, or the match is at the start of the file).",
                "items": {
                  "type": "string"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "column": {
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "line": {
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "path": {
                "description": "Absolute path under the canonical parent; symlinks at the entry itself are not resolved. Operations on it re-validate through the jail.",
                "type": "string"
              },
              "text": {
                "description": "Matched line; truncated to `max_line_bytes` and never spans newlines.",
                "type": "string"
              }
            },
            "required": [
              "column",
              "line",
              "path",
              "text"
            ],
            "type": "object"
          },
          "PathMatch": {
            "properties": {
              "kind": {
                "allOf": [
                  {
                    "$ref": "#/definitions/PathMatchKind"
                  }
                ],
                "description": "What matched: `file` or `dir`. Directories match by NAME only — content search never reads them."
              },
              "path": {
                "description": "Absolute path under the canonical parent; symlinks at the entry itself are not resolved. Operations on it re-validate through the jail.",
                "type": "string"
              }
            },
            "required": [
              "kind",
              "path"
            ],
            "type": "object"
          },
          "PathMatchKind": {
            "enum": [
              "file",
              "dir"
            ],
            "type": "string"
          }
        },
        "properties": {
          "content_matches": {
            "items": {
              "$ref": "#/definitions/ContentMatch"
            },
            "type": "array"
          },
          "path_matches": {
            "items": {
              "$ref": "#/definitions/PathMatch"
            },
            "type": "array"
          },
          "truncated": {
            "description": "True if results were cut off — either match list hit the `max_matches` cap, or the response hit the `search_response_budget_bytes` byte budget. When true, refine the query or add include_globs rather than paginate.",
            "type": "boolean"
          }
        },
        "required": [
          "content_matches",
          "path_matches",
          "truncated"
        ],
        "title": "SearchOutput",
        "type": "object"
      }
    },
    {
      "description": "Show a directory tree, bounded by max_depth, per_folder_limit and a node budget. Nodes carry only name (child path = parent path + '/' + name); over-limit folders are truncated stubs to paginate with coder::list-folder. Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {},
      "name": "coder::tree",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "max_depth": 3,
            "path": "."
          }
        ],
        "properties": {
          "include_hidden": {
            "default": true,
            "description": "List dot-prefixed entries; false omits them at every level (they then don't count toward per_folder_limit). The requested root is exempt.",
            "type": "boolean"
          },
          "max_depth": {
            "default": null,
            "description": "Maximum depth to descend; the root node is depth 0.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "path": {
            "default": ".",
            "description": "Base folder for the snapshot (default `.`).",
            "type": "string"
          },
          "per_folder_limit": {
            "default": null,
            "description": "Maximum children listed per folder. When more exist, the folder is flagged `truncated` and callers should switch to `coder::list-folder`.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "use_default_excludes": {
            "default": true,
            "description": "Prune directories matching default_exclude_globs (see coder::info) into childless `truncated` stubs; false lists everything.",
            "type": "boolean"
          }
        },
        "title": "TreeInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "NodeKind": {
            "enum": [
              "file",
              "dir",
              "symlink",
              "other"
            ],
            "type": "string"
          },
          "TreeNode": {
            "properties": {
              "children": {
                "items": {
                  "$ref": "#/definitions/TreeNode"
                },
                "type": [
                  "array",
                  "null"
                ]
              },
              "kind": {
                "$ref": "#/definitions/NodeKind"
              },
              "mtime": {
                "format": "int64",
                "type": "integer"
              },
              "name": {
                "description": "Entry basename. The ROOT node's path is the response's top-level `path` itself; every other node's path derives by joining from there: child path = parent path + \"/\" + name.",
                "type": "string"
              },
              "non_accessible": {
                "type": "boolean"
              },
              "size": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "truncated": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/TruncationInfo"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Set on directories whose `children` was capped at `per_folder_limit`, whose subtree was cut off by `max_depth`, which matched `default_exclude_globs` or omitted matching non-directory children (reason \"default_exclude\"), or where the snapshot's node budget ran out (reason \"max_nodes\")."
              }
            },
            "required": [
              "kind",
              "mtime",
              "name",
              "non_accessible",
              "size"
            ],
            "type": "object"
          },
          "TruncationInfo": {
            "properties": {
              "hint": {
                "type": "string"
              },
              "reason": {
                "description": "Reason this folder was truncated: hit `per_folder_limit`, cut off by `max_depth`, matched `default_exclude_globs` (`default_exclude`), omitted matching non-directory children, or the snapshot's total node budget ran out (`max_nodes`).",
                "type": "string"
              },
              "shown": {
                "description": "Number of children actually returned.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "total": {
                "description": "Total number of children eligible for listing in the folder, counted after hidden and default-exclude filtering (only populated when `reason == \"per_folder_limit\"`; for depth truncation we don't peek into the folder).",
                "format": "uint32",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "required": [
              "hint",
              "reason",
              "shown"
            ],
            "type": "object"
          }
        },
        "properties": {
          "path": {
            "description": "Canonical absolute path of the requested folder (resolved through the jail). Nodes carry only `name`, and the root node's path IS this `path` — do not join the root's `name` onto it; derive children by joining from here: child path = parent path + \"/\" + name. Operations on derived paths re-validate through the jail.",
            "type": "string"
          },
          "root": {
            "allOf": [
              {
                "$ref": "#/definitions/TreeNode"
              }
            ],
            "description": "Root node of the snapshot; its `name` is the folder's basename."
          }
        },
        "required": [
          "path",
          "root"
        ],
        "title": "TreeOutput",
        "type": "object"
      }
    },
    {
      "description": "Edit one or more files: batched line ops (1-based, inclusive, applied bottom-up), then regex replace ops; each file commits atomically. To replace a large region use two short anchors joined by .*? with dot_matches_newline: true instead of quoting it. Paths: relative to the primary root or absolute inside an allowed root (see coder::info).",
      "metadata": {
        "display": true
      },
      "name": "coder::update-file",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "UpdateFileSpec": {
            "properties": {
              "ops": {
                "items": {
                  "$ref": "#/definitions/UpdateOp"
                },
                "type": "array"
              },
              "path": {
                "description": "File to edit.",
                "type": "string"
              }
            },
            "required": [
              "ops",
              "path"
            ],
            "type": "object"
          },
          "UpdateOp": {
            "oneOf": [
              {
                "description": "Insert `content` before line `at_line` (1-based). `at_line = lines+1` appends to end of file.",
                "properties": {
                  "at_line": {
                    "format": "uint32",
                    "minimum": 0,
                    "type": "integer"
                  },
                  "content": {
                    "type": "string"
                  },
                  "op": {
                    "enum": [
                      "insert"
                    ],
                    "type": "string"
                  }
                },
                "required": [
                  "at_line",
                  "content",
                  "op"
                ],
                "type": "object"
              },
              {
                "description": "Delete lines `from_line..=to_line` (1-based, inclusive).",
                "properties": {
                  "from_line": {
                    "format": "uint32",
                    "minimum": 0,
                    "type": "integer"
                  },
                  "op": {
                    "enum": [
                      "remove"
                    ],
                    "type": "string"
                  },
                  "to_line": {
                    "format": "uint32",
                    "minimum": 0,
                    "type": "integer"
                  }
                },
                "required": [
                  "from_line",
                  "op",
                  "to_line"
                ],
                "type": "object"
              },
              {
                "description": "Overwrite lines `from_line..=to_line` with `content`.",
                "properties": {
                  "content": {
                    "type": "string"
                  },
                  "from_line": {
                    "format": "uint32",
                    "minimum": 0,
                    "type": "integer"
                  },
                  "op": {
                    "enum": [
                      "update_lines"
                    ],
                    "type": "string"
                  },
                  "to_line": {
                    "format": "uint32",
                    "minimum": 0,
                    "type": "integer"
                  }
                },
                "required": [
                  "content",
                  "from_line",
                  "op",
                  "to_line"
                ],
                "type": "object"
              },
              {
                "description": "Replace all regex matches in the file body (after line ops).",
                "properties": {
                  "dot_matches_newline": {
                    "default": false,
                    "description": "When true `.` also matches newline, so a short pattern like `start.*?end` spans lines; by default a multi-line pattern matches nothing.",
                    "type": "boolean"
                  },
                  "expect_matches": {
                    "default": null,
                    "description": "Expected match count; a mismatch fails this file with C210 and writes nothing (0 asserts absence). Omit to replace all matches.",
                    "format": "uint64",
                    "minimum": 0,
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "ignore_case": {
                    "default": false,
                    "type": "boolean"
                  },
                  "op": {
                    "enum": [
                      "replace"
                    ],
                    "type": "string"
                  },
                  "pattern": {
                    "type": "string"
                  },
                  "replacement": {
                    "description": "Substitution text; $1/${1}/$name/${name} expand captures ($0 = whole match), a literal $ is written $$ (`$${name}`). Unknown references fail C210.",
                    "type": "string"
                  }
                },
                "required": [
                  "op",
                  "pattern",
                  "replacement"
                ],
                "type": "object"
              }
            ]
          }
        },
        "examples": [
          {
            "files": [
              {
                "ops": [
                  {
                    "at_line": 1,
                    "content": "// generated by coder\n",
                    "op": "insert"
                  },
                  {
                    "content": "pub fn hello() {\n    println!(\"hello\");\n}\n",
                    "from_line": 5,
                    "op": "update_lines",
                    "to_line": 7
                  },
                  {
                    "dot_matches_newline": true,
                    "expect_matches": 1,
                    "op": "replace",
                    "pattern": "// BEGIN legacy.*?// END legacy",
                    "replacement": "// removed"
                  }
                ],
                "path": "src/lib.rs"
              }
            ]
          }
        ],
        "properties": {
          "files": {
            "items": {
              "$ref": "#/definitions/UpdateFileSpec"
            },
            "type": "array"
          }
        },
        "required": [
          "files"
        ],
        "title": "UpdateFileInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "OpEcho": {
            "description": "Post-apply snapshot of the region affected by one op. Line ops echo the affected region with ±2 context lines; regex `replace` ops emit one echo per match site (up to 5, no context): the FIRST and LAST line of the post-replace region, with `elided` set to the inner line count when the region spans more than 2 lines (single-line replacements echo just that line). Each site carries `total_replacements`. Provides just enough context to verify the edit landed in the right place without flooding the LLM context with the full file body.",
            "properties": {
              "elided": {
                "description": "Number of middle lines elided from a large region: for line ops, set when the affected region exceeded the per-echo cap; for replace sites, the count of inner lines between the region's echoed first and last line (set when the region spans >2 lines).",
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "from_line": {
                "description": "1-based line number of the first echoed line (after all ops applied).",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "lines": {
                "description": "The echoed lines, post-apply. When the region is large, middle lines are elided and `elided` is set to indicate how many were skipped.",
                "items": {
                  "type": "string"
                },
                "type": "array"
              },
              "op_index": {
                "description": "Index of the op in the request's ops array (0-based).",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "total_replacements": {
                "description": "Total number of replacements the regex op made across the whole file (set only on replace-op site echoes, duplicated on each site). Sites are capped at 5 — when more matched, this count is the only record of the extras.",
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              }
            },
            "required": [
              "from_line",
              "lines",
              "op_index"
            ],
            "type": "object"
          },
          "UpdateFileResult": {
            "properties": {
              "applied": {
                "description": "Number of operations applied (only meaningful when `success`).",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "echoes": {
                "description": "Per-op bounded post-apply echoes for edit verification; each applied op returns a snapshot of the affected region (±2 context lines) so the caller can confirm the edit landed at the right position without receiving the full file body. See `OpEcho` for field semantics. Empty on failure. Always present on the wire.",
                "items": {
                  "$ref": "#/definitions/OpEcho"
                },
                "type": "array"
              },
              "echoes_truncated": {
                "description": "True when the total echo budget (~4 KiB) was exhausted before all op echoes could be emitted. Use `coder::read-file` to inspect the full result if needed. Always present on the wire.",
                "type": "boolean"
              },
              "error": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/WireError"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Structured error for this entry. `code` is stable for programmatic branching (e.g. `\"C211\"` for not-found-or-denied; `\"C210\"` for bad input such as overlapping ops). `message` carries the corrective action an LLM agent needs to make a successful second call."
              },
              "new_line_count": {
                "description": "Final line count after applying (only meaningful when `success`).",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "path": {
                "description": "Canonical absolute path (resolved through the jail); the caller's input verbatim when resolution failed.",
                "type": "string"
              },
              "success": {
                "type": "boolean"
              }
            },
            "required": [
              "applied",
              "echoes",
              "echoes_truncated",
              "new_line_count",
              "path",
              "success"
            ],
            "type": "object"
          },
          "WireError": {
            "description": "Structured per-entry error as it appears on the wire.\n\nUse `code` for stable programmatic branching (e.g. `\"C211\"` for not-found-or-denied). `message` carries the human/LLM-readable problem description plus the corrective next call.",
            "properties": {
              "code": {
                "description": "Stable error code, e.g. \"C211\". See the README error table.",
                "type": "string"
              },
              "message": {
                "description": "Human/LLM-readable message: problem + actual values + corrective next call.",
                "type": "string"
              }
            },
            "required": [
              "code",
              "message"
            ],
            "type": "object"
          }
        },
        "properties": {
          "results": {
            "items": {
              "$ref": "#/definitions/UpdateFileResult"
            },
            "type": "array"
          }
        },
        "required": [
          "results"
        ],
        "title": "UpdateFileOutput",
        "type": "object"
      }
    },
    {
      "description": "Report the last configuration hot-reload outcome: last_outcome (applied|rejected), last_error, and rejected_reloads since boot. A rejected outcome means shell is enforcing an older policy than the central store. Takes no arguments.",
      "metadata": {
        "internal": true
      },
      "name": "shell::config-status",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "`shell::config-status` takes no arguments.",
        "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 a command in the foreground and return its full output. Put the program in `command` (string) and arguments in `args` (string[]), never argv in `command`. Host-only cwd/env/stdin reject S210 on a sandbox target. Errors return { code, message }; common: S216 host exec error, S300 VM boot failed, S200 in-VM failure.",
      "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`.",
        "properties": {
          "args": {
            "default": null,
            "description": "Arguments, in order (all strings). Omit/null to tokenize `command` shell-words style; pass an array (even empty) to use it verbatim.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "command": {
            "description": "Program name (PATH-resolved) or path to an executable, as a string; put arguments in `args`.",
            "type": "string"
          },
          "cwd": {
            "default": null,
            "description": "Working directory (host only, S210 on sandbox); jail-confined like shell::fs::* paths (S215 on escape), must be an existing directory.",
            "type": [
              "string",
              "null"
            ]
          },
          "env": {
            "additionalProperties": {
              "type": "string"
            },
            "default": null,
            "description": "Per-call env values (host only, S210 on sandbox); exec-hijacking keys (PATH, IFS, HOME, LD_*/DYLD_*, …) reject the whole call S210.",
            "type": [
              "object",
              "null"
            ]
          },
          "stdin": {
            "default": null,
            "description": "Bytes written to the program's stdin, then EOF (host only, S210 on sandbox); omit to leave stdin at /dev/null.",
            "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 in milliseconds, capped at the configured max; negative or fractional values fall back to the default.",
            "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 a background job; returns { job_id, argv } immediately. Accepts the shell::exec payload plus optional job_id. Register shell::job-finished with a fresh job_id before calling exec_bg with that same ID, or omit job_id to generate one. Duplicate retained IDs reject before execution. Host jobs ignore timeout_ms and use the configured background hard cap; read output with shell::status, list jobs with shell::list. Spawn-time failures are plain-string messages; later failures emit shell::job-finished and appear in shell::status.",
      "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`.",
        "properties": {
          "args": {
            "default": null,
            "description": "Arguments, in order (all strings). Omit/null to tokenize `command` shell-words style; pass an array (even empty) to use it verbatim.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "command": {
            "description": "Program name (PATH-resolved) or path to an executable, as a string; put arguments in `args`.",
            "type": "string"
          },
          "cwd": {
            "default": null,
            "description": "Working directory (host only, S210 on sandbox); jail-confined like shell::fs::* paths (S215 on escape), must be an existing directory.",
            "type": [
              "string",
              "null"
            ]
          },
          "env": {
            "additionalProperties": {
              "type": "string"
            },
            "default": null,
            "description": "Per-call env values (host only, S210 on sandbox); exec-hijacking keys (PATH, IFS, HOME, LD_*/DYLD_*, …) reject the whole call S210.",
            "type": [
              "object",
              "null"
            ]
          },
          "job_id": {
            "default": null,
            "description": "Optional non-blank ID for registering shell::job-finished before execution. Must be unique among retained jobs in this worker. Omit or pass null to generate a job-UUID automatically.",
            "minLength": 1,
            "type": [
              "string",
              "null"
            ]
          },
          "stdin": {
            "default": null,
            "description": "Bytes written to the program's stdin, then EOF (host only, S210 on sandbox); omit to leave stdin at /dev/null.",
            "type": [
              "string",
              "null"
            ]
          },
          "target": {
            "allOf": [
              {
                "$ref": "#/definitions/Target"
              }
            ],
            "default": {
              "kind": "host"
            },
            "description": "Where to run: the host (default) or { kind: \"sandbox\", sandbox_id }."
          },
          "timeout_ms": {
            "default": null,
            "description": "Per-call timeout in milliseconds; ignored by host background jobs, forwarded to sandbox targets.",
            "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 a file or directory's 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 or not accessible, S215 jail escape.",
      "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_roots are 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 for a pattern (Rust regex, RE2-like); `include_glob`/`exclude_glob` filter paths. Returns { matches, truncated }. Errors return { code, message }; common: S217 bad regex, S215 jail escape. coder::search adds context lines and noise filtering.",
      "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_roots are 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 the files and subdirectories of a directory on the filesystem, sorted by name and paginated: `page` (1-based, default 1) and `page_size` (default 500, max 2000); `has_more: true` means request the next page. Errors return { code, message }; common: S210 bad path, S211 not found or not accessible, S212 not a directory, S215 jail escape. For recursive listings prefer coder::tree.",
      "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"
              }
            ]
          }
        },
        "description": "Request for `shell::fs::ls`.",
        "properties": {
          "page": {
            "default": null,
            "description": "1-based page of the name-sorted listing. Default 1.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "page_size": {
            "default": null,
            "description": "Entries per page. Default 500, clamped to 2000. A directory larger than one page answers `has_more: true` — request the next `page` rather than expecting the whole directory in one result.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "path": {
            "description": "Jail-relative when fs.host_roots are 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 on this page, sorted by name.",
            "items": {
              "$ref": "#/definitions/FsEntry"
            },
            "type": "array"
          },
          "has_more": {
            "default": false,
            "description": "True when later pages exist; request `page + 1` to continue.",
            "type": "boolean"
          },
          "page": {
            "default": 0,
            "description": "The 1-based page this response carries.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "page_size": {
            "default": 0,
            "description": "The page size actually applied (after clamping).",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "total": {
            "default": 0,
            "description": "Entries in the whole directory, across every page.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "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 escape.",
      "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_roots are 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 or rename a file or directory. `overwrite: true` allows replacing an existing dst. Returns { moved, src, dst, overwrote }. Errors return { code, message }; common: S211 src not found or not accessible, S213 dst exists, S215 jail escape. coder::move moves several paths per call.",
      "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_roots are 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_roots are 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: returns a ContentRef handle (channel_id/access_key) plus size/mode/mtime, NOT the text — for text use coder::read-file, which returns content inline. Errors return { code, message }; common: S211 not found or not accessible, S212 is a directory, S218 over 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_roots are 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": "Delete a file or directory. `recursive: true` is required for a non-empty directory. Returns { removed, path, was_present }. Errors return { code, message }; common: S211 not found or not accessible, S214 dir not empty, S215 jail escape. coder::delete-file removes several paths per call.",
      "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_roots are 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 (regex: false for a literal). Provide either `files` or `path` (+ recursive). Returns { results, total_replacements }. Errors return { code, message }; common: S217 bad regex, S211 not found, S215 jail escape.",
      "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 one file or directory: its type, size, mode, and mtime (paths jail-relative when fs.host_roots are set). Errors return { code, message }; common: S211 not found or not accessible, S215 jail escape. coder::read-file with stat: true additionally reports total_lines.",
      "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_roots are 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. A string `content` is written inline (host only); large payloads or a sandbox target need a ContentRef { channel_id, access_key, direction } from a write stream channel. `files: [...]` writes several files (host, inline). Errors return { code, message }; common: S210 bad mode or inline-on-sandbox, S218 over max_write_bytes.",
      "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": "Write content: a plain string is written inline (host target only); an object is a streaming ContentRef { 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_roots are set, else absolute.",
                "type": "string"
              }
            },
            "required": [
              "content",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "content": {
            "anyOf": [
              {
                "$ref": "#/definitions/WriteContentWire"
              },
              {
                "type": "null"
              }
            ],
            "description": "Single-file content: an inline string (host only) or a ContentRef { channel_id, access_key, direction } for an open write stream; omit with `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\" (the default); omit when using `files`.",
            "type": [
              "string",
              "null"
            ]
          },
          "parents": {
            "default": null,
            "description": "Create missing parent directories (single-file form, default false); omit when using `files`.",
            "type": [
              "boolean",
              "null"
            ]
          },
          "path": {
            "default": null,
            "description": "Single-file form: the path to write. Jail-relative when fs.host_roots are 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 (returned by 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.",
        "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": {
        "internal": true
      },
      "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": "Take back an unattached PTY session whose reconnect token is gone, from the console page that owns it. Refuses a session someone is attached to, and a page that is not the session's own.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::pty::adopt",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Take back a session whose reconnect token is gone.",
        "properties": {
          "after_sequence": {
            "default": 0,
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "cols": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "output_function_id": {
            "description": "Where output goes next; must belong to the same console page family as the target it replaces.",
            "type": "string"
          },
          "rows": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "cols",
          "output_function_id",
          "rows",
          "session_id"
        ],
        "title": "AdoptRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "OutputFrame": {
            "properties": {
              "data": {
                "type": "string"
              },
              "sequence": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "data",
              "sequence"
            ],
            "type": "object"
          },
          "SessionStatus": {
            "oneOf": [
              {
                "enum": [
                  "attached",
                  "detached"
                ],
                "type": "string"
              },
              {
                "additionalProperties": false,
                "properties": {
                  "exited": {
                    "properties": {
                      "error": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "exit_code": {
                        "format": "uint32",
                        "minimum": 0,
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "signal": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "exited"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "access_key": {
            "type": "string"
          },
          "cwd": {
            "type": "string"
          },
          "frames": {
            "items": {
              "$ref": "#/definitions/OutputFrame"
            },
            "type": "array"
          },
          "next_sequence": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "reconnect_token": {
            "type": "string"
          },
          "status": {
            "$ref": "#/definitions/SessionStatus"
          },
          "truncated": {
            "type": "boolean"
          }
        },
        "required": [
          "access_key",
          "cwd",
          "frames",
          "next_sequence",
          "reconnect_token",
          "status",
          "truncated"
        ],
        "title": "AttachResponse",
        "type": "object"
      }
    },
    {
      "description": "Attach to a retained PTY session and replay buffered output.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::pty::attach",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "after_sequence": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "cols": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "output_function_id": {
            "type": "string"
          },
          "reconnect_token": {
            "type": "string"
          },
          "request_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "rows": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "after_sequence",
          "cols",
          "output_function_id",
          "reconnect_token",
          "rows",
          "session_id"
        ],
        "title": "AttachRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "OutputFrame": {
            "properties": {
              "data": {
                "type": "string"
              },
              "sequence": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              }
            },
            "required": [
              "data",
              "sequence"
            ],
            "type": "object"
          },
          "SessionStatus": {
            "oneOf": [
              {
                "enum": [
                  "attached",
                  "detached"
                ],
                "type": "string"
              },
              {
                "additionalProperties": false,
                "properties": {
                  "exited": {
                    "properties": {
                      "error": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "exit_code": {
                        "format": "uint32",
                        "minimum": 0,
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "signal": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "exited"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "access_key": {
            "type": "string"
          },
          "cwd": {
            "type": "string"
          },
          "frames": {
            "items": {
              "$ref": "#/definitions/OutputFrame"
            },
            "type": "array"
          },
          "next_sequence": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "reconnect_token": {
            "type": "string"
          },
          "status": {
            "$ref": "#/definitions/SessionStatus"
          },
          "truncated": {
            "type": "boolean"
          }
        },
        "required": [
          "access_key",
          "cwd",
          "frames",
          "next_sequence",
          "reconnect_token",
          "status",
          "truncated"
        ],
        "title": "AttachResponse",
        "type": "object"
      }
    },
    {
      "description": "Terminate and close a PTY session.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::pty::close",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "access_key": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "access_key",
          "session_id"
        ],
        "title": "CloseRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "closed": {
            "type": "boolean"
          }
        },
        "required": [
          "closed"
        ],
        "title": "CloseResponse",
        "type": "object"
      }
    },
    {
      "description": "Detach a browser output target while retaining its PTY session.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::pty::detach",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "access_key": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "access_key",
          "session_id"
        ],
        "title": "DetachRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "SessionStatus": {
            "oneOf": [
              {
                "enum": [
                  "attached",
                  "detached"
                ],
                "type": "string"
              },
              {
                "additionalProperties": false,
                "properties": {
                  "exited": {
                    "properties": {
                      "error": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "exit_code": {
                        "format": "uint32",
                        "minimum": 0,
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "signal": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "exited"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "status": {
            "$ref": "#/definitions/SessionStatus"
          }
        },
        "required": [
          "status"
        ],
        "title": "DetachResponse",
        "type": "object"
      }
    },
    {
      "description": "Open a persistent host PTY running the user's login shell, or the program named in `program`.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::pty::open",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "args": {
            "default": null,
            "description": "argv for `program`. Ignored without `program` (a login shell takes no arguments here).",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "cols": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "cwd": {
            "type": "string"
          },
          "env": {
            "additionalProperties": {
              "type": "string"
            },
            "default": null,
            "description": "Extra environment on top of what the worker forwards; exec-hijacking keys (PATH, LD_*, DYLD_*, BASH_ENV, …) fail the call.",
            "type": [
              "object",
              "null"
            ]
          },
          "output_function_id": {
            "type": "string"
          },
          "program": {
            "default": null,
            "description": "Program to run instead of the user's login shell (an agent CLI, REPL, TUI), resolved on the worker's PATH.",
            "type": [
              "string",
              "null"
            ]
          },
          "request_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "rows": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "cols",
          "cwd",
          "output_function_id",
          "rows"
        ],
        "title": "OpenRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "access_key": {
            "type": "string"
          },
          "cwd": {
            "type": "string"
          },
          "pid": {
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "program": {
            "description": "The program the session runs; absent for a login shell.",
            "type": [
              "string",
              "null"
            ]
          },
          "reconnect_token": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "access_key",
          "cwd",
          "reconnect_token",
          "session_id"
        ],
        "title": "OpenResponse",
        "type": "object"
      }
    },
    {
      "description": "Resize a PTY session in terminal columns and rows.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::pty::resize",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "access_key": {
            "type": "string"
          },
          "cols": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "rows": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "access_key",
          "cols",
          "rows",
          "session_id"
        ],
        "title": "ResizeRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "cols": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          },
          "rows": {
            "format": "uint16",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "cols",
          "rows"
        ],
        "title": "ResizeResponse",
        "type": "object"
      }
    },
    {
      "description": "List live PTY sessions with program, cwd, sequence, replay buffer size, and output target. Diagnostics only — no credentials.",
      "metadata": {},
      "name": "shell::pty::sessions",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "SessionsRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "SessionStatus": {
            "oneOf": [
              {
                "enum": [
                  "attached",
                  "detached"
                ],
                "type": "string"
              },
              {
                "additionalProperties": false,
                "properties": {
                  "exited": {
                    "properties": {
                      "error": {
                        "type": [
                          "string",
                          "null"
                        ]
                      },
                      "exit_code": {
                        "format": "uint32",
                        "minimum": 0,
                        "type": [
                          "integer",
                          "null"
                        ]
                      },
                      "signal": {
                        "type": [
                          "string",
                          "null"
                        ]
                      }
                    },
                    "type": "object"
                  }
                },
                "required": [
                  "exited"
                ],
                "type": "object"
              }
            ]
          },
          "SessionSummary": {
            "description": "One live session, without credentials: enough to tell a terminal that shows nothing apart from one that was never fed. A page that counts the frames it applied compares its own count against `sequence` — equal means the frames arrived and the browser is at fault, far behind means delivery is.",
            "properties": {
              "cwd": {
                "type": "string"
              },
              "frame_bytes": {
                "format": "uint",
                "minimum": 0,
                "type": "integer"
              },
              "frames": {
                "description": "Frames still replayable from the ring buffer.",
                "format": "uint",
                "minimum": 0,
                "type": "integer"
              },
              "output_function_id": {
                "description": "Where output is being delivered; absent while detached.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "pid": {
                "format": "uint32",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "program": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "sequence": {
                "description": "Sequence number of the last frame the session produced.",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "session_id": {
                "type": "string"
              },
              "status": {
                "$ref": "#/definitions/SessionStatus"
              },
              "truncated": {
                "description": "Whether the buffer has dropped frames a full replay would need.",
                "type": "boolean"
              },
              "ui": {
                "description": "The console page family this session belongs to — the `<name>` in `iii::<name>-ui::pty-output::console-<browser>`. Present whether or not anyone is attached, because it is what a page uses to recognise its own orphaned session; it names a page, never a browser.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "cwd",
              "frame_bytes",
              "frames",
              "sequence",
              "session_id",
              "status",
              "truncated"
            ],
            "type": "object"
          }
        },
        "properties": {
          "sessions": {
            "items": {
              "$ref": "#/definitions/SessionSummary"
            },
            "type": "array"
          }
        },
        "required": [
          "sessions"
        ],
        "title": "SessionsResponse",
        "type": "object"
      }
    },
    {
      "description": "Write base64-encoded keyboard input to a PTY session.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::pty::write",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "access_key": {
            "type": "string"
          },
          "data": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "access_key",
          "data",
          "session_id"
        ],
        "title": "WriteRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "written": {
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "written"
        ],
        "title": "WriteResponse",
        "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"
      }
    },
    {
      "description": "Internal: add a newly created project root to the active Harness turn after a confirmed workspace proposal.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::turns::adopt-root",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "root": {
            "type": "string"
          },
          "session_id": {
            "type": "string"
          },
          "turn_id": {
            "type": "string"
          }
        },
        "required": [
          "root",
          "session_id",
          "turn_id"
        ],
        "title": "AdoptRootInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "recorded": {
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "recorded"
        ],
        "title": "AdoptRootOutput",
        "type": "object"
      }
    },
    {
      "description": "Fetch one turn of a session's change history: every file it changed, the change kind, the function that made it, the sub-agent that did it when one did, the file's body before the turn and, once the turn has ended or a later turn kept it, the body it left behind (`after`). Bodies come from the root's tree snapshots when the turn has them, hooked or not, else from the hooks' pre-images; 1 MiB of bodies per response. Omit turn_id for the newest turn.",
      "metadata": {},
      "name": "shell::turns::get",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "type": "string"
          },
          "turn_id": {
            "default": null,
            "description": "Omit for the newest turn.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "session_id"
        ],
        "title": "GetInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "AgentRef": {
            "properties": {
              "name": {
                "description": "The `subagent_display` name the harness stamped, when it did.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "session_id": {
                "type": "string"
              }
            },
            "required": [
              "session_id"
            ],
            "type": "object"
          },
          "FileRecord": {
            "properties": {
              "after": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/PreImage"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The body after this turn's last write, when a later turn kept it as its own pre-image. Never stored; filled in by `shell::turns::get`. Absent means the working copy is the body after this turn."
              },
              "after_revision": {
                "description": "Revision after the last write, when the file could be read back.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "agent": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/AgentRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The sub-agent session that made the change, when it was not the turn's own session. Children's changes are recorded under the turn of the top-level agent that spawned them, so one turn reads as one unit of work whoever did the typing."
              },
              "before": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/PreImage"
                  },
                  {
                    "type": "null"
                  }
                ]
              },
              "cause": {
                "description": "Function id that made the change.",
                "type": "string"
              },
              "first_seen": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "from": {
                "description": "Set for a `moved` record: where the file came from.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "kind": {
                "description": "`created` | `modified` | `deleted` | `moved`.",
                "type": "string"
              },
              "last_seen": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "path": {
                "description": "Absolute path on the host.",
                "type": "string"
              },
              "root": {
                "description": "The session's workspace root at the time, when the harness named one.",
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "cause",
              "first_seen",
              "kind",
              "last_seen",
              "path"
            ],
            "type": "object"
          },
          "PreImage": {
            "properties": {
              "binary": {
                "type": "boolean"
              },
              "content": {
                "description": "UTF-8 body before the write. Never stored in the record; filled in by `shell::turns::get` from the blob store, up to `MAX_PRE_IMAGE_BYTES`.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "missing": {
                "description": "The path did not exist before the call.",
                "type": "boolean"
              },
              "revision": {
                "description": "`sha256:<hex>` of the bytes before the write; absent when the file did not exist or could not be read. Doubles as the blob key.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "stored": {
                "description": "A blob for `revision` exists in the store.",
                "type": "boolean"
              },
              "truncated": {
                "description": "The body was larger than the cap (not kept) or did not fit the response budget; the revision still identifies it.",
                "type": "boolean"
              }
            },
            "type": "object"
          },
          "TurnRecord": {
            "properties": {
              "ended_at": {
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "files": {
                "default": [],
                "items": {
                  "$ref": "#/definitions/FileRecord"
                },
                "type": "array"
              },
              "snapshot": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/TurnSnapshot"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "Trees of the root around the turn (see `turn_snapshot`): where `shell::turns::get` reads the sides of every file from, hooked or not."
              },
              "started_at": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "title": {
                "description": "The first characters of the message that started the turn, from the harness `turn-started` event; what a timeline calls the turn.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "turn_id": {
                "type": "string"
              }
            },
            "required": [
              "started_at",
              "turn_id"
            ],
            "type": "object"
          },
          "TurnSnapshot": {
            "properties": {
              "after": {
                "description": "The tree when the turn ended; absent while it runs.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "after_at": {
                "description": "When `after` was taken. A file the turn was still gaining after that (a sub-agent at work past its parent's end) is not in the tree; its after side is the working copy.",
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "before": {
                "description": "The tree when the turn started.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "before_at": {
                "description": "When `before` was taken, unix ms.",
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "root": {
                "description": "The canonical root the trees describe.",
                "type": "string"
              }
            },
            "required": [
              "root"
            ],
            "type": "object"
          }
        },
        "properties": {
          "session_id": {
            "type": "string"
          },
          "turn": {
            "anyOf": [
              {
                "$ref": "#/definitions/TurnRecord"
              },
              {
                "type": "null"
              }
            ]
          }
        },
        "required": [
          "session_id"
        ],
        "title": "GetOutput",
        "type": "object"
      }
    },
    {
      "description": "List the turns of a harness session with the files each one changed through shell or coder functions, newest first. Paths and kinds only; use shell::turns::get for the pre-images.",
      "metadata": {},
      "name": "shell::turns::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "session_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id"
        ],
        "title": "ListInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "AgentRef": {
            "properties": {
              "name": {
                "description": "The `subagent_display` name the harness stamped, when it did.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "session_id": {
                "type": "string"
              }
            },
            "required": [
              "session_id"
            ],
            "type": "object"
          },
          "FileHead": {
            "properties": {
              "agent": {
                "anyOf": [
                  {
                    "$ref": "#/definitions/AgentRef"
                  },
                  {
                    "type": "null"
                  }
                ],
                "description": "The sub-agent that made the change, when it was not the turn's own session."
              },
              "kind": {
                "type": "string"
              },
              "path": {
                "type": "string"
              },
              "root": {
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "required": [
              "kind",
              "path"
            ],
            "type": "object"
          },
          "TurnSummary": {
            "properties": {
              "ended_at": {
                "format": "uint64",
                "minimum": 0,
                "type": [
                  "integer",
                  "null"
                ]
              },
              "file_count": {
                "format": "uint",
                "minimum": 0,
                "type": "integer"
              },
              "files": {
                "description": "Paths with their change kind, without bodies.",
                "items": {
                  "$ref": "#/definitions/FileHead"
                },
                "type": "array"
              },
              "started_at": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "title": {
                "description": "First characters of the message that started the turn.",
                "type": [
                  "string",
                  "null"
                ]
              },
              "turn_id": {
                "type": "string"
              }
            },
            "required": [
              "file_count",
              "files",
              "started_at",
              "turn_id"
            ],
            "type": "object"
          }
        },
        "properties": {
          "session_id": {
            "type": "string"
          },
          "turns": {
            "description": "Newest first.",
            "items": {
              "$ref": "#/definitions/TurnSummary"
            },
            "type": "array"
          }
        },
        "required": [
          "session_id",
          "turns"
        ],
        "title": "ListOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: records a file change made through a shell or coder call into the session's durable change history. Observes only; always continues.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::turns::on-post-trigger",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "HookCall": {
            "properties": {
              "arguments": {
                "default": null
              },
              "function_id": {
                "type": "string"
              }
            },
            "required": [
              "function_id"
            ],
            "type": "object"
          },
          "HookResult": {
            "properties": {
              "is_error": {
                "default": false,
                "type": "boolean"
              }
            },
            "type": "object"
          }
        },
        "description": "The part of the harness hook envelope this module reads. One shape serves every hook point: `call` and `result` are the trigger hooks' (`result` as `{ is_error }`), while the `post_turn` envelope puts the turn's own result under `result`, whatever JSON that is, so that field is read leniently rather than typed.",
        "properties": {
          "call": {
            "anyOf": [
              {
                "$ref": "#/definitions/HookCall"
              },
              {
                "type": "null"
              }
            ]
          },
          "metadata": {
            "default": null
          },
          "result": {
            "anyOf": [
              {
                "$ref": "#/definitions/HookResult"
              },
              {
                "type": "null"
              }
            ]
          },
          "session_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "turn_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "HookInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "decision": {
            "type": "string"
          }
        },
        "required": [
          "decision"
        ],
        "title": "HookOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: photographs the session's workspace root as a turn ends, the after side of every file it changed. Observes only; always continues.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::turns::on-post-turn",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "HookCall": {
            "properties": {
              "arguments": {
                "default": null
              },
              "function_id": {
                "type": "string"
              }
            },
            "required": [
              "function_id"
            ],
            "type": "object"
          },
          "HookResult": {
            "properties": {
              "is_error": {
                "default": false,
                "type": "boolean"
              }
            },
            "type": "object"
          }
        },
        "description": "The part of the harness hook envelope this module reads. One shape serves every hook point: `call` and `result` are the trigger hooks' (`result` as `{ is_error }`), while the `post_turn` envelope puts the turn's own result under `result`, whatever JSON that is, so that field is read leniently rather than typed.",
        "properties": {
          "call": {
            "anyOf": [
              {
                "$ref": "#/definitions/HookCall"
              },
              {
                "type": "null"
              }
            ]
          },
          "metadata": {
            "default": null
          },
          "result": {
            "anyOf": [
              {
                "$ref": "#/definitions/HookResult"
              },
              {
                "type": "null"
              }
            ]
          },
          "session_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "turn_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "HookInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "decision": {
            "type": "string"
          }
        },
        "required": [
          "decision"
        ],
        "title": "HookOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: keeps the pre-image of a file a shell or coder call is about to change. Observes only; always continues.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::turns::on-pre-trigger",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "HookCall": {
            "properties": {
              "arguments": {
                "default": null
              },
              "function_id": {
                "type": "string"
              }
            },
            "required": [
              "function_id"
            ],
            "type": "object"
          },
          "HookResult": {
            "properties": {
              "is_error": {
                "default": false,
                "type": "boolean"
              }
            },
            "type": "object"
          }
        },
        "description": "The part of the harness hook envelope this module reads. One shape serves every hook point: `call` and `result` are the trigger hooks' (`result` as `{ is_error }`), while the `post_turn` envelope puts the turn's own result under `result`, whatever JSON that is, so that field is read leniently rather than typed.",
        "properties": {
          "call": {
            "anyOf": [
              {
                "$ref": "#/definitions/HookCall"
              },
              {
                "type": "null"
              }
            ]
          },
          "metadata": {
            "default": null
          },
          "result": {
            "anyOf": [
              {
                "$ref": "#/definitions/HookResult"
              },
              {
                "type": "null"
              }
            ]
          },
          "session_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "turn_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "HookInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "decision": {
            "type": "string"
          }
        },
        "required": [
          "decision"
        ],
        "title": "HookOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: photographs the session's workspace root before a turn's first call, so every file the turn changes has an exact before, however it is written. Observes only; always continues.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::turns::on-pre-turn",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "HookCall": {
            "properties": {
              "arguments": {
                "default": null
              },
              "function_id": {
                "type": "string"
              }
            },
            "required": [
              "function_id"
            ],
            "type": "object"
          },
          "HookResult": {
            "properties": {
              "is_error": {
                "default": false,
                "type": "boolean"
              }
            },
            "type": "object"
          }
        },
        "description": "The part of the harness hook envelope this module reads. One shape serves every hook point: `call` and `result` are the trigger hooks' (`result` as `{ is_error }`), while the `post_turn` envelope puts the turn's own result under `result`, whatever JSON that is, so that field is read leniently rather than typed.",
        "properties": {
          "call": {
            "anyOf": [
              {
                "$ref": "#/definitions/HookCall"
              },
              {
                "type": "null"
              }
            ]
          },
          "metadata": {
            "default": null
          },
          "result": {
            "anyOf": [
              {
                "$ref": "#/definitions/HookResult"
              },
              {
                "type": "null"
              }
            ]
          },
          "session_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "turn_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "HookInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "decision": {
            "type": "string"
          }
        },
        "required": [
          "decision"
        ],
        "title": "HookOutput",
        "type": "object"
      }
    },
    {
      "description": "Internal: closes a turn in the session's change history.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::turns::on-turn-completed",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ParentRef": {
            "properties": {
              "session_id": {
                "default": null,
                "type": [
                  "string",
                  "null"
                ]
              },
              "turn_id": {
                "default": null,
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          }
        },
        "properties": {
          "message_preview": {
            "default": null,
            "description": "First characters of the message that started the turn.",
            "type": [
              "string",
              "null"
            ]
          },
          "parent": {
            "anyOf": [
              {
                "$ref": "#/definitions/ParentRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "The spawning turn, for a sub-agent session."
          },
          "session_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "turn_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "TurnEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "Ack",
        "type": "object"
      }
    },
    {
      "description": "Internal: opens a turn in the session's change history.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::turns::on-turn-started",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ParentRef": {
            "properties": {
              "session_id": {
                "default": null,
                "type": [
                  "string",
                  "null"
                ]
              },
              "turn_id": {
                "default": null,
                "type": [
                  "string",
                  "null"
                ]
              }
            },
            "type": "object"
          }
        },
        "properties": {
          "message_preview": {
            "default": null,
            "description": "First characters of the message that started the turn.",
            "type": [
              "string",
              "null"
            ]
          },
          "parent": {
            "anyOf": [
              {
                "$ref": "#/definitions/ParentRef"
              },
              {
                "type": "null"
              }
            ],
            "description": "The spawning turn, for a sub-agent session."
          },
          "session_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          },
          "turn_id": {
            "default": null,
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "TurnEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "Ack",
        "type": "object"
      }
    },
    {
      "description": "Undo the file changes one turn made, from the pre-images kept in the change history: created files are removed, modified and deleted files get their earlier body back, moved files return to their original path. Pass `paths` to revert a subset. Per-file results say what happened; bodies that were never stored are reported, not guessed.",
      "metadata": {},
      "name": "shell::turns::revert",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "paths": {
            "default": null,
            "description": "Restrict the revert to these absolute paths; omit for every file the turn changed.",
            "items": {
              "type": "string"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "session_id": {
            "type": "string"
          },
          "turn_id": {
            "type": "string"
          }
        },
        "required": [
          "session_id",
          "turn_id"
        ],
        "title": "RevertInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "RevertFileResult": {
            "properties": {
              "action": {
                "description": "`restored` (body written back), `removed`, `moved-back`, or `skipped` (nothing to undo, e.g. a delete of a file that never existed).",
                "type": "string"
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "kind": {
                "description": "The recorded change kind that was undone.",
                "type": "string"
              },
              "path": {
                "type": "string"
              },
              "success": {
                "type": "boolean"
              }
            },
            "required": [
              "action",
              "kind",
              "path",
              "success"
            ],
            "type": "object"
          }
        },
        "properties": {
          "failed": {
            "description": "Files whose revert failed; their `error` says why.",
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          },
          "results": {
            "items": {
              "$ref": "#/definitions/RevertFileResult"
            },
            "type": "array"
          },
          "reverted": {
            "description": "Files whose revert succeeded (skips included).",
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          },
          "session_id": {
            "type": "string"
          },
          "turn_id": {
            "type": "string"
          }
        },
        "required": [
          "failed",
          "results",
          "reverted",
          "session_id",
          "turn_id"
        ],
        "title": "RevertOutput",
        "type": "object"
      }
    },
    {
      "description": "Serve the shell worker's injected console UI assets (content function for its console:script / console:style triggers).",
      "metadata": {
        "internal": true
      },
      "name": "shell::ui-content",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Input of the content function: the console asks for one asset by path.",
        "properties": {
          "path": {
            "description": "The asset path from the trigger config (e.g. `state/page.js`).",
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "UiContentInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Output of the content function.",
        "properties": {
          "content": {
            "description": "The asset source, verbatim.",
            "type": "string"
          },
          "content_type": {
            "description": "MIME type the console should serve the asset with.",
            "type": "string"
          }
        },
        "required": [
          "content",
          "content_type"
        ],
        "title": "UiContentResult",
        "type": "object"
      }
    },
    {
      "description": "Console-only: the explorer page's stored state for one pane (`key` is the console pane id; `legacy_key`, the workspace tab id, is read when the pane has nothing stored). `state` is null when nothing is stored. Kept under the worker's data directory, per pane.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::ui-state::get",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "key": {
            "description": "The pane's state key (the console pane id).",
            "type": "string"
          },
          "legacy_key": {
            "default": null,
            "description": "The workspace tab id: saves made before panes had ids are read from here when `key` has nothing stored.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "required": [
          "key"
        ],
        "title": "GetRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "key": {
            "type": "string"
          },
          "state": {
            "description": "The stored object, or null when nothing is stored."
          }
        },
        "required": [
          "key"
        ],
        "title": "GetResponse",
        "type": "object"
      }
    },
    {
      "description": "Console-only: replace the explorer page's stored state for one pane. Writes only that pane's file, atomically, so panes never clobber each other.",
      "metadata": {
        "internal": true,
        "trace_hidden": true
      },
      "name": "shell::ui-state::set",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "key": {
            "type": "string"
          },
          "state": {
            "description": "The pane's whole state; replaces what was stored."
          }
        },
        "required": [
          "key",
          "state"
        ],
        "title": "SetRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "bytes": {
            "description": "Size of the stored document.",
            "format": "uint",
            "minimum": 0,
            "type": "integer"
          },
          "key": {
            "type": "string"
          }
        },
        "required": [
          "bytes",
          "key"
        ],
        "title": "SetResponse",
        "type": "object"
      }
    },
    {
      "description": "Console-only workspace picker control plane: list child directories under an existing host directory. Returns canonical paths and never returns files.",
      "metadata": {},
      "name": "shell::workspace::list",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "page_size": {
            "default": null,
            "format": "uint",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "path": {
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "WorkspaceListRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "WorkspaceEntry": {
            "properties": {
              "kind": {
                "$ref": "#/definitions/WorkspaceEntryKind"
              },
              "name": {
                "type": "string"
              },
              "path": {
                "type": "string"
              }
            },
            "required": [
              "kind",
              "name",
              "path"
            ],
            "type": "object"
          },
          "WorkspaceEntryKind": {
            "enum": [
              "dir"
            ],
            "type": "string"
          }
        },
        "properties": {
          "entries": {
            "items": {
              "$ref": "#/definitions/WorkspaceEntry"
            },
            "type": "array"
          },
          "path": {
            "type": "string"
          }
        },
        "required": [
          "entries",
          "path"
        ],
        "title": "WorkspaceListResponse",
        "type": "object"
      }
    },
    {
      "description": "Console-only: one bounded byte range of a file, base64 (at most 4 MiB raw per call), so a page can stream a large image without one oversized frame.",
      "metadata": {
        "internal": true
      },
      "name": "shell::workspace::read-bytes",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "length": {
            "default": null,
            "description": "Bytes to return; omitted or over the cap = `READ_BYTES_MAX_CHUNK`.",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "offset": {
            "default": 0,
            "description": "First byte of the range (default 0).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "path": {
            "description": "Absolute path, or relative to the primary root (same rules as coder::read-file).",
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "WorkspaceReadBytesRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "content": {
            "description": "The chunk, base64 (standard alphabet, padded).",
            "type": "string"
          },
          "eof": {
            "description": "True when `offset + length` reached the end of the file.",
            "type": "boolean"
          },
          "length": {
            "description": "Raw bytes in this chunk (before base64).",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "mtime": {
            "description": "Last-modified time as a Unix epoch in seconds.",
            "format": "int64",
            "type": "integer"
          },
          "offset": {
            "description": "Offset the returned range starts at.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "path": {
            "description": "Canonical absolute path.",
            "type": "string"
          },
          "size": {
            "description": "Size of the whole file in bytes.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "content",
          "eof",
          "length",
          "mtime",
          "offset",
          "path",
          "size"
        ],
        "title": "WorkspaceReadBytesResponse",
        "type": "object"
      }
    },
    {
      "description": "Console-only workspace picker control plane: return canonical host directory anchors that an operator can browse before choosing a per-session working directory.",
      "metadata": {},
      "name": "shell::workspace::roots",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "title": "WorkspaceRootsRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "roots": {
            "items": {
              "type": "string"
            },
            "type": "array"
          }
        },
        "required": [
          "roots"
        ],
        "title": "WorkspaceRootsResponse",
        "type": "object"
      }
    },
    {
      "description": "Console-only workspace picker control plane: validate that `path` is an existing host directory and return its canonical path.",
      "metadata": {},
      "name": "shell::workspace::validate",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "WorkspaceValidateRequest",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "WorkspaceValidateResponse",
        "type": "object"
      }
    }
  ],
  "triggers": [
    {
      "description": "Fires when anything under the watched directory changes, whoever changed it — bind with config: { path } naming the directory (jail-checked like every coder::* path). Ignored paths (git-ignored; outside a repository data/, config/, .iii/, node_modules/) are skipped unless config also sets include_ignored: true.",
      "invocation_schema": {},
      "metadata": {},
      "name": "shell::changed",
      "return_schema": {}
    },
    {
      "description": "Fires once when a shell::exec_bg background job reaches a terminal status (finished, killed, or failed). Bind with config: { job_id } to wake on one job (replaying its result if already terminal and still retained), or omit it to receive future completions. The event carries job_id, argv, status, exit_code and timings; call shell::status for the job's output.",
      "invocation_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Select one background job, or observe future completions without a filter.",
        "properties": {
          "job_id": {
            "description": "Replay a terminal job while its record is retained in worker memory.",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "JobFinishedConfig",
        "type": "object"
      },
      "metadata": {},
      "name": "shell::job-finished",
      "return_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "JobStatus": {
            "enum": [
              "running",
              "finished",
              "killed",
              "failed"
            ],
            "type": "string"
          }
        },
        "description": "What ended. Lean by design, like [`crate::events::ChangedEvent`]: a subscriber that wants the job's output asks `shell::status`.",
        "properties": {
          "argv": {
            "description": "The command as spawned, so a subscriber bound without a `job_id` filter can tell jobs apart without a second call.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "duration_ms": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "exit_code": {
            "description": "`None` when the job never produced one (killed before exit, or a sandbox response that carried no code).",
            "format": "int32",
            "type": [
              "integer",
              "null"
            ]
          },
          "finished_at_ms": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "job_id": {
            "type": "string"
          },
          "started_at_ms": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "status": {
            "allOf": [
              {
                "$ref": "#/definitions/JobStatus"
              }
            ],
            "description": "`finished`, `killed`, or `failed` — never `running`."
          }
        },
        "required": [
          "argv",
          "duration_ms",
          "finished_at_ms",
          "job_id",
          "started_at_ms",
          "status"
        ],
        "title": "JobFinishedEvent",
        "type": "object"
      }
    }
  ]
}