# How `extensions/i-have-adhd.ts` Persists State Across Session Branches

> Discover how extensions/i-have-adhd.ts persists state across session branches. Learn about custom entry types and branch history scanning for ADHD mode persistence.

- Repository: [Ayoub Ghriss/i-have-adhd](https://github.com/ayghri/i-have-adhd)
- Tags: internals
- Published: 2026-09-10

---

**The i-have-adhd extension persists state across session branches by writing custom entry types to the Pi session manager and scanning the branch history on startup to restore the most recent ADHD mode flag.**

The `ayghri/i-have-adhd` repository implements a Pi extension that maintains "ADHD mode" status throughout complex conversation trees. Unlike global configuration, this design ensures each session branch maintains its own independent state, allowing users to toggle the mode differently across parallel conversation threads while preserving those choices through tree compactions and restorations.

## Custom Session Entries as the Persistence Mechanism

The extension defines a dedicated custom entry type to store its binary state. In [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts) at line 22, the constant is declared:

```typescript
const STATE_ENTRY_TYPE = "i-have-adhd-state";

```

Whenever the user toggles ADHD mode, the extension appends a new entry to the current branch rather than modifying external files. The `setEnabled` function calls `pi.appendEntry` with the current state (lines 174-176):

```typescript
pi.appendEntry(
  STATE_ENTRY_TYPE,
  { enabled } satisfies AdhdModeState,
);

```

This operation stores the entry directly in the session manager’s current branch, ensuring the state snapshot travels with the conversation history. Each toggle generates a new entry, creating a timeline of state changes within that branch.

## Reading and Restoring State from Branch History

On every session initialization or branch restoration, the extension scans the current branch to find the most recent state entry. The `getSavedState` function (lines 81-96) iterates through `ctx.sessionManager.getBranch()`:

```typescript
function getSavedState(ctx: ExtensionContext): boolean | undefined {
  let savedState: boolean | undefined;
  for (const entry of ctx.sessionManager.getBranch()) {
    if (entry.type !== "custom" || entry.customType !== STATE_ENTRY_TYPE) {
      continue;
    }
    const data = entry.data as Partial<AdhdModeState> | undefined;
    if (typeof data?.enabled === "boolean") {
      savedState = data.enabled;
    }
  }
  return savedState;
}

```

The function processes entries chronologically, so the **last matching entry** (the most recent state) overwrites previous values. This guarantees that the extension always reflects the latest user decision for that specific branch, even after navigating backward in time or loading saved snapshots.

## Session Lifecycle Integration

The extension hooks into Pi’s session lifecycle through the `restoreState` handler, which listens for `"session_start"`, `"session_tree"`, and `"session_compact"` events (lines 37-39). During restoration (lines 61-70), the code resolves the effective state through a priority chain:

```typescript
const savedState = getSavedState(ctx);
const enabledByDefault =
  pi.getFlag("adhd") === true ||
  config.alwaysOn === true ||
  existsSync(alwaysOnFlag);
enabled = savedState ?? enabledByDefault;
updateStatus(ctx);
syncContext(ctx);

```

If `getSavedState` returns `undefined` (no history exists for the branch), the extension falls back to CLI flags, configuration files, or the presence of a hidden `.i-have-adhd-always` file. After determining the state, `syncContext` reads the branch again to decide whether to inject the ADHD ruleset or a disabled notice, ensuring the UI matches the persisted logic.

## Branch-Local State Inheritance

Because the session manager serializes the branch with conversation history, any new branch created from an earlier point inherits the last persisted entry from that historical point. When users toggle the mode, `setEnabled` appends a fresh custom entry, and subsequent `syncContext` calls re-scan the branch to determine the active ruleset. This design makes the extension’s state **branch-local** rather than global, meaning:

- Parallel branches can maintain different ADHD mode states simultaneously
- Restoring an old branch restores the mode as it existed at that time
- Tree compactions preserve the relevant history because the extension handles `"session_compact"` events (line 39)

## Summary

- The extension defines `STATE_ENTRY_TYPE = "i-have-adhd-state"` at line 22 to identify its custom entries
- State changes persist via `pi.appendEntry()` which stores data in the current session branch (lines 174-176)
- Restoration relies on `getSavedState()` scanning `ctx.sessionManager.getBranch()` to find the most recent entry (lines 81-96)
- The "last entry wins" logic ensures branches reflect their most recent toggle state
- Integration with `"session_start"`, `"session_tree"`, and `"session_compact"` events guarantees state survives compactions and tree manipulations
- Fallback mechanisms check CLI flags, config files, and filesystem markers when no branch history exists

## Frequently Asked Questions

### What happens if no previous state exists in the branch history?

If `getSavedState` finds no matching entries, the extension falls back to a priority chain: it checks the `adhd` CLI flag (`pi.getFlag("adhd")`), the `config.alwaysOn` setting, and finally the existence of a `.i-have-adhd-always` file in the workspace. The first truthy value in this sequence determines the initial state, ensuring the extension works even in new conversations.

### How does the extension handle session tree compactions?

The `restoreState` handler listens for the `"session_compact"` event (line 39) alongside `"session_start"` and `"session_tree"`. When compaction occurs, the session manager preserves the custom entries within the branch, and the handler re-scans the compacted history to maintain the correct state. Since each state change appends a new entry rather than modifying old ones, compaction naturally retains the most recent values.

### Can different branches have different ADHD mode states simultaneously?

Yes. Because state is stored as branch-local custom entries rather than global variables, each conversation branch maintains its own independent history of toggles. A user can enable ADHD mode in one branch while leaving it disabled in another, and both states will restore correctly when their respective branches are accessed.

### What data structure is stored in the custom session entry?

The entry stores an object satisfying the `AdhdModeState` interface containing a single boolean property `enabled`. The code explicitly uses TypeScript’s `satisfies` keyword when appending to ensure type safety: `{ enabled } satisfies AdhdModeState`. This minimal structure keeps storage overhead low while providing clear semantic meaning when scanning branch history.