Earendil Pi Message Compaction: Managing Long Sessions in the AgentSession Pipeline
Earendil Pi automatically compacts conversation history into summaries when sessions exceed the LLM's context window, using an abort-aware pipeline that extensions can intercept and customize.
The earendil-works/pi repository implements intelligent message compaction to prevent context overflow during extended coding sessions. When conversations grow beyond the token limits of the underlying language model, the system summarizes older messages while preserving conversation continuity. This mechanism is orchestrated by the AgentSession class and is fully extensible through event hooks.
How Earendil Pi Message Compaction Works
Every conversational turn in Earendil Pi is stored as a session entry. When cumulative token usage approaches the model's limits, the runtime triggers an automatic compaction that replaces older messages with a concise summary. The system tracks compaction state using an AbortController (_compactionAbortController) to ensure operations can be cancelled mid-flight.
The Three Triggers for Auto-Compaction
The session checks assistant token usage after each turn (see the auto-compaction check around line 1780 in packages/coding-agent/src/core/agent-session.ts). Three specific conditions initiate compaction:
- Overflow (
"overflow"): When the LLM returns a context overflow error, the session discards the offending message, runs compaction, and automatically retries the request. - Threshold (
"threshold"): When accumulated tokens exceed the reserved amount configured insettings.compaction.reserveTokens, compaction runs without interrupting the user flow. - Manual (
"manual"): Explicit invocation viasession.compact()when users or extensions need on-demand summarization.
The Compaction Event Lifecycle
The compaction pipeline emits granular events that extensions and monitoring tools can observe:
session_before_compact: Emitted immediately before compaction begins, carrying asignalfor cancellation.compaction_start: Confirms the compaction decision and reason.session_compact: Fires after summary generation but before storage, containing thecompactionEntry.compaction_end: Signals completion (success, error, or cancellation) withabortedandwillRetryflags.
Core Implementation in AgentSession
The compaction logic resides in packages/coding-agent/src/core/agent-session.ts. The AgentSession class coordinates the entire workflow across approximately 130 lines of orchestration code (lines 1607–1737).
The compact() method creates the abort controller, emits lifecycle events, and delegates summarization to the compaction module. A critical guard exists around line 1786 where getLatestCompactionEntry() prevents stale usage data from triggering redundant compactions.
Customizing Compaction with Extension Hooks
Extensions can intercept compaction via the session_before_compact event. This hook receives an abort signal and the ID of the first message to be kept, allowing complete replacement of the generated summary or cancellation of the operation.
// packages/coding-agent/examples/extensions/custom-compaction.ts
pi.on("session_before_compact", async (event, ctx) => {
// Cancel the compaction if we don’t want it for this session
if (shouldSkipCompaction(event)) {
event.signal.abort();
return { cancel: true };
}
// Provide a custom summary instead of the default one
return {
compaction: {
summary: "User-provided short recap",
firstKeptEntryId: event.firstKeptEntryId,
tokensBefore: event.tokensBefore,
},
};
});
The runtime checks the extension result (extensionResult?.compaction) and stores the provided object as the new compaction entry if present (lines 1640–1665).
The Compaction Summarization Pipeline
The actual LLM-driven summarization lives in packages/coding-agent/src/core/compaction/index.ts. The compact helper function receives the abort signal, invokes the language model with a system prompt requesting concise summarization, and returns a CompactionResult containing:
summary: The generated condensed text.firstKeptEntryId: The ID of the first retained message post-compaction.tokensBefore: Token count of the session before compaction.details: Optional debugging metadata.
Session Manager Storage and Retrieval
All session entries—including compactions—are persisted by the SessionManager. Two key methods manage compaction state:
appendCompaction(summary, firstKeptId, tokensBefore): Stores a new compaction entry in the session log.getLatestCompactionEntry(): Retrieves the most recent compaction to prevent duplicate triggers and calculate current context usage accurately.
Monitoring and Metrics
The repository includes analytics tooling to track compaction effectiveness. scripts/session-context-stats.mjs aggregates compaction metadata across session logs, counting sessions with compactions and calculating the compaction rate (displayed in the context-usage summary lines 332–335). This metric appears in CI dashboards to monitor long-session handling efficiency.
Practical Code Examples
Manually trigger compaction from a REPL or extension:
// Manually compact a session with a custom directive
await pi.session.compact("Summarise the last 10 turns");
Configure automatic threshold-based compaction:
// Auto-compact when token usage exceeds 120k (reserving 100k)
pi.setSettings({
compaction: { enabled: true, reserveTokens: 100_000 },
});
Register a custom compaction extension:
import "./extensions/custom-compaction.ts"; // Registers the hook shown above
Summary
- Earendil Pi uses the
AgentSessionclass inpackages/coding-agent/src/core/agent-session.tsto automatically compact conversation history when approaching LLM token limits. - Compaction triggers include overflow errors, configurable token thresholds, and manual invocations, each emitting distinct lifecycle events.
- Extensions can customize or cancel compaction via the
session_before_compacthook, which receives anAbortControllersignal and entry metadata. - The
CompactionResultstructure preserves the summary, first kept entry ID, and token statistics for accurate context window management. - The
SessionManagerprevents redundant compactions by tracking the latest entry, whilesession-context-stats.mjsprovides metrics on compaction rates across sessions.
Frequently Asked Questions
What triggers automatic message compaction in Earendil Pi?
Automatic compaction triggers in three scenarios: when the LLM returns a context overflow error ("overflow"), when accumulated tokens exceed the configured reserve threshold ("threshold"), or when explicitly requested via session.compact() ("manual"). The system checks token usage after each assistant turn around line 1780 of agent-session.ts.
How can I customize the compaction behavior in Earendil Pi?
Register an extension listener for the session_before_compact event. The handler receives a signal that can abort the operation and can return a custom compaction object with your own summary, firstKeptEntryId, and tokensBefore values. If the handler returns { cancel: true }, the compaction is skipped entirely.
What data structure does a compaction entry contain?
Each compaction entry follows the CompactionResult interface from packages/coding-agent/src/core/compaction/index.ts, containing a text summary, the firstKeptEntryId referencing the first retained message, the tokensBefore count for metrics, and optional details for debugging. The SessionManager stores this via appendCompaction().
How does Earendil Pi prevent duplicate compaction runs?
The AgentSession queries getLatestCompactionEntry() before triggering auto-compaction (around line 1786). This ensures the system uses fresh usage calculations rather than stale data that might have been invalidated by previous compactions, preventing redundant summarization cycles.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →