# Routing reference

`pi-codex-router` makes a local, deterministic decision before an agent turn. It does not send a classification request to a model. Given the same prompt, configuration, available models, repository signals, and session state, the pure router returns the same route.

## Inputs and local signals

The adapter supplies the prompt, Pi's currently available models, current model, configuration, and bounded repository signals. Repository inspection skips `.git`, `.pi`, and `node_modules`, stops after 10,000 files or roughly 50 ms, and caches its result for 10 seconds. Failure to inspect a repository produces neutral signals.

The score starts with the prompt classification and adds at most 30 repository points:

| Signal | Added score |
| --- | ---: |
| 1,000+ files | 10 |
| 10,000+ files | 15 |
| Git working-tree diff of 500+ changed lines | 10 |
| Four or more file paths mentioned in the prompt | 10 |
| Four or more detected languages | 5 |

The final score is capped at 100. Repository signals are modifiers; they cannot independently select a model profile.

## Classification and initial routes

The classifier uses the first matching category below (except explanation requests, which are deliberately kept as explanations). Risk terms add to that category's base score.

| Category | Evidence | Base score | Initial profile | Thinking |
| --- | --- | ---: | --- | --- |
| `architecture` | architecture, redesign, security audit, system design, migration plan | 78 | advanced | high, or `xhigh` above 90 |
| `debugging` | debug across, race condition, multi-file debug, root-cause trace, deadlock | 70 | advanced | high, or `xhigh` above 90 |
| `research` | compare, research, investigate, find out, external behavior, dependency API | 65 | advanced at `advancedThreshold`; otherwise balanced | high, or `xhigh` above 90 |
| `refactor` | refactor, reorganize, extract, cleanup | 58 | balanced | high |
| `bugfix` | fix, bug, regression, broken, failing, error | 35 | balanced | low below 45; otherwise medium |
| `explanation` | explain, what does, how does, where is | 18 | fast below 35; otherwise balanced | low |
| `simple` | rename, typo, format, whitespace, lint | 10 | fast below 35; otherwise balanced | low |
| `feature` | add, implement, create, update, test, or no match | 48 | balanced | `defaultThinking` |

Risk evidence adds 12 points for security/authentication/credentials, data loss/delete data/backup, or concurrency/race condition/deadlock. Production and migration evidence add 6 points. Prompt classification itself is capped at 95 before repository modifiers are applied.

The model profile and thinking level are independent. In particular, `defaultThinking` applies to ordinary `feature` work; it does not replace the category-specific thinking level for simple tasks, bug fixes, refactors, debugging, architecture, or research.

## Model resolution and fallbacks

Each profile maps to a configured model string. The router resolves it against Pi's available-model list in this order:

1. exact provider-qualified ID;
2. a unique model suffix or display-name alias; then
3. a unique case-insensitive partial ID match.

Ambiguous matches are not selected. If the requested profile is unavailable, the router tries the balanced profile. If that is unavailable, it retains a resolvable current Pi model. If nothing resolves, it returns no model change. This fail-open behavior prevents an unavailable configuration from blocking Pi startup.

Use provider-qualified IDs when possible. To use another model family, replace `models.fast`, `models.balanced`, and `models.advanced` with IDs exposed by Pi; no routing-algorithm change is required.

## Stability and escalation

With `switchPolicy: "stable"`, the router compares the proposed profile with the session's current route:

- A current lower profile is retained unless the proposed escalation reaches `advancedThreshold`.
- A route within `hysteresis` points of the prior score is retained.
- A lower profile is retained only when the caller has not marked a task boundary.
- A current model that is no longer available is never retained.

The adapter marks every new `before_agent_start` prompt as a task boundary, so an unrelated simple prompt can downgrade after a Sol route. Set `switchPolicy` to `"always"` to remove hysteresis entirely. Explicit `/codex-router override` values always win; manual Pi model or thinking selection also disables automatic routing for that session. The adapter records and announces a route only after Pi accepts the model change, and reports the resolved model ID and Pi's effective thinking level after any capability clamp.

## Configuration reference

Configuration is JSON. Global settings are read from `~/.pi/agent/codex-router.json`; project settings from `.pi/codex-router.json` override them. The `PI_CODEX_ROUTER_FAST_MODEL`, `PI_CODEX_ROUTER_BALANCED_MODEL`, and `PI_CODEX_ROUTER_ADVANCED_MODEL` environment variables override their respective model settings last.

```json
{
  "enabled": true,
  "models": {
    "fast": "openai-codex/gpt-5.6-luna",
    "balanced": "openai-codex/gpt-5.6-terra",
    "advanced": "openai-codex/gpt-5.6-sol"
  },
  "defaultThinking": "medium",
  "advancedThreshold": 70,
  "hysteresis": 12,
  "switchPolicy": "stable",
  "debug": false
}
```

| Field | Default | Valid values / effect |
| --- | --- | --- |
| `enabled` | `true` | Boolean; disables automatic routing unless a session override is active. |
| `models.fast` | Luna ID above | Non-empty model ID or alias. |
| `models.balanced` | Terra ID above | Non-empty model ID or alias. |
| `models.advanced` | Sol ID above | Non-empty model ID or alias. |
| `defaultThinking` | `medium` | `off`, `minimal`, `low`, `medium`, `high`, `xhigh`, or `max`; used for ordinary features. |
| `advancedThreshold` | `70` | Number from 1–100; controls research escalation and stable-session escalation. |
| `hysteresis` | `12` | Number from 1–100; score band that keeps the current route. |
| `switchPolicy` | `stable` | `stable` or `always`. |
| `debug` | `false` | Boolean; shows the resolved model ID and a compact route reason on each routed turn. |

Malformed values produce warnings and preserve safe defaults. The adapter shows each warning once after load; configuration errors are not fatal. An explicit override whose model is unavailable is cleared with a warning before normal routing resumes.

## Evaluation

Evaluate changes with representative, anonymized prompts and expected profile/thinking ranges. Include ambiguous prompts, risk language, unavailable models, repository modifiers, stable-session behavior, and explicit overrides. Run the pure routing tests without a Pi process or network access:

```bash
npm test
npm run check
```

Before release, also inspect the publish artifact:

```bash
npm pack --dry-run
npm publish --dry-run
```

Do not record prompt contents, repository files, credentials, or provider usage data by default. Tune thresholds only when a fixture shows a repeatable false escalation or missed escalation.
