Brave web-search tool with per-message globe toggle

nwhitehouse added a `web_search` tool backed by the Brave Search API. It's a binary toggle per message, hard-gated so the tool never appears in the schema when the user hasn't enabled it.

searchchat-ui

backend/src/lib/webSearch.ts (77 LOC, new) wraps the Brave Search API with a 12s AbortController timeout and returns results in the same shape as legalSearch - { title, url, snippet, source: "Web" }. That shared shape means the existing reference card component from feat-004 renders web results without any new code.

The tool is conditionally appended to the active schema via a WEB_TOOLS array, the same pattern used for LEGAL_TOOLS. When sources.web is false or absent, web_search isn't in the schema at all; the model can't call it. A system-prompt line is appended when web is enabled, steering the model toward it for current events and commentary. The frontend adds a globe icon button between the model toggle and send, blue when active, gray when off, resetting after submit.

When BRAVE_SEARCH_API_KEY is unset, the tool is still visible in the UI but returns zero results with a logged warning. Graceful degradation, no hard failure.

Brave's free tier is 2,000 queries per month. The same pattern would swap in any search API with minimal changes to webSearch.ts.

So what Worth importing alongside `feat-002` if you want web grounding. The implementation is clean and the hard-gate-when-disabled pattern is the right default. Brave is the only new external dependency. If you'd prefer a different search provider, the adapter layer is thin enough that swapping it is a small job.

View this fork on GitHub →

Spotted something wrong? Or know the PR text has fresher detail than the writeup above?

Commits in this thread

1 commit from nwhitehouse/mike, oldest first. Source extracted verbatim from the harvested git log.

SHA Subject Author Date
00e1dd66 [feat-003] Brave web search with globe-icon toggle Nick Whitehouse 2026-05-04 ↗ GitHub
commit body
Adds a `web_search` tool backed by the Brave Search API, gated by a
globe-icon toggle on the right side of the chat input (Ruli pattern).
Hard-gated: globe off → tool not in the model's schema. Same shape as
the legal-sources picker, distinct UI surface because web search is a
binary toggle vs the legal sources' per-source multi-select.

Backend
- backend/src/lib/webSearch.ts (new): Brave Search client with 12s
  AbortController timeout. Returns the same shape as legalSearch
  results so feat-004 references-inline can render either source with
  one card component. Graceful degradation when BRAVE_SEARCH_API_KEY
  is missing.
- chatTools.ts: WEB_TOOLS array (kept separate from always-on TOOLS
  so it can be conditionally appended). Dispatch case for web_search
  in runToolCalls. runLLMStream accepts sources.web boolean; when
  true → append WEB_TOOLS + system-prompt line steering the model
  toward web for current/news/commentary.
- routes/chat.ts: extends sources type to { legal?, web? }.
- .env.example: documents BRAVE_SEARCH_API_KEY.

Frontend
- ChatInput.tsx: globe icon button between ModelToggle and send.
  Filled blue when on, gray when off. Per-message state (resets
  after submit, matches legal-sources behaviour). Click toggles.
- MikeMessage.sources extended to include web?: boolean.
- mikeApi.streamChat passes sources through unchanged.

Also writes detailed feat-004 (references inline) and feat-005
(multi-pass research orchestrator) plans into backlog.md so the
direction is documented before implementation.

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>

Capture this thread into my fork

Download a single Markdown prompt that tells Claude how to port every commit above into your working tree — adapting paths and structure to match your repo. Run it via claude -p < capture-thread-111.md from inside the repo you want the changes in.

⬇ Download capture-thread-111.md