crab0.4.0
crab/docs/Tool contracts

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

ToolDoesMutatesAsks
readUTF-8 text, truncated past 100 KB; marks the file read for the freshness gatenono
writeCreate or overwrite; overwriting needs a prior read and unchanged mtime; atomicyesyes
editExact-match string edits, all-or-nothingyesyes
bashShell command, 120 s timeout, output truncated head + tailyesunless read-only
globFind files by glob patternnono
grepRegex over contents; respects .gitignore, skips hidden filesnono
web_fetchURL → extracted main article text (readability)noyes

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 old must 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 old is 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.

esc