TypeScriptADK-TS

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:

FieldTypeRequiredDescription
compactionIntervalnumberYesNumber of new invocations needed to trigger compaction
overlapSizenumberYesPrior invocations to include from the last compacted range for context continuity
summarizerEventsSummarizerNoCustom 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