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.
| Mode | Behaviour |
|---|---|
off | web.search throws immediately — the tool is not even presented to the model |
cached | Searches use provider’s default cache behaviour (cheaper, may return stale results) |
live | Forces fresh results — X-No-Cache: true for Jina, search_depth: advanced for Tavily, no difference for others |
Providers
Section titled “Providers”| Provider | ID | Key required | Type | Notes |
|---|---|---|---|---|
| DuckDuckGo | ddg | No | SERP | Default; HTML scraping via POST to html.duckduckgo.com/html/; no API, no quota |
| Jina Search | jina | No (optional) | Neural | Free without key (plain-text format); JSON responses and higher limits with JINA_API_KEY |
| SearXNG | searxng | No | SERP | Self-hosted; set SEARXNG_BASE_URL to your instance; optional SEARXNG_API_KEY |
| Brave Search | brave | Yes | SERP | BRAVE_SEARCH_API_KEY; returns extra_snippets field |
| Tavily | tavily | Yes | Neural | TAVILY_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.
Auth resolution
Section titled “Auth resolution”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>.apiKey3. Default (key-free providers only)The resolved authSource is reported in umbra doctor output: env / runtime / default / missing.
Switching providers
Section titled “Switching providers”# 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..." } } }}Domain filtering
Section titled “Domain filtering”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 — full page as markdown
Section titled “web.fetch — full page as markdown”web.fetch fetches a URL and returns clean text with a maxChars cap (default 20 000, max 100 000).
Two fetch modes:
| Mode | How 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) |
raw | Direct 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.
Settings reference
Section titled “Settings reference”{ "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.