# How the i-have-adhd Plugin Persists State Across Conversation Turns

> Discover how the i-have-adhd plugin persists state across conversation turns. Learn about pi.appendEntry and getSavedState for seamless session management.

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

---

**The i-have-adhd plugin persists state across conversation turns by writing a custom session entry via `pi.appendEntry` and restoring it on every session restart using `getSavedState` to scan the session branch.**

The `ayghri/i-have-adhd` plugin tracks whether ADHD-friendly mode is active by leveraging Pi's session manager to persist state across conversation turns. Instead of relying on volatile memory, it writes a typed state object into the conversation tree and automatically retrieves the latest value whenever the session restarts or the tree changes. This design guarantees that a user's preference remains active until explicitly disabled.

## Using Custom Session Entries to Persist State Across Conversation Turns

The plugin implements a lightweight but durable persistence layer using three core operations: defining a unique entry type, appending state snapshots to the session branch, and scanning that branch to recover the most recent value.

### Defining the Custom State Entry Type

At the top of [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts), the plugin declares a constant that identifies its persisted data:

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

```

This string acts as a discriminator inside Pi's custom entry system, ensuring the plugin only reads entries it created and ignores unrelated session data.

### Saving State with pi.appendEntry

When a user toggles ADHD mode, the `setEnabled` function updates the in-memory flag and immediately persists it to the session tree:

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

```

The `pi.appendEntry` call writes a custom entry into the current session branch. Because Pi's runtime persists the session tree across conversation turns, this entry survives until the user clears the session or overwrites it with a new toggle.

### Restoring State from the Session Branch

On every `session_start` or `session_tree` event, the plugin invokes `restoreState`, which fetches the latest saved value or falls back to default flags:

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

```

The helper `getSavedState` iterates over `ctx.sessionManager.getBranch()` to locate the most recent custom entry:

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

```

By walking the branch in order, the function naturally returns the latest persisted toggle, making the plugin resilient across restarts and tree mutations.

## Synchronizing Context After Restore

After restoring or changing the flag, the plugin calls `syncContext` to inject the ADHD ruleset from [`skills/i-have-adhd/SKILL.md`](https://github.com/ayghri/i-have-adhd/blob/main/skills/i-have-adhd/SKILL.md) into the conversation context when enabled, or withdraw it when disabled. This ensures that the persisted state translates directly into visible behavior on every turn.

## Toggling ADHD Mode from Commands

Users interact with persistence through the registered `i-have-adhd` command. The handler parses arguments and delegates to `setEnabled`:

```typescript
pi.registerCommand("i-have-adhd", {
  description: "Toggle ADHD-friendly output for this session",
  handler: async (args, ctx) => {
    const arg = args.trim().toLowerCase();
    if (arg === "on")  setEnabled(true, ctx);
    else if (arg === "off" || arg === "stop") setEnabled(false, ctx);
    else setEnabled(!enabled, ctx);
  },
});

```

Issuing `stop adhd mode` writes a new custom entry with `enabled: false`, which `getSavedState` picks up on the next turn.

## Summary

- The plugin defines `STATE_ENTRY_TYPE = "i-have-adhd-state"` in [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts) to tag its custom entries.
- It persists the boolean flag by calling `pi.appendEntry` inside `setEnabled`, writing the value into the session branch.
- It restores state by iterating `ctx.sessionManager.getBranch()` in `getSavedState` to find the latest matching custom entry.
- The restored value drives `syncContext`, which adds or removes the ADHD ruleset from the active conversation context.
- Default enablement can also come from `pi.getFlag("adhd")` or an `alwaysOnFlag` file if no saved state exists.

## Frequently Asked Questions

### Where does the i-have-adhd plugin store its enabled state?

The plugin stores its state as a custom entry inside Pi's session manager by calling `pi.appendEntry(STATE_ENTRY_TYPE, { enabled })`. This writes the data directly into the current session tree, and the Pi runtime automatically persists that tree across conversation turns. No external database or file is required after the initial write.

### What happens if no previous state is found in the session branch?

If `getSavedState` finds no matching custom entry, the plugin evaluates `pi.getFlag("adhd")` and checks for an `alwaysOnFlag` file on disk. When none of these sources indicate the mode should be on, the plugin defaults to disabled.

### Can the persisted state survive a full session restart?

Yes. Because the entry is stored inside the session tree via `ctx.sessionManager`, it is automatically restored on `session_start` and `session_tree` events. The plugin calls `restoreState` during these events to recover the latest value.

### How does the plugin update the conversation after restoring state?

After determining the correct `enabled` value, the plugin calls `syncContext` to either inject the rules from [`skills/i-have-adhd/SKILL.md`](https://github.com/ayghri/i-have-adhd/blob/main/skills/i-have-adhd/SKILL.md) or withdraw them. This ensures the assistant's behavior matches the user's persisted preference on every subsequent turn.