Event compaction
Summarize older session events into a compact replacement so long-running conversations stop growing LLM context without bound.
Sessions are append-only, so a long-lived conversation eventually drags its entire history into every LLM call. Event compaction fixes the read side: after enough invocations accumulate, the runner asks a summarizer to compress the older window into one summary event, and history assembly replaces the covered events with that summary — the model sees summary + recent events instead of everything.
EventsCompactionConfig
EventsCompactionConfig::new(model: Arc<dyn Model>) -> Self- Construct with the default
LlmEventSummarizerovermodel,compaction_interval: 5andoverlap_size: 2. compaction_interval(self, n: usize) -> Self — default 5- Run compaction after this many invocations have accumulated since the previous compaction. Clamped to a minimum of 1.
overlap_size(self, n: usize) -> Self — default 2- Number of already-compacted events to re-include at the start of the next window, for continuity across summaries.
summarizer(self, s: Arc<dyn EventSummarizer>) -> Self- Swap in a custom summarizer.
The summarizer
The EventSummarizer trait has one method: summarize(&self, events: &[Event]) -> Result<Option<Content>>. Returning Ok(None) skips compaction for that window (e.g. nothing summarizable). The default LlmEventSummarizer uses the model you pass to EventsCompactionConfig::new — any Arc<dyn Model>, independent of the agents' models, so you can point it at a cheap, fast model. It renders the window as a transcript (text lines, [calls f(args)], [f returned ...]), sends it with a fixed compaction system instruction, and wraps the reply as "[Summary of earlier conversation] …".
trait EventSummarizer { async fn summarize(&self, events: &[Event]) -> Result<Option<Content>> }- Produces the replacement
Contentfor a window of events. LlmEventSummarizer::new(model: Arc<dyn Model>) -> Self- Default LLM-backed summarizer.
When and what gets compacted
After each invocation completes, the runner counts the distinct invocation ids in the tail — the events after the most recent compaction marker. Once the tail spans at least compaction_interval invocations, the window is formed: overlap_size events from just before the tail (skipping prior compaction markers) plus the whole tail. The summarizer runs, and a marker event is appended whose actions.compaction carries:
struct EventCompaction { start_timestamp: f64, end_timestamp: f64, compacted_content: Content }- Timestamps (seconds) of the earliest and latest compacted events, plus the replacement content — typically the summary.
How history is rebuilt
When an LlmAgent assembles conversation history it calls history_with_compaction(&session.events) instead of mapping events to contents directly. Events whose timestamps fall inside a compaction's [start_timestamp, end_timestamp] range (and that precede the marker) are replaced by the compacted_content, emitted once in place of the first covered event. Marker events themselves never appear in history, and when overlapping compactions cover the same event, the newest one wins.
pub fn history_with_compaction(events: &[Event]) -> Vec<Content>- Assemble LLM history from session events, honouring compaction ranges. Exported from
adk_rs::core.
Example
use adk_rs::core::Model;
use adk_rs::providers::gemini::Gemini;
use adk_rs::runner::{EventsCompactionConfig, Runner};
use std::sync::Arc;
let summarizer_model = Arc::new(Gemini::from_env("gemini-2.5-flash")?);
let runner = Runner::builder()
.app_name("longchat")
.agent(agent)
.session_service(svc.clone())
.compaction(
EventsCompactionConfig::new(summarizer_model as Arc<dyn Model>)
.compaction_interval(8)
.overlap_size(2),
)
.build()?;
// ... after >= 8 invocations on one session, the session log gains a
// marker event (event.actions.compaction is Some) and subsequent turns
// send "[Summary of earlier conversation] ..." instead of the old events.- Events — the event model and
EventActions. - Sessions & state — append-only sessions and persistence.
- Context caching — the complementary lever for large stable prefixes.