Skip to content

Web Search (`web.search` + `web.fetch`)

Use /web in the TUI to open the web search settings menu (mode toggle + provider picker), or edit webSearch.mode directly in ~/.umbra/settings.json.

ModeBehaviour
offweb.search throws immediately — the tool is not even presented to the model
cachedSearches use provider’s default cache behaviour (cheaper, may return stale results)
liveForces fresh results — X-No-Cache: true for Jina, search_depth: advanced for Tavily, no difference for others
ProviderIDKey requiredTypeNotes
DuckDuckGoddgNoSERPDefault; HTML scraping via POST to html.duckduckgo.com/html/; no API, no quota
Jina SearchjinaNo (optional)NeuralFree without key (plain-text format); JSON responses and higher limits with JINA_API_KEY
SearXNGsearxngNoSERPSelf-hosted; set SEARXNG_BASE_URL to your instance; optional SEARXNG_API_KEY
Brave SearchbraveYesSERPBRAVE_SEARCH_API_KEY; returns extra_snippets field
TavilytavilyYesNeuralTAVILY_API_KEY; search_depth: advanced in live mode

Auto-fallback: when activating web search without specifying a provider, Umbra picks the first provider that is either key-free or already has a key configured.

For each provider, the key and base URL are resolved in priority order:

1. Environment variable (BRAVE_SEARCH_API_KEY, JINA_API_KEY, etc.)
2. ~/.umbra/settings.json → webSearch.providers.<id>.apiKey
3. Default (key-free providers only)

The resolved authSource is reported in umbra doctor output: env / runtime / default / missing.

Terminal window
# In the TUI — open the /web menu and select a provider with arrow keys
# Or edit ~/.umbra/settings.json
{
"webSearch": {
"mode": "live",
"providerId": "brave",
"providers": {
"brave": { "apiKey": "BSA..." }
}
}
}

web.search accepts an optional domains array. Each entry is appended as a site: filter to the query before it is sent to the provider:

query: "sqlite WAL mode"
domains: ["sqlite.org", "fly.io"]
→ sent as: "sqlite WAL mode site:sqlite.org site:fly.io"

web.search — what it returns and what it doesn’t

Section titled “web.search — what it returns and what it doesn’t”

web.search returns a ranked list of { rank, title, url, snippet, displayUrl }. Snippets are short static previews extracted from the provider’s index — they do NOT contain live data (current time, prices, live scores, real-time weather).

For any question requiring live or current data, the agent is instructed to follow up with web.fetch on one of the returned URLs to read the actual page content.

web.fetch fetches a URL and returns clean text with a maxChars cap (default 20 000, max 100 000).

Two fetch modes:

ModeHow it works
reader (default)Proxies through Jina Reader (r.jina.ai); returns structured markdown with title; no key needed (optional JINA_API_KEY for higher limits)
rawDirect fetch to the URL; strips <script>, <style>, comments, HTML tags; decodes HTML entities

reader mode fails gracefully: if Jina Reader returns an error or a non-OK status, web.fetch automatically retries in raw mode. If even the raw fetch fails (non-2xx), the tool returns a descriptive error string instead of throwing — the model can read the error and try a different URL.

~/.umbra/settings.json
{
"webSearch": {
"mode": "off",
"providerId": "ddg",
"providers": {
"ddg": { "apiKey": null, "baseUrl": null },
"jina": { "apiKey": null, "baseUrl": null },
"searxng": { "apiKey": null, "baseUrl": "http://localhost:8080" },
"brave": { "apiKey": null, "baseUrl": null },
"tavily": { "apiKey": null, "baseUrl": null }
}
}
}

baseUrl overrides the provider’s default endpoint — useful for SearXNG instances or corporate proxies.