# How Ponytail Determines Its Default Mode: Environment, Config, and Fallback Priority

> Discover how Ponytail determines its default mode: prioritizing environment variables, config files, and fallback constants for flexible operation. Learn the priority system.

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

---

**Ponytail determines its default mode at startup through a three-tier priority system defined in [`hooks/ponytail-config.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-config.js): it first checks the `PONYTAIL_DEFAULT_MODE` environment variable, then falls back to the `defaultMode` field in the user's [`config.json`](https://github.com/DietrichGebert/ponytail/blob/main/config.json), and finally defaults to the built-in constant `"full"`.**

The DietrichGebert/ponytail repository implements this deterministic resolution strategy to establish runtime intensity before the MCP server begins serving instructions. When initializing, the system relies on the `getDefaultMode()` function to decide which instruction set—`lite`, `full`, or `ultra`—should be active, ensuring consistent behavior across sessions while respecting user preferences.

## The Three-Tier Resolution Priority

The resolution logic in [`hooks/ponytail-config.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-config.js) follows a strict hierarchy, processing each layer only if the previous one yields no valid result.

### 1. Environment Variable `PONYTAIL_DEFAULT_MODE`

The highest priority source is the **environment variable** `PONYTAIL_DEFAULT_MODE`. At lines 76‑84, the code checks if this variable is set, converts the value to lowercase, and validates it against the `RUNTIME_MODES` array. 

Only the runtime levels `off`, `lite`, `full`, and `ultra` are accepted. The `review` mode is **deliberately excluded** from this check because it is designed as a session-only state and cannot be persisted as a default.

### 2. User Configuration File [`config.json`](https://github.com/DietrichGebert/ponytail/blob/main/config.json)

If the environment variable is unset or invalid, Ponytail searches for a configuration file in the platform-specific config directory. The helper functions `getConfigDir()` and `getConfigPath()` (lines 54‑69) resolve the correct path:

- `$XDG_CONFIG_HOME/ponytail/config.json` on Linux (XDG compliant)
- `~/.config/ponytail/config.json` on Linux (fallback)
- `%APPDATA%\ponytail\config.json` on Windows

If the file exists and contains a `defaultMode` field matching a valid runtime level (validated at lines 91‑93), that value is used.

### 3. Built-in Fallback to `"full"`

If neither the environment variable nor the configuration file provides a valid mode, the system falls back to the constant `DEFAULT_MODE`, defined as the string `"full"` at lines 99‑100 in [`ponytail-config.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-config.js). This ensures the MCP server always starts with a functional instruction set rather than failing silently.

## Validation and Mode Restrictions

Both the environment variable and configuration file inputs are validated against the `RUNTIME_MODES` array: `['off','lite','full','ultra']`. This validation guard (implemented at lines 78‑84 and 91‑93) serves a critical purpose: it prevents the `review` mode from being established as a default.

The `review` mode is intended for temporary, single-session analysis tasks. By excluding it from the default resolution pipeline, Ponytail ensures users cannot accidentally persist a diagnostic state that disables normal operation.

## MCP Server Integration and `resolveMode()`

Once `getDefaultMode()` resolves the initial setting, the MCP server consumes this value through `resolveMode()` in **[`ponytail-mcp/instructions.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-mcp/instructions.js)** (lines 16‑22). This function handles runtime mode negotiation:

- If the `requested` parameter is empty or resolves to `"off"`, the function calls `getDefaultMode()`.
- If `getDefaultMode()` also returns `"off"`, the server finally defaults to `"full"` to guarantee service availability.
- Explicit valid requests (`lite`, `full`, `ultra`) bypass the default resolution entirely.

This dual-layer fallback ensures the server never launches in a state that cannot serve instructions.

## Practical Configuration Examples

### Setting the Default via Environment Variable

```bash
export PONYTAIL_DEFAULT_MODE=lite
ponytail

# → Server starts with the "lite" instruction set

```

### Persisting a Default in config.json

```json
{
  "defaultMode": "ultra"
}

```

Place this file at `~/.config/ponytail/config.json` (Linux) or `%APPDATA%\ponytail\config.json` (Windows). The environment variable, if set, will override this value on the next startup.

### Querying the Default Programmatically

```javascript
const { getDefaultMode } = require('./hooks/ponytail-config');

console.log('Resolved default:', getDefaultMode());
// → "lite", "full", "ultra", or the fallback "full"

```

### Resolving Modes in the MCP Server

```javascript
const { resolveMode } = require('./ponytail-mcp/instructions');

console.log(resolveMode(''));      // → Uses config/default → "lite" or "full"
console.log(resolveMode('off'));    // → Falls back to config/default
console.log(resolveMode('ultra'));  // → "ultra" (explicit request honored)

```

## Summary

- **Priority order**: Environment variable → [`config.json`](https://github.com/DietrichGebert/ponytail/blob/main/config.json) → built-in `"full"` constant.
- **Validation**: Only `off`, `lite`, `full`, and `ultra` are valid; `review` is excluded from default persistence.
- **File locations**: Configuration resides in `$XDG_CONFIG_HOME/ponytail`, `~/.config/ponytail`, or `%APPDATA%\ponytail` depending on the platform.
- **Server integration**: `resolveMode()` in [`ponytail-mcp/instructions.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-mcp/instructions.js) provides an additional safety net, ensuring the server never starts in an unservable state.
- **Implementation core**: The resolution logic is centralized in [`hooks/ponytail-config.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-config.js) via `getDefaultMode()`, with validation guards at lines 78‑84 and 91‑93.

## Frequently Asked Questions

### What happens if I set `PONYTAIL_DEFAULT_MODE` to an invalid value?

If the environment variable contains a value not in `['off','lite','full','ultra']`, the validation logic at lines 78‑84 rejects it and proceeds to check the [`config.json`](https://github.com/DietrichGebert/ponytail/blob/main/config.json) file. If that also fails, the system falls back to `"full"`.

### Why can't I set "review" as the default mode for Ponytail?

The `review` mode is intentionally excluded from the `RUNTIME_MODES` validation array used by `getDefaultMode()`. According to the source code comments, this mode is session-only and designed for temporary diagnostic analysis; allowing it as a persistent default could leave the system in a restricted state unintentionally.

### Where is the config.json file located on my operating system?

The exact path depends on your platform. The `getConfigDir()` function resolves:
- Linux: `$XDG_CONFIG_HOME/ponytail` or `~/.config/ponytail`
- Windows: `%APPDATA%\ponytail`
- macOS: Typically `~/.config/ponytail` unless overridden by XDG variables

### How does the MCP server prevent starting in "off" mode?

While `getDefaultMode()` can legally return `"off"` (if explicitly configured), the `resolveMode()` function in [`ponytail-mcp/instructions.js`](https://github.com/DietrichGebert/ponytail/blob/main/ponytail-mcp/instructions.js) contains a secondary guard at lines 16‑22. If the resolved default is `"off"`, it overrides to `"full"`, ensuring the server always maintains at least the full instruction capability.