Ask it what the internet says about something. It returns the evidence — with citations that were
checked, the places sources disagree, and an explicit account of what it could not establish.
Every sentence is re-derived from the rows it cites before it is allowed out. A sentence that fails
is not emitted.
61,205 fragments10,854 items196 sources871 outlets0 model calls in the read path
tesseract answer — real output, unedited
Q:what are the sources saying about Nielsen acquiring DoubleVerifymatched: doubleverify nielsenFindings — every line re-derived from its citations4 indexed fragments from 2 items, 2 outlets and 1 source types match "doubleverify nielsen". [163259 172585 163260 172586]The most recent matching item is 16.9 hours old. [163259 172585 …]2 outlets published accounts of one event ("acquire billion doubleverify nielsen ad-tech") within 2.5 hours of each other. [163259 172585]The sources disagree on "doubleverify": deadline.com says $2,500,000,000; variety.com says $2,150,000,000.[163259 172585]deadline.com: "Nielsen Set To Acquire Ad-Tech Firm DoubleVerify For $2.5 Billion" [163259]variety.com: "Nielsen to Acquire DoubleVerify for $2.15 Billion in Bid to Augment Digital Measurement" [172585]deadline.com: "Nielsen is paying $2.15 billion to acquire DoubleVerify… The all-cash acquisition will deliver shareholders $13.60 per share." [163260]Could not establishTikTok: could not be asked — no topic-search surface exists; signature forgery is refused on purposeReddit: not configured — zero results here means it was never queried12 registered sources are robots-refused and were never fetched48 fragments are in scripts this tokenizer cannot segment (CJK) — indexed and unreachablediscourse: 377 fragments indexed, 0 matched — this one is a fact about the data
Deadline's headline says $2.5 billion. Deadline's own body, two lines later in the same answer,
says $2.15 billion. A summariser reconciling forty accounts would have produced $2.3 billion —
a figure nobody published and no reader could trace.
Three things a chatbot cannot do.
Not because it is worse at language. Because it has no row to point at.
Refuse to say it
Each sentence carries the fragment ids it was computed from, and the assembler re-derives the
claim from exactly those rows. Strip the citations and it emits nothing. Staple a fake id to a true
sentence and it is refused. The gate is in the function, not in a prompt.
Report the disagreement
Where two outlets attach different figures to the same thing, that is the finding — quoted
verbatim, both sides, with their outlets. Averaging them invents a number. A figure that is merely
qualified — "up to", "per share" — is never reported as a conflict.
Say what it could not reach
"Nobody discusses this" and "we were never able to ask" are different answers. Every response
names which sources are structurally unaskable, which robots.txt forbids, which are stale, and which
are genuinely silent.
The economics are the architecture.
A wrapper buys a model call for every answer, so its gross margin falls as usage rises — the one cost
curve you cannot outrun. No model runs inside this. It assembles evidence; your Claude
writes the prose, over MCP, on your subscription. An acceptance test greps the entire read path and
finds zero model calls.
$0
Platform inference spend. Verified by a test, not asserted in a README.
$0
Perception cost. ffmpeg and OCR run on the customer's machine.
0 bytes
Media retained. Enforced on every exit path — an index, never an archive.
Measured, not asserted.
Fourteen assumptions were written down as settled and then contradicted by measurement.
These are the ones that changed the design.
What was believed
What was measured
SimHash finds "the same story in forty outlets"
Same-event and unrelated pairs have the same distance distribution — median 31 vs 32. Outlets don't republish a body; they each write one.
A published feed is a permission
12 of 157 feeds are robots-Disallowed. arXiv's export host blocks every crawler.
Fetching an article costs ~2 s
11.2 s per stored article — the cost is the failures (0.66 s success, 13.4 s timeout, more failures than successes).
Storage is 481 bytes/fragment
That was synthetic. Real text is 1,559 — so 100M fragments is ~156 GB, not 48.
The group-by is a wall at 10M rows
4,820 ms → 0.01 ms with a maintained rollup. Ranked queries halved, 2,444 → 1,139 ms.
The rate limiter was fixed
A 16-second event-loop stall fired every overdue timer at once: two requests 1 ms apart on a host limited to one per two seconds.
26 acceptance tests, a 10M-fragment scale run, and a 30-minute continuous run that is
SIGKILLed at the halfway point and has to resume. They do real network work on purpose: the failure mode
this has is not "a function returned the wrong value", it is "acquisition quietly returned less and
nothing threw", and a mock cannot catch that.
Runs from a clone.
Node 22+. No npm dependencies — not for the model calls, not for the MCP
server. SQLite via node:sqlite.
# seed the registry and start polling
node cli.mjs registry --seed --sync
node cli.mjs poll --forever
# ask it something
node cli.mjs answer "what is happening with Iran and munitions"
# or hand it to Claude over MCP — 18 tools, no SDK
{ "mcpServers": { "tesseract": { "command": "node", "args": ["mcp.mjs"] } } }
Logged-out only. Public surfaces only. robots.txt parsed and honoured with correct
longest-match precedence. Content-Signal:
ai-input=no withholds a body while keeping the citation and the link. No paywall circumvention, no
signature forgery, no screen capture — absent by choice, not by effort.