# Explain a search

> **Upcoming in 1.0 (preview). These pages describe the main branch of LLMSlim Core, which becomes the 0.13 release candidate and then 1.0. `pip install llmslim` installs 0.12.0 today, which doesn't have everything described here.** Docs for what pip installs: https://www.llmslim.app/docs.md

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

Memory.search(..., explain=True) says for every stored turn whether it was kept, cut or left out and why, with its tokens and share of the budget.

Category: Guides & Strategies · Version: 1.0 · Updated: November 2026 · 5 min read

## 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.py:

```python
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%}")
```

output:

```text
[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.

| Reason | Meaning |
|---|---|
| `outside_window` | Stored before `since`, or at or after `until`. |
| `over_budget` | It didn't fit in the room left in the budget. |
| `over_share` | It didn't fit in the share its pool or section may take (`user_room`, a section's `cap`). |
| `budget_full` | Never tried: fewer than 8 tokens of room were left when its turn came. |
| `share_full` | Never tried: the share of a summary for whole reply passages was full. |
| `duplicate` | Its words are almost all in text already kept. |
| `dropped_at_fit` | Selected, then dropped because the exact count of the rendered text ran over the budget. |
| `detail_kept` | In a summary, a passage of the turn was kept whole, so its opening wasn't used. |
| `no_candidate` | Nothing of the turn could be selected, such as a system or tool turn in a summary. |
| `clipped_at_end` | The 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:

| Reason | Meaning |
|---|---|
| `user_turn_cap` | A user turn longer than `user_turn_cap`: its most relevant sentences were kept. |
| `windows` | Some of the reply's windows were kept and others weren't. |
| `opening` | In a summary, only the turn's opening (and a reply's outline) is shown. |
| `last_resort` | Nothing fit whole, so the best candidate was cut to fit. |
| `cut_at_fit` | The last candidate left was cut short to fit the exact count. |
| `clipped_at_end` | The 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](https://github.com/Thanatos9404/llmslim/blob/main/docs/MEMORY.md#why-a-turn-was-kept-or-left-out).

## 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.

terminal:

```bash
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.

## FAQ

### 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.

## Related

- [Python quickstart](https://www.llmslim.app/docs/1.0/quickstart-python.md)
- [API reference: llmslim.memory](https://www.llmslim.app/docs/1.0/api-llmslim-memory.md)

Canonical page: https://www.llmslim.app/docs/1.0/explain
