# Earendil Pi Message Compaction: Managing Long Sessions in the AgentSession Pipeline

> Earendil Pi compacts long agent sessions by summarizing history, ensuring smooth LLM interaction. Discover its abort-aware pipeline for customizable session management.

- Repository: [Earendil Works/pi](https://github.com/earendil-works/pi)
- Tags: internals
- Published: 2026-05-25

---

**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`](https://github.com/earendil-works/pi/blob/main/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 in `settings.compaction.reserveTokens`, compaction runs without interrupting the user flow.
- **Manual** (`"manual"`): Explicit invocation via `session.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 a `signal` for cancellation.
- `compaction_start`: Confirms the compaction decision and reason.
- `session_compact`: Fires after summary generation but before storage, containing the `compactionEntry`.
- `compaction_end`: Signals completion (success, error, or cancellation) with `aborted` and `willRetry` flags.

## Core Implementation in AgentSession

The compaction logic resides in **[`packages/coding-agent/src/core/agent-session.ts`](https://github.com/earendil-works/pi/blob/main/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.

```typescript
// 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`](https://github.com/earendil-works/pi/blob/main/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:

```typescript
// Manually compact a session with a custom directive
await pi.session.compact("Summarise the last 10 turns");

```

Configure automatic threshold-based compaction:

```typescript
// Auto-compact when token usage exceeds 120k (reserving 100k)
pi.setSettings({
  compaction: { enabled: true, reserveTokens: 100_000 },
});

```

Register a custom compaction extension:

```typescript
import "./extensions/custom-compaction.ts"; // Registers the hook shown above

```

## Summary

- **Earendil Pi** uses the `AgentSession` class in [`packages/coding-agent/src/core/agent-session.ts`](https://github.com/earendil-works/pi/blob/main/packages/coding-agent/src/core/agent-session.ts) to 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_compact`** hook, which receives an `AbortController` signal and entry metadata.
- The **`CompactionResult`** structure preserves the summary, first kept entry ID, and token statistics for accurate context window management.
- The **`SessionManager`** prevents redundant compactions by tracking the latest entry, while **`session-context-stats.mjs`** provides 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`](https://github.com/earendil-works/pi/blob/main/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`](https://github.com/earendil-works/pi/blob/main/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.