shell
The shell worker is the single door every agent uses to touch the OS: run a build, call a CLI, read a file, list a directory.
Works with
Agent Skills format with YAML frontmatter. Claude Code reads it as-is.
---
name: "shell"
description: "The shell worker is the single door every agent uses to touch the OS: run a build, call a CLI, read a file, list a directory."
license: "Apache-2.0"
---
# shell
The shell worker is the single door every agent uses to touch the OS: run a
build, call a CLI, read a file, list a directory. Routing it all through
`shell::*` and `shell::fs::*` keeps a denylist, timeouts, output
caps, and an optional host-root jail (unjailed by default — see Boundaries)
in one enforceable place. Both surfaces take an
optional `target` field that forwards the call into a live `iii-sandbox`
microVM, so the same denylist gates host and sandbox execution alike.
Host-targeted `shell::exec` is not an isolation boundary. The denylist is a
regex tripwire on `argv.join(" ")`, and any interpreter (`sh`,
`node`, `python3`) can construct any forbidden token at runtime to bypass it.
Run untrusted input with `target: { kind: "sandbox", sandbox_id }`. Prefer the
`shell::fs::*` backends over `exec`-ing `ls`/`stat`/`grep`/`rg`: they stay
in-process, honor `fs.host_roots` when it's configured, and return
structured results.
Sandbox forwarding (and `shell::fs::*` into a VM) requires the `iii-sandbox`
worker; `iii worker add shell` does not pull it in. To surface `shell::*` to LLM
agents, pair with the `skills` worker.
## When to Use
- Run a one-shot command and block for its full output: `git status`, `wc`,
`head`, a quick compile probe (`shell::exec`).
- Kick off long work (build, watcher, wide grep) without blocking the turn,
then poll for completion (`shell::exec_bg` + `shell::status`).
- Survey or terminate in-flight background jobs (`shell::list`, `shell::kill`).
- List, stat, or read files with structured output instead of shelling out to
`ls`/`stat`/`cat` (`shell::fs::ls`, `shell::fs::stat`, `shell::fs::read`).
- Search or rewrite across a tree without spawning `rg`/`sed`
(`shell::fs::grep`, `shell::fs::sed`).
- Create, move, remove, or re-permission paths (jailed only if
`fs.host_roots` is set)
(`shell::fs::mkdir`, `shell::fs::mv`, `shell::fs::rm`, `shell::fs::chmod`).
- Persist a generated artefact, or bootstrap files into a sandbox, by streaming
bytes to a path (`shell::fs::write` with a `target`).
## Boundaries
- Host `shell::exec` is not a security sandbox: the denylist is bypassable by
any interpreter. Run untrusted commands with `target: sandbox`
(needs `iii-sandbox`).
- `shell::fs::*` honors `fs.host_roots` as a jail WHEN it's set — empty (the
shipped default) means unjailed, confined only by `fs.denylist_paths` — and
always refuses denylisted paths regardless; paths must be absolute (unless
jailed, where a relative path resolves against the primary root) and
symlinks are never followed.
- Sandbox-backed background jobs cannot be hard-killed: `shell::kill` flips the
record but the in-VM process runs until its `timeout_ms` (or `sandbox::stop`).
- Not for inlining file bytes into an LLM tool result: `shell::fs::read`/
`write` move bytes over channels; use the `harness` worker's
`harness::fs::read_inline` wrapper for inline reads on the web surface.
- No batch or glob form for single-path ops (`mv`, `rm`, `stat`, …); loop in the
caller.
- Not a package manager, editor, or migration tool; for SQL use the `database`
worker.
## Functions
- `shell::exec`: run a command in the foreground and return its
stdout, stderr, exit code, and timing; blocks until exit or timeout. Sandbox
execution is fully valid (`target: { kind: "sandbox", sandbox_id }`); only the
host-only override fields — `stdin` (string piped to the program's stdin, then
EOF), plus `cwd`/`env` — are rejected with `S210` when supplied on a sandbox
target, because the sandbox exec protocol does not forward them.
- `shell::exec_bg`: spawn a command as a background job and return
a `job_id` immediately. Host-targeted jobs run until they exit or `shell::kill`
terminates them — unbounded by default, capped only when the operator sets a
positive `max_bg_timeout_ms` (default `0` = unbounded), after which a runaway
job is killed and its status becomes `killed`. Sandbox jobs honor `timeout_ms`.
Same optional host-only `stdin` as `shell::exec`.
- `shell::status`: fetch one job's full record: state, exit code, and captured
stdout/stderr. A missing id (never existed or aged out) returns an `S211`
("no such job") error.
- `shell::list`: enumerate current jobs as lightweight summaries (no argv,
stdout, or stderr).
- `shell::kill`: terminate a running background job by `job_id`.
- `shell::config-status` *(operator/automation only — not agent-callable)*:
report the last hot-reload outcome — `last_outcome` (`applied`/`rejected`),
`last_error`, and `rejected_reloads` (count since boot). A rejected outcome or
non-zero count means a stored config was refused and shell is enforcing an
older policy than the central store. Takes no arguments.
- `shell::fs::ls`: list a directory's entries with structured metadata.
- `shell::fs::stat`: read one path's metadata (size, mode, symlink flag).
- `shell::fs::mkdir`: create a directory, optionally with missing parents. Returns `{ created: bool, path: string, already_existed: bool }`.
- `shell::fs::rm`: remove a file or directory, optionally recursive. Returns `{ removed: bool, path: string, was_present: bool }`.
- `shell::fs::chmod`: change a path's mode, and optionally its uid/gid. Returns `{ entries_changed: u64, path: string, recursive: bool }`. **Note**: the field was renamed from `updated` to `entries_changed` — callers relying on `updated` must migrate.
- `shell::fs::mv`: rename or move one path within the jail. Returns `{ moved: bool, src: string, dst: string, overwrote: bool }`.
- `shell::fs::grep`: recursive regex search across a tree, returning structured
matches.
- `shell::fs::sed`: regex find-and-replace across one file or many.
- `shell::fs::write`: write a file. Simplest form is inline string `content`
(host target only): `{ path, content: "file text" }`, with `mode` (octal,
default `"0644"`) and `parents: true`. A `ContentRef` object in `content`
instead streams large/staged payloads via a channel (temp file + atomic
rename) and is **required** for sandbox targets. Batch form: pass
`files: [{ path, content, mode?, parents? }, ...]` to write several files in
one host call; the response then carries per-file `files: [{ path,
bytes_written }]` (a single-file write leaves `files` empty).
- `shell::fs::read`: stream a file's bytes out through a channel.
Every `shell::fs::*` call accepts the same optional `target` as `exec`, so host
and sandbox share one wire shape; reads and writes move bytes over SDK channels
rather than inlining them.
## Code files (`coder::*`)
The shell worker also serves the `coder::*` code-file surface (formerly a
standalone worker) over the **same jail** (`fs.host_roots`): `coder::info`
(discover roots/caps first), `coder::read-file` (windowed + batch reads),
`coder::search`, `coder::list-folder`, `coder::tree`, and the batched
`coder::create-file` / `coder::update-file` / `coder::delete-file` /
`coder::move`. Prefer these structured ops over editing files through
`shell::exec`. They return `C2xx` error codes (distinct from `shell::*`'s
`S2xx`); protected paths are the shared `code.non_accessible_globs`.More General & Other skills
find-skills
vercel-labs/skills
Helps users discover and install agent skills when they ask questions like "how do I do X", "find a skill for X", "is there a skill that can...", or express interest in extending capabilities. This skill should be used when the user is looking for functionality that might exist as an installable skill.
grill-me
mattpocock/skills
A relentless interview to sharpen a plan or design.
grill-with-docs
mattpocock/skills
A relentless interview to sharpen a plan or design, which also creates docs (ADR's and glossary) as we go.

