Core concepts

The gate ladder

Every read walks a fixed ladder of gates. Since v0.7.0 the read path defaults to pool whenever a semantic embedder is available — the measured path behind every benchmark number; keyless construction falls back to the unit path (the legacy escape hatch, byte-identical pre-0.7).

Every get(query) walks a fixed ladder of gates, top to bottom. There are two ladders, selected by one constructor knob — and New in v0.7 the pool path (read_path="pool" — pooled cross-unit claim ranking, budgeted packing, an adaptive serve gate) is the default whenever a semantic embedder is available, which is every keyed or production deployment. It is the path every published benchmark number was measured on, and the path all v0.7 features (the failure chain included) run on. The unit path remains as the legacy escape hatch and the keyless fallback — pass read_path="unit" for byte-identical pre-0.7 behavior. 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.

TIP

v0.7.0 makes one deliberate default change. Since 0.7.0 the read path defaults to pool whenever a semantic embedder is available — which is every keyed or production deployment. Keyless zero-config construction falls back to the unit path with a warning (pool ranking is measured-unsafe on the keyword-hash fallback embedder). Pass read_path="unit" or "pool" to pin either explicitly; explicit "unit" is byte-identical to pre-0.7 behavior. The measured path is now the default path — 0.6/0.6.1 were the transition releases where it shipped opt-in — and every new v0.7 knob (gap detector, repair, subs/constraints, ...) remains default-OFF and byte-inert until armed.

get() dispatches on read_path first: "pool" (the default under a semantic embedder) runs the pool sequence; "unit" (legacy / keyless fallback) walks the eight-gate ladder exactly as in v0.5. 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. Pool ranking needs a semantic embedder: keyless auto-construction resolves to the unit path with a warning, and explicitly pinning read_path="pool" under the lexical HashingEmbedder still raises (a keyword cosine cannot rank claims) — set OPENAI_API_KEY or pass embedder=.

The two ladders, in firing order

A read flows down its spine — pooled or matched, fresh, covered, served. Each gate can branch off: a below-gate read (pool) or a miss (unit) builds, a stale unit re-materializes, an under-covered serve escalates to attributed raw (the RAG floor), and — with the behavioral stack armed — a downstream refusal re-enters through report_refusal. It never fabricates.

Pool path ladder (the default with a semantic embedder)

On the pool path — the default since v0.7.0 whenever a semantic embedder is available, shipped opt-in through the v0.6 transition releases — 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 (a pool_header callable, or New in v0.7 simply meta= at ingest — the default header is now metadata-first).

A pool read runs these stages in order — the code's own stage names:

StageWhat runsBranch
P0 — validate / embedEmbed the query once; mint a deterministic read_id (the handle for report_refusal / report_success)—
P1 — reuseSeed cosine ≥ reuse_threshold (0.9) = the same question asked again → forced serve, regardless of the gatea 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 scanThe 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 overlayClock-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_floorTTL-expired owners drop out of the candidates
P4 — serve gateTop pre-rerank cosine ≥ the effective serve_gate → serve; below → build. The reranker can never influence this decisionbelow the gate → P5
P5 — buildOne 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 poolan all-contained probe is a structural admission — serve with zero synthesis
serving pipelineDedup: 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 bandThe opt-in coverage_scorer judges the exact texts that will serve, only inside [coverage_floor, coverage_ceiling)—
P7 — RAG floorFinal coverage < coverage_floor → append attributed raw ([source: id]-prefixed chunks), deduplicated, budget-capped, never emptyraw priority: the P5 probe → a reuse-rebuild's chunks → retrieve now
P8 — pack & serveRender 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

KnobDefaultWhat it gates
read_pathauto New in v0.7Which ladder runs. Auto resolves to "pool" (claim-pool-first serving) whenever a semantic embedder is available; keyless construction falls back to "unit" with a warning. Pin either explicitly — explicit "unit" is byte-identical pre-0.7 behavior
serve_budgetNone → 600 unit / 1000 poolPacked payload size (tokens; headers counted). The one knob most users touch; <= 0 raises
serve_gateNone (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_headerNone → 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
rerankerNoneServing order only — never serve/build/floor decisions, so a bad reranker can't cause a false serve or a skipped build
claim_indexNone → built-inBYO pool storage (ClaimIndex protocol; numpy / pure-python built-ins). A bare instance is single-namespace only; pass the factory form for multi-namespace
coverage_floorauto (~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

Unit path ladder (legacy escape hatch · keyless fallback)

read_path="unit" is the legacy escape hatch — byte-identical pre-0.7 behavior: the same eight gates, same defaults, same firing order as v0.5. It was the shipped default through v0.6; since New in v0.7 it runs when you pin it explicitly, or automatically (with a warning) when construction is keyless — the lexical fallback embedder cannot rank a claim pool. (The matched unit is a list of query-independent extractive claims — see The Synthesizer — and cross-unit recall is on on this path, gate 5, so multi-hop questions resolve for free.)

#GateDefaultFires when → what happens
1hit_thresholdauto (~0.33 OpenAI)best unit's blended score 0.7·topic + 0.3·seed is below the floor → MISS: retrieve + synthesize a new unit
2hit_margin0.0 (off)top unit beats the runner-up by less than the margin → ambiguous: build the query's own unit instead of guessing
3freshnessalways onmatched unit is dirty or expired → re-materialize it before serving
4coveragemax per-claim cosinealways computed — how well the matched unit covers the query; gates 5–7 read this value
5cross_unit_recalloncoverage < 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)
6coverage_scorer (S2)None (off)coverage lands in [coverage_floor, coverage_ceiling) → a cross-encoder / NLI / one-token-LLM check overrides cosine
7coverage_floorauto (~0.28 OpenAI)coverage still below the floor → ESCALATE: append fresh raw retrieval (no LLM call) — the RAG floor
8select_floorNone (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.

KnobWhere it sitsWhat it does
adaptive_hitgate 1the hit bar self-calibrates against cross-unit score inflation as the cache grows; repeat/paraphrase queries keep hitting via the seed-reuse channel
provenance_admissionbefore any buildan exact-text containment probe — covered reads serve without building (duplicate-understanding prevention)
widen_chunkson 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_limitgate 5a 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_budgetafter the ladderthe v0.5 preview of pool serving — deprecated in v0.6, superseded by the pool read path (now the default) and never auto-mapped to it. Its deprecation clock runs from 0.7.0, the release that took the flip. See Context intelligence

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).

KnobDefaultWhat it gates
residual_spansFalseThe master switch: build-time span capture, side-channel serving, the report_refusal fallback, lossy marking, append-only repair
span_tau0.62Build-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_margin0.0Read-time rule: a span must outrank the best fresh claim by this margin to serve in the side channel
span_serve_floor0.35report_refusal retry floor: minimum span–query cosine to return a retry payload (up to 2 attributed [source excerpt] lines, or None)
lossy_threshold2Span 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_keysFalseBehavioral alternate keys from confirmed rescues. Requires read_path="pool" — a loud ValueError at construction otherwise
key_floor0.85Minimum 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.

The failure chain (v0.7, default-OFF)

New in v0.7 Around the pool ladder sits a second, deeper set of default-OFF triggers — the failure chain: an observe-only gap_detector that banks repair candidates on every read (serving byte-identical ON vs OFF, pinned), subs=/constraints= for a planner to feed the read, and the explicit verbs your app calls when its evaluator says a read failed — repair(read_id) (append-only, permanent), reprobe(read_id), serve_unserved(read_id). None of it runs unless armed, and arming any of it off the pool path fails loud. Full reference: The failure chain; integration recipes: your failure handling.

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) -> chunks callable that drops irrelevant retrieved chunks before synthesis on either path, so a new unit is built from cleaner evidence.
  • depth — on the LLMSynthesizer (0.0 terse → 0.5 balanced default → 1.0 exhaustive); trades synthesis cost against how often later reads escalate. See The Synthesizer.
  • calibrate_thresholds(embedder, positives, negatives) — derive hit_threshold / coverage_floor from labeled (query, understanding) pairs for a custom or text-embedding-3-large embedder; the shipped embedders are auto-calibrated already.

Which knob for which workload

WorkloadReach for
Multi-hop — facts split across sourcespreset="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 startswiden_chunks New in v0.5 — build from the whole source, not the retrieval keyhole
Long-running cachesprovenance_admission + adaptive_hit New in v0.5 — no duplicate builds; a hit bar that survives cache growth
Token-budgeted servingserve_budget — the pool path (the default) packs to it; serve="pool" (the v0.5 preview) is deprecated
Per-source / attribution-heavy questionsmeta= at ingest New in v0.7 or a pool_header callable — the measured 0.64 → 0.68 → 0.73 ladder
Contradiction / collision-heavy corpora (unit path)hit_margin > 0 — costs rebuilds; leave off on clean data
Paraphrase-heavy queries over large unitsthe pool path (the default) — select_floor (its unit-path ancestor) is deprecated since v0.5
Refusal-heavy answer loops / extraction tailsresidual_spans=True + wire report_refusal/report_success New in v0.6 — refusals −33% measured, zero newly-wrong answers
Repeat traffic on paraphrased questionsquery_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
An answer loop with its own evaluator / failure policygap_detector=True + wire repair to your failure event New in v0.7 — the read reports its own holes, and a failure heals the store permanently. See The failure chain
Extraction misses that keep recurring on the same corpusrepair(read_id) New in v0.7 — span-anchored re-extraction, append-only with per-claim provenance; paid once, first-pass forever
Messy real-world prose, numbers buried in textresidual_floor — recover extractor-missed number spans
High-stakes ambiguitycoverage_scorer (S2) — containment-grade check in the borderline band, on either path
Reproduce exact v0.3 behaviourextract=False + cross_unit_recall=False
CAUTION

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

# the pool dashboard (the default path):
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"]

# legacy unit-path thresholds (read_path="unit"):
s["hit_threshold"], s["hit_margin"], s["coverage_floor"]
s["recall_threshold"], s["select_floor"], s["residual_floor"]

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_header golden path, and the behavioral stack in detail.
  • The Synthesizer — where extractive understanding and depth come from.
  • get() & Result — the single read API and every field it returns.