How the i‑have‑adhd Plugin Stores Conversation State: Session Manager Persistence Explained

The i‑have‑adhd plugin stores conversation state by persisting an ADHD‑mode flag as a custom entry type "i-have-adhd-state" in the session manager, allowing the mode to survive session restarts and tree reconstructions.

The ayghri/i-have-adhd extension for Pi (the AI platform) needs to remember whether a user has enabled ADHD‑friendly output across multiple turns of a conversation. Rather than keeping this state only in memory, the plugin implements a robust persistence mechanism that writes directly to the session manager's branch data structure.

Where State Is Stored: The Session Manager Branch

The core storage mechanism relies on pi.appendEntry, a session manager API that appends custom metadata entries to the conversation branch. In extensions/i-have-adhd.ts, the extension defines a dedicated custom entry type for this purpose.

When a user toggles ADHD mode, the plugin calls appendEntry with:

  • Entry type: "i-have-adhd-state"
  • Payload: an object containing the enabled boolean
// Persist the new state
function setEnabled(nextEnabled: boolean, ctx: ExtensionContext) {
  enabled = nextEnabled;
  // Store in session manager – this is the persistent state
  pi.appendEntry("i-have-adhd-state", { enabled } satisfies AdhdModeState);
  updateStatus(ctx);
  syncContext(ctx);
}

This write operation occurs at lines 55–58 of the main extension file, ensuring the flag becomes part of the durable conversation record.

Retrieving State on Session Start or Rebuild

The plugin must recover this state whenever a conversation resumes or the tree structure changes. The getSavedState function in extensions/i-have-adhd.ts handles this by scanning the entire session branch:

// Retrieve the last saved state when a session starts
function getSavedState(ctx: ExtensionContext): boolean | undefined {
  let savedState: boolean | undefined;
  for (const entry of ctx.sessionManager.getBranch()) {
    if (entry.type !== "custom" || entry.customType !== "i-have-adhd-state")
      continue;
    const data = entry.data as Partial<AdhdModeState> | undefined;
    if (typeof data?.enabled === "boolean") savedState = data.enabled;
  }
  return savedState;
}

This iteration strategy (lines 66–80) intentionally processes the entire branch and keeps the last valid entry, ensuring that more recent toggles override earlier ones. The function returns undefined when no state entry exists, triggering fallback behavior.

Fallback Behavior and "Always-On" Configuration

When getSavedState returns undefined, the plugin checks for an alternative signal: the presence of an "always-on" marker file in the conversation context (lines 45–49). This allows administrators or template creators to pre-enable ADHD mode without requiring an explicit user toggle.

The fallback chain operates as:

  1. Last explicit "i-have-adhd-state" entry in the branch
  2. Always-on marker detection via latestMarkerIsActive
  3. Default disabled state

Syncing Rules Based on Retrieved State

Once the persisted state is determined, syncContext executes the actual behavioral change. This helper either injects the ADHD‑friendly rule set from skills/i-have-adhd/SKILL.md into the model context or removes it, depending on the enabled flag value.

The separation between persistence (setEnabled/getSavedState) and application (syncContext) creates clean architectural boundaries:

  • State storage knows nothing about model prompts
  • Rule injection knows nothing about where the flag came from

User-Facing Toggle Command

State changes originate from the slash command handler registered at lines 22–24:

// Toggle the mode (e.g. via the slash command)
pi.registerCommand("i-have-adhd", {
  description: "Toggle ADHD‑friendly output for this session",
  handler: async (args, ctx) => {
    const arg = args.trim().toLowerCase();
    if (arg === "" ) setEnabled(!enabled, ctx);      // flip
    else if (arg === "on") setEnabled(true, ctx);   // enable
    else if (arg === "off") setEnabled(false, ctx); // disable
  },
});

Each execution path ultimately triggers setEnabled, which pers persists the new value before updating the status bar and syncing context rules.

Key Implementation Files

File Purpose
extensions/i-have-adhd.ts Defines custom entry type "i-have-adhd-state"; implements setEnabled, getSavedState, and syncContext
extensions/context-compat.ts Provides contextMessages and latestMarkerIsActive utilities for rule presence detection
skills/i-have-adhd/SKILL.md Contains the actual ADHD‑friendly rule set injected when mode is enabled

Summary

  • Storage mechanism: Custom session manager entries via pi.appendEntry("i-have-adhd-state", { enabled })
  • Retrieval method: Full branch scan in getSavedState extracting the last matching entry
  • Durability: Survives session restarts, tree reconstructions, and conversation compactions
  • Fallbacks: Supports always-on marker files and defaults to disabled
  • Architecture: Clean separation between state persistence and rule application

Frequently Asked Questions

Does the i‑have‑adhd plugin store state in local files or browser storage?

No. According to the ayghri/i-have-adhd source code, state is stored in the session manager's branch structure, not local files or browser storage. The pi.appendEntry API writes to the conversation's persistent metadata, which is synchronized across devices through the platform's existing session infrastructure.

What happens if multiple ADHD state entries exist in the same conversation branch?

The getSavedState function intentionally iterates through the entire branch and keeps the last valid entry it finds. More recent toggles automatically override earlier ones, creating a natural history trail while ensuring current preference takes precedence.

Can ADHD mode be enabled by default without user interaction?

Yes. The plugin checks for an "always-on" marker via latestMarkerIsActive in extensions/context-compat.ts when no explicit state entry exists. This allows conversation templates or system configurations to pre-enable the mode before any user command.

Why scan the entire branch instead of reading the most recent entry directly?

The session manager API exposes getBranch() as an iterable sequence without indexed access to custom entry types. The linear scan approach (lines 66–80 of extensions/i-have-adhd.ts) handles edge cases like entry compaction or metadata interleaving while maintaining predictable O(n) performance for typical conversation lengths.

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 →