Alex's cost-first web extract/search backend. Replaces direct Firecrawl usage. Saves the Firecrawl free tier for hardest-to-render pages only.
Extract chain (per URL, falls through on miss):
1. r.jina.ai/<url> — free, no key, static pages, ~85% of cases
2. Self-hosted Crawl4AI on http://187.127.70.43:11235/md — JS-heavy pages
3. Firecrawl SDK — last-resort paid fallback (uses existing FIRECRAWL_API_KEY)
Search chain: 1. ddgs (DuckDuckGo) — free 2. Firecrawl search — fallback
~/.hermes/plugins/web/tiered/ (provider.py, init.py, plugin.yaml)~/.hermes/config.yaml under plugins.enabled: [..., web/tiered]web.backend: tiered1342943 @ 187.127.70.43 in Frankfurt (data_center_id=19), hostname crawl4ai-eu-hostinger.poofc.com.unclecode/crawl4ai:0.8.6 on port 11235 via /root/crawl4ai/docker-compose.yml; keep pinned to 0.8.6 because latest/0.9.0 binds loopback-only unless tokenized and breaks the current unauthenticated IP-allowlist pattern.11235/tcp to Alex's Hetzner VPS IPs (5.78.190.92, 5.78.199.81, 167.233.226.57, 5.78.84.191) plus ExxirHermes 2.25.213.208; SSH remains open.hermes-crawl4ai @ 5.78.214.66 (hil, cpx11, ~€20.49/mo) was deleted after gateway restart and live research smokes passed on 2026-07-01.# Live Crawl4AI health
curl -s --max-time 5 http://187.127.70.43:11235/health
# Expect: {"status":"ok","timestamp":...,"version":"0.8.6"}
# Live crawl test
curl -s -X POST http://187.127.70.43:11235/md \
-H "Content-Type: application/json" \
-d '{"url":"https://example.com"}'
# Plugin loads & registers
cd /home/avalon/.hermes/hermes-agent
/home/avalon/.hermes/hermes-agent/venv/bin/python -c "
from hermes_cli.plugins import PluginManager
pm = PluginManager(); pm.discover_and_load()
from agent.web_search_registry import get_active_search_provider, get_active_extract_provider
print('search:', get_active_search_provider().name) # tiered
print('extract:', get_active_extract_provider().name) # tiered
"
hermes_plugins.<slug> not plugins.<path>. So __init__.py MUST use relative imports (from .provider import …) not absolute (from plugins.web.tiered.provider …).plugins.web.firecrawl.provider, plugins.web.ddgs.provider) DO use absolute paths — those resolve correctly because bundled providers live at the real plugins.web.* path under hermes-agent.~/.hermes/hermes-agent/venv/bin/pip3 install ddgs (not system pip — PEP 668).HOME set explicitly when called from agent contexts where HOME isn't propagated: export HOME=/home/avalon.~/.hermes/hermes-agent/venv (named venv, not .venv). Activate or call binaries directly.Symptom (the exact shape this bug takes): the agent's web_search MCP tool keeps returning Firecrawl search failed: Payment Required: Insufficient credits even though web.backend: tiered is in ~/.hermes/config.yaml and the plugin files exist on disk. Direct Python tests inside hermes-agent/venv resolve tiered correctly (get_active_search_provider().name == 'tiered') — the discrepancy is the giveaway.
Cause: the long-running gateway process (the one serving Telegram / MCP for the active session) was started BEFORE the tiered plugin was installed. Plugins are loaded once at gateway startup. Editing config.yaml and dropping new plugin files into ~/.hermes/plugins/ does NOT hot-reload them into the running process.
Diagnosis recipe:
# What's the gateway process and when did it start?
ps -ef | grep "hermes_cli.main gateway run" | grep -v grep
ps -o lstart= -p <pid>
# Compare to plugin install / config edit time
stat -c '%y %n' ~/.hermes/plugins/web/tiered/*.py ~/.hermes/config.yaml
If the gateway lstart is earlier than the plugin/config mtime → stale gateway, restart needed.
Fix: restart the gateway. Whatever launches it (pm2 / systemd / hand-launched) — kill the PID and let it respawn, or pm2 restart hermes-gateway. Brief Telegram drop (a few seconds), then the new chain is wired in.
Generalize: this same pattern bites any time config.yaml / .env / a plugin directory is edited while the gateway is running. Whenever Alex says "but we already configured X, why is it still doing the old thing", check gateway start time vs config/plugin mtime BEFORE re-debugging the config.
SSH in via the main VPS (key already authorized):
ssh root@187.127.70.43
cd /root/crawl4ai
docker compose logs -f --tail 100
docker compose pull && docker compose up -d # update, but keep image pinned unless validating a newer release
docker stats crawl4ai # mem/cpu
JINA_API_KEY in ~/.hermes/.env, plugin reads it automatically.CRAWL4AI_URL env var (default http://187.127.70.43:11235)._try_firecrawl_extract to return an error result, leaving 2-tier (Jina + Crawl4AI). Pages that fail both will return error to the agent — usually fine.hermes-crawl4ai source was Hetzner US/Hillsboro cpx11 at ~€20.49/mo; it was deleted on 2026-07-01 after the Hostinger replacement passed smokes.cpx11 clone would have been ~€5.99/mo, but the 2GB host made Docker healthcheck report unhealthy (Less than 2GB RAM available) even though /health worked.cx23 x86, 2 vCPU / 4GB / 40GB at ~€6.49/mo in fsn1/hel1/nbg1; cheap and should satisfy Crawl4AI RAM healthcheck better.cpx21 showed a price but could no longer be ordered in fsn1; use modern cx23/cx33 where available.