chat.history.set(), so the next turn is derived from the already-reduced conversation. And because a native handle is provider-specific, this page also shows how a provider-agnostic Trigger.dev compaction summary lets you fall back between providers without re-expanding the context.
The full runnable example is
triggerdotdev/resilient-chat-example. See native-persist.ts for the Anthropic persistence flow and resilient-chat.ts for OpenAI stored responses plus provider fallback.Two kinds of compaction
They are not competing; they compose. Native compaction is the per-turn optimization, and Trigger.dev compaction is the durable, portable checkpoint.Persist Anthropic native context editing
Anthropic’scontextManagement clears old tool-use/tool-result blocks server-side per request, and reports how many it cleared in providerMetadata.anthropic.contextManagement.appliedEdits (clearedToolUses, clearedInputTokens). It does not touch your accumulated history, so on its own the next turn still re-sends everything.
The fix: read the appliedEdits counts as they stream in onStepFinish, then after the turn mirror that clearing into stored history with chat.history.set(). No custom summarizer is involved, since the provider’s native editing drives what gets persisted.
/trigger/native-persist.ts
onTurnComplete prunes those four from stored history, so turn 2 re-sends the smaller conversation (one tool result, not six) instead of the full transcript.
onTurnComplete is where persistence happens. Action turns fire onAction only, and a chat.history.set() inside run() is overwritten by the accumulator at turn end. See Persistence and replay.Persist OpenAI stored responses
OpenAI’sstore: true keeps the thread server-side and returns a responseId. Pass that back as previousResponseId on the next turn and send only the messages since the last assistant reply; everything before it lives on OpenAI’s side.
/trigger/openai-store.ts
1/3), because OpenAI already has the rest.
Fall back between providers without losing history
A native handle is a per-provider cache. An OpenAIpreviousResponseId means nothing to Anthropic, and Anthropic’s server-side edits don’t exist on OpenAI. So when a provider is down and you fall back to another, the native optimization is a cache miss, and a naive fallback re-sends the entire raw transcript to the new provider.
Trigger.dev’s compaction is the portable checkpoint that closes this gap. summarize returns a plain string and compactModelMessages returns neutral ModelMessage[], so the summary survives any provider switch. Tag each native handle with the provider that produced it. On a switch it’s a cache miss, and you rebuild from the summary instead of re-expanding the context.
/trigger/resilient-chat.ts
compaction has already reduced messages to a summary plus the last couple of exchanges, the switch sends the portable baseline, not megabytes of raw transcript.
Production notes
- Persist the handles. The
Maps above (nativeStore,clearedByChat) work in the example because the run stays alive across turns, but they don’t survive a run boundary. Store native handles and summaries in your database keyed bychatId, alongside your message persistence. - No cross-provider translation. Native compaction from one provider never transfers to another. The Trigger.dev
compactionsummary is the only portable baseline across a switch. - Native compaction is opt-in per turn. It applies only for the provider whose
providerOptionsyou set on that turn’sstreamTextcall.
See also
- Compaction: the provider-agnostic
compactionoption,onCompacted, and manualchat.compact(). - Prompt caching: the other per-turn token optimization, and how it interacts with a growing history.
- Database persistence: where to store native handles and summaries for real.
- Lifecycle hooks:
onTurnCompleteandonCompactedin the broader hook taxonomy.

