| Tool | Approval | Notes |
|---|---|---|
read_file | automatic | Numbered lines, offset/limit for paging; a PNG, JPEG, GIF or WebP comes back as a picture the model can look at |
glob | automatic | Newest first; *.go matches at any depth |
grep | automatic | ripgrep when present, pure Go otherwise, identical output either way |
edit_file | prompts | Requires a prior read; refuses ambiguous matches; returns a diff and any diagnostics |
write_file | prompts | Whole files only; separate from edit_file so the metrics stay honest |
bash | prompts | Own process group, killed whole on timeout; output capped head and tail. background: true returns a handle instead of waiting |
process_output | never | New output from a background process since you last asked, and whether it is still running |
process_kill | never | Stops a background process and its children; everything is killed when the session ends anyway |
todo_write | never | The working plan for a multi-step task; the whole list is sent each time |
context_status | never | How full the window is, how many turns and what they cost |
ask_user | never | Puts a multiple-choice question to you and waits — see questions |
web_search | prompts | Only with web_search: custom; runs ddgs on this machine |
web_fetch | prompts | With either web search mode; refuses private and loopback addresses |
mcp__<server>__<tool> | prompts | One per tool of each MCP server in the config |
The agent writes a file and finds out it broke the build three tool calls later, if at all. The cheapest fix is to put the compiler's own opinion in the result the model is already reading:
⏺ EditFile(path=internal/agent/loop.go)
⎿ 4 additions, 1 removal
…
diagnostics (gopls): 2 problems
loop.go:14:2: undefined: fmt.Printl
loop.go:19:6: declared and not used: n
After a successful edit_file or write_file, the changed file goes to
whatever this machine already has for its language — gopls,
pyright-langserver, typescript-language-server or rust-analyzer if one is
on PATH, and otherwise the compiler or linter: go build and go vet, ruff
or pyflakes, eslint or tsc, shellcheck. Nothing is installed and
nothing leaves the machine.
A clean file adds nothing at all. Silence is the good news, and a "0 problems" line on every edit would be a paragraph of tokens per edit saying that nothing happened. Ten problems are reported and the rest counted, and a diagnostic about a neighbouring file — the helper you have just renamed out from under — is kept and labelled, because that is the one the agent has no other way to learn about.
Language servers are started once and kept warm between edits, keyed by the
workspace around the file rather than by the working root, so /worktree does
not strand a warm gopls behind it. A check that has not finished in two
seconds is abandoned rather than waited for: a tool call that hangs because a
language server is confused is far worse than one that reports nothing. Every
other way this can go wrong lands in the same place — a server that will not
start, that dies mid-session, that answers with garbage, or one on PATH that
turns out to be a broken shim — and the answer is always no diagnostics and a
fall through to the linter, never a hung tool call. A server that fails to
start is put on a five-minute cooldown rather than retried on every edit.
It is on by default, which is the one setting here that leans that way: it runs
a tool you already have, against a file you already told the agent to write,
and nothing it reads goes anywhere. "diagnostics": "off" in the config, the
/settings row, or -no-diagnostics for a single run turns it off.