Pi vs OMP Extension Implementation Differences in i-have-adhd: A Technical Comparison

Both the Pi and OMP extensions in i-have-adhd share identical core logic but differ only in runtime-specific SDK imports and underlying infrastructure, with the same source file extensions/i-have-adhd.ts powering both runtimes.

The ayghri/i-have-adhd repository provides an ADHD-friendly coding assistant mode that can be injected into AI conversations. The project uniquely ships the same extension for two different agent runtimes: Pi (a local daemon-based runtime) and OMP (a web-socket server-based runtime). Understanding how these implementations differ helps developers port extensions between runtimes or debug runtime-specific issues.

Runtime-Specific SDK Imports

The most visible difference between the Pi and OMP extensions lies in their import statements. Both implementations reside in the same file (extensions/i-have-adhd.ts), but the runtime environment determines which SDK package provides the ExtensionAPI.

Pi extension import:

import { getAgentDir, type ExtensionAPI, type ExtensionContext } from "@earendil-works/pi-coding-agent"

OMP extension import:

import { getAgentDir, type ExtensionAPI, type ExtensionContext } from "@earendel-works/omp-agent"

Despite the different package names, both imports expose identical type names: ExtensionAPI, ExtensionContext, and getAgentDir. The concrete implementations differ internally—Pi's SDK wraps a local daemon process, while OMP's SDK communicates via web-socket—but the extension code consumes them through the same interface contract.

Flag and Command Registration APIs

Both runtimes use the same registration patterns, but the underlying registries are runtime-specific.

Flag Registration

pi.registerFlag("adhd", {
  description: "Enable ADHD-friendly mode",
  default: false
})

The registerFlag method exists on both Pi and OMP ExtensionAPI objects. According to the i-have-adhd source code, the semantics are identical: the flag can be set via CLI (--adhd) or through the "always-on" file mechanism. Only the flag store implementation differs—Pi maintains flags in its daemon memory, while OMP persists them through its server.

Command Registration

pi.registerCommand("i-have-adhd", {
  description: "Toggle ADHD mode",
  handler: async (ctx, args) => {
    // Toggle logic...
    return { action: "handled" }
  }
})

Both runtimes expect the same return shape: { action: "handled" | "continue" | "transform" }. However, the execution path differs: Pi invokes commands through local process IPC, while OMP routes them through web-socket messages.

Event Hook Implementations

The extension listens to four session lifecycle events in both runtimes:

  • "input" — triggered on every user utterance
  • "session_start" — fires when a new session begins
  • "session_tree" — fires when session state is restored
  • "session_compact" — fires after session compaction

Hook registration uses identical syntax:

pi.on("input", async (ctx, event) => {
  // Rule-set injection logic
})

The event names and handler signatures are runtime-agnostic. The difference lies in the event emitter implementation: Pi's events originate from the local daemon's event loop, while OMP's events propagate from the web-socket connection. Timing characteristics may vary slightly due to network latency in OMP versus local IPC in Pi.

State Persistence Mechanism

Both implementations persist the enabled state using the same entry type:

const STATE_ENTRY_TYPE = "i-have-adhd-state"

// Persisting state
pi.appendEntry(STATE_ENTRY_TYPE, { enabled: true })

// Reading state
const entries = ctx.session.getEntries(STATE_ENTRY_TYPE)

The appendEntry and getEntries methods are part of the ExtensionAPI contract. Pi's implementation appends to a local session branch; OMP's implementation serializes entries across the web-socket to the OMP server's session manager. The entry shape ({ enabled: boolean }) remains unchanged between runtimes.

Context Synchronization Logic

The extension uses the shared helper latestMarkerIsActive from extensions/context-compat.ts to determine whether to inject the rule-set or a disabled notice:

import { latestMarkerIsActive } from "./context-compat"

const shouldInject = latestMarkerIsActive(ctx, "adhd-rules")

This helper is runtime-agnostic. It operates on the ExtensionContext message list, which is populated by whichever runtime's session manager is active. The algorithm for determining the "latest marker" is identical in both Pi and OMP contexts.

"Always-On" File Detection

Both extensions check for an agent-local file to enable automatic ADHD mode:

const alwaysOnPath = path.join(getAgentDir(), ".i-have-adhd-always")
const alwaysOn = fs.existsSync(alwaysOnPath)

The getAgentDir() function resolves to:

  • Pi: ~/.config/pi-agent/ (or platform equivalent)
  • OMP: The OMP server's configured agent directory

This path resolution is the only runtime-specific behavior; the file existence check and resulting flag behavior are shared.

UI Theme Compatibility

Visual feedback uses identical theme tokens:

const statusIcon = ctx.ui.theme.fg("success", "●")
const statusText = ctx.ui.theme.fg("accent", "ADHD ON")

Both Pi and OMP UI themes implement the "success" and "accent" color tokens, ensuring consistent visual output regardless of runtime.

Architecture Summary Table

Aspect Pi Implementation OMP Implementation
SDK Package @earendil-works/pi-coding-agent @earendil-works/omp-agent
Transport Local daemon IPC Web-socket server
Event Source Pi daemon event loop OMP server event emitter
Session Storage Local branch files Server-persisted entries
Agent Directory ~/.config/pi-agent/ OMP server agent path
Core Logic Identical Identical

Practical Porting Example

The same extension loads into either runtime with only the agent creator changing:

Pi runtime entry point:

import { createPiAgent } from "@earendil-works/pi-coding-agent"
import iHaveAdhdExtension from "./extensions/i-have-adhd"

const pi = createPiAgent()
pi.loadExtension(iHaveAdhdExtension)
pi.start()

OMP runtime entry point:

import { createOmpAgent } from "@earendil-works/omp-agent"
import iHaveAdhdExtension from "./extensions/i-have-adhd"

const omp = createOmpAgent()
omp.loadExtension(iHaveAdhdExtension)
omp.start()

The i-have-adhd.ts file requires zero modifications between these two setups. The runtime-specific ExtensionAPI implementation is injected by the respective create*Agent function.

Summary

  • Single source file: Both Pi and OMP extensions compile from extensions/i-have-adhd.ts with no runtime-specific branches in the core logic.

  • SDK swap: The only code-level difference is the package name in the import statement—@earendil-works/pi-coding-agent versus @earendil-works/omp-agent.

  • Pluggable infrastructure: Flag registries, command dispatchers, event emitters, and session managers are abstracted behind the ExtensionAPI interface.

  • Identical user experience: Commands, configuration files, color themes, and state persistence behave the same regardless of runtime.

  • Shared utilities: Helper functions like latestMarkerIsActive in extensions/context-compat.ts are runtime-agnostic.

Frequently Asked Questions

Why does i-have-adhd use the same file for both Pi and OMP?

The extensions/i-have-adhd.ts file contains no runtime-specific conditional logic. Both Pi and OMP provide an ExtensionAPI that satisfies the same interface contract. The runtime environment (determined by which SDK is imported) supplies the concrete implementation, allowing the extension code to remain portable.

Can I run both Pi and OMP versions simultaneously?

Yes, but they operate as separate processes with separate session stores. The Pi extension manages local daemon sessions; the OMP extension connects to a remote or local OMP server. They do not share state unless you configure external synchronization.

What happens if I mix Pi and OMP SDK imports in the same project?

This produces type conflicts and runtime errors. The ExtensionAPI types from @earendil-works/pi-coding-agent and @earendil-works/omp-agent are structurally identical but nominally distinct. Your build system should enforce one runtime per process—the package.json scripts in i-have-adhd demonstrate this separation with distinct "pi" and "omp" entry points.

How do I debug which runtime is active?

Check the resolved path from getAgentDir(). In extensions/i-have-adhd.ts, the extension logs or error messages that include the agent directory will reveal whether you're running under Pi (~/.config/pi-agent/) or OMP (server-configured path). The runtime-specific package name will also appear in stack traces.

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 →