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

> Discover how ponytail-subagent.js automatically re-injects Ponytail rulesets into sub-agents, ensuring consistent policy enforcement across your agent hierarchy.

- Repository: [DietrichGebert/ponytail](https://github.com/DietrichGebert/ponytail)
- Tags: internals
- Published: 2026-09-11

---

**The [`hooks/ponytail-subagent.js`](https://github.com/DietrichGebert/ponytail/blob/main/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](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-subagent.js#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](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-subagent.js#L23-L29)), emitting the exact same payload that the main [`ponytail-start.js`](https://github.com/DietrichGebert/ponytail/blob/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](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-subagent.js#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](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-subagent.js#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](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-subagent.js#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](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-subagent.js#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:

```bash
export PONYTAIL_MODE=on
opencode run mytask.js

```

Scope injection to specific agent types using regex:

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

```

Consume the injected rules within a sub-agent:

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

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

```

## Related Source Files

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

- **[`hooks/ponytail-instructions.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-instructions.js)**: Generates the actual JSON ruleset payload injected by the hook.
- **[`hooks/ponytail-runtime.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-runtime.js)**: Provides the `readMode`, `writeHookOutput`, and `getPonytailInstructions` utilities used during injection.
- **[`tests/hooks.test.js`](https://github.com/DietrichGebert/ponytail/blob/main/tests/hooks.test.js)**: Contains unit tests verifying the injection logic and regex scoping behavior.

## Summary

- **[`hooks/ponytail-subagent.js`](https://github.com/DietrichGebert/ponytail/blob/main/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`](https://github.com/DietrichGebert/ponytail/blob/main/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](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-subagent.js#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](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-subagent.js#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](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-subagent.js#L50-L71)). This ensures that temporary communication issues never result in sub-agents running without the intended Ponytail personality or constraints.