How the i-have-adhd Skill Re-injects on Session Start: A Technical Deep Dive
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. 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 initializessession_tree– Fires when restoring an existing session treesession_compact– Fires after context compaction/summarization
// 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:
- Retrieves previously saved state via
getSavedState(ctx) - Evaluates default enablement from the
adhdflag,alwaysOnconfiguration, or the presence of a hidden.i-have-adhd-alwaysfile - Updates the UI status indicator
- Invokes
syncContext(ctx)to conditionally inject rules
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 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.
When enabled and rules are missing, the function injects them via the runtime's message API:
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 |
Core extension registering session listeners, implementing restoreState() and syncContext() |
extensions/context-compat.ts |
Provides latestMarkerIsActive() helper to detect existing rules in context |
scripts/check_context_compat.ts |
Validation script ensuring context detection works across runtime versions |
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, andsession_compactevents to trigger state restoration restoreState()determines enablement from saved state, theadhdflag,alwaysOnconfig, or the.i-have-adhd-alwaysfilesyncContext()useslatestMarkerIsActive()fromcontext-compat.tsto prevent duplicate injections- Rules are injected silently using
pi.sendMessage()withtriggerTurn: falseanddisplay: false - The mechanism survives context compaction by re-syncing after
session_compactevents
Frequently Asked Questions
How does the extension prevent duplicate rule injection?
The extension calls rulesAreInContext() (which internally uses latestMarkerIsActive() from 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.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →