# How the i-have-adhd Skill Re-injects on Session Start: A Technical Deep Dive

> Discover how the i-have-adhd skill restores ADHD-friendly rules on session start. Learn about the technical mechanism involving session lifecycle events and state restoration.

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

---

**The i-have-adhd extension hooks into the runtime's session lifecycle events (`session_start` and `session_tree`) to call `restoreState()`, which determines the enabled state and uses `syncContext()` to inject ADHD-friendly rules once per session using `pi.sendMessage()`.**

The `ayghri/i-have-adhd` repository implements a robust persistence mechanism that ensures ADHD-friendly conversation rules survive session boundaries without duplicate injections. When a new session begins or a session tree is restored, the extension automatically re-injects the ruleset by listening to specific runtime events and managing context state programmatically.

## Session Lifecycle Event Hooks

The re-injection mechanism relies on three core session events registered in [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts). These listeners ensure the skill's state is restored whenever the conversation context changes.

The extension registers handlers for:

- **`session_start`** – Fires when a new conversation session initializes
- **`session_tree`** – Fires when restoring an existing session tree
- **`session_compact`** – Fires after context compaction/summarization

```typescript
// In extensions/i-have-adhd.ts
pi.on("session_start", async (_event, ctx) => restoreState(ctx));
pi.on("session_tree", async (_event, ctx) => restoreState(ctx));
pi.on("session_compact", async (_event, ctx) => syncContext(ctx));

```

The `session_compact` listener is critical because context summarization can strip injected content; calling `syncContext()` after compaction ensures the rules survive this process.

## State Restoration and Default Enablement

The `restoreState()` function determines whether the skill should be active for the current session. It checks saved state first, then falls back to configuration sources to set the default enabled status.

The function performs four key operations:

1. Retrieves previously saved state via `getSavedState(ctx)`
2. Evaluates default enablement from the `adhd` flag, `alwaysOn` configuration, or the presence of a hidden `.i-have-adhd-always` file
3. Updates the UI status indicator
4. Invokes `syncContext(ctx)` to conditionally inject rules

```typescript
function restoreState(ctx: ExtensionContext): void {
  const savedState = getSavedState(ctx);
  const enabledByDefault =
    pi.getFlag("adhd") === true ||
    config.alwaysOn === true ||
    existsSync(alwaysOnFlag);

  enabled = savedState ?? enabledByDefault;
  updateStatus(ctx);
  syncContext(ctx);               // Re‑inject if needed
}

```

This logic ensures that user preferences persist across session restarts while respecting explicit configuration overrides.

## Context Synchronization and Rule Injection

The `syncContext()` function in [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts) manages the actual injection logic. It prevents duplicate rules by checking whether the ruleset is already present in the model's context using `latestMarkerIsActive()` from [`extensions/context-compat.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/context-compat.ts).

When enabled and rules are missing, the function injects them via the runtime's message API:

```typescript
function syncContext(ctx: ExtensionContext): void {
  const injected = rulesAreInContext(ctx); // Uses latestMarkerIsActive internally

  if (enabled && !injected) {
    pi.sendMessage(
      {
        customType: RULES_MESSAGE_TYPE,
        content: `${RULES_HEADER}\n\n${rules}`,
        display: false,
      },
      { triggerTurn: false },
    );
  } else if (!enabled && injected) {
    pi.sendMessage(
      {
        customType: DISABLED_MESSAGE_TYPE,
        content: DISABLED_NOTICE,
        display: false,
      },
      { triggerTurn: false },
    );
  }
}

```

The `triggerTurn: false` option ensures the injection happens silently without triggering a model response. This approach guarantees exactly one injection per session rather than spamming the context with duplicate rules on every request.

## Key Implementation Files

The re-injection mechanism spans four primary files in the repository:

| File | Purpose |
|------|---------|
| [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts) | Core extension registering session listeners, implementing `restoreState()` and `syncContext()` |
| [`extensions/context-compat.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/context-compat.ts) | Provides `latestMarkerIsActive()` helper to detect existing rules in context |
| [`scripts/check_context_compat.ts`](https://github.com/ayghri/i-have-adhd/blob/main/scripts/check_context_compat.ts) | Validation script ensuring context detection works across runtime versions |
| [`skills/i-have-adhd/SKILL.md`](https://github.com/ayghri/i-have-adhd/blob/main/skills/i-have-adhd/SKILL.md) | Source content containing the ADHD-friendly rules injected into conversations |

These components work together to create a persistent "system prompt" style injection that mirrors the behavior of Claude Code's `SessionStart` hook.

## Summary

- The extension listens to `session_start`, `session_tree`, and `session_compact` events to trigger state restoration
- `restoreState()` determines enablement from saved state, the `adhd` flag, `alwaysOn` config, or the `.i-have-adhd-always` file
- `syncContext()` uses `latestMarkerIsActive()` from [`context-compat.ts`](https://github.com/ayghri/i-have-adhd/blob/main/context-compat.ts) to prevent duplicate injections
- Rules are injected silently using `pi.sendMessage()` with `triggerTurn: false` and `display: false`
- The mechanism survives context compaction by re-syncing after `session_compact` events

## Frequently Asked Questions

### How does the extension prevent duplicate rule injection?

The extension calls `rulesAreInContext()` (which internally uses `latestMarkerIsActive()` from [`extensions/context-compat.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/context-compat.ts)) to check if the ruleset is already present before injecting. This ensures the ADHD-friendly rules appear exactly once per session regardless of how many times the session events fire.

### What triggers the re-injection of the i-have-adhd skill?

Re-injection occurs when the `session_start` or `session_tree` events fire, both of which call `restoreState()`. Additionally, `session_compact` triggers `syncContext()` to re-inject rules after context summarization might have removed them.

### Where is the enabled state stored between sessions?

The enabled state is persisted via `getSavedState()` and restored in `restoreState()`. If no saved state exists, the extension checks the `adhd` runtime flag, the `alwaysOn` configuration option, or the presence of a `.i-have-adhd-always` file in the workspace.

### Why is `triggerTurn: false` used when sending the rules?

The `triggerTurn: false` option in `pi.sendMessage()` prevents the injected rules from triggering an immediate model response. This keeps the injection invisible to the user while ensuring the rules are present in the context for subsequent interactions.