# Architecture — how Scout works
Scout is deterministic plumbing: plain `fetch()` + string parsing, zero npm dependencies, no tracking, no internal LLM, no summarizer. The [agent](../GLOSSARY.md) decides what to do; Scout finds, fetches, extracts, compresses, caches, and returns [evidence](../GLOSSARY.md) with [provenance](../GLOSSARY.md) so the agent can judge it. See `adr/0001-agent-owns-judgment-scout-owns-plumbing.md` for the change gate.
## Pipeline
```mermaid
flowchart TD
Agent(["Any AI agent"]) -->|query + alternatives ≤4
maxResults 3-5| S["scout_search
freeSearch()"]
S --> XV["Expand: query + variants
each variant hits the same providers"]
XV --> RT["Route per variant
intent = explicit > scope > auto classify
specials selected, reddit-pass gated"]
subgraph Fanout ["Parallel fan-out — one failure never blanks the search"]
RT --> K["Keyed / own-infra (optional)
SearXNG first; Brave;
Google CSE legacy-only"]
RT --> N["Google News RSS
keyless, always on"]
RT --> D["DuckDuckGo HTML
back off once on 429"]
RT --> B["Bing RSS x2 (unofficial)
normal + reddit-biased"]
RT --> W["Specials by intent
Wikipedia / HN / SO as routed"]
RT --> A["Arctic Shift
only if r/foo named"]
end
Fanout -->|per-backend timeout
hung backend → empty, breaker trips| I["Fuse ONE union: RRF retrieval agreement
same URL twice = easier to find, NOT confirmed
canonical key: no tracking/www/amp"]
I --> F["Domain + recency filters
include / exclude hosts · auto..year"]
F --> R["Intent-aware rerank
lexical title x3 + snippet dominates
small intent × type prior + capped agreement
+ diversity · dated-first inside a window"]
R --> T["Trim + sanitize snippets
~220 chars · strip bidi/zero-width"]
T --> Hits["Compact hits + provenance + health
[SRn] title + URL + snippet + date
domain · source type · providers
partial / unavailable explicit in meta"]
Hits --> Pick{"Agent picks 1-2 URLs"}
Pick -->|url + query| RD["scout_read
freeRead()"]
subgraph Reader ["Reader chain — fast-paths first, cached 10 min"]
RD --> Guard["SSRF guard + per-hop
redirect validation"]
Guard --> Cache["Page cache
follow-ups skip re-fetch"]
Cache --> Fast["Fast-paths
Reddit Arctic / SO API
HN Firebase / Discourse .json
Google News bridge"]
Fast -->|hit > 80 chars| Lean
Fast -->|miss| Gen["Generic HTML
strip nav, prefer article/main
tables → markdown"]
Gen -->|pdf| PDF["PDF text locally
uncompressed + Flate, no deps
page markers estimated"]
PDF --> Lean
Gen -->|ok| Lean["Lean output + paging
highlights ~1/5 · text + budget
find matches + cursor · links list"]
Gen -->|403 / 429 / thin| Jina["Public reader fallback r.jina.ai
jinaFallback: false to skip
signed URLs refused fail-closed"]
Jina --> Lean
end
Lean --> Answer(["Cited answer
untrusted data, never instructions"])
Lean -.->|"answer missing:
follow a link, or
re-search with alternatives"| S
```
## Retrieval modes
**Request only the retrieval mode you need.** `highlights`, `text`, `links`, and `find` are mutually prioritized; Scout never returns multiple full representations of the same page in one response — `view: 'highlights'` needs `query` (extractive, order preserved), `tokenBudget` caps at `budget × 4` chars on a sentence boundary. `view: 'links'` returns the page's link list (anchor + URL + internal/external) for traversal — follow subpages without guessing URLs. `find: 'phrase'` locates exact passages with section + offset + context (page `findCursor` for more matches) and wins over `view` when both are given. `links` and `find` run over the **full cached source**, not an offset window; output caps still apply. `