Event Compaction
Automatically summarise long ADK-TS session histories with LLM-generated compaction events so context windows stay manageable without losing key information.
As a conversation grows, sending the full event history to the LLM on every turn becomes expensive and eventually hits the model's context limit. Event compaction solves this by periodically replacing a window of older events with a single LLM-generated summary event. Your agent continues to receive relevant context; the session history stays compact.
How it works
After each agent turn, ADK-TS checks how many new invocations have completed since the last compaction. When that count reaches compactionInterval, it selects a sliding window of events to summarise, calls the configured summariser, and appends the result as a special compaction event. Future LLM requests skip the original events inside the window and use the summary instead.
The sliding window includes a configurable overlapSize — a few invocations from the previously-compacted range — so there is no abrupt context gap between successive summaries.
Quick start
Pass eventsCompactionConfig when building your runner. Compaction then happens automatically with no further code changes:
import { AgentBuilder } from "@iqai/adk";
const { runner } = await AgentBuilder.create("assistant")
.withModel("gemini-2.5-flash")
.withInstruction("You are a helpful assistant.")
.withEventsCompaction({
compactionInterval: 10, // compact after every 10 new invocations
overlapSize: 2, // include 2 prior invocations for continuity
})
.build();
// Use runner normally — compaction is transparent
const response = await runner.ask("Hello!");Configuration
EventsCompactionConfig has three fields:
| Field | Type | Required | Description |
|---|---|---|---|
compactionInterval | number | Yes | Number of new invocations needed to trigger compaction |
overlapSize | number | Yes | Prior invocations to include from the last compacted range for context continuity |
summarizer | EventsSummarizer | No | Custom summariser — defaults to LlmEventSummarizer using the agent's own model |
A higher compactionInterval retains more history before compacting. A lower
value compacts more aggressively — useful for very long sessions. Start with
10 / 2 and tune based on your model's context budget.
Configuring via Runner directly
If you construct the Runner manually rather than through AgentBuilder:
import { Runner } from "@iqai/adk";
import type { EventsCompactionConfig } from "@iqai/adk";
const compactionConfig: EventsCompactionConfig = {
compactionInterval: 10,
overlapSize: 2,
};
const runner = new Runner({
appName: "my-app",
agent: myAgent,
sessionService: mySessionService,
eventsCompactionConfig: compactionConfig,
});LlmEventSummarizer
When no summarizer is provided, ADK-TS uses LlmEventSummarizer with the agent's own model. You can also instantiate it explicitly to use a different model — for example, a smaller, cheaper model dedicated to summarisation:
import { AgentBuilder, LlmEventSummarizer, LLMRegistry } from "@iqai/adk";
const cheapModel = LLMRegistry.newLLM("gemini-2.0-flash-lite");
const summarizer = new LlmEventSummarizer(cheapModel);
const { runner } = await AgentBuilder.create("assistant")
.withModel("gemini-2.5-flash")
.withEventsCompaction({
summarizer,
compactionInterval: 10,
overlapSize: 2,
})
.build();LlmEventSummarizer accepts an optional second argument — a custom prompt template. Use {events} as the placeholder where the formatted event list will be inserted:
const summarizer = new LlmEventSummarizer(
myModel,
`Summarise this conversation history concisely, preserving:
- Key decisions and conclusions
- Important facts and named entities
- Any open questions or action items
Conversation:
{events}`,
);Custom summariser
For full control, implement the EventsSummarizer interface. Return an Event with actions.compaction set, or undefined to skip compaction for this batch:
import { Event, EventActions } from "@iqai/adk";
import type { EventsSummarizer } from "@iqai/adk";
class BulletPointSummarizer implements EventsSummarizer {
async maybeSummarizeEvents(events: Event[]): Promise<Event | undefined> {
if (events.length < 5) return undefined; // skip small batches
const lines = events
.filter(e => e.content?.parts?.[0]?.text)
.map(e => `${e.author}: ${e.content!.parts![0].text}`);
const summary = await generateBulletSummary(lines.join("\n"));
return new Event({
invocationId: Event.newId(),
author: "user",
actions: new EventActions({
compaction: {
startTimestamp: events[0].timestamp,
endTimestamp: events[events.length - 1].timestamp,
compactedContent: {
role: "model",
parts: [{ text: summary }],
},
},
}),
});
}
}Pass the custom summariser in the config:
const { runner } = await AgentBuilder.create("assistant")
.withModel("gemini-2.5-flash")
.withEventsCompaction({
summarizer: new BulletPointSummarizer(),
compactionInterval: 10,
overlapSize: 2,
})
.build();Inspecting compaction events
A compaction event has no text content of its own — its payload is entirely in event.actions.compaction. You can detect and inspect compactions in your event loop:
for await (const event of runner.runAsync(request)) {
if (event.actions.compaction) {
const { startTimestamp, endTimestamp, compactedContent } =
event.actions.compaction;
console.log(
`Compacted events from ${new Date(startTimestamp * 1000).toISOString()} ` +
`to ${new Date(endTimestamp * 1000).toISOString()}`,
);
console.log("Summary:", compactedContent.parts?.[0]?.text);
}
}To check after a run whether a session has ever been compacted:
const session = await runner.sessionService.getSession(
"my-app",
"user-1",
"session-1",
);
const hasCompaction = session?.events.some(e => e.actions?.compaction) ?? false;Next steps
Working with Events
Read text content, detect final responses, inspect tool calls, track state changes, and filter event streams from ADK-TS agents.
Streaming
Stream text responses in real time from ADK-TS agents using textStreamFrom, collectTextFrom, and streamTextWithFinalEvent — purpose-built utilities for partial event handling.