How Ponytail Handles Mode Switching and Deactivation Commands

Ponytail processes mode switches and deactivation commands through two core files: hooks/ponytail-mode-tracker.js parses /ponytail commands and natural language deactivation phrases, while hooks/ponytail-config.js manages default mode persistence and command detection utilities.

The DietrichGebert/ponytail repository implements a hook-based architecture that intercepts user prompts to dynamically adjust runtime behavior. Understanding how Ponytail mode switching and deactivation commands function requires examining the interaction between the mode tracker hook and configuration utilities.

Understanding Ponytail's Mode Switching Architecture

The Core Hook System (ponytail-mode-tracker.js)

The primary logic for Ponytail mode switching resides in hooks/ponytail-mode-tracker.js. This file handles incoming JSON prompts, parses command syntax, and manages state transitions between lite, full, ultra, and off states. When a prompt arrives, the hook lower-cases the input string and checks for command prefixes at lines 23-54.

Configuration Utilities (ponytail-config.js)

Supporting the mode tracker, hooks/ponytail-config.js provides essential helper functions including writeDefaultMode() for persisting user preferences and isDeactivationCommand() for detecting natural language shutdown requests. These utilities abstract file I/O operations and string normalization logic away from the main hook flow.

How Mode Switching Works in Ponytail

Command Syntax and Runtime Modes

Ponytail recognizes explicit command syntax when prompts begin with /ponytail. The parser supports several variants:

  • /ponytail lite|full|ultra – Immediately switches the current session to the requested runtime mode by calling setMode(mode) at lines 65-66
  • /ponytail off – Deactivates Ponytail for the current session using clearMode() at lines 77-81
  • /ponytail (no arguments) – Returns a status report without modifying the active mode
  • Any other text after /ponytail – Falls back to the configured default mode

When setMode() executes successfully, the hook emits a "mode changed" confirmation message via writeHookOutput().

Persisting Default Modes Across Sessions

Users can permanently store their preferred mode using the default subcommand. When the hook detects /ponytail default <mode> at lines 38-44, it invokes writeDefaultMode() to persist the selection (valid options: off, lite, full, ultra) to configuration storage. Future sessions automatically initialize to this default unless overridden by an explicit runtime command.

Special Handling for Qoder Client

The repository includes client-specific logic for "Qoder" at lines 70-76. Rather than emitting separate status messages, mode change confirmations are folded directly into the ruleset output for this client. This ensures the Qoder interface receives consolidated feedback without additional hook writes cluttering the response stream.

Deactivation Commands and Flow

Explicit Off Commands (/ponytail off)

The most direct method for disabling Ponytail involves sending /ponytail off. This command triggers clearMode() and emits the standardized "PONYTAIL MODE OFF" message at lines 77-81. This immediate deactivation takes precedence over other operations and signals the system to skip subsequent rule injections.

Natural Language Deactivation Phrases

Beyond explicit commands, Ponytail supports conversational deactivation through isDeactivationCommand() at lines 40-43 of ponytail-config.js. This helper recognizes two specific phrases:

  • "stop ponytail"
  • "normal mode"

The detection is case-insensitive, automatically trimmed, and ignores punctuation. When the mode tracker identifies these phrases at lines 84-89—and no mode switch has already occurred—it executes clearMode() and emits the same "off" confirmation used for explicit commands.

Deactivation Impact on Rule Injection

Post-deactivation behavior varies by client. For standard clients, the off state simply prevents mode-specific processing. However, for the Qoder client, deactivation carries specific consequences at lines 95-96: the hook explicitly skips automatic ruleset injection following a shutdown command, ensuring clean deactivation without residual rule output.

Implementation Flow and Code Examples

The complete processing flow follows this sequence:

  1. Incoming prompt JSON is parsed and the prompt field is lower-cased
  2. If the text begins with /ponytail, parse the command and potentially persist defaults via writeDefaultMode()
  3. For runtime modes (lite, full, ultra), invoke setMode() and emit confirmation
  4. For off, invoke clearMode() and emit "PONYTAIL MODE OFF"
  5. If no command prefix exists but isDeactivationCommand() returns true, clear mode and emit off message
  6. For Qoder clients, inject the full ruleset unless deactivation just occurred (lines 95-96)
// Switch to full mode for the current session
{ "prompt": "/ponytail full" }

// Persist ultra as the default for all future sessions
{ "prompt": "/ponytail default ultra" }

// Turn Ponytail off via explicit command
{ "prompt": "/ponytail off" }

// Natural language deactivation phrase
{ "prompt": "stop ponytail" }

// Alternative deactivation phrase (punctuation ignored)
{ "prompt": "Normal mode!" }

Summary

  • Dual-file architecture: Mode switching logic splits between hooks/ponytail-mode-tracker.js (orchestration) and hooks/ponytail-config.js (utilities).
  • Command flexibility: Supports both explicit /ponytail <mode> syntax and conversational phrases like "stop ponytail".
  • Persistent defaults: The default subcommand writes preferences via writeDefaultMode() for automatic future session initialization.
  • Clean deactivation: Both /ponytail off and natural language triggers execute clearMode() and suppress subsequent rule injection for Qoder clients.

Frequently Asked Questions

How does Ponytail detect deactivation commands in natural language?

Ponytail uses the isDeactivationCommand() helper in hooks/ponytail-config.js (lines 40-43) to identify the exact strings "stop ponytail" or "normal mode". The function normalizes input by converting to lowercase, trimming whitespace, and stripping punctuation before comparison, allowing flexible phrasing like "Stop Ponytail!" or "normal mode..." to trigger deactivation.

What happens when I set a default mode versus a runtime mode?

Setting a default mode via /ponytail default ultra calls writeDefaultMode() to permanently store the preference for future sessions, while runtime commands like /ponytail ultra call setMode() to temporarily change the active session without persisting the selection. The default only applies when no explicit mode command is issued.

Why does the Qoder client handle mode changes differently?

According to the source at lines 70-76 and 95-96 of ponytail-mode-tracker.js, the Qoder client receives mode confirmations embedded within ruleset output rather than separate hook messages. Additionally, Qoder skips automatic ruleset injection entirely after deactivation to prevent conflicting instructions, ensuring the client receives clean "off" state signaling.

Can I deactivate Ponytail without using the /ponytail prefix?

Yes. The hook checks for deactivation phrases independently of command prefixes at lines 84-89. If your prompt contains "stop ponytail" or "normal mode" (case-insensitive) and no mode switch has already occurred, clearMode() executes and emits "PONYTAIL MODE OFF" regardless of whether you used the /ponytail syntax.

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 →