Token Efficiency

The point of doing web work inside the server is that the model never has to read the pages. This page reports what that is actually worth, measured rather than asserted. The harness ships in the repository under benchmarks/.

Method#

Two workflows, the same questions, the same pages:

PathWhat lands in the agent’s context
baselineThe agent drives the tools: one web_search, then web_extract on each of the top 5 results. Every extracted page enters context in full.
researchOne web_research call. The response is the cited passages plus their metadata.

Both paths open the same URLs, so this isolates the compression the pipeline performs from any difference in what was retrieved.

Payload is counted in characters — exact and tokenizer-independent. Token figures are an estimate at 4 characters per token, for orientation only. The reduction ratio is the result that matters, and it barely moves between tokenizers.

Results#

12 questions spanning definition, technical, comparison, troubleshooting and research categories. Run 2026-09-02 against the live web with BETTER_WEB_SEARCH_DISABLE_BROWSER=true.

Baselineweb_research
Payload820,229 chars110,973 chars
Tokens (est.)~205,000~28,000
Wall clock137.3s49.6s

86.5% less text overall, 83.8% median.

Does the browser tier change this?#

The run above keeps to the HTTP and structured tiers. Repeating it with the browser tier enabled answers whether Tier 3 pages — the heaviest ones — shift the result:

HTTP + structuredBrowser enabled
Reduction (overall)86.5%86.2%
Reduction (median)83.8%83.9%
web_research wall clock49.6s61.3s
Baseline payload820,229 chars788,743 chars

It does not. The compression is the same within run-to-run noise — the two baselines differ by 3.8% on their own, since live pages change between runs — while research spends 24% longer. Tier 3 simply does not fire often for these questions: most pages carry enough content over plain HTTP.

That is a useful negative result. The browser tier earns its place on JavaScript-rendered pages that would otherwise extract nothing, not as a way to return less text.

Per question:

QuestionBaselineResearchReductionIndependent sources
What is the Model Context Protocol?101,95812,76587.5%4/5
What does Mozilla Readability do?48,3858,18683.1%4/5
How does Playwright wait for network idle?19,9468,61056.8%4/5
SQLite WAL mode tradeoffs54,5918,73884.0%5/5
BM25 term frequency saturation160,59610,80893.3%5/5
PostgreSQL vs MySQL for JSON43,8947,37183.2%4/4
Vitest compared to Jest45,9257,54383.6%5/5
Preventing SSRF on user URLs46,9218,46482.0%5/5
npm provenance from GitHub Actions57,7368,11785.9%5/5
Prompt-injection defenses for browsing agents74,36510,86985.4%4/5
RAG chunking strategies54,93012,23277.7%7/10
Technical SEO for static docs sites110,9827,27093.4%5/5

The 4/5 and 7/10 columns are the syndication check working on live pages: those runs opened five or ten URLs but found fewer genuinely separate accounts.

Does compression keep the answer?#

Payload reduction is only a win if the retained text still contains the answer. Measuring that usually needs a judge model. npm run bench:quality avoids one by restricting itself to questions with an unambiguous marker — a port number, a status code, a license name — and asking a narrower question a regex can settle: given the answer was in the raw pages, did compression keep it?

Retention is conditioned on the baseline on purpose. A question whose marker never appeared in the fetched pages is a retrieval miss, not a compression loss, and would otherwise blame the passage selector for weak search results.

34 questions, run 2026-09-04:

Answer present in baseline34 / 34
Answer retained after compression33 / 34
Retention97.1%
Payload reduction on this set92.4%

By category:

CategoryRetainedRetention
Numbers (ports, limits)8 / 8100%
HTTP status codes6 / 6100%
Acronym expansions12 / 12100%
Facts (licenses, defaults)7 / 887.5%

Acronym questions: 58.3% to 100%#

Earlier runs retained only 58.3% of acronym questions. The cause was specific: “What does TLS stand for?” is answered by a sentence that states the expansion once, while the pages around it repeat the acronym constantly, so BM25 ranked the answer below the noise. Passage selection now detects an acronym-definition question and surfaces the passage that spells the acronym out.

That claim is measured rather than asserted. Replaying a single frozen page corpus, so the only variable is the selection code:

BeforeAfter
Acronym6 / 12 (50.0%)12 / 12 (100%)
Overall27 / 34 (79.4%)33 / 34 (97.1%)

A live run afterwards reported the same 97.1% and 12/12, which is the check that the offline harness is not flattering itself.

The one remaining loss is not an acronym question. “Which HTTP request header is used to make a conditional request with an ETag?” is answered by If-None-Match inside reference tables, which the paragraph-based splitter does not keep intact.

This still does not measure whether the sources are correct, only whether the answer survived the pipeline.

What this does not measure#

  • Answer quality. Judging whether the retained passages still support a correct answer requires a judge model, which this harness does not have. A smaller payload is only a win if the right text survived. Per-question query_term_coverage is recorded as a weak proxy and is deliberately not folded into the headline figure.
  • End-to-end agent cost. A real agent may issue follow-up searches; this is one question, one pass.
  • Other tools. No comparison against Exa, Tavily’s answer endpoint or hosted web search, because they cannot be run under identical conditions against the same pages.

Reduction also tracks how much page text existed in the first place. The 56.8% case had a roughly 20k-character baseline, where the fixed cost of the response’s source list dominates.

Reproducing#

npm run build
npm run bench                    # all questions
npm run bench -- --questions 3   # a quick subset

Results are written to benchmarks/results/latest.json after every question, so an interrupted run still leaves usable data. Runs hit the live web and are not bit-reproducible: pages change, and DuckDuckGo rate-limits scrapers. Set BRAVE_API_KEY or TAVILY_API_KEY for steadier results.