# How Ponytail Integrates with Agents That Support Node.js Lifecycle Hooks

> Discover how Ponytail integrates with Node.js agents by leveraging lifecycle hooks like session_start and before_agent_start. Optimize agent output for various environments.

- Repository: [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail)
- Tags: how-to-guide
- Published: 2026-09-03

---

**Ponytail injects mode-specific instructions into Claude-style agents by hooking into Node.js lifecycle events like `session_start`, `before_agent_start`, and `SubagentStart`, formatting output differently for Copilot, Codex, Qoder, and native Claude environments.**

Ponytail is a lightweight "mode" engine from the DietrichGebert/ponytail repository that augments AI agents by intercepting standard lifecycle events in Node.js environments. When an agent supports hooks such as `session_start` or `before_agent_start`, Ponytail writes JSON payloads or additional context fields to ensure the agent receives the complete instruction set at precisely the right execution moment.

## Core Integration Components

Ponytail integrates with **Node.js lifecycle hooks** through four specialized components that detect the host environment and format instructions accordingly.

### ponytail-runtime.js

The **[`ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-runtime.js)** module serves as the environment detector and output formatter. Located at [[`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js)](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js), this script identifies whether the host is Copilot, Codex, Qoder, or native Claude. It manages the mode state via the `.ponytail-active` flag and exposes the `writeHookOutput(event, mode, context)` function that all hook scripts call to emit properly formatted JSON.

### ponytail-subagent.js

For task-spawned sub-agents, **[`ponytail-subagent.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-subagent.js)** implements the **`SubagentStart`** hook. According to the source at [[`hooks/ponytail-subagent.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-subagent.js)](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-subagent.js), this script reads the current mode and optionally filters injection via the `PONYTAIL_SUBAGENT_MATCHER` environment variable to limit instructions to specific `agent_type` values.

### ponytail-instructions.js

The instruction builder resides in **[`ponytail-instructions.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-instructions.js)** at [[`hooks/ponytail-instructions.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-instructions.js)](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-instructions.js). This module constructs the final instruction text from [`skills/ponytail/SKILL.md`](https://github.com/DietrichGebert/ponytail/blob/main/skills/ponytail/SKILL.md) (or a fallback) based on the active mode—`lite`, `full`, or `ultra`. Both hook scripts and the Pi extension call `getPonytailInstructions(mode)` to retrieve the payload.

### pi-extension/index.js

The Pi extension at [[`pi-extension/index.js`](https://github.com/DietrichGebert/ponytail/blob/main/pi-extension/index.js)](https://github.com/DietrichGebert/ponytail/blob/main/pi-extension/index.js) registers Ponytail commands (`/ponytail …`) and listens to Pi-specific lifecycle events. It handles `session_start`, `agent_start`, `agent_end`, and `before_agent_start` events, prepending generated instructions to the `systemPrompt` during `before_agent_start` to ensure the base agent receives full context.

## The Lifecycle Hook Execution Flow

Ponytail integrates with agents through a five-stage lifecycle process that ensures consistent instruction delivery across different entry points.

1. **Mode activation** – Users run `/ponytail <mode>` via the Pi extension or set the `PONYTAIL_MODE` environment variable. The `setMode` function in [`ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-runtime.js) writes the mode to `.ponytail-active`.

2. **Session initialization** – When the agent session begins, the `session_start` event fires. The Pi extension reads the stored mode and updates the UI status bar.

3. **Pre-agent injection** – The `before_agent_start` listener builds instruction text via `getPonytailInstructions` and returns a modified `systemPrompt`. This represents the primary injection path for agents exposing the `before_agent_start` hook.

4. **Sub-agent handling** – When tasks spawn new Claude sub-agents, the [`ponytail-subagent.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-subagent.js) script executes as the `SubagentStart` hook. It checks `PONYTAIL_SUBAGENT_MATCHER` if defined, then calls `writeHookOutput('SubagentStart', …)` to append instructions.

5. **Host formatting** – The `writeHookOutput` function detects the host environment and emits the appropriate JSON structure, ensuring Copilot receives `additionalContext`, Codex receives `systemMessage`, and native Claude receives `hookSpecificOutput`.

## Host-Specific Output Formats

The `writeHookOutput` function in [`ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-runtime.js) adapts its JSON output based on the detected host environment:

- **VS Code Copilot** – Writes `{ additionalContext: … }` during `SessionStart` events to populate the agent's additional context field.

- **Codex** – Outputs `{ systemMessage: "PONYTAIL:…", hookSpecificOutput: { … } }` to ensure the mode is visible in the system message while preserving hook metadata.

- **Qoder** – Emits only the `hookSpecificOutput` object containing the event name and context.

- **Native Claude** – For `SubagentStart` events, writes a plain JSON object with `hookSpecificOutput` containing the instruction payload.

Because every path uses the same [`ponytail-instructions.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-instructions.js) builder, the agent receives identical rules regardless of whether it starts via lifecycle event or sub-agent spawn.

## Practical Implementation Examples

### Activating Ponytail from a Pi Session

Use the Pi extension command interface to enable a specific mode:

```javascript
// In a Pi extension or console:
await pi.runCommand('/ponytail full');   // enables "full" mode

```

### Injecting Instructions via before_agent_start

Hook into the Pi SDK's `before_agent_start` event to prepend instructions to the system prompt:

```javascript
pi.on('before_agent_start', async (event) => {
  // Skip if Ponytail is off
  if (!currentMode || currentMode === 'off') return;
  const base = event.systemPrompt ? `${event.systemPrompt}\n\n` : '';
  return { systemPrompt: `${base}${getPonytailInstructions(currentMode)}` };
});

```

### Scoping Sub-Agent Injection

Limit Ponytail instructions to specific agent types using regex matching:

```bash

# Export a regex that only matches "General-purpose" agents

export PONYTAIL_SUBAGENT_MATCHER='^General-purpose$'

# Run a task that spawns a sub-agent – only matching agents receive instructions

```

### Runtime Output Handling

The core output logic detects the host and formats JSON accordingly:

```javascript
function writeHookOutput(event, mode, context = '') {
  if (isCopilot) {
    process.stdout.write(JSON.stringify(
      event === 'SessionStart' && context ? { additionalContext: context } : {}
    ));
  } else if (isCodex) {
    const out = { systemMessage: `PONYTAIL:${mode.toUpperCase()}` };
    if (context) out.hookSpecificOutput = { hookEventName: event, additionalContext: context };
    process.stdout.write(JSON.stringify(out));
  } else if (event === 'SubagentStart') {
    process.stdout.write(JSON.stringify(
      { hookSpecificOutput: { hookEventName: event, additionalContext: context } }
    ));
  } else {
    process.stdout.write(context);
  }
}

```

## Summary

- **Ponytail** augments Claude-style agents by hooking into Node.js lifecycle events including `session_start`, `before_agent_start`, and `SubagentStart`.
- The integration relies on four core files: [`ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-runtime.js) for host detection, [`ponytail-subagent.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-subagent.js) for task-spawned agents, [`ponytail-instructions.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-instructions.js) for payload generation, and [`pi-extension/index.js`](https://github.com/DietrichGebert/ponytail/blob/main/pi-extension/index.js) for Pi SDK event handling.
- **Host-specific formatting** ensures Copilot receives `additionalContext`, Codex receives `systemMessage`, and other agents receive appropriate `hookSpecificOutput` JSON.
- **Mode management** occurs via the `.ponytail-active` flag, supporting `lite`, `full`, and `ultra` modes that determine the instruction payload complexity.
- **Sub-agent scoping** via `PONYTAIL_SUBAGENT_MATCHER` allows precise control over which agent types receive Ponytail instructions during task execution.

## Frequently Asked Questions

### Which AI agents support Ponytail's Node.js lifecycle hooks?

Ponytail supports Claude-style agents including Claude Code, Codex, Qoder, and VS Code Copilot. According to the source code in [`ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-runtime.js), each host receives specially formatted output—Copilot uses `additionalContext`, Codex uses `systemMessage`, and native Claude uses `hookSpecificOutput`. The [[`AGENTS.md`](https://github.com/DietrichGebert/ponytail/blob/main/AGENTS.md)](https://github.com/DietrichGebert/ponytail/blob/main/AGENTS.md) file in the repository documents the specific capabilities and hook support for each agent type.

### How does Ponytail handle different execution modes?

Ponytail operates in three modes—`lite`, `full`, and `ultra`—defined in [`ponytail-instructions.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-instructions.js). When a user activates a mode via `/ponytail <mode>` or the `PONYTAIL_MODE` environment variable, the system stores the value in `.ponytail-active`. The `getPonytailInstructions(mode)` function then loads the appropriate instruction set from [`skills/ponytail/SKILL.md`](https://github.com/DietrichGebert/ponytail/blob/main/skills/ponytail/SKILL.md) (or a fallback), ensuring agents receive context calibrated to the selected complexity level.

### Can I prevent Ponytail from injecting instructions into certain sub-agents?

Yes. Set the `PONYTAIL_SUBAGENT_MATCHER` environment variable to a regex pattern that matches only the `agent_type` values you want to target. The [`ponytail-subagent.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-subagent.js) script evaluates this regex during the `SubagentStart` hook and only calls `writeHookOutput` for matching agents. If the variable is unset, Ponytail injects instructions into all sub-agents spawned during task execution.

### What is the difference between the Pi extension and the hook scripts?

The **Pi extension** ([`pi-extension/index.js`](https://github.com/DietrichGebert/ponytail/blob/main/pi-extension/index.js)) provides an interactive command interface (`/ponytail …`) and listens to Pi SDK lifecycle events like `before_agent_start` to modify the `systemPrompt` directly. The **hook scripts** ([`ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-runtime.js), [`ponytail-subagent.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-subagent.js)) run as standalone Node.js processes triggered by agent-specific hooks like `SubagentStart`, writing JSON to stdout via `writeHookOutput`. Both paths use [`ponytail-instructions.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-instructions.js) to generate identical instruction payloads, ensuring consistency across injection methods.