# Cost router escalation -- eco vs frontier

Fetch when the user asks: "por que cambio de modelo?", "estoy en
eco o frontier?", "me escalo solo", "como bajo a Haiku?", "deja
de usar Sonnet".

## What the router decides per turn

`src/core/cost_router.ts` -- pure function that picks the brain
tier for the next turn based on:

1. **User override** (highest priority). `yf brain set eco|frontier`
   pins one tier; the router short-circuits.
2. **Verb tier hint** -- if the user already said which verb to
   invoke + that verb has a known cheap path, eco wins.
3. **Workflow phase** -- phases I + II (intake + clarification)
   default to eco; phases III + IV + V (specs, build) default to
   frontier.
4. **Active SQ rules** -- presence of any rule from the workflow
   phase rules pushes to frontier (cost of breaking a rule >
   cost of using Sonnet).
5. **Phase-crossover signal** -- last turn was eco, current text
   contains "let's commit" / "ship it" / "proceed" -- escalate.
6. **Smart-auto classifier** -- ambient turn, eco-keyword vs
   frontier-keyword + length heuristic.
7. **Frontier inertia** -- if the last turn was frontier AND the
   current turn is short, stay on frontier for one more turn so a
   quick clarification doesn't lose context.

## Escalation triggers (push from eco -> frontier)

| Signal | Source |
|---|---|
| `active_sq_rules` non-empty + rule-violation text       | workflow_inject + sq_resolver |
| `phase_crossover: true` flag                            | incomplete_detector |
| User text has 3+ frontier keywords                      | turn_classifier |
| Last turn already escalated                             | frontier_inertia |
| Attachment present (image / audio)                      | F101 attachment handler |
| `next_turn_frontier_hint.json` written by last turn     | post-reply quality detectors (z.188) |

### Next-turn frontier hint (alpha.59z.188, PND-036/037/038)

After every reply the chat server runs three pure detectors:

1. **completion_hallucination**: the LLM said "voy a configurar /
   procedere a setup / ya he procesado" but invoked zero tools.
   Implemented in `src/chat/completion_hallucination_detector.ts`;
   waives when a tool fired, the reply mentions a verb id like
   `yujin.X.Y`, or contains a fenced code block (proof of intent).
2. **unwarranted_refusal**: the LLM said "no tengo capacidad de X"
   AND the user asked about a topic AND the manifest has a verb
   that does X. Implemented in `src/chat/refusal_detector.ts`.
3. **confusion_signal**: the LLM apologized ("mis disculpas /
   hubo un error / aun estoy en proceso"). Independent of (1);
   even when a tool fired, an apology means the tier is over its
   head.

Any of those flags triggers two side effects:
- append a row to `~/.yujin-forge/hallucinations.jsonl` (telemetry).
- write `~/.yujin-forge/next_turn_frontier_hint.json` (one-shot).

On the next user message, `selectBrainForTurn` reads + atomically
consumes the hint file. If present, the turn forces frontier
regardless of gate verdict. User overrides (/eco /frontier) still
win.

This is INDEPENDENT of the smart-auto classifier -- it kicks in
even when the classifier is unreachable.

## De-escalation (frontier -> eco)

- 5+ minutes idle since last turn -> reset to default per phase.
- User explicitly says `eco only` / `bajemos a Haiku`.
- Workflow phase advances back to I (rare).

## What to tell the user

When the user asks why their last turn cost more / less than the
previous one, the brain MAY say:

- "Cambie a Sonnet en este turno porque el texto disparo G2-vault-min-keys
  y el router escala las defensas de SQ."
- "Te baje a Haiku porque la consulta es corta y la fase es II
  (clarificacion)."

The brain MUST NOT lie about the tier picked. The panel shows a
badge with the actual tier per turn (alpha.58 #2).

## Manual override

```
yf brain set eco
yf brain set frontier
yf brain reset
```

`reset` returns the router to automatic picking.

## When the cap exhausts

If the managed brain monthly cap fires, the router cannot
escalate -- it surfaces a "cap exhausted" error and offers BYOK
(see `byok-vs-managed` doctrine).
