Skip to main content

Explain a search

Ask Memory.search why it kept, cut or left out each turn, with explain=True.

5 min readUpdated November 2026New in 1.0

An example

Four turns, a question, a budget of 60 tokens and a time window. The explanation covers every stored turn of the scope, oldest first, including the one the window left out.

explain.pypython
from datetime import datetime, timezone

from llmslim.memory import Memory

memory = Memory()
day = lambda d: datetime(2026, 10, d, 9, 0, tzinfo=timezone.utc)
memory.add({"role": "user", "content": "I live in Delhi, near Lodhi Garden."}, user_id="asha", timestamp=day(1))
memory.add({"role": "user", "content": "I moved to Pune last month, to Koregaon Park."}, user_id="asha", timestamp=day(20))
memory.add({"role": "assistant", "content": "Noted: you live in Pune now, in Koregaon Park."}, user_id="asha", timestamp=day(20))
memory.add({"role": "user", "content": "Book me a window seat whenever you can."}, user_id="asha", timestamp=day(21))

context = memory.search("Where did I move to?", user_id="asha", budget=60, since="2026-10-10", explain=True)
print(context.text, end="\n\n")
for turn in context.explanation.turns:
    print(f"{turn.role:9} {turn.status:8} {str(turn.reason):15} {turn.tokens:3} tokens  {turn.share:.0%}")
outputtext
[2026-10-20 (Tue) 09:00] user: I moved to Pune last month, to Koregaon Park.
assistant: Noted: you live in Pune now, in Koregaon Park.

user      dropped  outside_window    0 tokens  0%
user      kept     None             30 tokens  50%
assistant kept     None             17 tokens  28%
user      dropped  over_budget       0 tokens  0%

Each turn's tokens is what its block takes in the returned text, and share is that over the budget. The kept reply shows assistant: without a date because it is in the same session as the turn above it.

What an explanation holds

context.explanation is a SearchExplanation:

  • the call: mode (whole_history, turns or digest), route (the kind of question: lookup, advice, overview or other), budget, tokens, room (what the sections left for turns), user_room (the part reserved for user turns), user_turn_cap and clipped;
  • turns: every stored turn of the scope, with its session, its pool (user or reply), status (kept, clipped or dropped), reason, tokens and share;
  • each turn's candidates: a user turn is one, a reply is cut into windows that are each one, with its rank and score in its pool, its size and what became of it;
  • sections: the standing requests, preferences and conflicts sections, whether each was shown and why not, its cap, and every statement it weighed, best first.

The numbers add up: with tiktoken, the turns' and sections' tokens sum to tokens exactly. With the 4-characters-per-token estimate, counting block by block can round a few tokens either way, and the rest is unattributed_tokens.

Why a turn was left out

When more than one reason applies, the explanation gives the one the search checked first.

ReasonMeaning
outside_windowStored before since, or at or after until.
over_budgetIt didn't fit in the room left in the budget.
over_shareIt didn't fit in the share its pool or section may take (user_room, a section's cap).
budget_fullNever tried: fewer than 8 tokens of room were left when its turn came.
share_fullNever tried: the share of a summary for whole reply passages was full.
duplicateIts words are almost all in text already kept.
dropped_at_fitSelected, then dropped because the exact count of the rendered text ran over the budget.
detail_keptIn a summary, a passage of the turn was kept whole, so its opening wasn't used.
no_candidateNothing of the turn could be selected, such as a system or tool turn in a summary.
clipped_at_endThe last resort cut the text to whole words, and this block was after the cut.

Why a turn was cut

A clipped turn was kept in part:

ReasonMeaning
user_turn_capA user turn longer than user_turn_cap: its most relevant sentences were kept.
windowsSome of the reply's windows were kept and others weren't.
openingIn a summary, only the turn's opening (and a reply's outline) is shown.
last_resortNothing fit whole, so the best candidate was cut to fit.
cut_at_fitThe last candidate left was cut short to fit the exact count.
clipped_at_endThe last-resort cut went through its block.

Sections and the statements they weigh have reasons of their own, such as route (this kind of question doesn't get the section) or no_disagreement. The full list is in the reference.

Over HTTP

llmslim serve takes "explain": true on a search and adds explanation, as to_dict() gives it. The rest of the response is the same. The TypeScript client doesn't pass the flag yet, and the recall tool of llmslim mcp returns text for a model and doesn't explain.

terminalbash
curl -s localhost:8080/v1/memories/search -H 'content-type: application/json' \
  -d '{"user_id": "asha", "query": "Where did I move to?", "explain": true}'

Limits

When the text is clipped as a last resort, context.turns lists a turn only when a whole sentence of it is left, while the explanation counts every block the cut text reaches. A time window is read twice: to name the turns it left out, the scope is read again without it.

Questions

Does explain change the context?

No. The text, the turns and the token counts are the same as without it. With explain off, the default, no extra work is done.

Can I log explanations?

Yes. They name turns by id and never repeat their text, so an explanation can be stored without the conversation in it. to_dict() gives JSON and SearchExplanation.from_dict reads it back.

Can I compare scores across turns?

Only within one pool. Scores are the ranking's own numbers: they don't compare across pools, calls or relevance settings.