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
state alongside the messages, so a new run that boots to continue the conversation starts from the summary rather than re-reading the whole transcript and summarising it again. An undo or edit that reaches into the summarised part of the conversation clears the stored summary, and compaction runs again from the edited history when the threshold is next crossed.
This is Trigger.dev’s provider-agnostic compaction. To persist a provider’s own compaction across turns instead (Anthropic context editing or OpenAI stored responses), and to fall back between providers without re-sending history, see Native compaction & provider fallback.
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:
summarize event
Thesummarize callback receives similar context:
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.
