The agent's knowledge of a library ends where its training did, and the two questions it cannot otherwise answer — what does this error message mean, and what is this API called now — are the ones a search box answers in seconds. The wizard's fourth question decides whether it gets one, and the honest default is no: every other capability here stays inside the working tree, and a search query is written by the model out of whatever the model has read.
web_search | What it does | What it costs you |
|---|---|---|
none | nothing; the tools are not registered and the model is never told they exist | — |
claude | declares Anthropic's hosted web_search_20250305 tool, which runs on their side of the wire | billed per search by Anthropic; needs an endpoint that offers it |
custom | registers web_search and web_fetch, which run here | a search command on your PATH |
claude is a line in the request's tool array and nothing else — no
install, no key, no HTML parsing. The searches come back as server_tool_use
and web_search_tool_result blocks, which the loop turns into ordinary
WebSearch(...) transcript entries so a search is something you can watch
rather than a pause you have to guess at. A hosted search cannot be refused at
the prompt, because it happens inside a request you already approved; the
setting itself is the consent. It also needs the endpoint to implement it, so a
gateway or a local server speaking the Messages API will generally refuse the
whole request — if that happens, switch to custom.
custom runs a command. The default is
ddgs text -q {{query}} -m {{count}} -nc
which needs the DuckDuckGo CLI (pipx install ddgs) and no account at all.
{{query}} and {{count}} are substituted as whole argv elements and the
command is run without a shell, so a query containing ; or backticks is a
query rather than a second command.
Note the absence of -o json, which is the flag you would reach for and the
wrong one: it does not print JSON, it writes a file — into whatever directory
the agent happens to be working in, once per search. The plain listing goes to
stdout and is parsed, and -nc keeps the colour codes out of the titles.
Any tool will do, as long as it prints to stdout: a JSON array, one JSON object
per line, a {"results": […]} wrapper, or ddgs's own aligned listing are all
read. Titles may be title; links href, url or link; snippets body,
snippet, description or abstract. Edit the command in the wizard, or set
search_command in the config.
web_fetch is the other half, because a snippet is enough to choose a link and
never enough to write code against. It resolves the host and refuses loopback,
private, link-local and unique-local addresses, on the original URL and on
every redirect hop — the model invents these URLs, and 169.254.169.254 is a
cloud metadata endpoint. Pages come back as text, capped head and tail like
bash output, wrapped in markers that say to treat what is between them as
data rather than instructions. That does not make prompt injection impossible;
it makes it visible in the transcript, which is the most a fetch tool can
honestly offer.
Both tools prompt, and neither is in the read-only set: they read nothing local and send the model's words off the machine, which is the opposite of what that set is for. The query and the URL are the detail on the prompt, so what is leaving is on screen before it leaves. "Always this tool" is the answer most people give once.