Repository navigation
user tools
The model works through tools. Steiner owns each tool's input schema and result format, keeps output bounded, and enforces path policy and sandboxing around the calls. steiner tools lists the built-in, custom, MCP, and LSP tools for your configuration; it does not list web_search, advisor, sub_agent, or follow_up, which are added per run.
| Tool | Purpose | Available | Can ask for approval |
|---|---|---|---|
read |
Read a file with offset/limit paging; detects images | Always | No |
glob |
Find files by pattern | Always | No |
grep |
Search file contents with context and paging | Always | No |
ls |
List a directory | Always | No |
bash |
Run a shell command, sandboxed by default | Always | Yes: sandbox denial, or cwd outside the project root |
display_file |
Show a file in the TUI overlay without adding it to the conversation | Always | No |
mutate |
Apply file edits atomically: create, write, replace, delete_file, move
|
Always | Yes: a path outside the project root |
fetch_url |
Fetch a URL and return bounded content or saved image data | Always | No |
workflow_handoff |
Propose switching to a workflow with approved artefacts | Always | Asks you to accept the handoff |
web_search |
Search the web |
search.backend is set |
No |
lsp_definitions, lsp_implementations, lsp_type_definitions, lsp_references, lsp_diagnostics, lsp_hover, lsp_symbols
|
Code intelligence through a language server | lsp.enabled |
No |
advisor |
Ask a stronger model for guidance | advisor.enabled |
No |
sub_agent, follow_up
|
Delegate work to a child agent, or resume one | Sub-agent delegation enabled (sub_agent.enabled) |
Child tool calls can ask |
mcp__<server>__<tool> |
Tools advertised by MCP servers |
mcp.enabled and the server is enabled |
Yes, except in build mode when the server's approval is allow, or when trusted annotations mark the tool safe |
| Custom tools | Your own executables declared under tools:
|
Declared in config | No |
See Sandboxing for what triggers an approval prompt and what approving does, LSP, MCP, Advisor, and Sub-agent delegation for the conditional tools, and Execution modes for plan-mode restrictions.
Sub-agents get a per-type subset of these tools. MCP tools reach children only through mcp.servers.<name>.sub_agents.
read, glob, grep, ls, and display_file can read anywhere the filesystem allows, except paths.blocked_paths. Writes through mutate are limited to the project root while paths.project_root_only is on (the default), and to paths.writable_paths when that list is set. The bash working directory must be inside the project root while project_root_only is on and must not be blocked. These tools run inside Steiner, not in the sandbox. See the paths block.
glob, grep, and ls skip excluded paths. Exclusions are filters, not access control: only paths.blocked_paths denies access.
- Built-in exclusions match any path component named
.git,node_modules,.steiner,.claude,.git-worktrees,vendor,.cache,dist,build,out,target,.next, or.nuxt. They are matched relative to the search root, so a search rooted inside one of these directories, such asvendor/, still runs. -
paths.exclude_pathsandpaths.exclude_patternsadd workspace exclusions. They still apply when the search root is inside an excluded directory, so that search returns nothing. -
grepandglobfollow a nested symlink only when its target is a regular file inside the search root or workspace that also passespaths.blocked_pathsand the exclusions. Nested directory symlinks are not followed. Results show the link name, not the target.lslists link names without resolving them.
-
readreturns line numbers only when called withline_numbers: true. Text files over 1 MB are rejected, even when paging; usegreporbashto inspect them. Images are returned as image data, up to 5 MB. - A file with a NUL byte in its first 8192 bytes is treated as binary:
readrejects it andgrepskips it. -
mutateapplies all operations in a call as one batch.replaceon an existing file requires that the file was read earlier in the session, or that the call passes thefile_hashfrom areadresult. This catches edits to a file that changed since it was read. - After a successful
mutate, Steiner may append diagnostics for the changed files from a language server that is already running. See LSP.
grep pages through logical results: a file path in files_with_matches mode, a row in count mode, or a matching line in content mode.
-
head_limitsets the page size: 100 by default, at most 500. - A
contentpage renders at most 200 lines, including context lines and headers, so it can hold fewer results thanhead_limit. -
next_offsetis the offset of the next page. Pass it asoffsetto continue. It is omitted on the last page. - Rendered lines are capped at 400 characters around the match.
-
globfilters by path relative to the search root. A pattern without a slash matches a basename at any depth (*.go); a pattern with a slash matches the root-relative path (src/*.go); a leading./anchors to the root. -
typefilters by extension. Accepted values:c,cpp,css,go(aliasgolang),html,java,js,json,kotlin,md,php,py,rb,rust,scala,sh,sql,swift,ts,xml,yaml. -
multiline: truelets.match newlines and^/$match line boundaries.
bash returns at most max_output_bytes of output: 12288 bytes by default, at most 32768. Output is cleaned by default: ANSI escape sequences are removed, OSC 8 hyperlinks become text (uri), and adjacent CRLF becomes LF. Set raw_output: true to skip cleaning.
When output does not fit, Steiner keeps the start and end and marks the omitted bytes. On a nonzero exit, stderr gets priority. Steiner attempts to save the captured output, capped at 1 MiB, to .steiner/tmp/bash-output/ in the working directory. When saved, the result's file_path lets the model page through it with read. A timed-out command returns the output captured so far.
The per-call bash timeout is capped by limits.tool_timeouts.bash (600s by default). See the limits block.
- Saved output can contain secrets, tokens, or file contents the command printed. On Unix the directory is mode 0700 and each file 0600. On other platforms output is not saved.
- At startup, Steiner prunes files older than 7 days in
.steiner/tmp/bash-output/and.steiner/tmp/fetched/. It also removes older files towards a shared 250 MiB budget, but does not evict files less than one hour old to meet that budget. - Commands that can write the workspace can also change these files. Do not treat them as tamper-evident records.
fetch_url only connects to publicly routable addresses. Loopback, private, CGNAT, link-local (including cloud metadata at 169.254.169.254), unique-local IPv6, and reserved ranges are refused, including on every redirect hop. Each request times out after 15 seconds and follows at most 9 redirects.
fetch_url ignores HTTP_PROXY, HTTPS_PROXY, and NO_PROXY, because a proxy would bypass the address check. On a network whose only route out is a proxy, fetch_url cannot reach the internet. web_search does use the proxy variables.
web_search returns URL, title, and description results. It is off until you set search.backend, and it is registered only while sub-agent delegation is enabled (the default). When available, the research sub-agent gets it too.
search:
backend: braveSet search.backend to brave, kagi, or searxng and supply that backend's credentials. The unprefixed environment variables fill a key only when the config leaves it empty, and config validation fails if the selected backend has no credentials. Setup for each backend, a local SearXNG example, and troubleshooting are in Web search; the field reference is the search block.
Declare your own executables under tools:. Each needs exec and a timeout greater than zero; names of built-in tools are reserved. Field reference: tools block.
tools:
my_tool:
exec: /path/to/my-tool
description: Run my custom tool.
timeout: 10sSteiner runs exec, with subcommand as its first argument when set, as a subprocess in the project root for every call. The subprocess runs in the sandbox like bash, and in plan mode the project is read-only to it. Custom tools never trigger an approval prompt.
- stdin: the model's arguments as one JSON object, written once and then closed.
-
stdout: one JSON object.
- Success:
{"ok": true, "result": <any JSON>}. The model seesresult. - Optional
"model_result": <any JSON>next toresult: the model seesmodel_resultinstead ofresult. Use it when the full result carries more than the model needs. - Failure:
{"ok": false, "error": {"kind": "<short slug>", "message": "<human-readable>"}}.
- Success:
-
exit code: exit
0on every call that produced an envelope. A nonzero exit is always a failure, even with{"ok": true}on stdout. Useok: falseto report a specific failure. -
failures: for an
ok: falseenvelope, invalid JSON, a nonzero exit, or a process that failed to start, the model receives thekind, themessage, the exit code, and bounded summaries of stdout and stderr. Anerror.detailsvalue in your envelope is not passed to the model. A timeout or cancellation returns an execution error instead. - Raw stdout and stderr are captured up to
limits.tool_output_max_bytesand are not sent to the model on success.
// stdin
{"path": "main.go"}
// stdout, success
{"ok": true, "result": {"formatted": true}}
// stdout, success with a smaller model-facing result
{"ok": true, "result": {"formatted": true, "diff": "..."}, "model_result": {"formatted": true}}
// stdout, failure
{"ok": false, "error": {"kind": "invalid_input", "message": "path not found"}}Get started
Using Steiner
- How to work with steiner
- Interactive mode
- CLI reference
- Skills
- Sub-agent delegation
- Oneshot
- Context management
- Advisor
- Security audit
Tools and integrations
Safety
Usage and cost
Reference
Internals and maintenance