Core concepts

Why Coalent doesn't serve the wrong answer

The honesty architecture on the default pool path — the adaptive serve gate calibrated against the pool's own noise, the RAG floor, instant staleness masking, attributed refusals — and the v0.7 failure chain that turns the remaining failures into permanent repairs.

The RAG failure that hurts isn't a missing answer — it's a confident wrong one: an answer hallucinated over stale or merely-adjacent context. This page walks Coalent's honesty architecture end to end on the pool path — since v0.7.0 the default whenever a semantic embedder is available (every keyed or production deployment): how a read decides it doesn't have the answer, what it does about it, and what happens after a failure. (The legacy unit path's version of this story is compressed at the bottom, version-scoped.)

The architecture is a stack, not a single check:

  1. The serve gate filters — calibrated against the pool's own measured noise.
  2. Attribution makes the leaks refusable — a near-miss serve produces an honest refusal, not an interpolation.
  3. Freshness is structural — a stale claim is masked the instant its source changes; it cannot serve.
  4. The RAG floor bounds the downside — an under-covered read degrades to honest retrieval, never below it.
  5. New in v0.7 The failure chain converts failures into permanent repairs — the one layer no filter can be: recovery.

The serve gate — calibrated against the pool's own noise

Every read ranks the global fresh-claim pool and the serve gate (stage P4) decides serve-vs-build. Its semantics are worth knowing exactly:

  • The decision. Top pre-rerank claim cosine ≥ the effective gate → serve; below → the build read (probe classification below). The reranker orders serving only — it can never cause a false serve or a skipped build, because the decision always reads the pre-rerank score.
  • serve_gate=None (default) is adaptive — against provably-unanswerable queries. The gate calibrates on the pool's own null-shaped noise ceiling: the p95 of max claim cosines measured on provenance-disjoint probes (≤ 24 fixed-seed units) — how high does a claim score on a query it provably cannot answer? The effective gate is that ceiling plus a margin, clamped to [coverage-floor default, coverage-floor default + 0.27] (~0.28 to ~0.55 on the shipped OpenAI embedder) — a dense near-duplicate pool can never inflate the gate high enough to refuse real answers.
  • Recalibration is bounded and hidden. The ceiling recomputes at the end of a build read (post-synthesis, where LLM latency hides it), at most once per 16 builds. Serve-only traffic never pays for calibration.
  • An explicit float is absolute. serve_gate=0.4 disables adaptation — the off-ramp for exact, reproducible benchmarks.
  • The reuse bypass can't serve stale. A query whose seed cosine to a cached unit is ≥ 0.9 is the same question asked again and serves regardless of the gate — but a stale reuse match re-materializes in place first.
  • Read it live. stats()["serve_gate_effective"] and stats()["pool_noise_ceiling"]; a climbing builds_by_gap with flat accuracy means the gate sits too high.

When the gate says build: the probe classification

A read below the gate doesn't blindly rebuild anything. It runs one probe retrieval and classifies the result per artifact:

ClassWhat the probe foundActionSynthesis cost
CONTAINEDevery chunk of this source is already retained verbatim by a fresh unitserve — provenance proves coverage0
STALE-OWNEDa unit owns this source but is dirty/expiredrebuild that unit in place1
THINa unit owns this source but didn't retain these chunksrebuild that unit in place (widened, with widen_on_admission)1
NOVELno unit owns this sourcebuild a source-anchored unit1

Two containments bound the cost: synthesis is capped at ≤ 3 ops per read, and an all-CONTAINED probe is a structural admission — the read serves with zero synthesis, because building would mint a duplicate understanding (stats()["admission_reuses"] counts these). Empty cache vs warm cache is the same algorithm with different statistics: on an empty cache every artifact classifies NOVEL (that's warm-up, not a mode); on a warm cache most gate-misses come back CONTAINED or STALE-OWNED.

Attribution makes the leaks refusable

Cosine gates are statistical filters, not proofs — a near-miss topical serve will occasionally clear the gate. It is safe by construction because the payload is never an anonymous prose blob: it's attributed atomic claims under source headers. When a near-miss serves, the answer model sees claims about vacation accrual labeled as such, finds nothing about sick days, and refuses honestly instead of interpolating. Measured on the 605-question news rig: 95% refusal honesty on unanswerable questions, vs 85–88% for naive top-k — a stuffed raw-chunk context invites the model to synthesize something from adjacent noise; a short list of attributed claims makes "the context doesn't say" the easy completion.

Freshness is structural, not hoped-for

On the pool path, freshness is a live pull-mask: the moment source_changed fires, every claim derived from that source is masked out of the pool — it cannot be ranked, cannot be packed, cannot serve. TTL expiry clock-compares the candidate-head owners on every read (bounded: ≤ 3 revalidator calls). A stale answer isn't filtered out late; it's structurally unable to enter the payload. See Provenance & freshness.

Rebuild vs escalation — two different failures

Escalation (the RAG floor)Rebuild
Answers the failurecoverage — the cache is fresh but doesn't hold this factfreshness — the cache holds it, but the source changed
Triggerfinal coverage < coverage_floor (~0.28 OpenAI)the freshness mask / TTL, or a STALE-OWNED / THIN probe class
What happensappend attributed raw retrieval ([source: id]-prefixed chunks) to the payloadre-synthesize the unit from fresh source text before serving
Mutates the cache?no — a serving action onlyyes — the unit re-materializes
LLM callnoneone synthesis call
TIP

The RAG-floor covenant. An escalated read never carries less context than plain RAG would have retrieved: the floor appends attributed raw chunks — deduplicated, budget-capped, never empty. Under-coverage degrades Coalent to honest retrieval, never below it.

Why doesn't a coverage gap just trigger a rebuild? The same-extractor-same-loss finding: if the extractor dropped a fact once, re-running the same extractor over the same unchanged source mostly reproduces the same loss. So coverage gaps escalate to raw for the read at hand — and persistent extraction loss is the failure chain's job, below.

After the filters: the failure chain (v0.7)

New in v0.7 Filters can only refuse; they can't recover. The failure chain is the recovery layer — and its safety is structural, not statistical:

  • The gap detector fires without a refusal. With gap_detector=True, a read reports, per probe, when a raw evidence sentence outscores every served claim — the one signal that catches the confident-wrong class, where refusal-gated machinery is blind. Observe-only: serving is byte-identical ON vs OFF (pinned), and each gap is typed extraction_hole (repair terrain) or corpus_hole (route to your tools — the cache never fetches).
  • Each rung fires only on failure and cannot un-answer a correct read. repair(read_id) is gated on a failed first pass — your evaluator's call, never the library's. serve_unserved and reprobe run on refusals — and a refusal is never a correct answer, so the downstream rungs have nothing to break. Reprobe additionally re-ranks under a MAX-union that includes the original probes, so nothing already served can rank worse.
  • A repair is permanent and auditable. Admitted claims append with per-claim provenance (_repair_provenance) — traceable and evictable by inspection; admission hygiene mechanically rejects the measured defect shapes (pronoun-subject, truncated) before they can serve.

The wrong answer's last line of defense, in other words, is that a failure improves the store: the same miss never costs you twice. Full reference: The failure chain.

What's measured

The honesty properties above aren't design intent — they're benchmark rows:

PropertyMeasuredWhere
Refusal honesty on unanswerable questions95% vs naive top-k's 85–88%605-question news rig
Refusal repair (v0.6 behavioral stack, opt-in)refusals 91 → 61 (−33%), zero newly-wrong answerssame rig
Final refusals, full v0.7 chain vs the strongest v0.6 configuration−69% (61 → 19)same rig + locked adjudication
Right-unit routing (unit path)route@1 ≈ 1.00clean structured bench
Misattribution — a number served from the wrong source≈ 0–2% — the naive answerer's own noise levelsame bench; see the transparency note below

The misattribution number has a story worth reading: an earlier internal run showed a large misattribution rate that turned out to be a benchmark bug — contradictory duplicate sources no router could resolve — and the transparency note on the benchmark page documents the forensics in full. And the standing anti-claim applies here too: none of this claims accuracy above naive RAG (the v0.6 measured result was parity at fewer tokens; the v0.7 gain is measured against our own strongest v0.6 configuration). The claim is narrower and stronger: wrong-source, stale, and fabricated-from-adjacent-context answers are engineered out, measured, and honestly bounded — and what still fails, heals.

One clarification: the pool path has no recall step

A recurring reading error, worth killing explicitly: cross-unit recall is a unit-path mechanism New in v0.4 — it fires when the unit path's single matched unit under-covers, sweeping claims from other fresh units (surfaced as result.recalled). The pool path doesn't run recall as a step — the pool scan is already cross-unit by construction: every read ranks the global fresh-claim pool from all units. The recall_* knobs are inert on the pool path and result.recalled stays [] there.

The unit path (legacy escape hatch)

Version-scoped: this section describes read_path="unit" — the pre-0.7 default, kept byte-identical as the explicit escape hatch and as the automatic keyless fallback (pool ranking needs a semantic embedder). Skip it unless you run that path.

The unit path's honesty story is two questions at two granularities. The hit gate asks "is this the right territory?" — a blended 0.7·topic + 0.3·seed cosine over the whole unit, floored at ~0.33 (OpenAI). Topic similarity is diffuse: a leave-policy unit scores respectably against any leave-adjacent question. Coverage then asks "does any single claim actually answer this?" — the max single-claim cosine, floored at ~0.28. Fact similarity is sharp: a sick-days query matches a vacation-policy unit's territory but no individual claim, so the read escalates instead of answering thin. (This is why understanding moved from prose to atomic claims in v0.4 — a gate can only be as sharp as the thing it scores; claim granularity is what makes "relevant but insufficient" detectable.) The same RAG floor, S2 band (coverage_scorer), and freshness gates apply; widen_chunks New in v0.5 fixes the build-time keyhole (widened units read a median 23 chunks of their source vs 2 — rebuild churn fell 460 → 31); and route@1 ≈ 1.00 / misattribution ≈ 0–2% in the table above were measured on this path.

Next