Skip to main content
← All use cases

The scenario

A support agent answers a customer wrongly. You know the correct fact is in the space, because you put it there. The search did not return it. Without a record of the decision, there are only guesses: raise the limit, lower the score floor, reword the query, try again. Each is cheap to try and none of them tells you whether it was the right thing to try — a memory the search never matched looks exactly like a memory that does not exist. A context receipt is that decision written down. Every retrieve builds one, whether or not you ask for it, so an answer stays explainable after the fact.

Step 1 — Ask for the receipt id

retrieve returns a plain list and has nowhere to put a receipt id. retrieve_receipt runs the same search and returns both.
receipt_detail="full" also asks the search to account for its own cuts. Without it the receipt can only describe what happened after the search returned — which is the half of the story you already knew.

Step 2 — Read what was cut

Each entry carries a reason code, so “it was a near-duplicate of something you already got” is distinguishable from “it scored below your floor” and from “it fell past your limit”. Those three have different fixes. excluded_truncated is how a capped manifest admits it is capped. An incomplete list must never read as a complete one.

Step 3 — Ask about one specific memory

This is the call the whole feature exists for. You know an id. You did not get it back. Ask why.
The outcome splits your problem in two:
  • excluded — it was found and then dropped. stage says where. A bigger limit or a lower floor may well bring it back. arms tells you how it was found: a memory matched by keyword but not semantically shares words with your query but not meaning; the reverse is the more usual case.
  • not_retrieved — nothing matched it at any stage. No threshold change will help, because nothing ever found it. The query wording, or the scope you searched, is what to change.
That distinction is the difference between an afternoon of threshold-tuning and a one-line fix.

Evals

Before you trust a tuning change, check it against a case you can score:
  1. Write three memories you know the answer to, and one unrelated to anything.
  2. Retrieve with receipt_detail="full" and a limit of 1.
  3. Call explain on every id you wrote that did not come back — the runnable script does exactly this by diffing the ids the write reported against the ids the search returned.
  4. Expect the unrelated memory to be not_retrieved and the relevant ones to be excluded at the limit. If a relevant memory comes back not_retrieved, your extraction or your scoping is the problem, not your ranking.
Re-run after any change to scoping, tags or query wording. A recall change that moves nothing in the receipt has not done what you think it did.

Guardrails

A receipt is not a copy of your data, and that is deliberate. It carries ids, scores and reason codes only — never memory content. That is exactly what makes a receipt id safe to paste into a support ticket, a bug report or a vendor conversation.
  • Receipts are built for every retrieve, so you can investigate an answer you did not flag at the time. You do not need to have predicted the problem.
  • relevance_score can exceed 1.0. It is a product of several factors, not a probability. Compare scores within one result set; do not read an absolute threshold into them across queries.
  • A scoped query is strict. If you passed a user_id, agent_id or session_id, untagged memories cannot match — so a memory written before you adopted scoping will read as not_retrieved forever until it is backfilled. Check the scope before you blame the ranking.

The script

Complete and runnable: context_receipts.py. See also the context receipts API reference and Tuning Recall Quality.