ADR 0334: OpenAI Fast mode as a per-session switch gated by model settings
- Status: Accepted
- Date: 2026-10-17
- Deciders: Nib maintainers
- Related: ADR 0329 · 03-runtime/11-provider-model-system · 04-ux/04-builtin-commands · issue #1657 · E2E-FAST-MODE
Context
OpenAI serves a faster priority tier that Codex CLI and the OpenAI API call Fast mode. On the wire it is the Responses body field service_tier: "priority"; the Codex backend rejects "fast". The tier costs more: a ChatGPT subscription spends its plan limits faster, and the OpenAI API bills a higher per-token price. Nib never asked for a tier. pi-ai's Responses and Codex adapters already accept serviceTier and price the tier the response reports, but streamSimple dropped the option while building its base options.
The cost is a decision about one conversation, while whether an endpoint honors the field is something only the user knows for a relay. Issue #1657 asks for the subscription speed-up; maintainers asked that capability be configured in model settings.
Decision
- Model settings own capability.
ModelBinding.fastModeAllowedis an opt-in, stored in the existing providerconfig_jsonlikenativeWebSearch, so it needs no migration. The checkbox is enabled only for Responses wires (openai_codex_responses,responses), including relays; its hint names the cost on that wire. /fastowns the session switch. A new builtinbuiltin.session.fast(/fast) toggles a persistedsessions.fast_modecolumn (schema v27, additive, v25 backup).session.createandsession.configureaccept an optional booleanfastMode; omission keeps the stored value. Forks copy it; scheduled runs and delegated workers start off. A draft carries it into the session its first prompt creates. Remote sessions refuse it./fastonly works on a configured model. When the selected model's wire cannot carry the tier or the model is not allowed,/faststores nothing and points to Settings → Models → Advanced, in either direction. A stale stored switch on such a model is inert because launch also requires the opt-in.- The launcher resolves; the runtime only stamps the wire. Session launch sends
fastMode: trueto the sidecar only when the session switch is on and the user's stored binding allows it. The runtime passes pi-ai's nativeserviceTier: "priority"on main turns whose resolved wire isopenai-codex-responsesoropenai-responses; the existing pi-ai patch forwardsserviceTierthroughstreamSimple, so the adapters also apply their own tier pricing. Subagents, compaction, and one-shot plugin requests build their own options and keep the default tier. Authorization belongs to the launch model: when a trusted extension switches the runtime to one of its own models (bridgesetModelor agent activation), the runtime clears the switch, since that model has nofastModeAllowedbinding. The next launch re-applies it throughsetFastModefrom the stored binding.
Alternatives
- Per-model switch only (no session state). Avoids a migration, but turns a per-conversation cost into a global one: enabling Fast for one task speeds up and costs every conversation on that model.
- Hard-coded capability rule (official OpenAI host only). Removes a setting, but excludes relays the user knows support the field and hides the decision in code.
- Generic request-parameter injection with presets. More flexible, but an arbitrary body-field channel needs its own validation and permission story, which is larger than this need.
Consequences
- Schema v27 adds one column; older apps opening a v27 database refuse it as newer, the existing downgrade behavior.
- The capability can be wrong for a relay that ignores or rebills the field; the settings hint states that risk, and the user owns the opt-in.
- The Composer model chip shows a Fast mark while the switch is on for an allowed model, so the cost is visible at send time.
