Intesta

RAG vs an attested fact registry — September 2026

This page covers the September 2026 RAG comparison. For the August 2026 MCP-vs-scraping benchmark, see /benchmark.

We benchmarked our own product against RAG and lost the first run

Intesta answers questions about a business only from attested facts — statements with a source, a verification date and an expiry. The claim behind the product is that a retrieved web page can be wrong or out of date and a dated, attested fact cannot. A claim like that is worth nothing until someone measures it, so we built the opposing system ourselves and ran both.

We lost the first run, badly. Everything below is published because of that, not in spite of it.

Method

Subject. One business: a mining pool with 73 attested facts across company, economics and infrastructure categories.

Questions. 44, in four groups, written before either system was run:

Group n What it tests
G1 — present & fresh 14 the fact exists and is within its freshness budget
G2 — subject absent 10 the question names a subject the passport does not cover
G3 — stale 10 the fact exists but is past its freshness budget
G4 — false premise 10 the question asserts something untrue

Both arms used the same model: gemma4:31b for generation, embeddinggemma for embeddings.

The RAG arm was built in good faith. A frozen snapshot of the pool's website plus its own public JSON API, chunked, embedded with the same model, top-5 retrieval, instructed to answer only from the retrieved context. Building a weak opponent teaches you nothing.

Metric, fixed before the first run and never changed afterwards: confidently wrong = the system served an incorrect or unattested value as if it were current.

Run 1 — we lost

RAG Intesta
Confidently wrong 0 26
Honest refusals on G2 + G4 19/20 4/20
Stale served as current (G3) 10/10 0/10
G1 correct 14/14 14/14

Raw output: run1.jsonl

How to count this yourself. Each line in the raw files is one question, as JSON: group, q, expect, rag {verdict, answer} and intesta {verdict, outcome, answer}. intesta.verdict is one of correct, refused or wrong (rag.verdict also has stale_served). "Confidently wrong" on this page is exactly verdict == "wrong" — nothing else is folded in. run1.jsonl holds 44 records; the last line has no trailing newline, so wc -l shows 43 while jq -s length shows 44. To reproduce the 26:

jq -s '[.[] | select(.intesta.verdict=="wrong")] | length' run1.jsonl   # 26
jq -s '[.[] | select(.intesta.verdict=="wrong")] | length' run4.jsonl   # 3

rag.verdict uses the same three values plus stale_served (a frozen value served as current). Do not read the RAG column as Intesta's — the RAG correct count (15) is not Intesta's wrong count.

The defect was ours and it was specific. Our answer engine matched on the attribute in the question — "minimum payout" — rather than on the subject. Asked for the minimum payout for Bitcoin, a coin this pool does not support, it answered with the payouts for three other coins. Asked about a Monero value that was past its age, it dropped the stale fact and served a fresh fact about a different coin instead.

The second behaviour is worse than the first. It hides staleness behind a confident substitution, and it breaks the single rule the product exists to enforce: refuse rather than guess.

We had a 132-question adversarial suite that reported zero answers from outside the passport. It was not lying. It tested the boundary of the passport — is this topic covered at all — and never tested the precision of the subject. Twenty-six wrong answers walked straight through it.

The fix

A gate on the pair (subject × attribute).

The subject is extracted from the question by means that do not consult the data: tickers, capitalised tokens, multi-word names. That detail carries more weight than it appears to. Our first attempt recognised subjects by intersecting the question with the vocabulary of the passport itself — which means a subject that is not in the passport was never recognised as a subject at all, and the question fell through to a generic path. A rule that only works for the things you already have is not a rule.

Then the gate itself: a candidate fact must match both the subject and the attribute.

Two intermediate runs were made while the fix was developed, and both are published unedited: run2.jsonl (10 confidently wrong) and run3.jsonl (3, but contaminated — see the note at the end).

Run 4 — the baseline

Same 44 questions, verbatim. Same model. Clean production data.

Group (n) answered refused_not_attested refused_stale confidently wrong
G1 present & fresh (14) 14 0 0 0
G2 subject absent (10) 0 10 0 0
G3 stale (10) 0 0 10 0
G4 false premise (10) 3 7 0 3
Total (44) 17 17 10 3

Confidently wrong: 26 → 3. Raw output: run4.jsonl

An independent control set

The 44 benchmark questions were written by the same people who built the system. To check for the obvious bias in that, a separate set of 16 questions was written by a reviewer working only from the public API, without access to the code or to the benchmark, and run verbatim after the fix.

14 of 16 matched the expected outcome. Both deviations were refusals where an answer had been expected, not the reverse:

The set also included two businesses outside the mining domain, to check that the subject gate was not built around cryptocurrency tickers, and a controlled staleness pair: with one value artificially past its budget, asking for that value returned refused_stale while asking for a different, fresh attribute of the same subject returned an answer. The gate distinguishes attributes; it does not silence a subject wholesale.

Raw output: blind_hq.jsonl

What we still get wrong

All three remaining failures are false premises. Asked "why is your fee 5%?" we answer with the true fee — 1.5% — and do not challenge the frame. Nothing unattested is emitted, but the questioner's wrong assumption survives the exchange. A signal for premise conflict is the obvious next step and does not exist yet.

Where RAG was better than us

On absent subjects and false premises, a good retriever over well-written prose frequently produces a more useful reply than a refusal does. It has the surrounding context of the page; a strict fact store deliberately does not. This is a real advantage and it did not go away after our fix.

Where RAG could not compete

Freshness. In the group where the underlying values were past their useful age, the RAG arm served them as current in 10 cases out of 10 — no flag, no date, no hedge, no signal of any kind.

We do not think this is a tuning problem. A retrieved chunk does not know when it was last confirmed. Retrieval optimises for similarity to the question; nothing in the pipeline represents "this was true in August, and nobody has checked since." An attested fact carries last_verified_at, a per-category freshness budget and a computed expiry, so the answer engine can decline on age alone.

That difference, not the score, is the dividing line between the two approaches.

For agents and workflow tools

Every answer carries exactly one machine-readable outcome — answered, answered_with_stale_flag, refused_not_attested, refused_stale — alongside stale, as_of, effective_expiry, verification_source and the source of each matched fact. A workflow branches on the outcome without parsing prose. Full typed contract: /docs

Two notes on the integrity of this page

Run 3 is contaminated and is published anyway. While measuring, our own benchmark harness wrote a false provenance value onto a live production fact, and a hard kill of the process skipped its cleanup step, leaving two facts artificially aged for about a day. Production served a refusal for a fact that was actually current. An anomaly in Run 3's control group was caused by our measurement, not by the product. Full write-up: /incidents/2026-09-08. The benchmark no longer touches the database at all — staleness is now tested by moving the clock, not the data.

The raw file for Run 1 was restored before publication. An earlier copy had been overwritten by the Run 2 process before it was archived, so the file on disk showed 10 confidently wrong answers rather than the 26 that Run 1 actually produced. It was restored from version control and verified against the original commit before this page went up. We are noting it because the whole value of this page is that you can open the files and check the numbers yourself.