Overview
Long conversations accumulate tokens across turns. Eventually the context window fills up, causing errors or degraded responses. Compaction solves this by automatically summarizing the conversation when token usage exceeds a threshold, then using that summary as the context for future turns. Thecompaction option on chat.agent() handles this in both paths:
- Between tool-call steps (inner loop) — via the AI SDK’s
prepareStep, compaction runs between tool calls within a single turn - Between turns (outer loop) — for single-step responses with no tool calls, where
prepareStepnever fires
Basic usage
ProvideshouldCompact to decide when to compact and summarize to generate the summary:
The
prepareStep for inner-loop compaction is automatically injected when you spread chat.toStreamTextOptions() into your streamText call. If you provide your own prepareStep after the spread, it overrides the auto-injected one.How it works
After each turn completes:shouldCompactis called with the current token usage- If it returns
true,summarizegenerates a summary from the model messages - The model messages (sent to the LLM) are replaced with the summary
- The UI messages (persisted and displayed) are preserved by default
- The
onCompactedhook fires if configured
Customizing what gets persisted
By default, compaction only affects model messages — UI messages stay intact so users see the full conversation after a page refresh. You can customize this withcompactUIMessages:
Summary + recent messages
Replace older messages with a summary but keep the last few exchanges visible:Flatten to summary only
Replace all messages with just the summary (like the LLM sees):Customizing model messages
By default, model messages are replaced with a single summary message. UsecompactModelMessages to customize what the LLM sees after compaction:
Summary + recent context
Keep the last few model messages so the LLM has recent detail alongside the summary:Keep tool results
Preserve tool-call results so the LLM remembers what tools returned:shouldCompact event
TheshouldCompact callback receives context about the current state:
| Field | Type | Description |
|---|---|---|
messages | ModelMessage[] | Current model messages |
totalTokens | number | undefined | Total tokens from the triggering step/turn |
inputTokens | number | undefined | Input tokens |
outputTokens | number | undefined | Output tokens |
usage | LanguageModelUsage | Full usage object |
totalUsage | LanguageModelUsage | Cumulative usage across all turns |
chatId | string | Chat session ID |
turn | number | Current turn (0-indexed) |
clientData | unknown | Custom data from the frontend |
source | "inner" | "outer" | Whether this is between steps or between turns |
steps | CompactionStep[] | Steps array (inner loop only) |
stepNumber | number | Step index (inner loop only) |
summarize event
Thesummarize callback receives similar context:
| Field | Type | Description |
|---|---|---|
messages | ModelMessage[] | Messages to summarize |
usage | LanguageModelUsage | Usage from the triggering step/turn |
totalUsage | LanguageModelUsage | Cumulative usage |
chatId | string | Chat session ID |
turn | number | Current turn |
clientData | unknown | Custom data from the frontend |
source | "inner" | "outer" | Where compaction is running |
stepNumber | number | Step index (inner loop only) |
onCompacted hook
Track compaction events for logging, billing, or analytics:User-initiated compaction
Sometimes you want the user to decide when to compact — a “Summarize conversation” button, a/compact slash command, or a settings toggle. Wire this up with actions: the frontend sends a typed action, onAction runs the summary, and chat.history.set() replaces the conversation.
Backend
Define acompact action that reuses your existing summarize function:
onAction only (plus hydrateMessages if set) — run() and onTurnComplete do not fire for actions. Persist the compacted state directly inside onAction after the chat.history.set call. See Actions for the full lifecycle.
Frontend
Calltransport.sendAction() from a button or slash command:
onTurnComplete replaces the uiMessages with the summary, useChat receives the new state via the normal turn-complete flow — the UI updates automatically.
Indicating compaction in the UI
For “Compacting…” feedback while the summary generates, append a transient data part fromonAction via chat.stream.append():
chat.stream for the full API.
Using with chat.createSession()
Pass the samecompaction config to chat.createSession(). The session handles outer-loop compaction automatically inside turn.complete():
Using with raw tasks (MessageAccumulator)
Passcompaction to the MessageAccumulator constructor. Use prepareStep() for inner-loop compaction and compactIfNeeded() for the outer loop:
Fully manual compaction
For maximum control, usechat.compact() directly inside a custom prepareStep:
chat.compactionStep() factory:
The fully manual APIs only handle inner-loop compaction (between tool-call steps). For outer-loop coverage, use the
compaction option on chat.agent(), chat.createSession(), or MessageAccumulator.
