haiku.rag/docs/capabilities/compaction.md
Yiorgis Gozadinos e1dd8517f9
Refuse to compact evidence the host kept no record of
Both optional capabilities read what earlier questions retrieved and cited from
the capability's state, so a host that carries only the message history hands
every run an empty record. Compaction then replaced the earlier evidence with
receipts and retained nothing, and the loss was invisible: the citations the host
already displayed were still there. It now refuses when it finds evidence from an
earlier question and no record of what that question cited.

`state_carried` reaches the optional capabilities through discovery, so the
refusal distinguishes a host that never carries state from a question that simply
cited nothing.

The documentation taught the pattern that breaks: the compose example is now
stateful and the requirement is stated where each capability is introduced.

The app's browser storage was doing exactly this, keeping only the fields the UI
reads. It now persists the whole namespace map, so the citation policy's
violations survive a reload as well as the evidence record.
2026-08-13 15:46:23 +03:00

49 lines
2.1 KiB
Markdown

# Evidence compaction capability
`EvidenceCompactionCapability` keeps a multi-turn conversation from carrying every
search result it ever produced. Every question adds its evidence to the history, so
requests grow turn after turn, which degrades answers and can exceed a provider's
limits.
Register it alongside an evidence capability:
```python
from pydantic_ai import Agent
from haiku.rag.capabilities.compaction import create_capability as compaction
from haiku.rag.capabilities.rag import create_capability as rag
agent = Agent(
"openai:gpt-5",
capabilities=[rag(db_path="my.lancedb"), compaction()],
)
```
It exposes no tools and takes no configuration. Registering it is the only switch:
leave it out and the transcript reaches the model untouched.
The host must carry the capability state between runs, alongside the message
history: the capsule is built from what earlier questions recorded there. Given
only a message history, every run starts from an empty record, and compaction
refuses rather than replace evidence it cannot retain. See
[Compose an agent](index.md#compose-an-agent) for the shape.
## What it does
On each request, evidence from earlier questions is replaced by the evidence those
questions actually cited. Cited text and cited page images are kept in full, grouped by
the question that cited them, and stay citable by the same chunk ids. Every other
earlier evidence return becomes a short receipt. The current question is untouched.
Compaction rewrites the request, never the stored history, so `all_messages()` still
holds everything the run gathered.
This reduces what a request carries. It does not bound it: retained evidence still
grows with the conversation. A host that needs more aggressive pruning can compact its
own requests further, on the wire only.
## Resuming a question
Resuming a question (deferred tool results, an interruption, a suspension) requires the
host to carry the capability state from the run being resumed, alongside the message
history. Without it the identity of the question in progress is unknowable, and the run
fails rather than silently treating it as a new question.