Skip to content

Learn

Web lane

A search query is a message to a stranger, and a web page is a stranger's text.

How the engine looks up a public benchmark without leaking your customers' names or numbers, and without trusting the pages it reads.

Stage 5 of 10. WEB_CONTEXT and the planner’s search_web tool. The web lane is off by default; a job uses it only with --web.

Some questions need the outside world: is a refund rate of 3.4%N64 high for a coffee roaster? But a search query is a message to a third party. A careless query that names a customer, or carries a number from your books, tells a search engine who your customers are and how much they buy. And a web page is text written by a stranger, which may contain instructions aimed at AI systems.

So the lane cleans every query before it leaves the machine, refuses to fetch addresses that point inside your network, and treats every page as untrusted.

The run uses a fixed, offline set of pages (no internet), so it is the same every time.

WEB_CONTEXT · search_web · the coffee-roaster run

In

  • the planner askedHotel Group LLC refunds 1258 — specialty coffee refund rate benchmark 2026

WEB_CONTEXT · search_web

  1. 1 · SanitizeNames, numbers, emails and file names are cut before anything leaves the machine.
  2. 2 · Fetch safelyEvery address is checked before connecting; private and metadata addresses are refused.
  3. 3 · Screen and extractA page that talks to a model is quarantined; the rest goes to the quarantined extractor.

Out

  • sentrefunds specialty coffee refund rate benchmark 2026
  • removed2name: hotel group llc · number: 1258
  • kepthttps://coffee-benchmarks.example/refunds
  • quarantinedhttps://roaster-forum.example/thread
  • refusedhttp://169.254.169.254/latest/meta-data/
  • benchmark gap1.4 ppN270Specialty coffee roasters typically see refunds of 1–2% of sales.

From the run's web_results, quarantine and the search provider's log.

The query. The scripted planner made the careless query on purpose:

  • asked: Hotel Group LLC refunds 1258 — specialty coffee refund rate benchmark 2026
  • sent: refunds specialty coffee refund rate benchmark 2026

The sanitizer removed name “hotel group llc” and number “1258”. The list of private terms comes from the job itself: every customer and supplier name in the data and its spellings, file names, and email addresses found in documents. Years stay, because “2026” is public.

The results. The search returned three addresses:

AddressOutcomeWhy
https://coffee-benchmarks.example/refundskeptstored as external source W1; the extractor read it
https://roaster-forum.example/threadquarantinedthe page addresses a model (possibly in hidden text)
http://169.254.169.254/latest/meta-data/refusedBLOCKED_ADDRESS: 169.254.169.254 is a private or reserved address

The forum page looked harmless but hid an instruction in invisible text; the whole page was quarantined. The third address, 169.254.169.254, is where cloud servers expose their credentials; fetching it is the classic SSRF attack, refused before any connection.

The statement. From the benchmark page the extractor quoted: “Specialty coffee roasters typically see refunds of 1–2% of sales.” (W1). The planner compared it with the roaster’s June refund rate, and code computed the gap: 1.4 ppN270. In the report the claim is labelled External: a web number can sit next to your numbers, never replace one.

Send the least that answers the question. The sanitizer normalises before matching (case, accents, look-alike digits, legal forms like “LLC”), so “HOTEL GROUP llc” and “Hotel-Group” are caught too. When in doubt, a term is removed.

Server-side request forgery is tricking a server into fetching an address you choose, usually one only the server can reach: a database admin page, localhost, a cloud metadata service. The fetcher allows only http(s) on ports 80 and 443, checks every address the name resolves to at connect time (so a name that later points inside can’t slip through), and re-checks each redirect.

A model that reads web pages can be steered by the pages. The defence here is architectural: pages are screened, and only the quarantined extractor, which has no tools, reads them. See Security.

The web lane needs a search engine; the project ships a self-hosted SearXNG:

Terminal
docker compose --profile web up -d searxng
bun run aa run examples/coffee-roaster --web --embedder hash --today 2026-07-01 --currency USD

That needs a configured model for the planner. The offline record above comes from the test fixture the CI uses.

Go deeper: Web lane and privacy.

FactValueWhat it isHow it was computedVerified
N64 3.4% refund_rate_2026-06
SQL, run in DuckDB
WITH m1 AS (SELECT strftime("date", '%Y-%m') AS period, sum("amount") AS value FROM "t_t1" WHERE NOT _is_total AND "date" IS NOT NULL AND ("status" IN ($1)) AND strftime("date", '%Y-%m') BETWEEN $2 AND $3 GROUP BY ALL),
  m2 AS (SELECT strftime("date", '%Y-%m') AS period, sum("amount") AS value FROM "t_t1" WHERE NOT _is_total AND "date" IS NOT NULL AND (("status" IS NULL) OR ("status" NOT IN ($4))) AND strftime("date", '%Y-%m') BETWEEN $5 AND $6 GROUP BY ALL),
  m3 AS (SELECT coalesce(j0.period, j1.period) AS period, coalesce(j0.value, 0) / NULLIF(j1.value, 0) * 100 AS value FROM m1 AS j0 FULL JOIN m2 AS j1 ON coalesce(j0.period) IS NOT DISTINCT FROM j1.period)
SELECT period, value FROM m3 ORDER BY period

parameters: {"$1":"refunded","$2":"2026-06","$3":"2026-06","$4":"refunded","$5":"2026-06","$6":"2026-06","input_rows":1087}

recomputed
N270 1.4 pp benchmark N64 vs W1:gap benchmark_gap.v1 N64 N268N269 recomputed