Seven built-in tools, each with a fixed contract and one observation per call. Extra capability arrives over MCP, not through a plugin ABI.
The seven
| Tool | Does | Mutates | Asks |
|---|---|---|---|
read | UTF-8 text, truncated past 100 KB; marks the file read for the freshness gate | no | no |
write | Create or overwrite; overwriting needs a prior read and unchanged mtime; atomic | yes | yes |
edit | Exact-match string edits, all-or-nothing | yes | yes |
bash | Shell command, 120 s timeout, output truncated head + tail | yes | unless read-only |
glob | Find files by glob pattern | no | no |
grep | Regex over contents; respects .gitignore, skips hidden files | no | no |
web_fetch | URL → extracted main article text (readability) | no | yes |
Built-ins are named bare. MCP tools are namespaced <server>__<tool>, so a
server cannot shadow a built-in and two servers can both expose search.
Contracts
read
Returns file content bounded by offset and limit, and records the file in session state so a later
edit or write is allowed. Every call writes an observation with bytes returned,
so a suspiciously small bytes_out on a file you know is large means the model asked for the
wrong range.
write
Creates parent directories as needed and replaces the file wholesale. Overwriting requires the file to have been read this session and unchanged since — see the freshness gate below.
edit
The one worth reading carefully, because it is where most agent mistakes would otherwise become silent corruption:
- Literal, exact match. No whitespace tolerance, no fuzzy matching.
- Each
oldmust match exactly once. Zero matches or more than one is a loud error. - Multiple edits match against the original text, not incrementally against each other.
- All-or-nothing. A failed edit in a batch applies none of them.
- Empty
oldis rejected — an empty string would otherwise match exactly once in an empty file and perform a meaningless replacement.
The error message includes the closest candidate position. That similarity search is used only to write the hint, never to decide where to edit. A fuzzy edit would swap a visible failure for a silent wrong edit, and the model would read "ok" and keep going — which in an agent is a disaster, not a convenience.
The freshness gate
read, write and edit share session state: edit and
write refuse a file you have not read this session, and re-check that its mtime has not
changed since. This blocks the worst failure mode — you fix something in your editor while the agent is
working, and it clobbers your change with the stale copy it read two minutes ago.
The base system prompt teaches this explicitly, so the model knows to read first rather than discovering the rule from an error.
bash
Runs a command through the shell with a timeout. Output is truncated keeping head and tail, with the
elided byte count marked. Non-zero exits come back as tool results rather than raised failures, so the
model can read the compiler error and retry. The child is spawned with kill_on_drop(true) —
without it, an abort would leave a pile of still-running npm run build processes behind.
glob
Pattern matching, sorted and deduplicated. The recursive walk passes the original root through, so
nested prefixes are preserved (nested/lib.rs, not lib.rs).
grep
Regex search respecting .gitignore / .ignore and skipping hidden files, via
the grep-* and ignore crates. Results are bounded; when a search is truncated
the model is told, because a silently truncated search is how an agent concludes a function does not
exist.
web_fetch
Fetches a URL and returns readable main-article text with nav, ads and scripts stripped
(dom_smoothie), bounded by size caps. It is gated because it is the main path for pulling
outside content into the context. Run it in a network-less container when a task does not need it.
How confirmation works
⚠ bash — run: rm -rf build
allow? [y]es/[n]o/[a]ll:
y- Allow this call. The next call to the same tool class asks again.
n- Refuse. The refusal goes back to the model as the tool result, which is usually enough for it to change approach.
a- Allow this class for the rest of the session. Other classes still ask.
Read-only operations never prompt. --yolo skips all prompts; the footer shows
yolo ON while it is active. --print auto-approves because nobody is there to
answer — worth remembering when you pipe a prompt in from a script.
What each call records
Every call produces one tool_call observation: tool name, call_seq,
args_digest, result_digest, bytes_in, bytes_out,
duration_ms and outcome. Failures are counted per run as
tool_errors — the tool-err column in crab metrics.
Adding tools
Configure an MCP server and its tools join the list for the next session, as
<server>__<tool>. There is no rebuild, no manifest, and no capability negotiation
beyond what the server advertises at startup. See
MCP servers.