How Ponytail Injects Rulesets into Subagents Using ponytail-subagent.js

Ponytail bridges parent and subagent contexts by reading the active mode from state, optionally filtering by agent type, and injecting instruction text via the SubagentStart hook.

When spawning subagents in Claude, Codex, or compatible AI environments, parent processes typically lose the active ruleset context. The ponytail-subagent.js hook in the DietrichGebert/ponytail repository solves this by intercepting subagent creation events and automatically injecting the current Ponytail instructions, ensuring consistent behavior across agent hierarchies.

Overview of the Injection Mechanism

The injection process centers on hooks/ponytail-subagent.js, which acts as a middleware between the parent process and the subagent initialization. Unlike manual context passing, this hook operates automatically by reading stdin for the subagent's metadata, checking the current Ponytail mode, and conditionally writing ruleset instructions to the appropriate output channel.

The architecture relies on three core components:

Step-by-Step Injection Process

Detecting Ponytail Mode

The hook first determines whether injection should occur by calling readMode() at lines 42-49 of hooks/ponytail-runtime.js. This function reads the current state from the filesystem. If the mode is unset or explicitly "off", the hook exits immediately without modifying the subagent context, as shown at lines 18-21 of hooks/ponytail-subagent.js.

const { readMode } = require('./ponytail-runtime');

const mode = readMode();
if (!mode || mode === 'off') {
  process.exit(0);
}

This early exit prevents unnecessary processing and ensures dormant configurations do not interfere with standard subagent operations.

Building the Ruleset Instructions

When a valid mode is detected, the hook calls getPonytailInstructions(mode) from hooks/ponytail-instructions.js (lines 77-92). This function aggregates the appropriate ruleset text based on the selected mode—lite, full, or ultra—returning a complete instruction string ready for injection.

The specific content varies by mode, but the delivery mechanism remains consistent: the assembled text becomes the additionalContext payload attached to the subagent's initialization event.

Optional Agent Type Filtering

Before injection, the hook optionally validates the subagent type against the PONYTAIL_SUBAGENT_MATCHER environment variable. At lines 33-36 of hooks/ponytail-subagent.js, this variable is compiled into a case-insensitive RegExp.

The hook then reads the JSON payload from stdin (lines 53-67) to extract the agent_type field:

// Simplified logic from ponytail-subagent.js lines 53-67
const payload = JSON.parse(stdinData);
const agentType = payload.agent_type;

if (matcher && !matcher.test(agentType)) {
  process.exit(0); // Silently skip non-matching agents
}

Fail-open behavior ensures reliability: if stdin parsing fails, the payload is missing, or the read times out, the hook defaults to injecting the ruleset rather than withholding it (lines 60-66). This prevents accidental loss of critical instructions due to malformed input.

Writing Output to the Subagent Context

The inject() helper function (lines 24-26) calls writeHookOutput from hooks/ponytail-runtime.js. This utility handles runtime-specific formatting:

  • Claude: Emits JSON with hookSpecificOutput and additionalContext for the SubagentStart event (lines 84-88 of ponytail-runtime.js)
  • Codex, Copilot, Qoder: Uses platform-specific output formats (lines 51-80 of ponytail-runtime.js)
const { writeHookOutput } = require('./ponytail-runtime');

function inject() {
  writeHookOutput('SubagentStart', mode, getPonytailInstructions(mode));
}

Configuration and Environment Variables

Control the injection behavior through environment variables before launching the parent process:

  • PONYTAIL_MODE: Sets the operating mode (lite, full, ultra, off). When off or unset, injection is skipped.
  • PONYTAIL_SUBAGENT_MATCHER: Regex pattern to filter which subagent types receive the ruleset. If omitted, all subagents get the injection.

The matcher supports standard JavaScript regular expression syntax without delimiters. For example, setting it to explore|general limits injection to agents with those specific types.

Practical Examples

Enable ruleset injection for all subagents:

export PONYTAIL_MODE=full

# No matcher defined → every subagent receives the full ruleset

Restrict injection to specific agent types:

export PONYTAIL_MODE=ultra
export PONYTAIL_SUBAGENT_MATCHER="explore|general"

# Only "explore" or "general" type subagents get the instructions

Simulate a subagent launch where the matcher blocks injection:

echo '{"agent_type":"code"}' | node hooks/ponytail-subagent.js

# Matcher does not match "code" → exits without output

Simulate a successful injection:

echo '{"agent_type":"explore"}' | node hooks/ponytail-subagent.js

# Outputs the Ponytail instruction JSON to stdout

Summary

  • ponytail-subagent.js automatically propagates Ponytail rulesets from parent to subagent contexts
  • Mode detection via readMode() prevents injection when Ponytail is disabled
  • Optional filtering using PONYTAIL_SUBAGENT_MATCHER allows selective injection based on agent_type
  • Fail-open design ensures parsing errors or timeouts result in injection rather than silent omission
  • Non-blocking execution uses a 1-second timeout and error listeners to prevent hanging the parent process
  • Multi-platform support through writeHookOutput() handles Claude, Codex, Copilot, and Qoder output formats

Frequently Asked Questions

What happens if Ponytail mode is set to "off"?

When readMode() returns "off" or an undefined value, ponytail-subagent.js exits immediately at lines 18-21 without reading stdin or producing output. The subagent launches normally without any Ponytail instructions injected into its context.

How does the PONYTAIL_SUBAGENT_MATCHER work?

The environment variable is converted to a case-insensitive regular expression at lines 33-36. The hook parses the subagent's agent_type from the JSON payload on stdin and tests it against this regex. If the match fails, the process exits silently without injection. If the variable is unset, the hook bypasses this check and injects all subagents.

What platforms are supported by ponytail-subagent.js?

The hook supports Claude (native), Codex, GitHub Copilot, and Qoder runtimes. The writeHookOutput function in hooks/ponytail-runtime.js (lines 51-88) detects the target platform and formats the JSON payload accordingly, ensuring compatibility across different AI coding environments.

Is the injection process blocking or non-blocking?

The implementation is explicitly non-blocking. At lines 73-78, ponytail-subagent.js sets a 1-second timeout on stdin reading and attaches error listeners. This guarantees the hook exits promptly even if the parent process provides no payload, preventing delays in subagent initialization.

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 →