# How Ponytail Handles Output Formats for Different AI Agents

> Ponytail adapts its output formats for different AI agents by detecting the host environment via environment variables. Get context in the expected format for each agent.

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

---

**Ponytail detects the host environment through specific environment variables and adapts its console output serialization so that each AI agent receives context in its expected format.**

The `DietrichGebert/ponytail` repository implements a flexible hook system that customizes **output formats for different agents** to ensure seamless integration. By inspecting runtime environment markers, Ponytail determines whether it is running inside GitHub Copilot, OpenAI Codex, Qoder, or native Claude, then structures its JSON payloads accordingly.

## Environment Detection and Format Logic

The adaptation logic resides primarily in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js), where the `writeHookOutput` function branches based on boolean flags derived from `process.env` variables. The activation hook [`ponytail-activate.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-activate.js) determines the operational mode, generates instruction text via [`hooks/ponytail-instructions.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-instructions.js), and delegates serialization to `writeHookOutput`.

### GitHub Copilot (VS Code)

Ponytail identifies Copilot by checking for `process.env.COPILOT_PLUGIN_DATA` or an *agent-plugins* path within `process.env.CLAUDE_PLUGIN_ROOT` (flagged internally as `isCopilot`).

For this agent, output is strictly limited to `SessionStart` events. The function writes either a JSON object containing `{ "additionalContext": "…" }` or an empty object `{}` as raw text. All other events produce no console output, preventing noise during the coding session.

```javascript
// Inside ponytail-runtime.js → isCopilot branch
process.stdout.write(JSON.stringify(
  event === 'SessionStart' && context ? { additionalContext: context } : {}
));

```

### OpenAI Codex

When `process.env.PLUGIN_DATA` is present (`isCodex`), Ponytail emits a JSON object containing a `systemMessage` field formatted as `PONYTAIL:${mode.toUpperCase()}`. If the hook provides supplementary text, a `hookSpecificOutput` object is appended containing `hookEventName` and `additionalContext` properties.

```javascript
// Inside ponytail-runtime.js → isCodex branch
const output = { systemMessage: `PONYTAIL:${mode.toUpperCase()}` };
if (context) {
  output.hookSpecificOutput = {
    hookEventName: event,
    additionalContext: context,
  };
}
process.stdout.write(JSON.stringify(output));

```

### Qoder

Detection occurs via `process.env.QODER_SESSION_ID` (`isQoder`). Qoder receives a JSON payload identical to Codex's `hookSpecificOutput` structure but **omits** the `systemMessage` field entirely. This minimalist approach ensures Qoder receives only the contextual data without mode identifiers.

```javascript
// Inside ponytail-runtime.js → isQoder branch
const output = {};
if (context) {
  output.hookSpecificOutput = {
    hookEventName: event,
    additionalContext: context,
  };
}
process.stdout.write(JSON.stringify(output));

```

### Native Claude (Claude Code)

If none of the above environment variables are set, Ponytail assumes a native Claude environment. This handler differentiates between event types: for `SubagentStart` events, it outputs a JSON wrapper containing `hookSpecificOutput`, while all other events stream the raw `context` string directly to stdout.

```javascript
// Inside ponytail-runtime.js → native Claude branch
if (event === 'SubagentStart') {
  process.stdout.write(JSON.stringify(
    { hookSpecificOutput: { hookEventName: event, additionalContext: context } }));
  return;
}
process.stdout.write(context);

```

## Summary

- **Environment-based detection** allows Ponytail to identify GitHub Copilot, Codex, Qoder, and native Claude through specific `process.env` variables.
- **Format adaptation** occurs in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js), where the `writeHookOutput` function selects JSON schemas or raw text based on the detected host.
- **Copilot** receives `additionalContext` only during `SessionStart` events, while **Codex** includes a `systemMessage` prefix indicating the operational mode.
- **Qoder** consumes the same contextual payload as Codex without the system-level message wrapper.
- **Native Claude** switches between JSON-wrapped output for subagent initiation and plain text for standard events.

## Frequently Asked Questions

### How does Ponytail detect which AI agent is running?

Ponytail inspects specific environment variables in `process.env`. It checks for `COPILOT_PLUGIN_DATA` or `CLAUDE_PLUGIN_ROOT` to identify Copilot, `PLUGIN_DATA` for Codex, and `QODER_SESSION_ID` for Qoder. When none of these markers are present, the system defaults to native Claude behavior.

### Why does GitHub Copilot only receive output during SessionStart events?

According to the source code in [`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js) (lines 19-27), Copilot's integration only consumes the `additionalContext` field during session initialization. The `isCopilot` branch explicitly filters for `event === 'SessionStart'`, returning early or writing empty objects for all subsequent events to avoid interfering with the editor's output stream.

### What distinguishes the Codex output format from the Qoder format?

Both agents receive a `hookSpecificOutput` object containing `hookEventName` and `additionalContext`. However, Codex additionally receives a `systemMessage` field (e.g., `"PONYTAIL:FULL"`) that communicates the current operational mode. Qoder's payload, as implemented in lines 69-80 of [`ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-runtime.js), deliberately omits this field to match its consumption protocol.

### Where does the instruction content originate that gets formatted for these agents?

The ruleset text is generated by [`hooks/ponytail-instructions.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-instructions.js). The [`ponytail-activate.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-activate.js) hook builds this content and passes it as the `context` argument to `writeHookOutput`. This ensures that regardless of the serialization format—whether Copilot's minimal JSON or Codex's wrapped payload—each agent receives identical underlying instructions tailored to its parser expectations.