# Architectural Difference Between RUNTIME_MODES and VALID_MODES in Ponytail

> Understand the architectural difference between RUNTIME_MODES and VALID_MODES in Ponytail. Learn how internal engine strings and user-facing validation differ.

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

---

**In [`hooks/ponytail-mode-tracker.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-mode-tracker.js), `RUNTIMEMODES` defines the internal canonical strings used by the Ponytail runtime engine for state persistence, while `VALIDMODES` serves as the user-facing whitelist that validates CLI inputs before they reach the runtime layer.**

Ponytail is an open-source utility that synchronizes operational modes across AI coding agents like Claude, Copilot, and Qoder. Understanding the distinction between these two constants in [`hooks/ponytail-mode-tracker.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-mode-tracker.js) is essential for developers extending the mode system, as it establishes a clean architectural boundary between the public API and internal state representation.

## Internal Runtime Representation vs. User-Facing Validation

The separation between `RUNTIMEMODES` and `VALIDMODES` creates a decoupled architecture that isolates user input handling from runtime state management. This distinction allows the system to evolve its public interface without breaking internal persistence contracts.

### RUNTIMEMODES: The Engine's Canonical Reference

Defined at [lines 9‑14](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-mode-tracker.js#L9-L14), `RUNTIMEMODES` maps mode identifiers to the exact strings the runtime uses when persisting state to the filesystem or communicating between agents:

```javascript
const RUNTIMEMODES = {
  active: 'active',
  testing: 'testing',
  replay: 'replay',
  paused: 'paused',
};

```

The `modeToRuntime(mode)` function consumes this mapping to translate user input into the internal representation stored in `.ponytail-active`. According to the source, this function returns `RUNTIMEMODES[mode] || null`, ensuring the runtime only processes canonical identifiers.

### VALIDMODES: The Public API Boundary

Defined at [lines 17‑22](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-mode-tracker.js#L17-L22), `VALIDMODES` enumerates the set of modes a user may request via CLI or configuration:

```javascript
const VALIDMODES = {
  active: 'active',
  testing: 'testing',
  replay: 'replay',
  paused: 'paused',
};

```

The `isValidMode(mode)` function validates input against this object using `!!VALIDMODES[mode]`, acting as a gatekeeper that prevents invalid or deprecated mode strings from reaching the runtime layer.

## Implementation in ponytail-mode-tracker.js

Both constants are exported alongside utility functions that manage mode persistence across different AI agent environments. The file handles state storage in platform-specific directories—checking `process.env.COPILOT_PLUGIN_DATA` for GitHub Copilot or `process.env.QODER_SESSION_ID` for Qoder sessions—while maintaining consistent internal mode strings via `RUNTIMEMODES`.

The key functions demonstrating this separation are:

- **`modeToRuntime(mode)`**: Converts validated user input to runtime strings using `RUNTIMEMODES`
- **`isValidMode(mode)`**: Validates input against `VALIDMODES` before processing
- **`writeMode(mode)`**: Persists the runtime mode string to the state file
- **`readMode()`**: Retrieves the current runtime mode from storage

## Architectural Benefits of the Separation

While the current mappings are identical, the architectural distinction provides critical flexibility for future development:

- **API Evolution**: New user-facing aliases or renamed modes can be added to `VALIDMODES` while preserving backward compatibility in `RUNTIMEMODES`, ensuring existing state files remain readable.
- **Input Sanitization**: `VALIDMODES` acts as a security boundary, preventing arbitrary strings from being written to the state file or passed to sub-agents.
- **Multi-Agent Support**: Different AI agents (Copilot, Qoder, native Claude) may require different internal state representations in the future; `RUNTIMEMODES` can accommodate these variations without exposing complexity to the user.

## Summary

- **`RUNTIMEMODES`** ([lines 9‑14](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-mode-tracker.js#L9-L14)) provides the internal canonical representation used for state persistence and inter-agent communication.
- **`VALIDMODES`** ([lines 17‑22](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-mode-tracker.js#L17-L22)) defines the whitelist of acceptable user inputs, validated by `isValidMode()`.
- The `modeToRuntime()` function bridges these layers by mapping validated input to runtime strings.
- This decoupling allows the CLI interface to evolve independently from the persistence layer, supporting future extensions like mode aliases or agent-specific state formats.
- Both constants are exported from [`hooks/ponytail-mode-tracker.js`](https://github.com/DietrichGebert/ponytail/blob/main/hooks/ponytail-mode-tracker.js) alongside persistence utilities used by activation hooks and runtime coordinators.

## Frequently Asked Questions

### What happens if a mode exists in VALIDMODES but not RUNTIMEMODES?

If a mode is added to `VALIDMODES` but omitted from `RUNTIMEMODES`, the `modeToRuntime()` function will return `null` when that mode is requested. This causes the validation to pass but the runtime conversion to fail, effectively creating a valid but non-functional mode that cannot be persisted to the state file.

### Why are the two mappings currently identical?

The mappings share the same values because the Ponytail project has not yet required public-facing aliases or agent-specific internal identifiers. The duplicate structure exists as forward-looking architecture, allowing future PRs to introduce user-friendly aliases (e.g., mapping `"test"` to `"testing"` at the API layer) or agent-specific runtime strings without refactoring the validation logic.

### How does this architecture support environment-specific state paths?

While `RUNTIMEMODES` and `VALIDMODES` handle mode *values*, the `getStatePath()` function uses the runtime context (detecting Copilot via `COPILOT_PLUGIN_DATA` or Qoder via `QODER_SESSION_ID`) to determine *where* the mode is stored. The separation ensures that regardless of which agent writes the file, the internal mode string written (governed by `RUNTIMEMODES`) remains consistent and interpretable across all environments.

### Can I extend these constants for custom agent integrations?

Yes. To add a custom mode, define it in both `VALIDMODES` (to expose it to users) and `RUNTIMEMODES` (to enable runtime processing), then update any agent-specific logic that consumes `readMode()`. Because the validation layer is separate, you can also experiment with internal-only modes by adding them exclusively to `RUNTIMEMODES` while developing, preventing users from accidentally activating unstable features.