Skip to content

Web search in Whizzard cells — firecrawl (contained) + ddgs (open) [D-194 Phase C] - #38

Open
BuckG71 wants to merge 7 commits into
mainfrom
websearch-firecrawl
Open

BuckG71 wants to merge 7 commits into
mainfrom
websearch-firecrawl

Conversation

@BuckG71

@BuckG71 BuckG71 commented Jul 25, 2026

Copy link
Copy Markdown
Owner

What

Makes web search work inside Whizzard Hermes cells, model/provider-agnostic and without opening a hole in the sandbox. Two rungs, off by default:

  • firecrawl (contained, recommended) — routed through a second broker instance (reuse of the D-184 credential-broker proxy, pinned to api.firecrawl.dev, new bearer_plain auth scheme). The cell holds only a placeholder FIRECRAWL_API_KEY; the broker holds the real key and is the cell's single route out. Requires a mediated/hybrid net (validated, fail-loud).
  • ddgs (open, keyless) — no signup, but fans out directly to many search hosts via TLS impersonation (un-brokerable), so it requires open egress. Launch surfaces an expanded-security-surface notice.

Delivery: host-authored read-only managed-scope config sets web.backend (D-194 linchpin); a --search cell image carries the firecrawl/ddgs clients so the base image stays lean.

Key changes

  • config.pyweb_search field, WEB_SEARCH_MODES = off|firecrawl|ddgs.
  • adapters/hermes.py — managed-scope authoring; SearchContext + FIRECRAWL_* env; hybrid NO_PROXY exempts both brokers.
  • search_broker.py (new) — the firecrawl broker (second proxy instance).
  • broker/proxy.pybearer_plain scheme (Authorization: Bearer, no Anthropic beta header).
  • cli/_launch.py — start/teardown ordering, per-rung network validation, launch-banner notices, dry-run fidelity.
  • broker.py — reaper sweeps crash-orphaned search brokers.
  • cli/hermes.pywhiz hermes image build --search.
  • README "Web search" section; D-194 Phase C note.

Also folds in the Phase A Hermes 0.19 bump and Phase B managed-scope linchpin (already reviewed/merged to main; included here as branch history).

Verification

  • make check green — 877 passed, lint + typecheck clean.
  • Decisions validator clean (194).
  • Pending: live smoke against a real FIRECRAWL_API_KEY — confirms Hermes's firecrawl backend reads FIRECRAWL_API_URL/_KEY (the one runtime assumption unit tests can't cover). Recommend running before merge.

🤖 Generated with Claude Code

https://claude.ai/code/session_01PRQ6JeWxpD9zrYcPz6VFTS

BuckG71 and others added 6 commits July 24, 2026 20:08
Phase C groundwork: an opt-in derived image (whizzard-hermes-search, FROM the
hermes image) that bakes the web-search client libs so a CONTAINED cell — which
has no PyPI egress (only its backend endpoint is allowlisted) — can run
web_search without lazy-installing at first use. firecrawl-py (contained-rung
default, single endpoint) + ddgs (keyless open-rung alternative). Containment
stays at the network layer: a firecrawl cell can't reach DuckDuckGo even though
ddgs is present. New WHIZZARD_HERMES_SEARCH_IMAGE constant + package-data entry.
Image builds clean; both clients import (firecrawl-py 4.32.1, ddgs 9.14.4).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRQ6JeWxpD9zrYcPz6VFTS
A profile with web_search != off now launches the search-enabled cell image
(whizzard-hermes-search) instead of the base Hermes image — the firecrawl/ddgs
clients live only in that opt-in layer (least-privilege). Only applies when no
explicit --image is given; mirrors the existing Hermes-adapter image coupling.
Dry-run test locks both directions (search profile -> search image; off ->
base image).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRQ6JeWxpD9zrYcPz6VFTS
…er_plain

The containment core for contained web search (D-194 Phase C). A SECOND instance
of the vetted credential-broker proxy, pinned to api.firecrawl.dev — so each
proxy stays single-upstream (the 'no code path forwards elsewhere' invariant
holds per broker). This is the Fork-1 'reuse the broker' choice done as a second
instance, not a dual-upstream router.

- whizzard/search_broker.py: start/stop a Firecrawl broker that joins the cell's
  existing internal net (from the model broker) + its own egress net. Resolves
  FIRECRAWL_API_KEY host-side (fail-closed with a clear message pointing at
  firecrawl.dev or keyless ddgs); key bind-mounted read-only, never a run arg.
- proxy.py: new 'bearer_plain' auth scheme (Authorization: Bearer, no
  Anthropic beta header) for non-Anthropic upstreams like Firecrawl.

Unit-tested (docker mocked): Firecrawl pinning, bearer_plain, cell-net join,
own egress, key-never-in-argv, fail-closed on missing key, rollback on start
failure, and teardown that removes the container + egress but NOT the internal
net (the model broker owns it). Verified firecrawl-py takes a custom api_url so
the cell can point at the broker.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRQ6JeWxpD9zrYcPz6VFTS
First session-handoff entry. Captures Phase C progress (image, launch image-
selection, search broker + bearer_plain all committed), the launch-integration
next task, rejected approaches, and the resume protocol — including the post-C
setup-wizard UX pass.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRQ6JeWxpD9zrYcPz6VFTS
Bring contained web search online end-to-end. When a Hermes profile sets
web_search="firecrawl", _perform_launch starts the search broker (a second
broker instance pinned to api.firecrawl.dev) on the model broker's internal
net, points the cell's firecrawl backend at it via FIRECRAWL_API_URL + a
placeholder key, and tears it down before the model broker.

- adapters/hermes.py: SearchContext + FIRECRAWL_* env injection; hybrid
  NO_PROXY now exempts BOTH the model and search broker hosts so the cell
  reaches them directly instead of via the OneCLI gateway.
- cli/_launch.py: validate web_search=firecrawl requires mediated/hybrid
  (fail loud, not a silent no-search launch); start/teardown ordering;
  dry-run fidelity (reports the FIRECRAWL_* argv).
- broker.py: reaper sweeps a crash-orphaned search broker + its egress net
  + key dir before removing the shared internal net.
- tests: adapter env/NO_PROXY, dry-run image+argv selection, validation.

make check green (873 passed). Live smoke (real FIRECRAWL_API_KEY) still
pending — confirms Hermes's firecrawl backend reads FIRECRAWL_API_URL/KEY.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRQ6JeWxpD9zrYcPz6VFTS
…README (D-194 Phase C)

Completes the two web-search rungs and their user-facing surfaces.

- config: WEB_SEARCH_MODES gains "ddgs" (keyless, open-rung). off|firecrawl|ddgs.
- _launch.py: split firecrawl (needs broker → mediated/hybrid) from ddgs
  (un-brokerable → needs open egress); each validated fail-loud. Launch banner
  now surfaces the posture: firecrawl = contained (key-required); ddgs = keyless
  with an EXPANDED-SECURITY-SURFACE warning.
- cli/hermes.py: `whiz hermes image build --search` builds the search-enabled
  cell image (firecrawl + ddgs clients) from Dockerfile.hermes-search, so the
  base image stays lean and there's a real command to point users at. (Wizard
  prompting to offer it remains the deferred post-C UX pass.)
- README: "Web search" section — the two rungs, their trade-offs, the build step.
- decisions: D-194 Phase C note (broker firecrawl + open ddgs; live smoke pending).

make check green (877 passed); decisions validator clean (194).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRQ6JeWxpD9zrYcPz6VFTS
Comment thread whizzard/search_broker.py Dismissed
…web reach

Addresses known_issues alignment finding 6 — the "contained" web-search rung
restricts the cell to one network host, but Firecrawl server-side-fetches any
URL the agent names, so it bounds where the cell connects, not what it can read.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01PRQ6JeWxpD9zrYcPz6VFTS
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants