New in v0.7 Every production pipeline already has a failure structure — retries, fallback ladders, evaluator/critic nodes, tool escalation. Coalent never owns your failure policy — it composes with it. The cache contributes two things to the structure you already run:
- Signals ($0, advisory, on every read):
confidence,coverage,needs_retrieval,gaps,read_id— inputs to your existing failure logic, never actions. Coalent never string-matches answers and never decides "failed" itself; your evaluator (a structured answer flag, a judge node, or these signals) makes the call. - Verbs (explicit, called by your app when its evaluator says failed):
repair(read_id)→ a secondget()→reprobe(read_id)→serve_unserved(read_id), plus v0.6'sreport_refusal(read_id). The recommended ladder on a persistent refusal — but each verb is independent and safe alone.
Behind both sits the trust section: every non-disturbance property is a pinned test, not a promise — see the guarantees table.
The two adoption modes — the risk decision
Make this choice explicitly; it is the only real risk decision in adopting v0.7:
| Mode | What you pass | First-pass behavior | The claim |
|---|---|---|---|
| Zero-risk | nothing new | the plain pool default — first-pass serving identical to the measured v0.6 pool configuration; the chain engages only on failure | qualitative by construction: no new first-pass behavior to risk |
| Accuracy-max | your planner's subs= | first-pass ranking changes too | the measured 0.826 vs 0.774 composition (benchmark) |
Both compositions ship; subs= is caller-owned, never guessed. Start in zero-risk mode — it costs nothing and cannot regress a correct read — and graduate to subs= when your planner already produces decompositions.
Recipe A — plain RAG chain, no failure handling yet
One if-statement. This becomes your first failure-handling rung ever — and unlike a retry, it permanently heals the store:
r = cache.get(question)
answer = my_llm(r.context["pool"])
if refused(answer): # your own refusal check
cache.repair(r.read_id) # re-extract what the build missed — permanent
r = cache.get(question) # repaired claims now compete
answer = my_llm(r.context["pool"])
The same failure never costs you twice: the repaired claims serve first-pass on every future read, for every user.
Recipe B — agent with an evaluator/critic node
Don't add a new failure taxonomy — map the outcome classes your evaluator already emits:
| Your evaluator's existing class | What Coalent adds |
|---|---|
| refusal / incomplete | the repair ladder: repair(read_id) → get() → reprobe → serve_unserved |
| judged wrong, with evidence served | nothing — retrieval cannot fix a judgment miss, and pretending otherwise wastes a repair. Route it to your answerer-side handling (prompt, model, adjudication) |
missing source (gaps with kind == "corpus_hole") | your fetch tool → ingest → retry. The cache tells you what's missing; it never fetches on its own authority |
verdict = evaluator(answer, r) # YOUR node, YOUR classes
match verdict:
case "refusal" | "incomplete":
cache.repair(r.read_id)
r = cache.get(question); answer = my_llm(r.context["pool"])
case "missing_source":
for gap in r.gaps:
if gap["kind"] == "corpus_hole":
ingest(my_fetch_tool(gap["probe"])) # your tools, your index
r = cache.get(question); answer = my_llm(r.context["pool"])
case "wrong_with_evidence":
pass # not a retrieval problem — honestly
Coalent slots under classes you already have; nothing about your graph topology changes.
Recipe C — pipeline with an existing fallback ladder
You already run rerank → bigger-k → web search. Insert the chain before the expensive rungs — repair and reprobe cost embeds plus one small extraction, cheaper than a web search — and if the chain fails, your ladder continues unchanged:
answer = try_answer(r)
if failed(answer):
# NEW, cheap rungs first: embeds + one small BYO extraction
cache.repair(r.read_id)
r2 = cache.get(question); answer = try_answer(r2)
if failed(answer) and (r3 := cache.reprobe(r2.read_id)):
answer = try_answer(r3)
if failed(answer) and (r4 := cache.serve_unserved(r2.read_id)):
answer = try_answer(r4)
if failed(answer):
answer = your_existing_ladder(question) # rerank → bigger-k → web search, unchanged
An additive rung, never a replacement — the chain's worst case hands your ladder exactly the read it would have received anyway, plus a permanently healthier store for next time.
Recipe D — batch / async QA
No serve-path latency budget? Collect flagged read_ids and run repair as a background pass — the store is healed for the next batch, at zero serve-path latency cost:
flagged: list[str] = []
for question in batch:
r = cache.get(question)
answer = my_llm(r.context["pool"])
if failed(answer):
flagged.append(r.read_id)
emit(question, answer)
# later / overnight — the healing pass
for read_id in flagged:
cache.repair(read_id) # permanent: the NEXT batch reads the repaired store
Every recipe ends the same way: your failure structure stays in charge; Coalent adds rungs and signals to it.
The knob-adoption ladder
One step at a time; each step names what it costs and what it cannot break — the full ladder with guarantees:
- OFF — none of the chain runs; serving is the plain pool default (every new v0.7 knob byte-inert, pinned).
gap_detector=True— the freegapssignal; changes nothing (serving byte-identical, pinned).- Wire
repairto your failure event — the pump. Failure-gated, it matches always-on accuracy at 14% of the extraction calls. reprobe/serve_unservedon persistent refusal — $0, structurally safe.subs=/constraints=from your planner — the opt-in serving changes; the risk decision above.
The DON'Ts
- Don't string-match answers to detect refusal inside library glue. Define failure in your own contract — a structured answer flag, a judge node, or the read's signals. The library will never do it for you, by design.
- Don't call
repairon successful reads. It's measured waste: always-on repair spends most of its extraction on already-correct reads for zero gain — failure-gating matches its accuracy at 14% of the extraction calls. - Don't treat
gapsas errors. They're advisory — the read's own doubt, typed so your evaluator can weigh them. A non-emptygapson a correct answer needs no action. - Don't expect the library to fetch. A
corpus_holenames what's missing; fetching is your tool's job, on your authority, into your index.
Next
- The failure chain — every surface in full: activation conditions, costs, pinned guarantees.
- get() & Result — the signals on every read.
- Claude Code & MCP / LangChain — the same signals surfaced through the integrations.