Purpose of `hooks/ponytail-subagent.js` in Re-Injecting Ponytail Rulesets

The hooks/ponytail-subagent.js file acts as a SubagentStart hook that automatically re-injects Ponytail rulesets into every sub-agent spawned by the main agent, ensuring consistent policy enforcement across the entire agent hierarchy when Ponytail mode is active.

This hook solves the critical problem where child agents (sub-agents) would otherwise run without awareness of the parent agent's Ponytail configuration. By intercepting the sub-agent initialization process, the script guarantees that behavioral rules propagate correctly through the agent tree, whether injecting universally or targeting specific agent types via regex matching.

Detecting Ponytail Mode Before Injection

The hook begins by determining whether rule injection should occur at all. It calls readMode() (source L16-L21) to check the current Ponytail activation state.

If the mode is missing or explicitly set to off, the hook exits immediately without writing any output. This prevents unnecessary processing and ensures sub-agents remain untouched when Ponytail is disabled. Only when the mode indicates active operation does the hook proceed to the injection phase.

Re-Injecting Rulesets into Sub-Agents

When Ponytail mode is confirmed active, the hook calls the inject() helper function to write the ruleset to the hook output stream. This function uses writeHookOutput('SubagentStart', mode, getPonytailInstructions(mode)) (source L23-L29), emitting the exact same payload that the main ponytail-start.js hook sends to the primary agent runtime.

The re-injection process ensures that sub-agents inherit the same behavioral constraints and capabilities as their parent, solving issue #252 where sub-agents previously ran "pony-tail-unaware" and operated outside the defined ruleset.

Fast-Path Injection Without Matcher

When no scoping is configured, the hook takes an optimized fast path. It immediately injects the rules without reading from stdin (source L41-L48).

This approach specifically avoids a Windows-specific deadlock condition where PowerShell can swallow piped JSON input. By skipping the stdin read entirely when PONYTAIL_SUBAGENT_MATCHER is undefined, the hook guarantees reliable operation across all platforms without hanging the parent session.

Scoped Injection with Regex Matching

For targeted rule application, the hook supports the PONYTAIL_SUBAGENT_MATCHER environment variable containing a case-insensitive regular expression. The hook compiles this regex at runtime (source L31-L39), defaulting to unconditional injection if the pattern is invalid.

When a valid matcher exists, the hook reads the incoming JSON payload from stdin to extract the agent_type field. It then tests this value against the compiled regex (matcherRe.test(agentType)) (source L50-L71):

  • Match found: The ruleset is injected into that specific sub-agent.
  • No match: The sub-agent spawns without Ponytail rules.
  • Missing, malformed, or timed-out input: The hook defaults to injection (fail-open) to prevent silent omission of the persona.

Robust Stdin Handling and Timeout Protection

To prevent the hook from stalling the parent agent session, it implements comprehensive stream management. The code listens for data, end, and error events on stdin, supplemented by a fallback timeout mechanism (source L73-L78).

This ensures that even if the sub-agent initialization process hangs or provides malformed JSON, the hook completes within a reasonable timeframe and allows the agent hierarchy to continue initialization.

Configuration and Usage Examples

Enable Ponytail globally to inject rules into all sub-agents:

export PONYTAIL_MODE=on
opencode run mytask.js

Scope injection to specific agent types using regex:

export PONYTAIL_SUBAGENT_MATCHER="explore|general"
export PONYTAIL_MODE=on
opencode run mytask.js

Consume the injected rules within a sub-agent:

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

if (readMode() === 'on') {
  const rules = getPonytailInstructions('on');
  // Apply rules to agent behavior
}

According to the DietrichGebert/ponytail repository structure, this hook collaborates with several critical components:

Summary

  • hooks/ponytail-subagent.js functions as a SubagentStart hook that intercepts child agent initialization to re-inject Ponytail rulesets.
  • The hook checks readMode() to determine if injection should occur, exiting immediately when Ponytail is disabled.
  • Rules are injected via writeHookOutput('SubagentStart', mode, getPonytailInstructions(mode)), ensuring sub-agents receive identical configuration to parent agents.
  • Fast-path execution avoids stdin reads when no matcher is configured, preventing Windows PowerShell deadlocks.
  • The PONYTAIL_SUBAGENT_MATCHER environment variable enables regex-based scoping, allowing selective rule injection based on agent_type.
  • Robust error handling with timeout fallbacks ensures the hook never stalls the parent agent session.

Frequently Asked Questions

Why does the hook need to re-inject rules instead of inheriting them automatically?

Sub-agents in the Ponytail architecture spawn as separate processes that do not automatically inherit the parent agent's runtime configuration. Without the hooks/ponytail-subagent.js hook intercepting the SubagentStart event, child agents would initialize without the ruleset context, causing inconsistent behavior across the agent hierarchy. The explicit re-injection guarantees policy consistency from parent to child.

What happens if the PONYTAIL_SUBAGENT_MATCHER regex is invalid?

If the environment variable contains an invalid regular expression, the hook treats this as "no matcher" defined (source L31-L39). In this case, it falls back to the fast-path behavior and injects the ruleset into every sub-agent unconditionally, ensuring the configuration remains fail-open rather than failing silently.

How does the hook prevent deadlocks on Windows systems?

When no regex matcher is configured, the hook skips reading from stdin entirely and immediately writes the output (source L41-L48). This avoids a known issue where PowerShell can consume or block piped JSON input, which would otherwise cause the hook to hang indefinitely waiting for data that never arrives.

What is the fail-open behavior when stdin times out?

If the hook cannot read valid JSON from stdin within the timeout period—due to malformed data, stream errors, or delays—it defaults to injecting the ruleset (source L50-L71). This ensures that temporary communication issues never result in sub-agents running without the intended Ponytail personality or constraints.

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 →