Shell access
Declare shell tools
Choose inline commands or manifested tools and understand arguments and output handling.
View Markdown source ↗Host integration: see native/legacy/daemon authorization and administration.
The declaration language is shared, but bootstrap grants, approvers, persistence
owners and management commands differ. --authorize-shell-manifest is a legacy
local TUI option, not a native --local or daemon flag. Legacy Session_store
management does not accept daemon IDs as a way to select daemon state.
Shell tools expose commands to a model through a named <shell_access>
runtime. The runtime—not the tool alone—controls resolution, capabilities,
sandboxing, policy, approval, interception, limits, secrets, and audit.
Start with fixed for a narrow operation or structured for general agent
shell access. Use chain, raw, and script only when their additional
semantics are required.
See also:
Common attributes
Section titled “Common attributes”<tool name="tool-name" type="shell" mode="fixed|structured|chain|raw|script" runtime="runtime-reference" description="Model-visible description" stdin="none|optional|required" rationale="none|optional|required" result="combined|stdout|structured" stream="finalized|sanitized" nonzero="result|error"> ...</tool>name, type="shell", mode, and runtime are required for the long form.
Tool names must be unique in a model request. The referenced runtime is fully
compiled and authorized before the tool is published.
Model-visible descriptions
Section titled “Model-visible descriptions”Every published shell tool receives a built-in, mode-aware description even
when the declaration omits description. The built-in text tells the model:
- whether the tool runs a fixed command, one structured executable, a parsed command chain, raw shell source, or a verified script file;
- which model-controlled fields to provide, including
arguments,program,command,script,stdin, andrationalewhen applicable; - how output and nonzero exits are returned; and
- that execution remains governed by the runtime’s configured policy, approvals, sandboxing, limits, interceptors, sanitization, secret redaction, and audit settings.
The optional description attribute adds task-specific instructions; it does
not replace the built-in usage contract. The final model-visible description
is composed as:
<built-in mode and runtime guidance>
Additional tool guidance: <description attribute>For example:
<tool name="search" type="shell" mode="fixed" runtime="readonly" command="rg --json" description="Search repository text and prefer concise patterns."/>The model sees both the fixed-command input contract and the additional search guidance. This description affects only how the tool is presented to the model; it grants no authority and cannot widen runtime policy.
Defaults:
stdin="none"rationale="none"for fixed tools andoptionalfor arbitrary toolsresult="combined"stream="finalized"nonzero="result"
combined returns finalized stdout followed by finalized stderr. stdout
returns stdout and leaves stderr as diagnostics. structured returns bounded,
sanitized JSON with status, channels, truncation, backend, runtime/manifest,
and per-command metadata.
nonzero="result" returns a safe result normally. nonzero="error" attaches
the same safe result to a tool error.
stream="sanitized" additionally publishes bounded live progress as a single
combined Stdout stream, including stderr; it does not change the returned
result selected above. It requires the built-in streaming-safe literal-filter
subset and rejects every after-interceptor at registration. See the
exact restrictions and lifecycle
before enabling it. finalized remains the default and emits no pipe progress.
Fixed command tools
Section titled “Fixed command tools”Fixed tools declare the program and optional leading arguments. Model-supplied arguments remain literal argv elements.
Compact compatibility syntax:
<tool name="search" command="rg --json" runtime="readonly" description="Search repository files"/>Canonical long form:
<tool name="search" type="shell" mode="fixed" runtime="readonly"> <command program="rg"> <arg value="--json"/> </command> <arguments mode="required" min_count="1" max_count="20" max_item_bytes="4096"/></tool>The default model schema is equivalent to:
{ "type": "object", "properties": { "arguments": {"type": "array", "items": {"type": "string"}} }, "required": ["arguments"], "additionalProperties": false}Fixed tools default to rationale="none": the schema omits rationale, and
supplying it is rejected. Set rationale="optional" or rationale="required"
on the tool to enable that field.
Control model arguments with:
<arguments mode="none"/><arguments mode="optional" max_count="20" max_item_bytes="4096"/><arguments mode="required" min_count="1" max_count="20"/>Declared arguments precede model arguments. An argument such as
TODO; rm -rf / is one argv element; it does not execute rm.
Fixed argument sources
Section titled “Fixed argument sources”<command program="curl"> <arg value="--header"/> <secret_arg env="API_TOKEN" prefix="Authorization: Bearer "/> <path_arg base="workspace" path="config/query.json"/></command><arg value="..."/>adds one literal argv element.<secret_arg>reads one environment value, prefixes it, registers it for redaction, and excludes the raw value from the canonical manifest.<path_arg>resolves and canonicalizes one path.basemay name a standard path variable such asworkspaceorsource_dir.
Executable aliases
Section titled “Executable aliases”<resolver> <executable id="project-linter" path="${workspace}/tools/lint" sha256="..." trusted="true"/></resolver>
<tool name="lint" type="shell" mode="fixed" runtime="readonly"> <command executable_ref="project-linter"/> <arguments mode="none"/></tool>Aliases avoid PATH ambiguity and support hash pinning.
The compact command attribute is parsed by the conservative command parser
and must produce exactly one command. Pipelines and conditionals are rejected
in fixed mode. Prefer the child form for complex literal arguments.
Structured arbitrary-command tools
Section titled “Structured arbitrary-command tools”<tool name="shell" type="shell" mode="structured" runtime="development" rationale="required" result="structured" description="Run a structured project command"/>Model input:
{ "program": "dune", "arguments": ["build", "@runtest"], "rationale": "Verify the project after editing"}No shell parser runs. program and arguments become a structured command,
then pass through resolution, effects, capability checks, policy, approvals,
interceptors, backend selection, and audit. This is the recommended general
shell interface because argument strings cannot inject shell syntax.
Conservative chain tools
Section titled “Conservative chain tools”<tool name="shell_chain" type="shell" mode="chain" runtime="development" rationale="required" result="structured"/>Model input:
{ "command": "dune build && dune runtest | tee test.log", "rationale": "Build and capture test output"}Chain mode supports quoted/unquoted words, pipelines (|), sequence (;),
and conditionals (&&, ||). It deliberately rejects:
- redirection and here-documents;
$()and backticks;- background jobs;
- grouping and subshells;
- variable and glob expansion;
- functions, aliases, and startup files.
Pipeline stages execute concurrently through Eio pipes. Each executable is
resolved, checked, approved, fingerprinted, and audited. Conditional branches
are prepared only when selected, so skipped branches do not prompt.
pipefail comes from the runtime.
Unsupported syntax is an error. It never causes an implicit /bin/sh or raw
shell fallback.
Raw shell tools
Section titled “Raw shell tools”<tool name="raw_shell" type="shell" mode="raw" runtime="raw-reviewed" executable="/bin/zsh" arguments_before_script='["-c"]' rationale="required" stdin="optional" result="structured"/>Model input:
{ "script": "for f in lib/*.ml; do wc -l \"$f\"; done", "stdin": "", "rationale": "Count source lines"}The shell executable and argument prefix are fixed by ChatMD. The script is one argument, not concatenated into the launch command. Raw mode is always classified as arbitrary code, child-process capable, and conservatively unknown. The runtime must declare those capabilities.
Raw approvals bind the shell fingerprint and complete script digest. Review uses a bounded redacted preview; audit may record the digest rather than raw script. Prefer required sandboxing and once-only approval.
Script-file tools
Section titled “Script-file tools”<tool name="project_checks" type="shell" mode="script" runtime="checks" script="${source_dir}/scripts/check-project.sh" interpreter="/bin/sh" fixed_arguments='["--ci"]' verification="sha256" max_source_bytes="1048576" description="Run checked-in validation"/>Model input contains literal arguments, optional stdin, and optional
rationale according to the common attributes. The script and interpreter are
resolved and fingerprinted. The script is loaded through Eio under the source
bound, hashed into the manifest/approval identity, and reverified before every
start. A changed script produces a typed failure; it is not silently accepted.
An executable script may omit an interpreter:
<tool name="project_checks" type="shell" mode="script" runtime="checks" script="${source_dir}/scripts/check-project" executable="true"/>stdin="optional" or required adds a UTF-8 stdin field to the model
schema. The runtime checks max_stdin before approval and records only its
length and digest in approval/audit identity by default. Input is sent through
an Eio flow and is never interpolated into command text. For a pipeline it
reaches only the first stage.
Rationale
Section titled “Rationale”Rationale appears in approval requests and audit metadata. It does not change static policy unless a custom reviewer inspects it. It is untrusted model text, so ochat bounds and terminal-sanitizes it.
Structured result
Section titled “Structured result”result="structured" returns a canonical safe object similar to:
{ "request_id": "shell-42", "status": {"exited": 1}, "stdout": "", "stderr": "File \"lib/x.ml\", line 4: error...\n", "stdout_truncated": false, "stderr_truncated": false, "backend": "macos-seatbelt", "runtime_id": "development", "manifest_sha256": "...", "commands": [ { "program": "dune", "arguments": ["build"], "executable": "/opt/opam/default/bin/dune", "executable_sha256": "...", "status": {"exited": 1}, "intercepted_by": null } ]}Returned output has passed byte bounds, terminal filtering, literal secret replacement, and configured output interceptors. The finalized-result path can truncate within a UTF-8 sequence; it does not inherit the incremental UTF-8 guarantees of sanitized live progress. See the output contract.
Moderator Process.run
Section titled “Moderator Process.run”Moderator process access uses the same runtime registry:
<moderator_runtime shell_runtime="moderator-processes"/>
<script id="conversation" language="chatml" kind="moderator"> ... Process.run("git", [|"status"; "--short"|]) ...</script>The call is structured argv and receives the same resolution, policy,
capability, approval, interception, limits, backend, output, and audit behavior
as a shell tool. Without <moderator_runtime>, Process.run is unavailable.
Legacy command declarations
Section titled “Legacy command declarations”Existing declarations such as:
<tool name="rg" command="rg" description="Search files"/>are desugared into a fixed shell tool and executed through the centralized shell runtime path. Export may serialize the explicit shell form. No production path falls back to the old direct custom-command runner.