Web Search channel (Stage 4d) — keyless, agent-driven
A zero-dependency discovery channel for users who have only Claude Code and no
search accounts (no SearchAPI / Gemini / Undermind). It uses the agent's own
WebSearch / WebFetch tools to find real literature on the open web, then funnels
the hits through the same dedup -> verify -> screen pipeline as every other channel.
There is no subprocess driver here: a Python script cannot run WebSearch. The
agent (you, in Claude Code) does the searching; scripts/websearch_ingest.py only
normalizes what you gather into the pipeline schema.
When to use
- The user has no
SEARCHAPI_API_KEY, GEMINI_API_KEY, or Undermind login, OR
- You want a broad open-web sweep (working papers, very recent work, SSRN / arXiv /
NBER / OpenReview / publisher pages) alongside the keyed channels.
Run it in parallel with whatever other channels are available; its output merges
with theirs at dedup.
Recipe (agent-driven, subagent fan-out)
This is the default. The orchestrator emits a batched task plan, fans the batches
out across parallel Opus subagents — so the raw WebSearch/WebFetch text stays inside
the subagent contexts — and then merges the distilled candidates. It runs the same
way whether web search is the sole channel (no keys) or an add-on alongside the keyed
channels.
- Emit the task plan from the Stage-0 queries:
python websearch-search/scripts/websearch_ingest.py --emit-tasks \
--queries-file OUT/scholar_queries.json --research-question "<rq>" \
--batch-size 3 -o OUT/websearch_tasks.json
This writes {system_prompt, research_question, tasks:[{batch_id, queries:[...]}]}.
With no queries it prints WEBSEARCH_DEFERRED and writes an empty plan.
- Fan out across Opus subagents — one per
tasks[k]. Hand each subagent the
system_prompt, the research_question, and its queries, and have it run
WebSearch on each query, WebFetch the most promising hits (publisher /
SSRN / arXiv / NBER / OpenAlex / Semantic Scholar) to read the real title,
authors, year, venue, DOI, and abstract — never inventing a field, leaving
unknowns "" — and Write its candidates to
OUT/websearch_results_batch_<id>.json:[{"title": "...", "authors": "First Last, Second Author", "year": "2021",
"journal": "...", "doi": "10.xxxx/...", "url": "https://...", "abstract": "..."}]
Only title is required. Do NOT WebFetch scholar.google.com (bot-blocked).
- Merge the partial files into the stage output:
python websearch-search/scripts/websearch_ingest.py \
--results OUT/websearch_results_batch_*.json -o OUT/stage4d_websearch.json
This dedups by title across all batches, does best-effort keyless Crossref DOI
fill (--no-enrich to skip), and writes stage4d_websearch.json (+ .ris,
source="websearch"). The stage[0-9]*.json dedup glob then picks it up. If no
batch yielded a usable candidate it prints WEBSEARCH_DEFERRED and writes an
empty file, so the pipeline continues on the other channels.
Inline fallback (a handful of queries)
For a small query set you can skip the fan-out: run WebSearch/WebFetch yourself,
collect everything into one OUT/websearch_results.json, and merge the single file
(--results accepts one or many):
python websearch-search/scripts/websearch_ingest.py \
--results OUT/websearch_results.json -o OUT/stage4d_websearch.json
Anti-hallucination
Web hits are real records, so fabrication is far lower than asking the model to
recall papers from memory. It is not zero — a snippet can carry a wrong year, or a
non-peer-reviewed page can slip in — so keep Stage 5b verification ON: it
confirms every paper against OpenAlex / Crossref / Semantic Scholar and drops
anything that cannot be confirmed. Never pair this channel with --no-verify.
Notes
- Keyless: the only network call the script makes is the keyless Crossref polite
pool (set
LITREVIEW_CONTACT_EMAIL to use your own contact). No LLM API calls.
- Google Scholar itself is bot-blocked, so do not WebFetch
scholar.google.com
directly; rely on WebSearch results and on fetching the underlying source pages.
- Coverage depends on what surfaces in search; this is a strong keyless baseline,
not a replacement for Undermind / Deep Research / the SearchAPI Google Scholar
channel.
1---2name: websearch-search3description: Run the lit-review orchestrator keyless agent-driven web search channel that uses WebSearch and WebFetch outputs normalized through websearch_ingest.py. Use when the user invokes the web search channel, asks for Stage 4d open-web literature discovery, or needs a Claude Code web-search fallback without SearchAPI, Gemini, or Undermind credentials.4---56# Web Search channel (Stage 4d) — keyless, agent-driven78A zero-dependency discovery channel for users who have **only Claude Code** and no9search accounts (no SearchAPI / Gemini / Undermind). It uses the agent's own10**WebSearch / WebFetch** tools to find real literature on the open web, then funnels11the hits through the same dedup -> verify -> screen pipeline as every other channel.1213There is **no subprocess driver** here: a Python script cannot run WebSearch. The14agent (you, in Claude Code) does the searching; `scripts/websearch_ingest.py` only15normalizes what you gather into the pipeline schema.1617## When to use1819- The user has no `SEARCHAPI_API_KEY`, `GEMINI_API_KEY`, or Undermind login, OR20- You want a broad open-web sweep (working papers, very recent work, SSRN / arXiv /21 NBER / OpenReview / publisher pages) alongside the keyed channels.2223Run it **in parallel** with whatever other channels are available; its output merges24with theirs at dedup.2526## Recipe (agent-driven, subagent fan-out)2728This is the default. The orchestrator emits a batched task plan, fans the batches29out across parallel Opus subagents — so the raw WebSearch/WebFetch text stays inside30the subagent contexts — and then merges the distilled candidates. It runs the same31way whether web search is the sole channel (no keys) or an add-on alongside the keyed32channels.33341. **Emit the task plan** from the Stage-0 queries:35 ```bash36 python websearch-search/scripts/websearch_ingest.py --emit-tasks \37 --queries-file OUT/scholar_queries.json --research-question "<rq>" \38 --batch-size 3 -o OUT/websearch_tasks.json39 ```40 This writes `{system_prompt, research_question, tasks:[{batch_id, queries:[...]}]}`.41 With no queries it prints `WEBSEARCH_DEFERRED` and writes an empty plan.422. **Fan out across Opus subagents** — one per `tasks[k]`. Hand each subagent the43 `system_prompt`, the `research_question`, and its `queries`, and have it run44 **WebSearch** on each query, **WebFetch** the most promising hits (publisher /45 SSRN / arXiv / NBER / OpenAlex / Semantic Scholar) to read the real title,46 authors, year, venue, DOI, and abstract — never inventing a field, leaving47 unknowns `""` — and **Write** its candidates to48 `OUT/websearch_results_batch_<id>.json`:49 ```json50 [{"title": "...", "authors": "First Last, Second Author", "year": "2021",51 "journal": "...", "doi": "10.xxxx/...", "url": "https://...", "abstract": "..."}]52 ```53 Only `title` is required. Do NOT WebFetch `scholar.google.com` (bot-blocked).543. **Merge** the partial files into the stage output:55 ```bash56 python websearch-search/scripts/websearch_ingest.py \57 --results OUT/websearch_results_batch_*.json -o OUT/stage4d_websearch.json58 ```59 This dedups by title across all batches, does best-effort keyless Crossref DOI60 fill (`--no-enrich` to skip), and writes `stage4d_websearch.json` (+ `.ris`,61 `source="websearch"`). The `stage[0-9]*.json` dedup glob then picks it up. If no62 batch yielded a usable candidate it prints `WEBSEARCH_DEFERRED` and writes an63 empty file, so the pipeline continues on the other channels.6465### Inline fallback (a handful of queries)6667For a small query set you can skip the fan-out: run WebSearch/WebFetch yourself,68collect everything into one `OUT/websearch_results.json`, and merge the single file69(`--results` accepts one or many):70```bash71python websearch-search/scripts/websearch_ingest.py \72 --results OUT/websearch_results.json -o OUT/stage4d_websearch.json73```7475## Anti-hallucination7677Web hits are real records, so fabrication is far lower than asking the model to78recall papers from memory. It is not zero — a snippet can carry a wrong year, or a79non-peer-reviewed page can slip in — so **keep Stage 5b verification ON**: it80confirms every paper against OpenAlex / Crossref / Semantic Scholar and drops81anything that cannot be confirmed. Never pair this channel with `--no-verify`.8283## Notes8485- Keyless: the only network call the script makes is the keyless Crossref polite86 pool (set `LITREVIEW_CONTACT_EMAIL` to use your own contact). No LLM API calls.87- Google Scholar itself is bot-blocked, so do not WebFetch `scholar.google.com`88 directly; rely on WebSearch results and on fetching the underlying source pages.89- Coverage depends on what surfaces in search; this is a strong keyless **baseline**,90 not a replacement for Undermind / Deep Research / the SearchAPI Google Scholar91 channel.