The Faithfulness Floor¶
What does an agent without an evidence gate do on the same corpus, the same questions, judged by the same verifier? This benchmark answers that with a deterministic, reproducible measurement — no LLM anywhere in the measurement, no LLM judge, no accounts — comparing Tessera's evidence-gated answer engine against its own retrieval layer run ungated. Anyone can reproduce every number below from a clone:
uv sync
uv run tessera-benchmark # the summary
uv run tessera-benchmark --markdown # exactly the tables below
uv run tessera-benchmark --cases # every case (gold + synthetic), both sides
A CI test regenerates the tables below on every build and fails on any byte
difference, so the tables on this page cannot silently drift from the
measured truth (a second test pins the headline numbers this page and the
README quote in prose).
Spec: specs/0122.
What is being compared¶
Both answerers run over the same corpora (the three measured batteries:
business, devex, and github_actions — the last built from real CI logs
of this repository), the same cases (curated gold + generated synthetic),
and are judged by the same deterministic verifier with the same claim
grammars (ADR 0005,
ADR 0011). The scoring function
is the eval harness's own — imported, not reimplemented — so benchmark
semantics cannot drift from eval semantics.
Tessera (gated) — the full engine: question routing, knowledge-graph entity resolution, cross-source composition with recomputed (fully cited) aggregates, multi-hop RCA, conflict surfacing, principled refusal on ambiguity / missing evidence / incomparability, and the verifier standing behind every claim.
Ungated retrieval (retrieve-and-recite) — Tessera's own retrieval layer with everything above it removed: BM25 top-5 over the identical records, each hit recited verbatim as a claim with its provenance, refusing only when the question shares no content token with any record. This is deliberately not an authored strawman: it is the exact retrieval the gated engine itself uses, so the measured gap is attributable to precisely the layers being claimed — the gate, not the retriever. Where recitation suffices (plain lookups, zero-overlap refusals), the baseline scores full marks, and the per-case table below shows it.
What the numbers mean¶
All scoring semantics are the eval's own
(ADR 0005, tessera/eval/harness.py):
- Faithfulness — fraction of emitted claims deterministically supported by their cited evidence (the verifier). Tessera gates its build on this being 1.000; it is provably able to fail and has failed before (WRITEUP).
- Coverage — fraction of the expected supporting evidence an answer actually cites.
- Quality — fraction of cases handled correctly: answerable cases must contain the expected facts; refusal cases must refuse.
- Trustworthy outcomes (the composite, defined here) — fraction of cases where nothing went wrong: an answer-kind case counts only if the answer is grounded, every claim passes the verifier, all expected facts appear, and all expected evidence is cited; a refuse-kind case counts only if the system refuses — and any claim it still emits alongside the refusal must pass the verifier too (claims on refuse-kind cases are scored, not exempt). Each component is computed by the harness's own scoring function run per-case, so the composite introduces no new semantics. (Weighting differs by design: faithfulness/coverage/quality are pooled over claims / expected ids / cases exactly as the eval pools them; trustworthy outcomes are per-case, unweighted.)
The numbers¶
Mode: offline, deterministic (lexical BM25, no semantic index, no LLM); baseline depth k=5; 110 cases across 3 batteries.
| Battery · cases | Answerer | Faithfulness | Coverage | Quality | Trustworthy outcomes |
|---|---|---|---|---|---|
| business · gold (11) | Tessera (gated) | 1.000 | 1.000 | 1.000 | 1.000 |
| business · gold (11) | ungated retrieval | 1.000 | 0.375 | 0.182 | 0.182 |
| business · synthetic (53) | Tessera (gated) | 1.000 | 1.000 | 1.000 | 1.000 |
| business · synthetic (53) | ungated retrieval | 1.000 | 0.194 | 0.038 | 0.038 |
| devex · gold (9) | Tessera (gated) | 1.000 | 0.950 | 0.889 | 0.889 |
| devex · gold (9) | ungated retrieval | 1.000 | 0.300 | 0.222 | 0.222 |
| devex · synthetic (24) | Tessera (gated) | 1.000 | 1.000 | 1.000 | 1.000 |
| devex · synthetic (24) | ungated retrieval | 1.000 | 0.414 | 0.583 | 0.125 |
| github_actions · gold (5) | Tessera (gated) | 1.000 | 0.833 | 0.800 | 0.800 |
| github_actions · gold (5) | ungated retrieval | 1.000 | 0.667 | 0.000 | 0.000 |
| github_actions · synthetic (8) | Tessera (gated) | 1.000 | 1.000 | 1.000 | 1.000 |
| github_actions · synthetic (8) | ungated retrieval | 1.000 | 0.500 | 0.625 | 0.250 |
Trustworthy-outcome gap (gated − ungated):
- business · gold: 1.000 vs 0.182 (+0.818)
- business · synthetic: 1.000 vs 0.038 (+0.962)
- devex · gold: 0.889 vs 0.222 (+0.667)
- devex · synthetic: 1.000 vs 0.125 (+0.875)
- github_actions · gold: 0.800 vs 0.000 (+0.800)
- github_actions · synthetic: 1.000 vs 0.250 (+0.750)
Structural notes (computed — properties of the cases and corpus, independent of answerer). reachable Q: answer-kind cases whose expected facts all occur verbatim in some record, i.e. where recitation could in principle pass the quality check — on the rest, the expected phrasing is the gated engine's own composed output, so the quality gap there measures whether composition happened at all, not retrieval quality. >k support: cases expecting more evidence ids than the baseline's retrieval depth (k=5) — full coverage is structurally out of reach there. vacuous: answer-kind cases declaring no expected support / no expected facts (those components auto-pass, per the harness's own convention).
- business · gold: 6 answer / 5 refuse · reachable Q 1/6 · >k support 1 · vacuous 1 support, 0 facts
- business · synthetic: 45 answer / 8 refuse · reachable Q 0/45 · >k support 18 · vacuous 0 support, 0 facts
- devex · gold: 6 answer / 3 refuse · reachable Q 3/6 · >k support 1 · vacuous 0 support, 0 facts
- devex · synthetic: 11 answer / 13 refuse · reachable Q 10/10 · >k support 0 · vacuous 0 support, 1 facts
- github_actions · gold: 3 answer / 2 refuse · reachable Q 2/3 · >k support 0 · vacuous 0 support, 0 facts
- github_actions · synthetic: 3 answer / 5 refuse · reachable Q 0/0 · >k support 0 · vacuous 0 support, 3 facts
Per-case outcomes on the curated gold sets (✓ trustworthy; ✗ with the failed checks — F: a claim failed the verifier, C: expected evidence not cited, Q: wrong disposition or expected facts missing):
| Case | Kind | Engine | Tessera (gated) | Ungated retrieval |
|---|---|---|---|---|
| business/01_mueller_cross_source | answer | compose | ✓ | ✗ C+Q |
| business/02_mueller_lookup_retrieve | answer | retrieve | ✓ | ✓ |
| business/03_lumiere_billing_compose | answer | compose | ✓ | ✗ Q |
| business/04_atlas_mixed_currency | answer | compose | ✓ | ✗ C+Q |
| business/05_ambiguous_refusal | refuse | compose | ✓ | ✗ Q |
| business/06_out_of_scope_refusal | refuse | compose | ✓ | ✓ |
| business/07_mueller_renewal_conflict | answer | compose | ✓ | ✗ C+Q |
| business/08_superlative_synonym | answer | route | ✓ | ✗ Q |
| business/09_currency_scope_refusal | refuse | route | ✓ | ✗ Q |
| business/10_same_name_address_refusal | refuse | compose | ✓ | ✗ Q |
| business/11_same_name_same_address_refusal | refuse | compose | ✓ | ✗ Q |
| devex/01_r1042_rca_recurrence | answer | rca | ✓ | ✗ C+Q |
| devex/02_pr201_change_summary | answer | summary | ✓ | ✗ C+Q |
| devex/03_payments_oncall_lookup | answer | route | ✓ | ✓ |
| devex/04_notifications_oncall | answer | route | ✓ | ✗ C+Q |
| devex/05_passed_run_refusal | refuse | route | ✓ | ✗ Q |
| devex/06_unknown_run_refusal | refuse | route | ✓ | ✗ Q |
| devex/07_out_of_scope_refusal | refuse | route | ✓ | ✓ |
| devex/08_r1042_fix_chain | answer | rca | ✓ | ✗ C+Q |
| devex/09_checkout_oncall_semantic | answer | route | ✗ C+Q | ✗ C+Q |
| github_actions/01_ruff_format_failure | answer | rca | ✓ | ✗ C+Q |
| github_actions/02_pages_deploy_recurrence | answer | rca | ✓ | ✗ Q |
| github_actions/03_passed_run_refusal | refuse | rca | ✓ | ✗ Q |
| github_actions/04_unknown_run_refusal | refuse | rca | ✓ | ✗ Q |
| github_actions/05_pages_synonymy_lookup | answer | lookup | ✗ C+Q | ✗ C+Q |
How to read this honestly¶
The baseline's faithfulness is 1.000 — and that is the finding, not an error. A system that only recites verbatim snippets passes a containment verifier by construction: recitation is trivially "faithful". It still fails the task — it answers questions it should refuse (ambiguous entities, conflicting evidence, cross-currency sums, runs that never failed), and it cannot state what the evidence adds up to (a recomputed total, a recurrence across runs, a fix chain), so the facts a correct answer must contain never appear. The floor is cheap if you never assert anything beyond quotes. Tessera holds the same floor while actually asserting things — sourced aggregates, entity-resolved cross-source links, multi-hop conclusions — and that combination, not faithfulness alone, is what "trustworthy outcomes" measures. On these corpora, the gap column is what removing the gate costs.
Part of the quality gap is definitional — and the artifact computes how
much. The expected facts on compose/RCA cases are exact strings in the
gated engine's own phrasing ("Recurring failure", "Total net order value
across …"), because the gold sets double as that engine's regression suite.
An answerer that does not compose — or composes in different words — cannot
satisfy those facts regardless of retrieval quality. The worked example is
in the per-case table: on github_actions/02_pages_deploy_recurrence the
baseline cited every expected record and fails only the engine-phrased
recurrence fact. The structural notes inside the generated block count
exactly where this line falls ("reachable Q"): on business synthetic, the
baseline's published 0.038 is its structural ceiling — those rows
measure the value of composing at all, under our phrasing, not retrieval
ranking. Where the expected phrasing is plain record text (devex synthetic:
10/10 reachable), the baseline is credited — quality 0.583 — which is how
you can tell the harness credits whatever recitation can legitimately earn.
The refusal and coverage columns are phrasing-independent throughout
(disposition and evidence ids, not wording).
Tessera's own misses are in the table. The gated side is not decorated
to 1.000: offline, devex gold shows 0.889 trustworthy (a named semantic
near-miss the deterministic matcher does not bridge:
devex/09_checkout_oncall_semantic) and github_actions gold shows 0.800
(a real error-class synonymy in live CI logs:
github_actions/05_pages_synonymy_lookup; its close via in-database
embeddings on SAP HANA is recorded separately in the
WRITEUP — it needs an account, so it is not part of this
offline artifact). Both sides fail those two cases for the same lexical
reason. A benchmark where our own side shows its misses is the kind you can
trust.
What the extractive baseline does — and does not — proxy. The deterministic baseline never paraphrases, never blends sources, never invents a number. For faithfulness that makes it a best case by construction: recitation maxes a containment verifier, and paraphrase, blending, and invention can only score worse there. On task outcomes the direction is not guaranteed: a retrieve-then-generate agent might recover some of this baseline's quality/coverage failures (state the total, cite the right rows) — and anything it asserts would still have to survive the same verifier. Measuring that honestly would require an LLM inside the benchmark, which would end its determinism and reproducibility (and put a model in a trust path, which this project refuses). The seam at the bottom of this page takes any answerer — measure yours.
How this can fail¶
A benchmark that cannot fail is decorative. This one can, in four ways:
- The doc pin. CI regenerates the tables above and fails on any difference — if the corpus, engine, or scoring changes, this page must be regenerated in the same PR or the build is red.
- The direction pin. A CI test asserts the gated engine's trustworthy-outcome rate is strictly above the ungated baseline's on every battery and case set. If a change makes that false anywhere, the build says so; the claim is not allowed to outlive its truth.
- The equality pin. A CI test asserts the gated side reproduces the offline eval numbers exactly — the benchmark cannot quietly diverge from the numbers the project publishes elsewhere.
- The floor itself. Faithfulness 1.000 on the gated side is enforced by the build gate independently of this benchmark, and the verifier is adversarially tested (an injected unfaithful claim is caught).
Limitations¶
- The corpora are ours.
businessanddevexare authored synthetic corpora (with deliberate ambiguity, conflicts, and variant entities);github_actionsis real CI data from this repository. Nothing here is a third-party benchmark suite; the artifact's claim is about the measured gap between gated and ungated answering over identical evidence, not about generalization to your data. (For that,tessera smokeruns a trust-floor battery on your own connected repo — see PILOT.md.) - The cases are ours, written against the gated engine. The gold sets double as its regression suite (two cases it fails are kept in), and the refuse-kind share (40% of gold) shapes the headline gap — which is why the kind mix and every per-case outcome are printed above rather than left to aggregate impressions.
- n is small and stated: 110 cases (25 gold, 85 synthetic) across three batteries. Synthetic expected values are recomputed from the data at eval time (ADR 0007); on business compose cases the expected phrasing is the engine's template (the definitional boundary above; recorded in the ADR 0007 addendum), while devex/github_actions synthetic facts are plain record text.
- The baseline is extractive. By construction this understates the faithfulness risk of generation (see above); on task quality a generating agent could recover some of the failures — the answerer seam below exists to test exactly that.
- Offline mode only. The recorded online close of the synonymy case (embeddings on SAP HANA) is documented in the WRITEUP but excluded here to keep the artifact account-free and byte-reproducible.
If you want to attack this: swap in your own baseline answerer
(tessera.eval.benchmark.ungated_variant shows the seam — any
(case, graph, kb, index) -> Answer callable), or add cases to the gold
sets and re-run. The coverage and refusal columns are open challenges
for any answerer; the quality column on compose/RCA cases keys on the
gated engine's phrasing (the structural notes say exactly where), so the
honest way to attack it is with record-phrased cases of your own. Issues
and PRs that break the number are the point.