# Native Extension Patterns for Pi and OMP in i-have-adhd

> Discover native extension patterns for Pi and OMP in i-have-adhd. Learn how dual runtime targets and a compatibility layer enable seamless integration.

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

---

**The i-have-adhd repository implements a runtime-agnostic native extension for both Pi and Oh-My-Pi (OMP) by declaring dual runtime targets in [`package.json`](https://github.com/ayghri/i-have-adhd/blob/main/package.json), exporting a default function that receives the `ExtensionAPI`, and using a compatibility layer to normalize session-manager API differences between the two environments.**

The `i-have-adhd` project demonstrates how to build cross-platform AI coding agent extensions that inject specialized conversation rules and UI controls. By leveraging a single TypeScript module with runtime-specific declarations and a thin abstraction shim, the extension operates identically on Pi and OMP without requiring platform-specific branches.

## Declaring Dual Runtime Support in package.json

The native extension pattern begins with explicit runtime registration in [`package.json`](https://github.com/ayghri/i-have-adhd/blob/main/package.json). The extension declares which file each runtime should load by defining separate `pi` and `omp` configuration keys that both reference the same entry point.

In [`package.json`](https://github.com/ayghri/i-have-adhd/blob/main/package.json) (lines 9-14), the `extensions` array under both runtime configurations points to [`./extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/./extensions/i-have-adhd.ts):

```json
{
  "pi": {
    "extensions": ["./extensions/i-have-adhd.ts"]
  },
  "omp": {
    "extensions": ["./extensions/i-have-adhd.ts"]
  }
}

```

This dual declaration instructs both Pi and OMP to load the identical TypeScript module as a native extension, establishing the foundation for the runtime-agnostic architecture.

## The Extension Entry Point Pattern

The core implementation resides in [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts), which exports a default function receiving the `ExtensionAPI` object. This function implements six key responsibilities that work uniformly across both runtimes:

1. **Registers the boolean flag** (`adhd`) for feature toggling
2. **Registers the slash-command** (`/i-have-adhd`) for user interaction  
3. **Persists state** via the session-manager entry `i-have-adhd-state`
4. **Injects the rule set** loaded from [`skills/i-have-adhd/SKILL.md`](https://github.com/ayghri/i-have-adhd/blob/main/skills/i-have-adhd/SKILL.md) as a custom message
5. **Updates the UI status line** to display "ADHD ON"
6. **Listens to lifecycle events** (`input`, `session_start`, `session_tree`, `session_compact`) to maintain synchronization

The default export follows this signature structure:

```typescript
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";

export default function iHaveAdhdExtension(pi: ExtensionAPI) {
  // Register persistent flag
  pi.registerFlag("adhd", {
    description: "Enable ADHD assistance mode",
    type: "boolean",
    default: false,
  });
  
  // Register user-facing command
  pi.registerCommand("i-have-adhd", {
    description: "Toggle ADHD mode",
    handler: (args, ctx) => {
      // Toggle implementation
    },
  });
}

```

### Event-Driven State Management

The extension utilizes Pi and OMP's event system to maintain conversation context across turns. It registers handlers for `session_start` to restore previous activation states from persistent storage and `session_compact` to re-inject rules after context summarization. The `input` event handler processes specific trigger phrases—such as `/skill:i-have-adhd` to enable the mode and predefined stop phrases to disable it—returning `{ action: "continue" }` to allow the conversation to proceed.

## Abstracting Runtime Differences with context-compat.ts

To maintain a single codebase across both runtimes, the extension implements a compatibility shim in [`extensions/context-compat.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/context-compat.ts). This module normalizes differences in how Pi and OMP expose session-manager context, specifically abstracting the `buildSessionContext` (Pi) versus `buildContextEntries` (OMP) APIs.

The shim exports two critical utilities imported by the main extension (lines 10-13):

- **`contextMessages`**: Unifies reading session context from either runtime's specific API
- **`latestMarkerIsActive`**: Detects whether the most recent custom marker represents an active rules injection or a disable marker

The main extension uses these helpers to determine when to inject or withdraw the ADHD rule set (lines 90-99):

```typescript
import { contextMessages, latestMarkerIsActive } from "./context-compat";

// Usage within event handlers
const messages = contextMessages(ctx);
const isActive = latestMarkerIsActive(messages);

if (isActive) {
  ctx.session.addContextMessage({ 
    role: "system", 
    content: loadedRules 
  });
}

```

## Practical Implementation Example

A minimal native extension following this pattern requires the [`package.json`](https://github.com/ayghri/i-have-adhd/blob/main/package.json) declaration and a default-exported function implementing the `ExtensionAPI` interface:

```typescript
import type { ExtensionAPI, ExtensionContext } from "@earendil-works/pi-coding-agent";
import { contextMessages } from "./context-compat";

export default function myCrossPlatformExtension(pi: ExtensionAPI) {
  // Register feature flag
  pi.registerFlag("my-feature", {
    description: "Enable custom assistance",
    type: "boolean",
    default: false,
  });

  // Register slash command
  pi.registerCommand("toggle-my-feature", {
    description: "Toggle assistance mode",
    handler: (args, ctx) => {
      const current = ctx.session.getFlag("my-feature");
      ctx.session.setFlag("my-feature", !current);
      return { message: `Feature ${!current ? "enabled" : "disabled"}` };
    },
  });

  // Listen for conversation start to restore state
  pi.on("session_start", async (_, ctx) => {
    const state = ctx.session.get("my-extension-state");
    if (state?.enabled) {
      ctx.session.addContextMessage({ 
        role: "system", 
        content: "Custom rules active" 
      });
    }
  });
}

```

## Summary

- **Dual runtime declaration**: Configure both `pi.extensions` and `omp.extensions` in [`package.json`](https://github.com/ayghri/i-have-adhd/blob/main/package.json) to point to the same TypeScript entry point for cross-platform support.
- **Default export function**: Export a function receiving `ExtensionAPI` to register flags, commands, and event listeners that work identically in Pi and OMP.
- **Compatibility abstraction**: Use a shim module like [`context-compat.ts`](https://github.com/ayghri/i-have-adhd/blob/main/context-compat.ts) to normalize runtime-specific APIs such as `buildSessionContext` versus `buildContextEntries`.
- **State persistence**: Store activation state using the session-manager's key-value API under a custom key like `i-have-adhd-state` to survive conversation lifecycle events.
- **Rule injection**: Inject system instructions via `ctx.session.addContextMessage()` and manage their presence using marker detection utilities.

## Frequently Asked Questions

### How does i-have-adhd handle API differences between Pi and OMP session managers?

The extension uses [`extensions/context-compat.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/context-compat.ts) to normalize differences between runtime APIs. It exports `contextMessages` to read session context from either `buildSessionContext` (Pi) or `buildContextEntries` (OMP), and `latestMarkerIsActive` to detect the current injection state. This abstraction allows the main logic in [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts) to remain identical across both runtimes.

### What specific events does the extension monitor to maintain state?

According to the source code in [`extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/extensions/i-have-adhd.ts), the extension registers handlers for `input` (processing toggle commands like `/skill:i-have-adhd`), `session_start` (restoring persisted state), `session_tree` (handling conversation branching), and `session_compact` (re-injecting rules after context summarization). These events ensure the UI status bar and injected rules remain synchronized with the conversation.

### Can one TypeScript file realistically serve both Pi and OMP without platform detection?

Yes. The repository demonstrates this architecture by pointing both `pi.extensions` and `omp.extensions` to [`./extensions/i-have-adhd.ts`](https://github.com/ayghri/i-have-adhd/blob/main/./extensions/i-have-adhd.ts) in [`package.json`](https://github.com/ayghri/i-have-adhd/blob/main/package.json). The extension loads successfully in both environments because the compatibility shim handles runtime-specific variations, while the core logic relies solely on the standardized `ExtensionAPI` interface provided by both platforms.

### Where are the ADHD conversation rules stored and how are they injected?

The extension loads rules from [`skills/i-have-adhd/SKILL.md`](https://github.com/ayghri/i-have-adhd/blob/main/skills/i-have-adhd/SKILL.md) and injects them as custom messages via `ctx.session.addContextMessage()`. The extension persists an activation marker in the session state under the key `i-have-adhd-state`, enabling it to restore and re-inject rules after compaction events that might otherwise clear temporary context entries.