The web_search and web_extract tools in Hermes are backed by pluggable provider modules under ~/.hermes/hermes-agent/plugins/web/<provider>/. This skill covers the architecture, the config surface, and how to add a new provider when none of the built-ins fit (Jina Reader, Crawl4AI, Playwright-direct, etc.).
tools/web_tools.py exposes web_search and web_extract to the agent. It does NOT contain provider logic — it just dispatches.plugins/web/<name>/ (e.g. plugins/web/firecrawl/provider.py). Registered via the web-provider registry at agent/web_search_registry.py / agent/web_search_provider.py.firecrawl — paid SaaS, free tier ~500 pages/mo. Best DX. Recursive crawl supported.searxng — free, self-hosted or public instance. Search-only, no extract.brave-free — Brave Search free tier. Search-only.ddgs — DuckDuckGo via ddgs Python lib. No API key. Search-only.tavily — paid SaaS. Search + extract. Crawl supported.exa — paid SaaS. Search + extract.parallel — paid SaaS. Search + extract.~/.hermes/config.yaml)web:
backend: firecrawl # shared fallback when per-capability keys empty
search_backend: '' # overrides backend for web_search
extract_backend: '' # overrides backend for web_extract
Resolution order:
1. web.search_backend / web.extract_backend (explicit per-capability)
2. web.backend (shared fallback)
3. Auto-detect from which API key/URL is present in .env
There is NO native tiered fallback chain. Hermes selects ONE backend per capability per call. If that backend fails, the tool fails. Crucially: do not promise the user a "Jina → Crawl4AI → Firecrawl chain" via config alone — that requires a custom plugin (see "Building a tiered provider" below).
# Free search, paid extract for hard pages
web:
search_backend: ddgs # or searxng (self-hosted)
extract_backend: firecrawl
# Free everything (search-only — extract will fail)
web:
search_backend: searxng
extract_backend: '' # ERROR: searxng can't extract
# Cheapest mixed
web:
search_backend: ddgs
extract_backend: tavily # or exa
hermes setup and hermes tools → "Web Search & Extract" both have wizards for this. CLI: hermes config set web.search_backend ddgs.
web_extract runs an auxiliary LLM call to summarize long pages before returning. By default this uses the main chat model — on Opus/Sonnet that's expensive. Configure separately:
auxiliary:
web_extract:
provider: openrouter
model: google/gemini-3-flash-preview
timeout: 360
This is orthogonal to the web.backend (which controls the fetcher, not the summarizer). Pointing this at a cheap model is often a bigger cost win than swapping fetchers.
When the user wants "try X, fall back to Y, fall back to Z" (the typical economical stack: Jina Reader → Crawl4AI → Firecrawl → Playwright-direct), you write a single plugin that wraps the chain.
Layout:
~/.hermes/hermes-agent/plugins/web/tiered/
├── __init__.py
└── provider.py # implements the provider contract + chain logic
The provider contract is defined in agent/web_search_provider.py (read it before coding). Mirror the shape of an existing provider — plugins/web/firecrawl/provider.py is the richest reference. Key surface:
- search(query, limit) -> list[dict]
- extract(urls, mode) -> list[dict]
- Capability flags (search vs extract vs crawl)
Inside the chain implementation:
1. Try the cheapest first (Jina: GET https://r.jina.ai/<url>, no key needed for low volume).
2. On HTTP non-2xx, content-too-small, or known JS-render failure markers, fall through.
3. Crawl4AI tier (only if self-hosted on the VPS) — point at the local service.
4. Firecrawl tier — uses the existing FIRECRAWL_API_KEY, preserves Alex's free tier as last-resort.
5. Final tier: the built-in browser_tool is the nuclear option for JS-heavy holdouts.
Register the new provider name in agent/web_search_registry.py (or whichever the current registration entry point is — read web_tools.py imports first; the layout has migrated before per PR #25182's comment in that file).
Lighter-weight than a tiered chain. Plain requests.get(f"https://r.jina.ai/{url}") returns clean markdown for most pages. Wrap it as a single provider, register it, set web.extract_backend: jina. Pair with searxng or ddgs for free search. ~80% of Firecrawl's value for $0.
web.backend: firecrawl is the default after install — don't assume the user picked it deliberately. Confirm before changing.FIRECRAWL_API_KEY env var also gates auto-detection. If you remove the key but leave web.backend: firecrawl, you get hard failures, not auto-fallback to another configured provider.web.backend (not just search_backend) breaks web_extract with a "search-only backend" error. Always pair with an extract provider.tools/web_tools.py into plugins/web/firecrawl/provider.py. Before writing new code, read_file an existing provider to confirm current shape — don't trust older docs/blog posts.web_extract ≠ web fetcher alone. It also runs an auxiliary LLM summary; check auxiliary.web_extract before blaming the fetcher for slow/expensive extracts.web.backend is single-select. Tiered chains require code.The honest answer, captured here so future sessions don't have to re-derive it:
browser_* tools.The strongest budget stack: search_backend: ddgs (or self-hosted SearXNG), custom jina provider as extract_backend with Firecrawl free tier as the in-plugin fallback inside that provider, and browser_* for the rare holdouts.
When quoting infra costs (VPS, SaaS tiers) to Alex, lead with monthly figures, not annualized. He explicitly prefers it that way and will correct you if you do "$X/yr". Annual is fine as a secondary comparison line ("…or ~$X/yr for reference") but never the headline number. Hetzner specifically bills hourly/monthly with no annual prepay — mention that to defuse "am I committing for a year?" worries.
references/competitor-research.md — distilled research dump from a Nov 2026 conversation comparing Firecrawl with Crawl4AI, Jina Reader, Apify, Browserbase, Bright Data, Crawlee, Playwright. Use when the user is re-evaluating the web-fetcher stack.references/crawl4ai-self-hosting-cost.md — RAM/CPU footprint, VPS sizing sweet spots, co-locate-vs-dedicated decision rule, current monthly prices across Hetzner / Fly / DO / Vultr / Contabo / Oracle Free, and a deployment outline. Load this when sizing the Crawl4AI tier or deciding whether to co-locate.