← 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:
- Write three memories you know the answer to, and one unrelated to anything.
- Retrieve with
receipt_detail="full" and a limit of 1.
- 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.
- 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.