Every get(query) walks a fixed ladder of gates, top to bottom. Since v0.6 there are two ladders, selected by one constructor knob: the default unit path (read_path="unit" — byte-identical v0.5 behavior, its ladder unchanged) and the opt-in pool path (read_path="pool" — pooled cross-unit claim ranking, budgeted packing, an adaptive serve gate). Each rung is also a knob — but you rarely touch one. The defaults are pure cosine: both ladders run with no extra model and no extra dependency, and only reach for raw when the cache genuinely under-covers.
The default path costs nothing extra. Every gate decision on both ladders is cosine over embeddings you already computed — no cross-encoder, no NLI, no second LLM call on the read path. You turn a rung up only when a specific workload needs it (see the table below), and a v0.5 setup that touches nothing behaves identically.
New in v0.6 get() now dispatches on read_path first: "unit" (default) walks the eight-gate ladder exactly as in v0.5; "pool" (opt-in) runs the pool sequence instead. On the pool path the unit-path read knobs (hit_threshold, hit_margin, route_by_claim, recall_*, select_floor, ...) are inert — but coverage_floor (the RAG floor) and the S2 band (coverage_scorer) keep their jobs on both paths.
The two ladders, in firing order
A read flows down its spine — matched or pooled, fresh, covered, served. Each gate can branch off: a miss (unit) or a below-gate read (pool) builds, a stale unit re-materializes, an under-covered serve escalates to attributed raw (the RAG floor), and — on the pool path with the behavioral stack armed — a downstream refusal re-enters through report_refusal. It never fabricates.
Unit path ladder (default, unchanged from v0.5)
read_path="unit" is the default and is byte-identical v0.5 — the same eight gates, same defaults, same firing order. (New in v0.4 the matched unit is a list of query-independent extractive claims — see The Synthesizer — and cross-unit recall is on by default, gate 5, so multi-hop questions resolve for free on the read path.)
| # | Gate | Default | Fires when → what happens |
|---|---|---|---|
| 1 | hit_threshold | auto (~0.33 OpenAI) | best unit's blended score 0.7·topic + 0.3·seed is below the floor → MISS: retrieve + synthesize a new unit |
| 2 | hit_margin | 0.0 (off) | top unit beats the runner-up by less than the margin → ambiguous: build the query's own unit instead of guessing |
| 3 | freshness | always on | matched unit is dirty or expired → re-materialize it before serving |
| 4 | coverage | max per-claim cosine | always computed — how well the matched unit covers the query; gates 5–7 read this value |
| 5 | cross_unit_recall | on | coverage < recall_threshold → sweep the best claims across all fresh units (MaxSim) and surface bridge facts; lifts coverage, no LLM call. A unit-path mechanism — the pool path has no recall step (its scan is already cross-unit) |
| 6 | coverage_scorer (S2) | None (off) | coverage lands in [coverage_floor, coverage_ceiling) → a cross-encoder / NLI / one-token-LLM check overrides cosine |
| 7 | coverage_floor | auto (~0.28 OpenAI) | coverage still below the floor → ESCALATE: append fresh raw retrieval (no LLM call) — the RAG floor |
| 8 | select_floor | None (lexical trim) | serve the atoms whose per-claim cosine ≥ floor — the query-relevant claims by meaning, fewer tokens. Deprecated in v0.5 — superseded by the pool read path (read_path="pool") |
A hit that survives gate 1–2, is fresh (3), covers the query (4–7), and is trimmed (8) is served straight from the cache. A miss at gate 1 or an ambiguous tie at gate 2 builds a new unit; an under-covered hit at gate 7 escalates to fresh raw — never a fabricated answer.
Before the ladder: residual_floor (build time)
residual_floor runs once, at build, not on the read path. When the extractor drops a number-bearing source span — its max per-claim cosine falls below the floor — that span is kept verbatim as an extra atom bound to the unit, closing the extractor-recall gap for stray numbers. residual_limit (default 24) caps how many are kept, ranked most-missed first, so the safety net can't defeat the token win. Embedding-only, no extra LLM call; None = off.
v0.5 hooks around the unit ladder
New in v0.5 Five knobs sit at fixed points around the eight gates. All default off; preset="multi_hop" arms the recall pair with calibrated thresholds, and explicit kwargs always override the preset.
| Knob | Where it sits | What it does |
|---|---|---|
adaptive_hit | gate 1 | the hit bar self-calibrates against cross-unit score inflation as the cache grows; repeat/paraphrase queries keep hitting via the seed-reuse channel |
provenance_admission | before any build | an exact-text containment probe — covered reads serve without building (duplicate-understanding prevention) |
widen_chunks | on a miss (build) | the build reads up to N chunks of the dominant source (a duck-typed retriever.widen(artifact_id, limit=) or a BYO source_fetcher) instead of the retrieval keyhole; never fires at ingest. widen_on_admission extends it to thin-coverage admission rebuilds |
recall_bridge / bridge_limit | gate 5 | a hop-2 restart: rank other units' claims by similarity to the matched unit's own claims, reaching the second hop the query alone can't. Armed by preset="multi_hop" |
serve="pool" / serve_budget | after the ladder | the v0.5 preview of pool serving — deprecated in v0.6 (removal v0.7), superseded by read_path="pool" and never auto-mapped to it. See Context intelligence |
Pool path ladder (v0.6, opt-in)
New in v0.6 On the pool path, every read is answered by budget-packing the global fresh-claim pool — the best claims from any fresh unit, ranked against the query — while units remain the ownership / freshness / build / provenance skeleton. Validated at n=605 (strict grading): 0.731 accuracy @ 981 mean tokens with a metadata pool_header — naive top-9's accuracy (0.711 @ 1311) at ~25% fewer context tokens, and naive's best measured point (k12: 0.731 @ 1729) at ~43% fewer; gold-claim serving rank p50/p75/p90 = 1/6/15. The v0.5 preview point (0.699 @ 1036) holds. This is parity at fewer tokens, not an accuracy win over naive — and the shipping default header grades 0.68 on the same rig; the 0.73 rung needs your metadata (pool_header, below).
A pool read runs these stages in order — the code's own stage names:
| Stage | What runs | Branch |
|---|---|---|
| P0 — validate / embed | Embed the query once; mint a deterministic read_id (the handle for report_refusal / report_success) | — |
| P1 — reuse | Seed cosine ≥ reuse_threshold (0.9) = the same question asked again → forced serve, regardless of the gate | a stale reuse match re-materializes in place first; a lossy-marked one (behavioral stack) repairs append-only first — the serve is never stale |
| P2 — pool scan | The ClaimIndex cosine scan over the namespace's claim rows; freshness is a live pull-mask — a stale unit's claims never serve | — |
| P3 — bounded TTL + key overlay | Clock-compare the candidate-head owners only (≤ 3 revalidator calls, ≤ 1 re-scan); then query_keys overlays the scan: a claim's serving score becomes max(content, key), keys counting only at/above key_floor | TTL-expired owners drop out of the candidates |
| P4 — serve gate | Top pre-rerank cosine ≥ the effective serve_gate → serve; below → build. The reranker can never influence this decision | below the gate → P5 |
| P5 — build | One probe retrieval (the read's single query-shaped retrieval), classified per artifact: CONTAINED (zero synthesis — provenance proves coverage) · STALE-OWNED / THIN (rebuild that unit in place) · NOVEL (build a source-anchored unit); ≤ 3 synthesis ops per read, then re-scan the pool | an all-contained probe is a structural admission — serve with zero synthesis |
| serving pipeline | Dedup: within-owner-only near-dup collapse (0.95) — cross-owner duplicates survive as corroboration → rerank hook (serving order only) → pack to serve_budget: headers and separators count, stop at the first overflow, at-least-one always serves | — |
| span side channel | (opt-in residual_spans) a fresh unit's residual span that outranks every fresh claim by span_margin serves as a labeled [source excerpt] line inside the same budget (≤ 2 per read) | each span serve counts a lossy signal on its owner |
| P6 — S2 band | The opt-in coverage_scorer judges the exact texts that will serve, only inside [coverage_floor, coverage_ceiling) | — |
| P7 — RAG floor | Final coverage < coverage_floor → append attributed raw ([source: id]-prefixed chunks), deduplicated, budget-capped, never empty | raw priority: the P5 probe → a reuse-rebuild's chunks → retrieve now |
| P8 — pack & serve | Render claim groups under their attribution headers; cache_hit on this path = zero synthesis calls ran | — |
Three guard rails fire at construction, not mid-read: read_path="pool" under the lexical HashingEmbedder raises (claim cosine collapses to keyword overlap); query_keys=True off the pool path raises (keys could attach and confirm yet structurally never fire); and pool without a pool_header warns — attribution falls back to the built-in header, the middle rung of a measured ~9-point ladder (below).
The pool knobs
| Knob | Default | What it gates |
|---|---|---|
read_path | "unit" | Which ladder runs. "unit" = byte-identical v0.5; "pool" = claim-pool-first serving |
serve_budget | None → 600 unit / 1000 pool | Packed payload size (tokens; headers counted). The one knob most users touch; <= 0 raises |
serve_gate | None (adaptive) | The P4 serve-vs-build decision. Explicit float = absolute (reproducible benches); None adapts against the pool's null-shaped noise ceiling — floored at the embedder-derived coverage default (~0.28 on the shipped OpenAI embedder), ceilinged at that + 0.27 |
pool_header | None → built-in ## {query[:60]}, else [source: {artifact_id}] | Per-source-group attribution line. A measured ladder (n=605, strict): opaque id 0.64 → shipping default 0.68 → metadata callable [title | source | date] 0.73 — wire your own metadata (see the golden path). A raising hook is swallowed, never breaks serving |
reranker | None | Serving order only — never serve/build/floor decisions, so a bad reranker can't cause a false serve or a skipped build |
claim_index | None → built-in | BYO pool storage (ClaimIndex protocol; numpy / pure-python built-ins). A bare instance is single-namespace only; pass the factory form for multi-namespace |
coverage_floor | auto (~0.28 OpenAI) | The same knob as unit gate 7 — on the pool path it is the escalation floor, read against the final (post-build, post-S2) coverage |
Behavioral triggers (default-OFF)
New in v0.6 The behavioral stack sits around the pool ladder, not on it — nothing below runs unless switched on, and the pool defaults are byte-identical with it off. The loop: build-time span capture → read-time side channel → your answerer refuses → report_refusal(read_id) returns an attributed retry payload → report_success(read_id) confirms the rescue as a durable query key → the key overlays the next scan (P3) → repeated signals mark the unit lossy → append-only repair on its next rebuild touch. Measured on the n=605 rig: refusals 91 → 61 (−33%) with zero newly-wrong answers; the full stack +3.1 pts same-store at ~2% token cost; keyed-class first-pass 0% → 61% on mild paraphrases (small n — keys are a latency/round-trip lever, not an accuracy lever on diverse rewordings).
| Knob | Default | What it gates |
|---|---|---|
residual_spans | False | The master switch: build-time span capture, side-channel serving, the report_refusal fallback, lossy marking, append-only repair |
span_tau | 0.62 | Build-time capture: a fact-bearing source sentence whose max claim cosine is below this becomes a tier-2 span on the unit, never in the pool |
span_margin | 0.0 | Read-time rule: a span must outrank the best fresh claim by this margin to serve in the side channel |
span_serve_floor | 0.35 | report_refusal retry floor: minimum span–query cosine to return a retry payload (up to 2 attributed [source excerpt] lines, or None) |
lossy_threshold | 2 | Span serves + raw fallbacks against one unit before it's marked lossy → append-only repair on its next rebuild touch (claims are never lost while the source hash is unchanged) |
query_keys | False | Behavioral alternate keys from confirmed rescues. Requires read_path="pool" — a loud ValueError at construction otherwise |
key_floor | 0.85 | Minimum key–query cosine for a key to count at all — below it the key row is ignored entirely. Raise it in dense same-topic corpora: sibling-article collisions are a measured watch item |
report_refusal and report_success never raise: an unknown or aged-out read_id (64-read ring), a disarmed stack, or no qualifying span is a silent None / no-op. Neither makes a retrieval or LLM call; the only side effect is report_success persisting the confirmed key when a store is configured.
Other hooks on the read path
These sit alongside the ladders rather than on them:
route_by_claim(default off; unit path) — route the match on the best per-claim embedding instead of the unit's topic embedding; useful when one unit holds many distinct facts.relevance_gate— a(query, chunks) -> chunkscallable that drops irrelevant retrieved chunks before synthesis on either path, so a new unit is built from cleaner evidence.depth— on theLLMSynthesizer(0.0terse →0.5balanced default →1.0exhaustive); trades synthesis cost against how often later reads escalate. See The Synthesizer.calibrate_thresholds(embedder, positives, negatives)— derivehit_threshold/coverage_floorfrom labeled(query, understanding)pairs for a custom ortext-embedding-3-largeembedder; the shipped embedders are auto-calibrated already.
Which knob for which workload
| Workload | Reach for |
|---|---|
| Multi-hop — facts split across sources | preset="multi_hop" New in v0.5 — arms recall + the hop-2 bridge with calibrated thresholds (replaces the manual recall_threshold ≈ 0.7) |
| Real-document corpora / cold starts | widen_chunks New in v0.5 — build from the whole source, not the retrieval keyhole |
| Long-running caches | provenance_admission + adaptive_hit New in v0.5 — no duplicate builds; a hit bar that survives cache growth |
| Token-budgeted serving | read_path="pool" + serve_budget New in v0.6 — the pool read path; serve="pool" (the v0.5 preview) is deprecated |
| Per-source / attribution-heavy questions | pool_header New in v0.6 — wire [title | source | date] metadata; the measured 0.64 → 0.68 → 0.73 ladder |
| Contradiction / collision-heavy corpora | hit_margin > 0 — costs rebuilds; leave off on clean data |
| Paraphrase-heavy queries over large units | select_floor — deprecated in v0.5; use read_path="pool" |
| Refusal-heavy answer loops / extraction tails | residual_spans=True + wire report_refusal/report_success New in v0.6 — refusals −33% measured, zero newly-wrong answers |
| Repeat traffic on paraphrased questions | query_keys=True New in v0.6 — keyed-class first-pass 0% → 61% (small n); latency/round-trip compounding, not an accuracy lever on diverse rewordings |
| Messy real-world prose, numbers buried in text | residual_floor — recover extractor-missed number spans |
| High-stakes ambiguity | coverage_scorer (S2) — containment-grade check in the borderline band, on either path |
| Reproduce exact v0.3 behaviour | extract=False + cross_unit_recall=False |
hit_margin and coverage_scorer cost you something — extra rebuilds and an extra check respectively — so they earn their place only on ambiguous or collision-heavy data. On clean, structured corpora the pure-cosine defaults are already at parity; don't turn rungs up you don't need.
Reading the ladder in production
s = cache.stats()
s["hit_rate"] # share of reads served from cache
s["escalation_rate"] # share that hit the RAG floor
s["hit_threshold"], s["hit_margin"], s["coverage_floor"]
s["recall_threshold"], s["select_floor"], s["residual_floor"]
# read_path="pool" adds the pool dashboard:
s["serve_gate_effective"], s["pool_noise_ceiling"] # the gate actually applied
s["pool_serves"], s["probe_reads"], s["admission_reuses"]
s["rebuilds_by_read"], s["builds_by_gap"] # why builds happened
s["pool_claims_fresh"], s["pool_claims_total"], s["pool_mask_rate"]
s["avg_units_per_serve"], s["pool_scan_slow"]
cache.stats() reports hit_rate and escalation_rate alongside the effective operating thresholds — so you can see which rung is doing the work and confirm exactly which knobs are live. A climbing escalation_rate means understanding is too thin (raise depth); a low hit_rate on paraphrases of the same question means the match bar is too high (hit_threshold, unit path). On the pool path, cache_hit means zero synthesis calls ran — probe_reads counts reads that retrieved without building (the gate-miss dashboard), and a climbing builds_by_gap with flat accuracy means the serve gate is set too high.
Next
- Context intelligence — the pool payload contract, the
pool_headergolden path, and the behavioral stack in detail. - The Synthesizer — where extractive understanding and
depthcome from. - get() & Result — the single read API and every field it returns.