iii / worker
$worker

coder

v0.2.0

Path-jailed code worker — read/search/update/create/delete files plus paginated list-folder and tree, with non-accessible glob protection.

  • macOS: arm64 · x64
  • Linux: arm64 · armv7 · x64
agent-ready brief for v0.2.0
install + config + dependencies + readme + api reference, all in one place. fetch as agent-context.md for an llm to consume.
the same content rendered as discrete blocks below is exposed as a single markdown document at /workers/coder.md. paste it into an llm prompt or pipe it through curl from a worker.

install

install
$iii worker add coder@0.2.0

configuration

iii-config.yaml
- base_path: ./
  list_default_page_size: 100
  list_max_page_size: 1000
  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
  tree_default_depth: 4
  tree_per_folder_limit: 50

dependencies

no dependencies for v0.2.0

readme

README.md

coder

A path-jailed code worker for iii agents. coder::* lets agents read, search, edit, create, and delete files inside a single configured base_path — without ever escaping it via .., absolute paths, 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.

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::read-file Read a single file (capped at max_read_bytes).
coder::search Search file contents (literal/regex) and/or paths under base_path.
coder::update-file Apply batched insert / remove / update_lines / regex replace ops across one or more files. Line ops bottom-up; atomic per file.
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::list-folder Paginated single-folder listing; non-accessible entries flagged.
coder::tree Recursive snapshot bounded by max_depth and per_folder_limit.

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. The whole batch is committed via a sibling temp file + rename, so a failure mid-write leaves the original file intact.

{
  "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 strings of the form {"code":"C2xx","message":"..."}.

Code Meaning
C210 Bad input (malformed payload, illegal line numbers, overlapping ops, absolute path, …)
C211 Path not found OR matches a non_accessible_globs entry
C213 File exceeds max_read_bytes or max_write_bytes
C215 Path escapes base_path lexically or through a symlink
C216 Underlying I/O error
C217 coder::create-file saw an existing file with overwrite=false

Configuration

base_path: ./                                # root every coder::* call is scoped under
non_accessible_globs:                        # listable but unreadable/unwritable
  - "**/.env"
  - "**/.env.*"
  - "**/*.pem"
  - "**/*.key"
  - "**/secrets/**"
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

non_accessible_globs uses the same syntax as the globset crate (so **/, *, ?, character classes, …). Matching is done against the relative path from base_path, so **/.env blocks .env, a/.env, and a/b/.env.

Security boundary

  • base_path is canonicalised at startup; the worker refuses to start if it can't be reached.
  • Every wire path must be relative to base_path; absolute paths return C210 rather than being silently re-jailed.
  • .. and symlinks are resolved against the longest existing ancestor and rejected if they leave base_path (C215). Dangling symlinks in the tail are also rejected because the kernel would otherwise follow them on the next syscall.
  • 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 rather than removing it.

api reference (json)

agent-api-reference.json
{
  "functions": [
    {
      "description": "Recursive directory snapshot bounded by `max_depth` and a `per_folder_limit`. Folders that hit the limit are flagged `truncated` and the caller is pointed at coder::list-folder for pagination.",
      "metadata": {},
      "name": "coder::tree",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "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 relative to `base_path`. Defaults to `.`.",
            "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"
            ]
          }
        },
        "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": {
                "type": "string"
              },
              "non_accessible": {
                "type": "boolean"
              },
              "path": {
                "type": "string"
              },
              "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` or whose subtree was cut off by `max_depth`."
              }
            },
            "required": [
              "kind",
              "mtime",
              "name",
              "non_accessible",
              "path",
              "size"
            ],
            "type": "object"
          },
          "TruncationInfo": {
            "properties": {
              "hint": {
                "type": "string"
              },
              "reason": {
                "description": "Reason this folder was truncated: hit `per_folder_limit` or `max_depth`.",
                "type": "string"
              },
              "shown": {
                "description": "Number of children actually returned.",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "total": {
                "description": "Total number of children in the folder (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": {
          "root": {
            "$ref": "#/definitions/TreeNode"
          }
        },
        "required": [
          "root"
        ],
        "title": "TreeOutput",
        "type": "object"
      }
    },
    {
      "description": "Paginated single-folder listing, sorted by name. Non-accessible entries are still listed with a `non_accessible: true` flag.",
      "metadata": {},
      "name": "coder::list-folder",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "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, relative to `base_path`. Defaults to `.` (the base itself).",
            "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": {
                "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": {
            "type": "string"
          },
          "total": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "entries",
          "has_more",
          "page",
          "page_size",
          "path",
          "total"
        ],
        "title": "ListFolderOutput",
        "type": "object"
      }
    },
    {
      "description": "Remove one or more paths. Directories need `recursive: true`; missing paths are idempotent successes; recursive removal refuses to descend through non-accessible entries.",
      "metadata": {},
      "name": "coder::delete-file",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "paths": {
            "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": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "path": {
                "type": "string"
              },
              "removed": {
                "type": "boolean"
              },
              "success": {
                "type": "boolean"
              }
            },
            "required": [
              "path",
              "removed",
              "success"
            ],
            "type": "object"
          }
        },
        "properties": {
          "results": {
            "items": {
              "$ref": "#/definitions/DeleteFileResult"
            },
            "type": "array"
          }
        },
        "required": [
          "results"
        ],
        "title": "DeleteFileOutput",
        "type": "object"
      }
    },
    {
      "description": "Read a file relative to base_path. Returns content plus size/mtime/mode. Capped by max_read_bytes; non-accessible paths return C211.",
      "metadata": {},
      "name": "coder::read-file",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "path": {
            "description": "File to read, relative to `base_path`.",
            "type": "string"
          }
        },
        "required": [
          "path"
        ],
        "title": "ReadFileInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "content": {
            "description": "File content as a UTF-8 string. Binary files are returned with invalid bytes replaced by U+FFFD; use a future binary-aware function if exact bytes matter.",
            "type": "string"
          },
          "is_utf8": {
            "description": "Whether `content` lost bytes to UTF-8 sanitisation.",
            "type": "boolean"
          },
          "mode": {
            "description": "Unix permission bits (lower 9 bits of `st_mode`), e.g. 0o644.",
            "format": "uint32",
            "minimum": 0,
            "type": "integer"
          },
          "mtime": {
            "description": "Last-modified time as a Unix epoch in seconds.",
            "format": "int64",
            "type": "integer"
          },
          "path": {
            "description": "The original `path` argument echoed back for caller correlation.",
            "type": "string"
          },
          "size": {
            "format": "uint64",
            "minimum": 0,
            "type": "integer"
          }
        },
        "required": [
          "content",
          "is_utf8",
          "mode",
          "mtime",
          "path",
          "size"
        ],
        "title": "ReadFileOutput",
        "type": "object"
      }
    },
    {
      "description": "Apply batched line-oriented and regex edits across one or more files. 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? } runs on the file body after line ops. Each file commits atomically via temp + rename.",
      "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": {
                "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": {
                  "ignore_case": {
                    "default": false,
                    "type": "boolean"
                  },
                  "op": {
                    "enum": [
                      "replace"
                    ],
                    "type": "string"
                  },
                  "pattern": {
                    "type": "string"
                  },
                  "replacement": {
                    "type": "string"
                  }
                },
                "required": [
                  "op",
                  "pattern",
                  "replacement"
                ],
                "type": "object"
              }
            ]
          }
        },
        "properties": {
          "files": {
            "items": {
              "$ref": "#/definitions/UpdateFileSpec"
            },
            "type": "array"
          }
        },
        "required": [
          "files"
        ],
        "title": "UpdateFileInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "UpdateFileResult": {
            "properties": {
              "after": {
                "description": "UTF-8 body after ops (only on success, capped by `max_read_bytes`).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "applied": {
                "description": "Number of operations applied (only meaningful when `success`).",
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "before": {
                "description": "UTF-8 body before ops (only on success, capped by `max_read_bytes`).",
                "type": [
                  "string",
                  "null"
                ]
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "new_line_count": {
                "description": "Final line count after applying (only meaningful when `success`).",
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "path": {
                "type": "string"
              },
              "success": {
                "type": "boolean"
              }
            },
            "required": [
              "applied",
              "new_line_count",
              "path",
              "success"
            ],
            "type": "object"
          }
        },
        "properties": {
          "results": {
            "items": {
              "$ref": "#/definitions/UpdateFileResult"
            },
            "type": "array"
          }
        },
        "required": [
          "results"
        ],
        "title": "UpdateFileOutput",
        "type": "object"
      }
    },
    {
      "description": "Search file contents and/or paths under base_path. Supports literal or regex queries with include/exclude globs; non-accessible files are excluded from both content and path results.",
      "metadata": {},
      "name": "coder::search",
      "request_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "properties": {
          "exclude_globs": {
            "default": [],
            "description": "Glob patterns (relative to `base_path`) that exclude matching paths.",
            "items": {
              "type": "string"
            },
            "type": "array"
          },
          "ignore_case": {
            "default": false,
            "type": "boolean"
          },
          "include_globs": {
            "default": [],
            "description": "Glob patterns (relative to `base_path`) 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, relative to `base_path`, scoping the walk. Defaults to `.` (the base itself). Globs and result `path`s remain anchored at `base_path` regardless of this value.",
            "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"
          }
        },
        "required": [
          "query"
        ],
        "title": "SearchInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "ContentMatch": {
            "properties": {
              "column": {
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "line": {
                "format": "uint32",
                "minimum": 0,
                "type": "integer"
              },
              "path": {
                "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": {
                "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 either match list was cut off at the configured cap.",
            "type": "boolean"
          }
        },
        "required": [
          "content_matches",
          "path_matches",
          "truncated"
        ],
        "title": "SearchOutput",
        "type": "object"
      }
    },
    {
      "description": "Create one or more files. Per-file `overwrite` and `parents` flags; non-accessible paths return C211.",
      "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": {
                "type": "string"
              }
            },
            "required": [
              "content",
              "path"
            ],
            "type": "object"
          }
        },
        "properties": {
          "files": {
            "items": {
              "$ref": "#/definitions/CreateFileSpec"
            },
            "type": "array"
          }
        },
        "required": [
          "files"
        ],
        "title": "CreateFileInput",
        "type": "object"
      },
      "response_schema": {
        "$schema": "http://json-schema.org/draft-07/schema#",
        "definitions": {
          "CreateFileResult": {
            "properties": {
              "bytes_written": {
                "format": "uint64",
                "minimum": 0,
                "type": "integer"
              },
              "error": {
                "type": [
                  "string",
                  "null"
                ]
              },
              "path": {
                "type": "string"
              },
              "success": {
                "type": "boolean"
              }
            },
            "required": [
              "bytes_written",
              "path",
              "success"
            ],
            "type": "object"
          }
        },
        "properties": {
          "results": {
            "items": {
              "$ref": "#/definitions/CreateFileResult"
            },
            "type": "array"
          }
        },
        "required": [
          "results"
        ],
        "title": "CreateFileOutput",
        "type": "object"
      }
    }
  ],
  "triggers": []
}