web_search— search the web and return ranked resultsweb_extract— fetch and extract readable content from one or more URLs
mibyan tools or set directly in config.yaml.
Backends
Brave Search, DDGS, xAI, and OpenAI Native are search-only — pair any of them with Firecrawl/Tavily/Perplexity/Keenable/Exa/Parallel when you also need
web_extract. DDGS uses the ddgs Python package under the hood; if it isn’t already installed, run python -c "import pm; pm.sync_venv(['ddgs'], explicit=True)" (or let Mibyan lazy-install it on first use). xAI runs Grok’s server-side web_search tool on the Responses API — results are LLM-generated rather than index-backed, so titles, descriptions, and URL choice are all model output (see the trust-model caveat below). OpenAI Native declares the same kind of provider-executed tool on the Codex Responses endpoint (see below).
Per-capability split: you can use different providers for search and extract independently — for example SearXNG (free) for search and Firecrawl for extract. See Per-capability configuration below.
Works out of the box — keyless free-tier rotationA fresh install with no web credentials at all gets working
web_search and web_extract out of the box: requests rotate round-robin across the ring vendors’ public free tiers — Exa, Parallel, Firecrawl, and Keenable — spreading load evenly, and a rate-limited request automatically retries on the next vendor in the ring (multi-hop, until one serves or all are throttled). No signup, no key. This tier is strictly last-resort — any configured backend or present API key always wins — and requests carry no user identifiers (only a random per-process session id, rotated on restart). For guaranteed, unthrottled service, set up a keyed provider. Disable the keyless tier entirely with web.keyless_fallback: false.mibyan tools, Exa, Parallel, and Keenable each appear as two rows — Free (keyless) and Paid (API key). Picking Free pins that vendor’s anonymous endpoint (even if you later add a key); picking Paid pins the keyed path (a missing key then errors instead of silently downgrading to the free tier). The selection is stored as web.provider_tier.<name>: free|paid; leave it unset for auto (key present → paid, otherwise the keyless ring).
How web_extract handles long pages
Backends return raw page markdown, which can be huge (forum threads, docs sites, news articles with embedded comments). To keep your context window usable, web_extract applies a deterministic character budget — no LLM summarization is involved:
The per-page budget is configurable via
web.extract_char_limit in config.yaml (default 15000, clamped to 2 000–500 000), and the agent can raise it per-call with the tool’s char_limit argument.
Each provider dispatch is also bounded by a wall-clock timeout (web.extract_timeout in config.yaml, default 120 seconds; 0 disables it). A backend that keeps the response open without finishing returns per-URL timeout errors instead of stalling the tool call indefinitely.
When truncation gets in the way
If you specifically need the live DOM rather than extracted markdown — for example, a JS-heavy page where extraction returns little content — usebrowser_navigate + browser_snapshot instead. The browser tool returns the live accessibility tree (subject to its own snapshot cap on huge pages).
Result caching
Repeat web calls within a short window are served from cache instead of the paid backend — this saves credits and latency in the two patterns where duplicates are common: subagent fan-outs (several delegated agents researching the same topic) and the agent re-checking a page it read minutes ago.
Concurrent identical searches (a parallel subagent fan-out firing the same query at once) are coalesced into a single backend request — the first caller pays; the rest share the response. Requested search limits are bucketed up to 10/20/50/100 so near-identical requests (
limit=5 vs limit=8) share one entry, with each caller receiving its requested count.
Only successful responses are cached, each under the requested URL the provider reports for it (a page the provider returns without naming a requested URL is served but not cached, so a partial or reordered batch never files one page under another URL’s key). Failures always retry the backend, responses served by the one-shot keyless rescue are never cached (the next call attempts your chosen backend again), and URLs matched by your security.website_blocklist are never served from cache. Cached extracts re-run the normal truncation pipeline, so a different char_limit on the second call works off the same stored scrape.
Local development URLs are never cached. Anything on localhost, 127.0.0.1, *.local, single-label LAN hostnames, or private/link-local IP ranges (192.168.*, 10.*, 172.16-31.*) bypasses the extract cache entirely — dev servers, hot-reload builds, and chat-GUI artifact previews change on every save, and a cached copy would show you a stale build. Every fetch of a local page is live. (These URLs are only reachable at all when security.allow_private_urls is enabled.)
Testing over the public internet? Staging deploys and tunnel URLs are public DNS, so the local-dev rule can’t catch them — list them in web.cache_exempt_hosts and they’re always fetched live too. Entries match exactly, as a *. wildcard, or as a domain suffix (mysite.dev also covers preview.mysite.dev):
web.cache_enabled: false.
Setup
Quick setup via mibyan tools
Run mibyan tools, navigate to Web Search & Extract, and pick a provider. The wizard prompts for the required URL or API key and writes it to your config.
Firecrawl (default)
Full-featured search and extract. Recommended for most users.FIRECRAWL_API_URL is set, the API key is optional (disable server auth with USE_DB_AUTHENTICATION=false).
SearXNG (free, self-hosted)
SearXNG is a privacy-respecting, open-source metasearch engine that aggregates results from 70+ search engines. No API key required — just point Mibyan at a running SearXNG instance. SearXNG is search-only —web_extract requires a separate extract provider.
Option A — Self-host with Docker (recommended)
This gives you a private instance with no rate limits. 1. Create a working directory:docker-compose.yml:
~/searxng/searxng/settings.yml.
If use_default_settings: true is present, the file only contains your overrides. All other settings are inherited from the built-in defaults.
To enable JSON responses for Mibyan, add the following override:
settings.yml should look similar to:
10 results. If you get a 403 Forbidden, JSON format is still disabled — recheck step 4.
7. Configure Mibyan:
~/.mibyan/config.yaml:
mibyan tools → Web Search & Extract → SearXNG.
Option B — Use a public instance
Public SearXNG instances are listed at searx.space. Filter by instances that have JSON format enabled (shown in the table).Pair SearXNG with an extract provider
SearXNG handles search; you need a separate provider forweb_extract. Use the per-capability keys:
Tavily
AI-optimised search and extract. Select Tavily inmibyan tools (or set web.backend: tavily) to use it keyless with no account (rate-limited). Set an API key when you want higher limits.
Perplexity
Perplexity’s Search API returns ranked, date-stamped results from Perplexity’s own index (web_search). For web_extract it uses the same query-relevant snippets route as the official pplx CLI: you get the passages of each page that matter, with elisions marked …, rather than a verbatim full-page dump — pick Firecrawl / Exa / Parallel as web.extract_backend when you need the whole page. Keyed only; there is no anonymous tier.
PERPLEXITY_BASE_URL to route through a proxy.
Exa
Neural search with semantic understanding. Good for research and finding conceptually related content.Parallel
AI-native search and extraction with deep research capabilities.xAI (Grok)
Routesweb_search through Grok’s server-side web_search tool on the Responses API. Grok runs the actual searching and returns the top results as structured JSON.
Works with either credential path — no new env vars, no new setup wizard:
web_extract. On 401 the provider performs a single forced OAuth-token refresh and retries (covers mid-window revocation and opaque tokens the proactive expiry check can’t decode); env-var credentials skip the retry.
OpenAI Native (Codex Responses)
Declares OpenAI’s provider-executedweb_search tool on the Codex Responses endpoint (ChatGPT/Codex subscriptions). The model drives search server-side and folds the results into its own answer — Mibyan never runs a client-side search in this mode.
- Credentials: an openai-codex OAuth login (
mibyan auth add openai-codex). This backend has no API key of its own; without a login it is simply unavailable. - Transport: only the Codex Responses endpoint exposes the built-in. On any other transport — a custom OpenAI-compatible
base_url, or a non-OpenAI model — the client-sideweb_searchfunction is left untouched, because the endpoint cannot be relied on to host the tool. Pointweb.search_backendat an ordinary provider for those. - Search only: the built-in covers search, not extraction. Pair it with Firecrawl (or another extract-capable backend) through
web.extract_backendwhen you also needweb_extract.
web_search function for the built-in 1:1 — it is not an additive grant. A session whose toolset has no web_search never gets server-side search injected.
Configuration
Single backend
Set one provider for all web capabilities:Per-capability configuration
Use different providers for search vs extract. This lets you combine free search (SearXNG) with a paid extract provider, or vice versa:web.backend. Only when no shared web selection has ever been written (web.backend or the managed mibyan tools row) is the backend auto-detected from whichever API key/URL is present — once a shared selection exists, the runtime always uses it, and adding a key to .env does not reroute web traffic. A per-capability key affects only its own capability: setting web.extract_backend alone leaves web_search on its auto-detected backend.
Priority order (per capability):
web.search_backend/web.extract_backend(explicit per-capability)web.backend(shared fallback;nous= managed Tool Gateway)- Auto-detect from environment variables (no shared selection written)
Auto-detection
If no shared backend has ever been selected (noweb.backend written by you or mibyan tools), Mibyan picks the first available one based on which credentials are set:
Keyless free-tier ring: when no credential above is present, requests rotate across the ring vendors’ public free tiers (Exa, Parallel, Firecrawl, Keenable) so web tools work on a fresh install with zero setup — and a rate-limited request fails over to the next vendor in the ring automatically. Pin one vendor in
mibyan tools to stop the rotation (the ring is then only used as failover succession on throttles). All free tiers are vendor-rate-limited under burst load; sustained normal usage goes through fine. Set web.keyless_fallback: false to turn the tier off — with it off and no credentials, web tools are unavailable until a provider is configured. A registered Nous Portal identity is unaffected by that switch: managed web_search still applies, though web_extract needs a configured provider.
One-shot keyless rescue for keyed backends: when your chosen/keyed backend — including the Nous Tool Gateway route (web.backend: nous) — fails a call (bad key, outage, unreachable gateway, upstream 5xx), that single call automatically retries on the keyless free-tier ring instead of erroring — the result notes which vendor served it and why (rescued_from / backend_error). The failover is never sticky: the very next web_search/web_extract call attempts your chosen backend again. Disable with web.keyless_rescue: false (also off whenever keyless_fallback is off).
xAI Web Search is not in the auto-detection chain — having XAI_API_KEY set (or being signed in via xAI Grok OAuth) does not automatically route web traffic through xAI, since those credentials are also used for inference / TTS / image gen and the user may want a different backend for web. Opt in explicitly with web.backend: "xai".
Verify your setup
Runmibyan setup to see which web backend is detected:
Troubleshooting
web_search returns {"success": false}
- Check
SEARXNG_URLis reachable:curl -s "http://localhost:8888/search?q=test&format=json" - If you get HTTP 403, JSON format is disabled — add
jsonto theformatslist insettings.ymland restart - If you get a connection error, the container may not be running:
docker ps | grep searxng
web_extract says “search-only backend”
SearXNG cannot extract URL content. Set web.extract_backend to a provider that supports extraction:
SearXNG returns 0 results
Some public instances disable certain search engines or categories. Try:- A different query
- A different public instance from searx.space
- Self-hosting your own instance for reliable results
Rate limited on a public instance
Switch to a self-hosted instance (see Option A above). With Docker, your own instance has no rate limits.web_extract returns truncated content with a [TRUNCATED] footer
That’s expected for pages over the character budget. The footer names the on-disk file holding the full clean text and the exact read_file call to page through the omitted middle. To see more inline, raise web.extract_char_limit in config.yaml or pass a larger char_limit on the call.
Optional skill: searxng-search
For agents that need to use SearXNG via curl directly (e.g. as a fallback when the web toolset isn’t available), install the searxng-search optional skill:
- Call the SearXNG JSON API via
curlor Python - Filter by category (
general,news,science, etc.) - Handle pagination and error cases
- Fall back gracefully when SearXNG is unreachable

