Getting started

Quickstart

Build, read, and invalidate a cognitive cache in a minute — query in, decision-ready context out.

This walks through the full loop with zero services and no API key (the in-memory retriever + stub synthesizer). One thing to know: keyless construction runs the legacy unit path with a warning — pool ranking (the default with a semantic embedder since v0.7) needs real embeddings. The freshness loop below is identical on both paths, and step 5 goes real.

1. Create a cache

Coalent needs two things: a retriever (where your sources live) and a synthesizer (how raw becomes understanding).

from coalent import SemanticCache, InMemoryRetriever, StubSynthesizer

retriever = InMemoryRetriever()
retriever.add("confluence:98231", "Leave policy: full-time staff get 21 days of annual leave.")
retriever.add("confluence:44120", "Remote work: up to 3 days per week with manager approval.")

cache = SemanticCache(retriever, StubSynthesizer())

2. Get understanding

There is one read method: get() — just pass the question.

ctx = cache.get("how much annual leave do we get?")

print(ctx.cache_hit)             # False the first time (cold build)
print(ctx.understanding["claims"])  # atomic, source-grounded claims — the substance, not a prose blob
print(ctx.context)               # the minimum decision-relevant payload
print(ctx.raw_text)              # the retained raw evidence — has the "21 days"

Ask it again, phrased differently, and it's a semantic hit — same meaning, served from cache:

again = cache.get("what is the leave allowance?")
print(again.cache_hit)  # True — matched by meaning, not by keywords

3. Keep it fresh

When a source changes, Coalent marks only the units that used it stale. The artifact_id is the source's natural id — the same one your retriever stamped.

result = cache.source_changed("confluence:98231", text="Leave policy: now 25 days of annual leave.")
print(result.dirtied)   # the units that used confluence:98231

The next matching get() rebuilds that unit lazily — everything else stays cached.

NOTE

You rarely hand-type source_changed. In production it's wired to your ingestion pipeline, a webhook/CDC feed, or a FreshnessPolicy for feed-less APIs. The id always comes from the source itself — see the examples.

4. Inspect

print(cache.stats())    # units, tracked_artifacts, hit_rate, escalation_rate, effective thresholds, ...

5. Go real — the pool path + the failure chain

Swap in a real LLM and real embeddings, and two things upgrade at once: the understanding gets sharper (LLMSynthesizer builds extractive understanding by default — a query-independent list of atomic, source-grounded claims that keeps every number), and New in v0.7 the read path resolves to pool — the measured default whenever a semantic embedder is available. Pass meta= at ingest and the payload's attribution headers render the measured [title | source | date] golden path with no extra code:

from coalent import SemanticCache, LLMSynthesizer, OpenAIProvider, OpenAIEmbedder

retriever.add("confluence:98231",
              "Leave policy: full-time staff get 21 days of annual leave.",
              meta={"title": "Leave policy", "source": "HR wiki", "date": "2026-01-10"})

cache = SemanticCache(retriever, LLMSynthesizer(OpenAIProvider()),
                      embedder=OpenAIEmbedder())          # semantic embedder → pool path

r = cache.get("how much annual leave do we get?")
print(r.context["pool"])   # the budget-packed, attributed claim payload — hand it to your LLM
r.read_id                  # the handle for the failure chain, below

And when a read fails by your app's own judgment, the cache heals itself permanently — repair(read_id), reprobe, serve_unserved. See The failure chain.

TIP

The extractive default is extract=True New in v0.4 — claims, not a question-shaped prose summary (understanding["summary"] may be terse or empty; the claims are the substance). One line back to prose: LLMSynthesizer(OpenAIProvider(), extract=False).

Multi-hop answers

On the pool path (the default) multi-hop needs no knob at all: every read ranks the claims of all fresh units, so evidence split across documents competes in one pool by construction.

On the legacy unit path (read_path="unit"), multi-hop is a mechanism: when the matched unit under-covers a query, cross-unit recall pools per-claim memory across all fresh units (MaxSim) and surfaces the bridge facts at zero extra LLM calls — preset="multi_hop" New in v0.5 arms it with calibrated thresholds:

cache = SemanticCache(retriever, LLMSynthesizer(OpenAIProvider()),
                      read_path="unit", preset="multi_hop")   # the legacy path, pinned

ans = cache.get("can a remote employee still take their full annual leave?")
for r in ans.recalled:      # RecalledClaim(claim, score, unit_id) — claims pulled from OTHER units
    print(r.unit_id, round(r.score, 2), r.claim)
NOTE

The v0.5 serve="pool" preview is deprecated (superseded by the pool read path; never auto-mapped; its removal clock runs from 0.7.0). See Context intelligence for the full pool reference.

Where to go next