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

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, the plugin declares a constant that identifies its persisted data:

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:

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:

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:

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 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:

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 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 or withdraw them. This ensures the assistant's behavior matches the user's persisted preference on every subsequent turn.

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:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →