# Data flow — DietrichGebert/ponytail

> Canonical page: https://codevetter.com/unpack/ponytail/data-flow

# Data flow — DietrichGebert/ponytail

Scanned commit e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156 (2026-09-14). Generated by codevetter codevetter 1.12.0 on 2026-09-22.

Data flow is prompt-text injection: config resolution produces a mode, the mode filters the canonical SKILL.md body, and the host-specific hook output protocol (raw stdout, additionalContext JSON, or additional_context JSON) carries the ruleset into the model's context. State is minimal: a one-word flag file per host state dir, a config.json for the persisted default, and a nudge marker file. Inputs are env vars (host detection, mode default, subagent matcher) and stdin JSON (prompt text, agent_type); outputs are stdout writes the host harness parses.
- Ruleset path: skills/ponytail/SKILL.md → getPonytailInstructions(mode) → filterSkillBodyForMode strips frontmatter and drops intensity-table rows (`| **lite** |`) and worked-example bullets (`- lite: "…"`) whose label isn't the active mode → prefixed with 'PONYTAIL MODE ACTIVE — level: <mode>'. — [hooks/ponytail-instructions.js#L11-41](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/hooks/ponytail-instructions.js#L11-L41) [hooks/ponytail-instructions.js#L77-92](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/hooks/ponytail-instructions.js#L77-L92)
- Mode resolution path: PONYTAIL_DEFAULT_MODE env → ~/.config/ponytail/config.json (or %APPDATA%\ponytail) defaultMode → 'full'; only RUNTIME_MODES (off/lite/full/ultra) are accepted, so 'review' can never become a default (#377). — [hooks/ponytail-config.js#L16-34](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/hooks/ponytail-config.js#L16-L34) [hooks/ponytail-config.js#L76-100](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/hooks/ponytail-config.js#L76-L100)
- Session persistence: ponytail-activate.js writes the resolved mode into <stateDir>/.ponytail-active; ponytail-mode-tracker.js rewrites or deletes it on /ponytail commands; ponytail-subagent.js and the statusline scripts read it. — [hooks/ponytail-activate.js#L53-58](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/hooks/ponytail-activate.js#L53-L58) [hooks/ponytail-mode-tracker.js#L85-114](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/hooks/ponytail-mode-tracker.js#L85-L114) [hooks/ponytail-subagent.js#L16-21](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/hooks/ponytail-subagent.js#L16-L21) [hooks/ponytail-statusline.sh#L3-6](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/hooks/ponytail-statusline.sh#L3-L6)
- Stdout protocol is host-shaped: Copilot gets {additionalContext} only on SessionStart, Codex gets systemMessage + hookSpecificOutput, Qoder gets hookSpecificOutput without systemMessage, Cursor gets {additional_context, continue}, native Claude gets raw stdout except SubagentStart which needs hookSpecificOutput JSON. — [hooks/ponytail-runtime.js#L80-131](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/hooks/ponytail-runtime.js#L80-L131)
- UserPromptSubmit input arrives as piped stdin JSON; the tracker parses data.prompt, lowercases it, matches ^[/@$]ponytail or the exact deactivation phrases, then emits the mode-change header (Cursor also gets the new level's full ruleset since it has no /ponytail command that loads the skill). — [hooks/ponytail-mode-tracker.js#L21-107](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/hooks/ponytail-mode-tracker.js#L21-L107)
- Qoder lacks SessionStart, so its UserPromptSubmit path does double duty: first prompt initializes the flag from getDefaultMode(), and every prompt re-injects the full ruleset, folding the mode-change confirmation into one JSON write. — [hooks/ponytail-mode-tracker.js#L116-138](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/hooks/ponytail-mode-tracker.js#L116-L138)
- Subagent scoping input: when PONYTAIL_SUBAGENT_MATCHER is set, the hook reads agent_type from stdin JSON and skips injection only on a definite mismatch; missing/unparseable payloads, regex errors, and timeouts all fail open to injecting. — [hooks/ponytail-subagent.js#L31-77](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/hooks/ponytail-subagent.js#L31-L77)
- Config writes: only `/ponytail default <mode>` calls writeDefaultMode, which mkdirs the config dir, merges defaultMode into any existing JSON (preserving unrelated keys), and refuses non-runtime modes; plain switches stay session-scoped. — [hooks/ponytail-config.js#L136-151](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/hooks/ponytail-config.js#L136-L151) [hooks/ponytail-mode-tracker.js#L55-66](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/hooks/ponytail-mode-tracker.js#L55-L66) [tests/hooks.test.js#L429-469](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/tests/hooks.test.js#L429-L469)
- OpenCode data flow: command.execute.before persists the /ponytail argument to ~/.config/opencode/.ponytail-active; experimental.chat.system.transform reads it each turn and appends the filtered ruleset to the last system message (or pushes one), so a mode switch applies from the next message. — [.opencode/plugins/ponytail.mjs#L27-44](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/.opencode/plugins/ponytail.mjs#L27-L44) [.opencode/plugins/ponytail.mjs#L74-97](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/.opencode/plugins/ponytail.mjs#L74-L97)
- pi data flow: session_start replays persisted 'ponytail-mode' entries via resolveSessionMode to recover the mode, before_agent_start returns {systemPrompt: base + ruleset}, and the input event listener turns the deactivation phrase into setMode('off'). — [pi-extension/index.js#L24-37](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/pi-extension/index.js#L24-L37) [pi-extension/index.js#L174-210](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/pi-extension/index.js#L174-L210)
- Hermes data flow: _pre_llm_call returns {'context': build_injected_context(mode)} each turn; mode lives in the module-global _current_mode set by the /ponytail command handler (process-local, not persisted); review mode injects the ponytail-review SKILL.md body instead of the filtered ladder. — [__init__.py#L27](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/__init__.py#L27) [__init__.py#L105-128](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/__init__.py#L105-L128) [__init__.py#L167-177](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/__init__.py#L167-L177)
- OpenClaw/ClawHub artifacts are generated: scripts/build-openclaw-skills.js copies each skills/<name>/SKILL.md body verbatim into .openclaw/skills/ with a rewritten single-line <160-char description frontmatter, so the ruleset can't drift from canonical. — [scripts/build-openclaw-skills.js#L1-60](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/scripts/build-openclaw-skills.js#L1-L60)
- Benchmark data flow: run.py seeds a temp workspace (fixture repo or seed files + git baseline), runs `claude -p` with --plugin-dir for skill arms or --append-system-prompt for prompt arms, then scores the git diff / file stats for correctness+safety and reads cost/tokens from the CLI's JSON output. — [benchmarks/agentic/run.py#L283-339](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/benchmarks/agentic/run.py#L283-L339) [benchmarks/agentic/run.py#L257-281](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/benchmarks/agentic/run.py#L257-L281) [benchmarks/agentic/tasks.py#L1-31](https://github.com/DietrichGebert/ponytail/blob/e3ba2aa6f1e6f0bc4d69eb09c9f0d0a93af56156/benchmarks/agentic/tasks.py#L1-L31)

Every claim links to its cited files at the pinned commit. Citations identify source evidence; they do not establish runtime correctness. Independent CodeVetter scan — not reviewed by upstream maintainers.

## Public product links

- [CodeVetter](https://codevetter.com/)
- [Download](https://codevetter.com/download)
- [Documentation](https://codevetter.com/docs/)
- [Source](https://github.com/Codevetter/codevetter)
