How Ponytail's Mode State Machine Manages Settings Across AI Agent Sessions
Ponytail's mode state machine orchestrates runtime intensity levels through a three-stage architecture that resolves defaults via environment variables and JSON configuration, activates modes through session hooks, and persists changes via filesystem flag files in agent-specific directories.
The mode state machine in DietrichGebert/ponytail governs how the AI coding assistant applies different rule intensities—from minimal (off) to maximum (ultra) enforcement—across Claude, Copilot, Qoder, and Codex environments. Unlike static configuration files, this state machine distinguishes between persistent user preferences, temporary session overrides, and transient review modes, ensuring that settings survive agent restarts while allowing rapid context switching during active development.
Understanding the Mode State Machine Architecture
The state machine manages four distinct runtime levels (off, lite, full, ultra) alongside a temporary review mode. It separates concerns across three dedicated hooks in the hooks/ directory:
ponytail-config.js– Resolves the default mode from environment variables, JSON configuration, or hard-coded fallbacks.ponytail-activate.js– Executes once perSessionStartevent to initialize the active mode and emit the appropriate rule set.ponytail-mode-tracker.js– Listens toUserPromptSubmitevents to process user commands and update modes dynamically.
This separation ensures that default configuration, session activation, and runtime mode changes operate independently while maintaining consistency through a shared flag file mechanism.
Default Mode Resolution Strategy
When a new session initializes, the mode state machine determines the starting intensity through a cascading resolution strategy implemented in hooks/ponytail-config.js:
Environment Variable Priority
The system first checks for the PONYTAIL_DEFAULT_MODE environment variable. If set to a valid runtime level, this value takes precedence over all other configuration sources.
Configuration File Fallback
If the environment variable is unset, the state machine searches for config.json in the following order:
$XDG_CONFIG_HOME/ponytail/config.json~/.config/ponytail/config.json(Linux/macOS fallback)%APPDATA%\ponytail\config.json(Windows fallback)
The defaultMode field in this JSON file stores the user's persistent preference across sessions.
Hard-Coded Default
If neither the environment variable nor the configuration file exists, the state machine defaults to full mode, ensuring consistent behavior for first-time users.
Only runtime levels (off, lite, full, ultra) are valid as defaults; the review mode is explicitly excluded from persistence and remains session-scoped only.
Session Activation and Rule Injection
Upon every SessionStart event, hooks/ponytail-activate.js executes the activation sequence:
- Resolution: Calls
getDefaultMode()to determine the starting intensity. - Flag Creation: Invokes
setMode(mode)to write the current mode to.ponytail-activein the agent-specific configuration directory. - Rule Emission: Calls
getPonytailInstructions(mode)to filter and emit the appropriate rule subset to the AI agent. - Status Nudging: Prompts the user to configure a status-line badge if not already present.
If the resolved default is off, the hook immediately invokes clearMode() to remove any existing flag file and emits no rules, effectively deactivating Ponytail for the session.
Runtime Mode Tracking and Command Processing
During active sessions, hooks/ponytail-mode-tracker.js monitors the UserPromptSubmit event to handle real-time mode transitions. This hook parses user input for specific command patterns:
/ponytail default <mode>– Persists a new default viawriteDefaultMode(mode), updating the JSON configuration file for future sessions./ponytail <mode>– Switches the session-scoped mode immediately viasetMode(), writing to the flag file without modifying persistent defaults./ponytail-review– Activates the temporary review mode for the current prompt only; does not persist or write to the flag file./ponytail– Queries the current mode viareadMode()and reports the active status.
The tracker also recognizes deactivation phrases (stop ponytail, normal mode) to trigger clearMode() and remove the active flag.
For Qoder sessions, which lack a SessionStart event, the tracker additionally injects the complete rule set on every prompt to ensure consistency.
Persistence Mechanisms for Settings
The mode state machine employs a dual-persistence strategy to balance durability with performance.
The Flag File System
The .ponytail-active file serves as the session state source of truth, stored in agent-specific directories:
- Claude:
~/.claude/ - Copilot:
$COPILOT_PLUGIN_DATA/ - Qoder:
~/.qoder/
Three core functions manage this file:
setMode(mode)– Usesfs.writeFileSyncto atomically write the current mode to the flag file.readMode()– Retrieves the active mode from the flag file for rule filtering.clearMode()– Removes the flag file entirely, signaling deactivation.
Default Configuration Storage
The writeDefaultMode(mode) function in hooks/ponytail-config.js normalizes the input, creates the config.json directory structure if missing, and writes the validated mode to the defaultMode field. Subsequent sessions retrieve this value via getDefaultMode(), ensuring preferences survive agent restarts and system reboots.
Practical Usage Examples
Interact with the mode state machine through natural chat commands:
// Switch to ultra mode for the current session only
await agent.sendMessage('/ponytail ultra');
// Persist lite as the default for all future sessions
await agent.sendMessage('/ponytail default lite');
// Temporarily activate review mode without affecting defaults
await agent.sendMessage('/ponytail-review');
// Query current active mode
await agent.sendMessage('/ponytail');
// Deactivate Ponytail for this session
await agent.sendMessage('/ponytail off');
Summary
- Three-stage architecture separates default resolution (
ponytail-config.js), session activation (ponytail-activate.js), and runtime tracking (ponytail-mode-tracker.js). - Cascading defaults prioritize
PONYTAIL_DEFAULT_MODEenvironment variables, then~/.config/ponytail/config.json, falling back tofullmode. - Dual persistence uses JSON files for permanent defaults and
.ponytail-activeflag files for session state. - Review mode remains transient and never contaminates persistent configuration, while runtime levels (
off,lite,full,ultra) support both temporary and persisted states. - Cross-agent support adapts flag file locations for Claude, Copilot, Qoder, and Codex environments.
Frequently Asked Questions
How does Ponytail distinguish between temporary and persistent mode changes?
Session-scoped changes use the /ponytail <mode> command, which invokes setMode() to update the .ponytail-active flag file without touching the JSON configuration. Persistent changes require /ponytail default <mode>, which calls writeDefaultMode() to modify config.json. The review mode activated via /ponytail-review bypasses both mechanisms entirely, existing only for the current prompt processing cycle.
What happens if I set an invalid mode name?
The writeDefaultMode() function normalizes and validates input against the four runtime levels (off, lite, full, ultra). Invalid values are rejected before reaching the configuration file or flag file system, ensuring the state machine never enters an undefined state.
Why does Ponytail use a flag file instead of environment variables for session tracking?
Environment variables cannot be modified dynamically within a running Node.js process without external shell manipulation. The .ponytail-active flag file provides a cross-platform, agent-agnostic mechanism that readMode() can check synchronously before every prompt, supporting real-time mode switches without requiring session restarts. This design also accommodates Qoder's lack of a SessionStart event by allowing rule injection on every prompt based on the current flag file contents.
Where does Ponytail store my default mode preference across system restarts?
Persistent defaults are stored in config.json within the platform-specific configuration directory: $XDG_CONFIG_HOME/ponytail/ on Linux, ~/.config/ponytail/ on macOS, or %APPDATA%\ponytail\ on Windows. The getDefaultMode() function reads this file only during the SessionStart event, while writeDefaultMode() updates it when you issue /ponytail default <mode> commands.
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:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →