skip to content
$worker

coder

v0.5.4

Path-jailed code worker — info/read/search/update/create/delete/move files plus paginated list-folder and tree, with non-accessible glob protection, default noise excludes (.git, node_modules, … — hide-only, per-call opt-out), and token-bounded read/search response budgets.

iiiverified
73 installs17 in 7d0 today
install
$iii worker add coder@0.5.4
binary
  • macOS: arm64 · x64
  • Linux: arm64 · armv7 · x64

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

agent-ready brief for v0.5.4
install + config + dependencies + readme + api reference, all in one place. fetch as agent-context.md for an llm to consume.
the same content rendered as discrete blocks below is exposed as a single markdown document at /workers/coder.md?version=0.5.4. paste it into an llm prompt or pipe it through curl from a worker.

install

install
$iii worker add coder@0.5.4

configuration

iii-config.yaml
- base_paths:
    - ./
    - /tmp
  batch_read_budget_bytes: 1048576
  default_exclude_globs:
    - **/.git/**
    - **/node_modules/**
    - **/target/**
    - **/dist/**
    - **/.venv/**
    - **/__pycache__/**
  list_default_page_size: 100
  list_max_page_size: 1000
  max_output_bytes: 131072
  max_read_bytes: 10485760
  max_write_bytes: 10485760
  non_accessible_globs:
    - **/.env
    - **/.env.*
    - **/*.pem
    - **/*.key
    - **/secrets/**
  search_default_max_line_bytes: 4096
  search_default_max_matches: 1000
  search_response_budget_bytes: 262144
  tree_default_depth: 4
  tree_per_folder_limit: 50

dependencies

no dependencies for v0.5.4

readme

README.md

coder

A path-jailed code worker for iii agents. coder::* lets agents read, search, edit, create, and delete files inside one or more configured allowed roots — without ever escaping them via .. or symlinks. A glob-based non_accessible list keeps sensitive files (.env, *.pem, anything under secrets/) visible to directory listings but unreadable and unwritable.

Install

iii worker add coder

iii worker add fetches the binary, writes a config block into ~/.iii/config.yaml, and the engine starts the worker on the next iii start.

Skills

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

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

Browse or install every worker skill at once:

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

Quickstart

use iii_sdk::{register_worker, InitOptions, TriggerRequest};
use serde_json::json;

#[tokio::main]
async fn main() -> anyhow::Result<()> {
    let iii = register_worker("ws://localhost:49134", InitOptions::default());

    // Create a file.
    iii.trigger(TriggerRequest {
        function_id: "coder::create-file".into(),
        payload: json!({
            "files": [{
                "path": "notes.md",
                "content": "# notes\n- one\n- two\n",
                "overwrite": false
            }]
        }),
        action: None,
        timeout_ms: Some(5_000),
    }).await?;

    // Apply two ops bottom-up in a single batch.
    iii.trigger(TriggerRequest {
        function_id: "coder::update-file".into(),
        payload: json!({
            "files": [{
                "path": "notes.md",
                "ops": [
                    { "op": "insert", "at_line": 2, "content": "draft" },
                    { "op": "update_lines", "from_line": 3, "to_line": 3, "content": "- ONE" }
                ]
            }]
        }),
        action: None,
        timeout_ms: Some(5_000),
    }).await?;

    // Read it back.
    let read = iii.trigger(TriggerRequest {
        function_id: "coder::read-file".into(),
        payload: json!({ "path": "notes.md" }),
        action: None,
        timeout_ms: Some(5_000),
    }).await?;
    println!("{read:#?}");

    Ok(())
}

Functions

Function id What it does
coder::info Discover the jail: canonical allowed roots, per-file size caps, response budgets, listing/search limits, default_exclude_globs, and non-accessible glob patterns. Call first when unsure where coder may read or write.
coder::read-file Read a single file: stat: true probe (size + total_lines, no content), a streamed line_from/line_to window (numbered: true prefixes absolute line numbers), or a full read budgeted by max_output_bytes. Batch mode: pass paths[] to read multiple files in one call against a shared batch_read_budget_bytes cap; batch stat probes are budget-free.
coder::search Search file contents (literal/regex) and/or paths inside the allowed roots. context_lines_before/context_lines_after (≤10) attach surrounding lines per match; responses are bounded by max_matches and search_response_budget_bytes (truncated: true, never an error). Noise dirs are skipped by default (default_exclude_globs).
coder::update-file Apply batched insert / remove / update_lines / regex replace (dot_matches_newline, expect_matches) ops across one or more files. Line ops bottom-up; per-file atomic. Each applied op returns a bounded post-apply echo.
coder::create-file Create one or more files with overwrite and parents flags.
coder::delete-file Remove one or more paths; recursive: true required for non-empty dirs.
coder::move Move or rename one or more paths; same-root moves are per-file atomic; cross-root file moves use copy+delete with rollback.
coder::list-folder Paginated single-folder listing; entries carry name only — join onto the response's top-level path; non-accessible entries flagged.
coder::tree Recursive snapshot bounded by max_depth and per_folder_limit. Nodes carry name only (child path = parent path + "/" + name; the root node's path is the response's top-level path). Noise dirs surface as childless truncated stubs; pass use_default_excludes: false to look inside.

coder::update-file semantics

Line ops (insert, remove, update_lines) use 1-based inclusive line numbers and are applied bottom-up (highest affected line first), so each op still references the original line numbers from the caller's perspective. Overlapping line ops are rejected (C210). Regex replace ops run after line ops on the full file body; dot_matches_newline: true lets . span newlines (multi-line regions via two short anchors joined by .*?), and expect_matches: N is a pre-write guard that fails the entry with C210 when the actual match count differs (0 asserts absence). replacement expands capture references ($1, $name, ${name}); a literal $ must be written $$ (JS/TS template literals in a replacement are the classic collision: `Hello, $${name}!` outputs `Hello, ${name}!`), and a reference to a group the pattern does not define fails pre-write with C210 — nothing is written. The whole batch is committed via a sibling temp file + rename, so a failure mid-write leaves the original file intact.

On success, each applied line op echoes a bounded post-apply window (±2 context lines). Regex replace ops return up to 5 per-match-site echoes, each showing the first and last line of its post-replace region (inner line count reported via elided). An echoes_truncated flag is set when the budget is exhausted before all echoes could be returned.

{
  "files": [{
    "path": "schema.sql",
    "ops": [
      { "op": "insert",       "at_line": 1, "content": "-- header\n-- v2" },
      { "op": "remove",       "from_line": 5, "to_line": 12 },
      { "op": "update_lines", "from_line": 30, "to_line": 30, "content": "PRIMARY KEY (id)" },
      { "op": "replace",      "pattern": "OLD_", "replacement": "NEW_" }
    ]
  }]
}

Error codes

All errors return as JSON objects of the form {"code":"C2xx","message":"..."}.

Code Meaning
C210 Bad input (malformed payload, illegal line numbers, overlapping ops, expect_matches mismatch, undefined $ capture reference in a replacement, conflicting config keys, …)
C211 Path not found OR matches a non_accessible_globs entry
C213 File exceeds max_read_bytes or max_write_bytes; full read exceeds max_output_bytes (carries size/lines/recovery); batch budget exhausted
C215 Path is outside every allowed root (lexically or through a symlink); names all roots + recovery
C216 Underlying I/O error
C217 coder::create-file or coder::move saw an existing file with overwrite=false
C218 A per-call base_dir was set and the path is inside an allowed root but outside the session directory; names the session dir + recovery (distinct from C215 so it does not contradict the allowed-roots list)

Configuration

As of 0.5.0 coder's runtime config lives in the configuration worker under id coder (the same pattern database and storage use). At boot coder registers its JSON Schema, reads the live value via configuration::get (the configuration worker env-expands ${VAR}), and binds a configuration trigger so it re-fetches on change. Persisted values default to ./data/configuration/coder.yaml — edit that file directly or call configuration::set id=coder; both propagate without re-reading the seed file.

iii trigger configuration::get id=coder
iii trigger configuration::set id=coder value='{"base_paths":["/srv/project"],"max_read_bytes":20971520}'

--config is a first-register seed

Pass --config to supply a YAML seed file. When present AND no value is yet stored for id coder, its contents are passed as initial_value on configuration::register. After that first register the stored value is authoritative — re-running with --config does NOT overwrite it. With no seed and no stored value coder runs on the built-in default jail (base_paths ["./", "/tmp"], no non_accessible_globs — seed the shipped config.yaml to keep secret-file protection). See config.yaml.example. ${VAR} placeholders are expanded only when the seed file is read; configuration::get values are already expanded, so they are never expanded twice.

Reload policy

coder splits its keys the way storage splits its topology — the security jail is restart-only, the numeric tuning knobs hot-reload:

  • JAIL fields — RESTART-REQUIRED: base_paths (and legacy base_path), non_accessible_globs, default_exclude_globs. These four are everything the PathResolver compiles. On a configuration:updated event coder re-fetches the authoritative value (it never trusts the trigger payload); if the change alters any jail field it is refused — the running jail is kept and coder logs restart coder to apply. The PathResolver is built once at boot and is never rebuilt at runtime. coder::info keeps reporting the boot-time roots until restart.
  • HOT-RELOADABLE — numeric budgets/limits: max_read_bytes, max_write_bytes, tree_default_depth, tree_per_folder_limit, list_default_page_size, list_max_page_size, search_default_max_matches, search_default_max_line_bytes, search_response_budget_bytes, batch_read_budget_bytes, max_output_bytes. When the jail signature is unchanged, coder swaps the config snapshot live and handlers read the current snapshot on their next call. Invalid configs are rejected and the previous snapshot is kept.

Value shape

base_paths: ["./", "/tmp"]                   # allowed roots; first entry is the primary root
non_accessible_globs:                        # listable but unreadable/unwritable
  - "**/.env"
  - "**/.env.*"
  - "**/*.pem"
  - "**/*.key"
  - "**/secrets/**"
default_exclude_globs:                       # noise filter for tree/search (hide-only)
  - "**/.git/**"
  - "**/node_modules/**"
  - "**/target/**"
  - "**/dist/**"
  - "**/.venv/**"
  - "**/__pycache__/**"
max_read_bytes: 10485760                     # per-file read cap (10 MiB)
max_write_bytes: 10485760                    # per-file create/update cap (10 MiB)
tree_default_depth: 4                        # coder::tree depth when unset
tree_per_folder_limit: 50                    # children before tree truncates a folder
list_default_page_size: 100                  # coder::list-folder default page size
list_max_page_size: 1000                     # hard cap on coder::list-folder page_size
search_default_max_matches: 1000             # coder::search match cap
search_default_max_line_bytes: 4096          # per-line cap when scanning content
search_response_budget_bytes: 262144         # byte budget per search response (256 KiB)
batch_read_budget_bytes: 1048576             # aggregate cap for paths[] batch reads (1 MiB)
max_output_bytes: 131072                     # budget for single-path FULL reads (128 KiB)

base_paths is a list of allowed roots. The first entry is the primary root: relative wire paths resolve against it. Absolute wire paths are accepted when they canonicalize inside any listed root; outside all roots → C215. Default when neither base_paths nor the legacy base_path is set: ["./", "/tmp"]. Legacy base_path:

is still honored as a one-entry list; setting both keys is a startup error (C210).

non_accessible_globs uses the same syntax as the globset crate (so **/, *, ?, character classes, …). Matching is done against the path relative to its containing root, so **/.env blocks .env, a/.env, and a/b/.env in every allowed root.

default_exclude_globs (same syntax and root-relative matching as non_accessible_globs) is a hide-only noise filter: coder::tree skips descent into matching directories (they appear as childless nodes flagged truncated with reason "default_exclude") and coder::search omits matching paths. It grants no access protection — that remains non_accessible_globs. Callers opt out per call with use_default_excludes: false. Default: .git, node_modules, target, dist, .venv, __pycache__.

search_response_budget_bytes bounds one coder::search response in converted wire bytes (path + matched text + context lines). When the next match would exceed the budget the search stops accumulating and sets truncated: true — it degrades, it never errors. Default 256 KiB.

max_output_bytes budgets single-path full reads in coder::read-file (bytes of returned content after UTF-8 sanitization, numbered prefixes included). Oversize full reads fail with a C213 carrying the file's size, line count, and recovery calls; callers can raise it per call via the max_output_bytes request field (clamped to max_read_bytes) or switch to line_from/line_to windows. Windowed reads and batch mode (batch_read_budget_bytes) are not governed by this key. Default 128 KiB.

Instrumentation

Count C2xx errors by code and function over time with scripts/error-frequency.py — accepts session export .md files or queries the live engine directly. See the script header for usage and the baseline (session vqrfg31f, 3× C210 → tool abandonment, pre-0.3.0).

Security boundary

  • Each allowed root is canonicalized at startup. Unreachable roots are skipped with a warning; if zero roots remain the worker refuses to start. The final canonical root list is logged at startup.
  • Every wire path must be relative (resolves against the primary root and must stay inside it → C215) or absolute inside an allowed root (accepted as of 0.3.0 → C215 if outside every root).
  • .. and symlinks are resolved against the longest existing ancestor and rejected if they leave the containing root (C215). Dangling symlinks in the tail are also rejected.
  • Non-accessible globs apply to reads as well as writes — the same glob hides the file from coder::read-file, coder::update-file, coder::create-file, coder::delete-file, and from coder::search's content/path matches.
  • Recursive coder::delete-file refuses to descend through a subtree that contains a non-accessible entry.
  • All responses carry canonical absolute paths so multi-root results are unambiguous.
  • /tmp posture: /tmp is world-writable and shared. It is included in the default base_paths so agent tasks targeting /tmp work out of the box on a trusted bus. Operators on multi-tenant hosts should remove it and configure only the project root(s) they intend to expose.

api reference (json)

agent-api-reference.json
{
  "functions": [
    {
      "description": "Create one or more files. Request shape: {\"files\": [{\"path\": \"...\", \"content\": \"...\"}]}. Per-file `overwrite` and `parents` flags; non-accessible paths return C211. Paths are relative to the primary allowed root or absolute inside any allowed root (coder::info lists them); for host paths outside the jail use shell::fs::*.",
      "metadata": {},
      "name": "coder::create-file",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "CreateFileSpec": {
            "properties": {
              "content": {
                "type": "string"
              },
              "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": "Path relative to the primary allowed root, or an absolute path inside any allowed root. Call `coder::info` to see the allowed roots. Paths outside every allowed root are rejected — use the shell worker's `shell::fs::*` for host paths outside the jail.",
                "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": {
          "base_dir": {
            "default": null,
            "description": "Optional per-call session working directory. When set, relative `path`s anchor here instead of the primary allowed root, and every resolved path must stay inside it. `base_dir` itself must canonicalize inside an allowed root (`coder::info` lists them). Omit to resolve against the primary allowed root exactly as before.",
            "type": [
              "string",
              "null"
            ]
          },
          "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. `\"C217\"` 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"
              },
              "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. Request shape: {\"paths\": [\"...\"]}. Directories need `recursive: true`; missing paths are idempotent successes; recursive removal refuses to descend through non-accessible entries. Paths are relative to the primary allowed root or absolute inside any allowed root (coder::info lists them); for host paths outside the jail use shell::fs::*.",
      "metadata": {},
      "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": {
          "base_dir": {
            "default": null,
            "description": "Optional per-call session working directory. When set, relative `paths` anchor here instead of the primary allowed root, and every resolved path must stay inside it. `base_dir` itself must canonicalize inside an allowed root (`coder::info` lists them). Omit to resolve against the primary allowed root exactly as before.",
            "type": [
              "string",
              "null"
            ]
          },
          "paths": {
            "description": "Paths to remove. Each entry is relative to the primary allowed root, or an absolute path inside any allowed root. Call `coder::info` to see the allowed roots. Paths outside every allowed root are rejected — use the shell worker's `shell::fs::*` for host paths outside the jail.",
            "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 jail: canonical allowed roots (primary first), per-file size caps, response budgets (max_output_bytes, batch_read_budget_bytes, search_response_budget_bytes), listing/search limits, the non-accessible glob patterns, and the default_exclude_globs noise filter applied by tree/search. Call this FIRST when unsure where coder may read or write, or when a path was rejected — paths outside every allowed root need the shell worker's shell::fs::* instead.",
      "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#",
        "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. 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 C213 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 C213 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 C213; 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 C213.",
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          },
          "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.",
            "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"
          },
          "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",
          "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": "Paginated single-folder listing, sorted by name. Entries carry only `name`; derive an entry's absolute path as the response's `path` + '/' + name. Non-accessible entries are still listed with a `non_accessible: true` flag. Paths are relative to the primary allowed root or absolute inside any allowed root (coder::info lists them); for host paths outside the jail use shell::fs::*.",
      "metadata": {},
      "name": "coder::list-folder",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "page": 1,
            "page_size": 50,
            "path": "src"
          }
        ],
        "properties": {
          "base_dir": {
            "default": null,
            "description": "Optional per-call session working directory. When set, a relative `path` anchors here instead of the primary allowed root, and the resolved folder must stay inside it. `base_dir` itself must canonicalize inside an allowed root (`coder::info` lists them). Omit to resolve against the primary allowed root exactly as before.",
            "type": [
              "string",
              "null"
            ]
          },
          "page": {
            "default": 1,
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "page_size": {
            "default": null,
            "description": "Capped by `config.list_max_page_size`; falls back to `config.list_default_page_size` when omitted.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "path": {
            "default": ".",
            "description": "Folder to list. Relative to the primary allowed root, or an absolute path inside any allowed root. Defaults to `.` (the primary root itself). Call `coder::info` to see the allowed roots. Paths outside every allowed root are rejected — use the shell worker's `shell::fs::*` for host paths outside the jail.",
            "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 inside the jail. Request shape: {\"files\": [{\"from\": \"...\", \"to\": \"...\"}]}. Paths are relative to the primary allowed root or absolute inside any allowed root (coder::info lists them); for host paths outside the jail use shell::fs::*. Per-entry `overwrite` and `parents` flags. Same-root moves use a per-file-atomic rename; cross-root moves use copy+delete (files only — cross-root directory moves are unsupported, move files individually). Copy+delete is rollback-safe: if source deletion fails after a successful copy the copy is removed and the error names the failure; if rollback also fails the error names both states for manual cleanup.",
      "metadata": {},
      "name": "coder::move",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "MoveFileSpec": {
            "properties": {
              "from": {
                "description": "Source path: relative to the primary allowed root, or an absolute path inside any allowed root. Call `coder::info` to see the allowed roots. Paths outside every allowed root are rejected — use the shell worker's `shell::fs::*` for host paths outside the jail.",
                "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: relative to the primary allowed root, or an absolute path inside any allowed root. Call `coder::info` to see the allowed roots. Paths outside every allowed root are rejected — use the shell worker's `shell::fs::*` for host paths outside the jail.",
                "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": {
          "base_dir": {
            "default": null,
            "description": "Optional per-call session working directory. When set, relative `from`/`to` paths anchor here instead of the primary allowed root, and BOTH resolved endpoints must stay inside it. `base_dir` itself must canonicalize inside an allowed root (`coder::info` lists them). Omit to resolve against the primary allowed root exactly as before.",
            "type": [
              "string",
              "null"
            ]
          },
          "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. `\"C217\"` 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": "Internal: reload coder's tuning limits from the authoritative configuration when it changes; jail-defining changes require a restart.",
      "metadata": {},
      "name": "coder::on-config-change",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Internal `coder::on-config-change` trigger payload. The handler re-fetches the authoritative configuration, so this carries only the (advisory) configuration id; a struct (not `Value`) keeps the request schema concrete and unknown fields are ignored.",
        "properties": {
          "id": {
            "default": null,
            "description": "Configuration id that changed (advisory; the handler re-fetches the value).",
            "type": [
              "string",
              "null"
            ]
          }
        },
        "title": "OnConfigChangeEvent",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "description": "Ack returned by the internal `coder::on-config-change` handler.",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        },
        "required": [
          "ok"
        ],
        "title": "OnConfigChangeResponse",
        "type": "object"
      }
    },
    {
      "description": "Read a file window-first: probe with stat: true (size/mtime/mode plus total_lines, no content), then fetch just the lines you need with line_from/line_to (1-based, inclusive) — windows keep files larger than max_read_bytes readable window by window, with more_lines/total_lines reporting what remains. numbered: true prefixes each line with its absolute 1-based file line number, matching coder::update-file's line ops exactly. Full reads are budgeted by max_output_bytes (default 128 KiB; per-call override clamped to max_read_bytes) — an over-budget full read fails with a C213 carrying the file's size, line count, and the window/stat recovery calls. Batch mode: pass paths[] (XOR path) to read multiple files in one call — entries are processed in request order against batch_read_budget_bytes, measured in bytes of returned content (after UTF-8 sanitization); per-entry errors (C211/C213) leave other entries unaffected. Paths are relative to the primary allowed root or absolute inside any allowed root (coder::info lists them); for host paths outside the jail use shell::fs::*. Non-accessible paths return C211.",
      "metadata": {},
      "name": "coder::read-file",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ReadTarget": {
            "anyOf": [
              {
                "description": "Bare path string: read the whole file (within remaining batch budget and `max_read_bytes`).",
                "type": "string"
              },
              {
                "description": "Object form: path plus optional 1-based window parameters. Omit `line_from` to start from line 1; omit `line_to` to read to EOF.",
                "properties": {
                  "line_from": {
                    "default": null,
                    "description": "First line of the window, 1-based inclusive (must be >= 1; 0 is rejected with C210 for this entry). 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. Must be >= `line_from` (C210 for this entry otherwise). Omit to read from `line_from` to EOF.",
                    "format": "uint64",
                    "minimum": 1,
                    "type": [
                      "integer",
                      "null"
                    ]
                  },
                  "numbered": {
                    "default": false,
                    "description": "Prefix this entry's content lines with their absolute 1-based file line numbers (`N→`) — same semantics as the top-level `numbered` field. Prefix bytes are charged against `batch_read_budget_bytes`.",
                    "type": "boolean"
                  },
                  "path": {
                    "description": "File to read. Same jail rules as the top-level `path` field.",
                    "type": "string"
                  },
                  "stat": {
                    "default": false,
                    "description": "Per-entry metadata probe: same semantics as the top-level `stat` field — size/mode/mtime always, `total_lines`/`is_utf8` when the file fits `max_read_bytes`, content null, no batch budget consumed. C210 when combined with this entry's `line_from`/`line_to` or `numbered`.",
                    "type": "boolean"
                  }
                },
                "required": [
                  "path"
                ],
                "type": "object"
              }
            ],
            "description": "A single entry in a `paths[]` batch request. Pass either a bare file path string (whole-file read, same cap as `max_read_bytes`) or an object with optional per-entry `line_from`/`line_to` window parameters (1-based, inclusive — same rules as the top-level `path` mode)."
          }
        },
        "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": {
          "base_dir": {
            "default": null,
            "description": "Optional per-call session working directory. When set, every relative path (the single `path` or each `paths[]` entry, in both the bare string and `{path,...}` object forms) anchors here instead of the primary allowed root, and every resolved path must stay inside it. `base_dir` itself must canonicalize inside an allowed root (`coder::info` lists them). Omit to resolve against the primary allowed root exactly as before.",
            "type": [
              "string",
              "null"
            ]
          },
          "line_from": {
            "default": null,
            "description": "First line of the window, 1-based inclusive (must be >= 1; 0 is rejected with C210). Setting `line_from` and/or `line_to` switches to windowed mode: the file is streamed and only the requested lines are returned, so files larger than `max_read_bytes` stay readable slice by slice — the byte cap then applies to the returned window, never the file size. Defaults to 1 when only `line_to` is set. A window starting past EOF succeeds with empty content and reports the file's `total_lines`. Only valid in single-path mode (`path`); ignored when `paths` is set. Lines are 0x0A- or EOF-terminated segments; a trailing newline does not create a phantom line (same convention as `coder::update-file`).",
            "format": "uint64",
            "minimum": 1,
            "type": [
              "integer",
              "null"
            ]
          },
          "line_to": {
            "default": null,
            "description": "Last line of the window, 1-based inclusive. Must be >= `line_from` (C210 otherwise). Omit to read from `line_from` to end-of-file (still bounded by `max_read_bytes` on the returned bytes). Only valid in single-path mode (`path`); ignored when `paths` is set.",
            "format": "uint64",
            "minimum": 1,
            "type": [
              "integer",
              "null"
            ]
          },
          "max_output_bytes": {
            "default": null,
            "description": "Per-call override of the `max_output_bytes` config (default 131072) that budgets single-path FULL reads, measured in returned content bytes after UTF-8 conversion (numbered prefixes included). Values above `max_read_bytes` are silently clamped to it. When the full content would exceed the effective budget the call fails with a C213 naming the file's size and `total_lines` — recover by windowing with `line_from`/`line_to`, probing with `stat: true`, or raising this field. Full reads only: combining it with `line_from`/`line_to` is C210 (windows are bounded by `max_read_bytes` instead); ignored when `paths` is set (batch mode is governed by `batch_read_budget_bytes`).",
            "format": "uint64",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "numbered": {
            "default": false,
            "description": "When true every returned content line is prefixed `N→`, where N is the line's ABSOLUTE 1-based number in the file — a window starting at `line_from: 40` is numbered from 40, not 1. Numbers match `coder::update-file`'s 1-based line ops exactly, so you can go from a numbered read straight to a line edit. Prefix bytes count toward all byte caps and budgets (no hidden bypass). C210 with `stat: true` (no content to number). Batch entries take a per-entry `numbered` field instead; this top-level flag is ignored when `paths` is set.",
            "type": "boolean"
          },
          "path": {
            "default": null,
            "description": "Single file to read. Relative to the primary allowed root, or an absolute path inside any allowed root. Call `coder::info` to see the allowed roots. Paths outside every allowed root are rejected — use the shell worker's `shell::fs::*` for host paths outside the jail. Mutually exclusive with `paths` (XOR): pass either `path` or `paths`, not both — C210 if both or neither is set.",
            "type": [
              "string",
              "null"
            ]
          },
          "paths": {
            "description": "Batch of files (or windowed slices) to read in a single call. Each entry is either a plain path string (whole-file read) or an object `{path, line_from?, line_to?}` with per-entry window parameters. Entries are processed in request order against a shared `batch_read_budget_bytes` cap, measured in bytes of returned content (after UTF-8 sanitization) — see `coder::info` for the configured value. Results are returned in the `results` field; top-level fields are null. Mutually exclusive with `path` (XOR): pass either `path` or `paths`, not both — C210 if both or neither is set.",
            "items": {
              "$ref": "#/definitions/ReadTarget"
            },
            "type": [
              "array",
              "null"
            ]
          },
          "stat": {
            "default": false,
            "description": "Metadata probe — the cheap \"how big is it\" call. When true the response carries size/mode/mtime plus `total_lines` and `is_utf8` (both null when the file exceeds `max_read_bytes` — size/mode/mtime still populate, so stat on a huge file SUCCEEDS); `content` is null, `lines_returned` 0, `more_lines` false. Probe BEFORE reading an unknown file, then fetch just the slice you need with `line_from`/`line_to`. Mutually exclusive with `line_from`, `line_to`, `numbered`, and `max_output_bytes` (C210 — stat returns no content for them to act on). Batch entries take a per-entry `stat` field instead; this top-level flag is ignored when `paths` is set.",
            "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; use a future binary-aware function if exact bytes matter. **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"
            ]
          },
          "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 and/or paths. Supports literal or regex queries with include/exclude globs; non-accessible files are excluded from both content and path results. Only the FIRST match on each line is reported (one content match per matching line). Optional context_lines_before/context_lines_after (max 10) attach surrounding lines to each content match so many edits can go straight to coder::update-file with no read in between. Noise paths matching default_exclude_globs (.git, node_modules, target, … — coder::info lists them) are skipped by default; pass use_default_excludes: false to search inside them. Files larger than max_read_bytes are silently skipped during content scanning. Results are capped by max_matches AND a response byte budget (search_response_budget_bytes); when truncated is true, refine the query or add include_globs rather than paginate. Paths are relative to the primary allowed root or absolute inside any allowed root (coder::info lists them); for host paths outside the jail use shell::fs::*.",
      "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": {
          "base_dir": {
            "default": null,
            "description": "Optional per-call session working directory. When set, a relative `path` anchors here instead of the primary allowed root, and the walk root must stay inside it. `base_dir` itself must canonicalize inside an allowed root (`coder::info` lists them). Result paths stay absolute. Omit to resolve against the primary allowed root exactly as before.",
            "type": [
              "string",
              "null"
            ]
          },
          "context_lines_after": {
            "default": null,
            "description": "Lines of context to return AFTER each content match. Same max (10), truncation, and budget rules as `context_lines_before`; unset = 0.",
            "format": "uint32",
            "minimum": 0,
            "type": [
              "integer",
              "null"
            ]
          },
          "context_lines_before": {
            "default": null,
            "description": "Lines of context to return BEFORE each content match (same file only, in file order), max 10 — larger values are rejected with C210. With context lines many edit tasks can go straight from search output to coder::update-file with no read in between. Context lines are truncated to `max_line_bytes` like the matched text and count toward the response byte budget. Unset = 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"
          },
          "ignore_case": {
            "default": false,
            "type": "boolean"
          },
          "include_globs": {
            "default": [],
            "description": "Glob patterns (matched against the path relative to its containing root) that paths must match to be considered. Empty = include everything.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "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 scoping the walk. Relative to the primary allowed root, or an absolute path inside any allowed root. Defaults to `.` (the primary root itself). Globs are matched relative to the containing root; result paths are absolute regardless of this value. Call `coder::info` to see the allowed roots. Paths outside every allowed root are rejected — use the shell worker's `shell::fs::*` for host paths outside the jail.",
            "type": "string"
          },
          "query": {
            "description": "Pattern to search for. Treated as a regex when `regex: true`, otherwise as a literal substring.",
            "type": "string"
          },
          "regex": {
            "default": false,
            "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": "Apply the worker's `default_exclude_globs` config (noise paths like .git, node_modules, target, dist — call coder::info for the active list): the walk does not descend into matching directories and matching files are omitted from BOTH content and path results. Pass `false` to search 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": {
              "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": [
              "path"
            ],
            "type": "object"
          }
        },
        "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": "Recursive directory snapshot bounded by `max_depth` and a `per_folder_limit`. Slim wire shape: nodes carry only `name` — the root node's path IS the response's top-level `path`; derive any child's path as parent path + '/' + name. Folders that hit the limit are flagged `truncated` and the caller is pointed at coder::list-folder for pagination. Noise directories matching default_exclude_globs (.git, node_modules, target, … — coder::info lists them) appear as childless `truncated` stubs; pass use_default_excludes: false to descend into them. Paths are relative to the primary allowed root or absolute inside any allowed root (coder::info lists them); for host paths outside the jail use shell::fs::*.",
      "metadata": {},
      "name": "coder::tree",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "examples": [
          {
            "max_depth": 3,
            "path": "."
          }
        ],
        "properties": {
          "base_dir": {
            "default": null,
            "description": "Optional per-call session working directory. When set, a relative `path` anchors here instead of the primary allowed root, and the resolved base folder must stay inside it. `base_dir` itself must canonicalize inside an allowed root (`coder::info` lists them). Omit to resolve against the primary allowed root exactly as before.",
            "type": [
              "string",
              "null"
            ]
          },
          "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. Relative to the primary allowed root, or an absolute path inside any allowed root. Defaults to `.` (the primary root itself). Call `coder::info` to see the allowed roots. Paths outside every allowed root are rejected — use the shell worker's `shell::fs::*` for host paths outside the jail.",
            "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": "Apply the worker's `default_exclude_globs` config (noise folders like .git/node_modules/target — call `coder::info` for the active list). Excluded directories still appear as childless nodes flagged `truncated` with reason \"default_exclude\"; excluded files are omitted. Pass `false` to list 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`, or which matched `default_exclude_globs` (reason \"default_exclude\")."
              }
            },
            "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`, or matched `default_exclude_globs` (`default_exclude`).",
                "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 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": "Apply batched line-oriented and regex edits across one or more files. Request shape: {\"files\": [{\"path\": \"...\", \"ops\": [...]}]}. Line ops: { op: 'insert', at_line, content } | { op: 'remove', from_line, to_line } | { op: 'update_lines', from_line, to_line, content } — 1-based, inclusive, applied bottom-up. Regex op: { op: 'replace', pattern, replacement, ignore_case?, dot_matches_newline?, expect_matches? } runs on the file body after line ops. Replace large regions WITHOUT quoting them: two short anchors joined by .*? with dot_matches_newline: true — always prefer wildcards over pasting the block into the pattern. expect_matches: 1 turns a silent multi-site clobber into a safe pre-write C210; expect_matches: 0 asserts absence. In `replacement`, $1/${name} are capture references and a literal $ must be written $$ (JS/TS template literals: `Hello, $${name}!`); undefined references fail pre-write with C210. Each file commits atomically via temp + rename. On success each applied line op returns a bounded post-apply echo (±2 context lines); regex replace ops return up to 5 per-match-site echoes (first + last line of each replaced region, inner lines elided) — verify from the echoes instead of re-reading the file. Paths are relative to the primary allowed root or absolute inside any allowed root (coder::info lists them); for host paths outside the jail use shell::fs::*.",
      "metadata": {},
      "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": "Path relative to the primary allowed root, or an absolute path inside any allowed root. Call `coder::info` to see the allowed roots. Paths outside every allowed root are rejected — use the shell worker's `shell::fs::*` for host paths outside the jail.",
                "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, `.` in `pattern` also matches `\\n`, so a short anchored pattern like `\"fn parse_config\\\\(.*?\\\\n\\\\}\"` replaces a whole multi-line region without quoting it — prefer two short anchors joined by `.*?` over pasting the block into the pattern. Without this flag (the default), `.` does not cross newlines and a multi-line pattern silently matches nothing.",
                    "type": "boolean"
                  },
                  "expect_matches": {
                    "default": null,
                    "description": "Expected number of matches for this op. When set and the actual count differs, this FILE fails with C210 and nothing is written to it (other files in the batch still apply). Set `expect_matches: 1` to make a targeted read-free edit safe — a mismatch means the pattern is anchored too loosely or matches nothing. Set `expect_matches: 0` to assert ABSENCE: the op succeeds only when nothing matches (the replacement is unused). Omit to replace all matches unconditionally.",
                    "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 for each match. Capture references expand: `$1`/`${1}` by index (`$0` is the whole match) and `$name`/`${name}` by name. A literal `$` MUST be written `$$` — JS/TS template literals in a replacement are the classic collision: write `Hello, $${name}!` to output `Hello, ${name}!`. Unbraced references consume the longest `[0-9A-Za-z_]` run, so `$1a` means a group named \"1a\", NOT group 1 then \"a\" (use `${1}a`). A reference to a group the pattern does not define fails pre-write with C210 — nothing is written. References are validated even when the replacement goes unused (e.g. `expect_matches: 0`): the replacement must be well-formed even when unused.",
                    "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": {
          "base_dir": {
            "default": null,
            "description": "Optional per-call session working directory. When set, relative `path`s anchor here instead of the primary allowed root, and every resolved path must stay inside it. `base_dir` itself must canonicalize inside an allowed root (`coder::info` lists them). Omit to resolve against the primary allowed root exactly as before.",
            "type": [
              "string",
              "null"
            ]
          },
          "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"
      }
    }
  ],
  "triggers": []
}