# Fast tier — detection, storage, and wires

Where the fast feature's code lives, where its data comes from, and the
detection rules. Sibling doc: [`thinking-effort.md`](thinking-effort.md)
(the same "model capability → UI toggle" declarative pattern; the structure
is deliberately aligned).

## 1. What Fast is

The gauge button in the composer's thinking-effort panel. When on, the request carries the
vendor's high-speed knob:

| Family | Wire shape | Billing reality |
|---|---|---|
| GPT 5.4 / 5.5 / 5.6 | body `service_tier: "priority"` (OpenAI's priority processing) | On the Codex subscription the endpoint advertises it as a per-model tier ("1.5x speed, increased usage"); which models expose it comes straight from `service_tiers` (§2.1), not a guess |
| Claude Opus 4.6 / 4.7 / 4.8 | body `speed: "fast"` + header `anthropic-beta: fast-mode-2026-02-01` | Also pay-as-you-go; a subscription account without usage credits gets Anthropic's 429 "Usage credits are required for fast mode", surfaced **as-is** — an account problem, not lack of support |

Absence from this table does not prove that a provider lacks a fast tier.
xAI documents Priority Processing for its official API. Grok 4.6 on the
subscription CLI chat proxy accepts the same `service_tier` values; other
Grok subscription models stay unverified. The UI and route-aware
behavior are specified in [Composer speed control](../../ui/composer-fast-control.html).
Implementation verification is tracked in the linked design status.

## 2. Route capability

`providers.fast.fast_capability` is shared by the agent-settings projection and
turn dispatch. `supports_fast` is its boolean compatibility projection. Explicit
configuration false disables support. Codex uses the account catalogue; explicit
route true can declare support; Claude retains its existing declaration. Official
xAI API Grok 4.6 has a provider-and-endpoint-scoped declaration. Grok 4.6 on
the Grok subscription route is supported; other subscription models remain
unknown. Other routes retain catalogue lookup.

The thinking panel replaces its help icon with the supplied GaugeIcon. Activation
adds no border or background; the needle remains advanced. The preference is
stored per session and provider/model. Standard explicitly overrides agent defaults.
The user-facing contract and prototype live in
[Composer speed control](../../ui/composer-fast-control.html).

### 2.1 Codex's official source

`GET https://chatgpt.com/backend-api/codex/models?client_version=<ver>` — the
same account-level endpoint the official `codex` CLI hits on startup —
authorized with the subscription OAuth bearer + `chatgpt-account-id`. Each
model carries `service_tiers` (an `id:"priority"` entry ⇔ has a fast tier),
`supported_reasoning_levels` (the thinking picker), and the real subscription
`context_window` (372k, not the API platform's 1050k). Requests and dispatch
use the `originator: codex_cli_rs` + `version` identity — the backend
greylists ids (e.g. `gpt-5.6-luna`) by client identity, so a different
originator gets the model in the list but 404s at dispatch.

models.dev is not the source here because it tracks the public *API platform*
catalogue rather than the subscription front-door: it lists un-runnable ids,
reports the wrong context window, and can only reconstruct a fast flag from an
id-prefix guess.

## 3. Storage: read official → write config → read the file thereafter

Codex principle: **prefer the vendor's live catalogue, then OpenProgram's
last-known-good catalogue, then the local official CLI cache.**

| Layer | Location | Persistence |
|---|---|---|
| Codex official endpoint and CLI cache | `openprogram/providers/openai_codex/list_models.py` | live endpoint first; `~/.codex/models_cache.json` is a stale display fallback |
| OpenProgram subscription catalogue | `providers/subscription_catalog.py` | atomic last-known-good copy; refreshed after login and periodically |
| config spec row (with `fast`/`thinking_levels`/`context`) | successful official refreshes automatically add, update, and retire account models while honoring disable tombstones | config file |
| `Model.fast` field | `_build_model_from_row` reads the config row's `fast` (row wins; codex rows always carry it); enters `ENABLED_MODELS` at registry build | memory only (in-process dict, sourced from config) |
| claude-code hand table | `providers/enabled_models.py::default_fast` (only the Opus part is on the detection path now) | source code |
| models.dev catalogue | `openprogram/providers/sources/models_dev.py` | remote; 1h in-memory cache, no disk cache |

Flow: **official endpoint → normalise → last-known-good cache + config.json →
registry → supports_fast / dispatch**. CLI cache rows can keep the browser
useful offline, but their accompanying error prevents them from deleting or
adding configured models as if they were fresh.

## 4. Event flow: adapts to any switch, no page reload

```
connect / session switch / model switch / every turn ack+settle
  → frontend loadAgentSettings()  (lib/runtime-bridge/providers.ts)
      → GET /api/agent_settings       (apps/server/openprogram_server/_webui/routes/execution/runtime.py)
      chat.fast = supports_fast(session's provider, model)   ← recomputed
  → zustand agentSettings.chat.fast
  → composer re-renders: enables or explains the gauge control
```

Send-side double gate: the composer only attaches
`service_tier: "priority"` when `fastEnabled && fastSupported`; otherwise it
sends `default`. Dispatch revalidates the actual model. A stale preference never
enables priority on an unsupported route.

## 5. Wire side (request builders)

| Builder | Behavior |
|---|---|
| `providers/openai_responses` / `openai_completions` | `opts.service_tier` → body `service_tier` (pre-existing) |
| `providers/openai_codex` (ChatGPT subscription) | `opts.service_tier` → body passthrough; dispatch uses the `originator: codex_cli_rs` + `version` identity (the backend greylists ids by client identity — see §2.1) |
| `providers/anthropic` | `opts.service_tier` present **and** `model.fast` → body `extra_body={"speed":"fast"}` + `_BETA_FAST` header (appended via `_build_client(fast=...)`, never clobbering other betas) |
| all other wires | no passthrough; the knob never leaves the process |

## 6. File map

```
openprogram/providers/types.py                     Model.fast field
openprogram/providers/enabled_models.py            default_fast (claude-code Opus only) + config-row backfill
openprogram/providers/openai_codex/{openai_codex,runtime}.py   service_tier passthrough; codex_cli_rs identity + _CODEX_CLIENT_VERSION
openprogram/providers/anthropic/{anthropic,_claude_code_direct_runtime}.py  Claude fast wire + registration backfill
openprogram/providers/openai_codex/list_models.py                    official endpoint fetch + normalise (fast/thinking/context source)
apps/server/openprogram_server/_webui/_model_listing/fetchers/__init__.py  orchestration: passes through fetcher fast/thinking, enrich can't overwrite
apps/server/openprogram_server/_webui/_model_listing/listing.py        supports_fast entry; list_models_for_provider prefers fetcher thinking
apps/server/openprogram_server/_webui/routes/execution/runtime.py                /api/agent_settings emits chat.fast
apps/web/lib/session-store/types.ts                     AgentBadgeInfo.fast type
apps/web/components/chat/composer/index.tsx             toggle visibility + send gate
```

Change guide: codex fast/thinking is fully automatic — add/remove a model
needs nothing but a Fetch; change detection logic → touch only
`listing.py::supports_fast`; claude-code add/remove fast → the Opus part of
`default_fast`; another provider wanting fast → works once models.dev knows it.

## Response evidence

Completions and Responses retain requested and actual tier separately in Usage.
Per-call tier evidence is accumulated as a list, saved with the assistant message,
and rendered in the existing usage footer, including after reload. Missing tier
metadata is unconfirmed. xAI reported cost takes precedence over catalogue cost;
no speed ratio is inferred from a requested tier.
