# Pi Extension Session Persistence and Compaction Re-injection Process Explained

> Learn about Pi extension session persistence and compaction re-injection. Understand how ADHD mode state is maintained across sessions and ruleset markers are re-injected after compaction.

- Repository: [Ayoub Ghriss/i-have-adhd](https://github.com/ayghri/i-have-adhd)
- Tags: deep-dive
- Published: 2026-08-22

---

**The i-have-adhd Pi extension persists ADHD mode state across session lifecycles by appending custom session entries and automatically re-injects ruleset markers after compaction events to maintain consistent model behavior.**

The **ayghri/i-have-adhd** repository implements a Pi extension that must maintain a consistent ruleset presence throughout a conversation session. Because Pi compacts session trees to manage context window limits, the extension implements a robust persistence mechanism that survives restarts, tree rebuilds, and compaction cycles.

## Persisting State with Custom Session Entries

The extension saves the enabled/disabled state as a durable session entry using the `pi.appendEntry` API. When a user toggles ADHD mode via the `/i-have-adhd on` command, the `setEnabled` function serializes the state to the session tree.

In [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts) (lines 57-58), the implementation appends an entry of type `i-have-adhd-state`:

```typescript
function setEnabled(nextEnabled: boolean, ctx: ExtensionContext): void {
  enabled = nextEnabled;
  pi.appendEntry(STATE_ENTRY_TYPE, { enabled } satisfies AdhdModeState);
  updateStatus(ctx);
  syncContext(ctx);
}

```

This **custom session entry** survives across session restarts because it becomes part of the persistent session tree structure, unlike transient in-memory variables.

## Restoring State Across Session Lifecycle Events

To recover the saved state after interruptions or rebuilds, the extension registers handlers for Pi's lifecycle events. The implementation listens for both `session_start` and `session_tree` events in [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts) (lines 219-220):

```typescript
pi.on("session_start", async (_event, ctx) => restoreState(ctx));
pi.on("session_tree",  async (_event, ctx) => restoreState(ctx));

```

The `restoreState` function delegates to `getSavedState` (lines 66-78), which queries the session tree for the most recent `i-have-adhd-state` entry. This ensures that **session persistence** maintains user preferences even when the Pi client reloads or reconstructs the conversation tree from storage.

## Detecting Ruleset Presence in Context

Before injecting or re-injecting content, the extension must determine whether the ruleset currently exists in the model's context window. The helper `rulesAreInContext` (lines 90-96 of [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts)) utilizes `contextMessages` from [`extensions/context-compat.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/context-compat.ts) (lines 12-34) to fetch current context entries.

The detection logic relies on `latestMarkerIsActive` (lines 41-60), which iterates through context messages to identify the most recent custom marker:

```typescript
export function latestMarkerIsActive(
  messages: readonly ContextMessageMarker[],
  activeType: string,
  disabledType: string,
): boolean {
  let active = false;
  for (const message of messages) {
    if (message.role !== "custom" && message.type !== "custom_message") continue;
    if (message.customType === activeType) active = true;
    else if (message.customType === disabledType) active = false;
  }
  return active;
}

```

This function returns `true` only when the last relevant custom message contains the rules injection (`i-have-adhd-rules`) rather than a disabled notice (`i-have-adhd-disabled`).

## Re-injecting Rules After Session Compaction

Pi periodically compacts session trees by summarizing or dropping older entries to manage token limits. The extension handles **compaction re-injection** by listening for the `session_compact` event in [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts) (line 221):

```typescript
pi.on("session_compact", async (_event, ctx) => syncContext(ctx));

```

The `syncContext` function implements the core re-injection logic. It checks `rulesAreInContext` to verify whether the ruleset marker survived the compaction. If ADHD mode is enabled but the marker is missing, the function sends a new custom message containing the full ruleset (lines 21-30). Conversely, if the mode is disabled but a stale rules marker remains, it sends a disabled notice to clear the context (lines 33-40).

```typescript
function syncContext(ctx: ExtensionContext): void {
  const injected = rulesAreInContext(ctx);
  if (enabled && !injected) {
    pi.sendMessage(
      { customType: RULES_MESSAGE_TYPE, content: `${RULES_HEADER}\n\n${rules}` },
      { triggerTurn: false },
    );
  }
  // Handles the disabled case to prevent stale rule application
}

```

This guarantees that the model always receives the correct behavioral instructions regardless of compaction events, without duplicating the ruleset on every turn.

## Summary

- **Custom session entries** store the ADHD mode state via `pi.appendEntry` with type `i-have-adhd-state`, ensuring persistence across restarts.
- **Lifecycle event handlers** for `session_start` and `session_tree` trigger `restoreState` to rehydrate preferences when sessions initialize or rebuild.
- **Context detection** through `latestMarkerIsActive` determines whether the ruleset currently exists in the model's working context.
- **Compaction resilience** via the `session_compact` listener ensures rules are automatically re-injected when tree summarization removes them, using the `syncContext` reconciliation logic.

## Frequently Asked Questions

### How does the Pi extension maintain state after a browser refresh?

The extension persists the enabled/disabled flag as a custom session entry using `pi.appendEntry` with the type `i-have-adhd-state`. When the session restarts, the `session_start` event triggers `restoreState`, which reads the most recent state entry from the session tree and reapplies the user's preference, maintaining continuity across page reloads.

### What triggers the ruleset re-injection mechanism?

The `session_compact` event triggers the re-injection check. When Pi compacts the conversation tree—removing or summarizing older entries to conserve context window space—the extension receives this event and executes `syncContext`. If the compaction removed the ruleset marker while ADHD mode remains enabled, the function automatically sends a fresh ruleset message to restore the intended behavior.

### Why distinguish between `i-have-adhd-rules` and `i-have-adhd-disabled` markers?

The extension uses distinct custom message types to handle state transitions precisely. When ADHD mode is disabled, sending an `i-have-adhd-disabled` marker explicitly overrides any previous ruleset in the context. This prevents the model from continuing to apply ADHD formatting instructions after the user has turned off the feature, ensuring clean state transitions without requiring full context clearing.

### Where does the extension check if the ruleset is currently active?

The `rulesAreInContext` function in [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts) (lines 90-96) performs this check by calling `contextMessages` from [`extensions/context-compat.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/context-compat.ts). It then passes the results to `latestMarkerIsActive` to determine whether the most recent relevant custom message contains active rules or a disabled notice, preventing duplicate injections.