| 1 | # Provider, model, and settings contract for v0.9.1 |
| 2 | |
| 3 | This note records the live-code answers used for the v0.9.1 cutover. A provider |
| 4 | is a route/account boundary. A model is a provider-qualified choice. Provider |
| 5 | setup and adding a model are deliberately separate operations. |
| 6 | |
| 7 | ## Live state definitions |
| 8 | |
| 9 | 1. **Configured, enabled, current, saved, and default are distinct.** A provider |
| 10 | is configured when `config::provider_is_configured` finds the active route, |
| 11 | usable auth/external consent, or meaningful explicit provider configuration |
| 12 | (`provider_is_configured` in `crates/tui/src/config.rs`; grep the symbol |
| 13 | rather than trusting a line number). A used model is a |
| 14 | `(provider identity, model id)` route in the recent-use index |
| 15 | (`crates/tui/src/model_relevance.rs`, #6533); the |
| 16 | current model is `App::{api_provider,model,auto_model}`; a saved |
| 17 | provider-specific preference is `Settings::provider_models`; and the startup |
| 18 | default is `Settings::default_provider` plus the provider-scoped preference |
| 19 | (with `default_model` retained only as the DeepSeek compatibility fallback). |
| 20 | Startup resolves these layers in `App::new` (`tui/app.rs:2964-3220`). |
| 21 | |
| 22 | 2. **Duplicate IDs remain provider-qualified.** `ModelPickerRow` carries both an |
| 23 | `ApiProvider` and wire model ID. Cross-provider rows render as |
| 24 | `Provider display name · model-id`, and apply events preserve the provider |
| 25 | (`tui/model_picker.rs:203-213,1247-1255`). No bare model ID is treated as a |
| 26 | globally unique owner. |
| 27 | |
| 28 | 3. **Non-catalog cases are conservative.** The active custom/unknown/local tag |
| 29 | remains a selectable current row when the route accepts passthrough IDs. |
| 30 | Retired aliases are normalized for display without losing the pre-apply |
| 31 | value. `auto` is synthetic and is never recorded as a used route. |
| 32 | Self-hosted/keyless means only that authentication is unnecessary; it does |
| 33 | not imply reachability or health. Row selectability and explanations come |
| 34 | from the route-specific readiness snapshot |
| 35 | (`tui/model_picker.rs:434-459,934-1018`). |
| 36 | |
| 37 | 4. **Discovery is intentional; the default view ranks by use.** The ordinary |
| 38 | `Configured` view shows, in order: the current route, pins and Fleet models, |
| 39 | up to eight routes ranked by decayed recent use (sessions in the last 30 |
| 40 | days, one-week half-life, each route counted once per session), then one |
| 41 | default per credentialed route (`assign_default_sections` in |
| 42 | `tui/model_picker.rs`). `Catalog`, `Recent`, `Coding`, `Cheap`, and |
| 43 | `Long context` are explicit discovery views; a typed query also searches the |
| 44 | full lake. Applying a catalog row records that route as used this session, |
| 45 | so it ranks in later ordinary opens without exposing the rest of the |
| 46 | catalog; routes left unused age out of the default view. |
| 47 | |
| 48 | 5. **Cross-provider apply has a bounded effect.** Merely moving focus previews |
| 49 | destination route facts and changes nothing. Enter validates the destination, |
| 50 | switches only the current session route, saves that provider's model |
| 51 | preference, and records the route in the recent-use index. It does not rewrite the global |
| 52 | startup provider/model unless the separate save-as-default API is used |
| 53 | (`tui/ui.rs:9495-9880`, `settings.rs:1461-1499`). Escape emits only picker |
| 54 | browsing memory and does not mutate session or settings |
| 55 | (`tui/model_picker.rs:1604-1611`). |
| 56 | |
| 57 | 6. **Existing configuration paths stay available.** The native `ConfigView`, |
| 58 | `/config`, `/config <key>`, `/config <key> <value>`, `--save`, diagnostics, |
| 59 | root/legacy config resolution, and CLI overrides remain consumers of the |
| 60 | same `Config` and `Settings` structures (`commands/groups/config/config.rs`, |
| 61 | `tui/views/mod.rs:1192-1770`). The modal is an additional typed editor, not a |
| 62 | replacement storage format. |
| 63 | |
| 64 | 7. **First-run safety is narrower than education.** Trust/workspace scope, |
| 65 | permission posture, external-credential consent, and any credential needed |
| 66 | by the chosen route are runtime gates. Mode/Fleet/Workflow explanations, |
| 67 | theme selection, and catalog browsing are optional education and must remain |
| 68 | skippable. Onboarding cannot imply that a keyless route is healthy. |
| 69 | |
| 70 | 8. **Provider names appear only for provider facts.** Auth environment variables, |
| 71 | endpoints/protocols, provider telemetry, external credential sources, and |
| 72 | legacy compatibility name the exact provider. Generic cache, retry, |
| 73 | permission, model-validation, and recovery copy uses the active provider or |
| 74 | neutral wording. |
| 75 | |
| 76 | 9. **Readiness comes from one resolved snapshot.** UI labels use |
| 77 | `provider_readiness::resolve_for_model`, which combines effective config, |
| 78 | credential/consent state, live session health, protocol capability, and the |
| 79 | selected model (`provider_readiness.rs`, `tui/model_picker.rs:971-1018`). |
| 80 | `configured`, `ready`, `managed`, and `unavailable` are not synonyms. |
| 81 | |
| 82 | 10. **Old settings still load; use is derived, not stored.** `enabled_models` |
| 83 | stays optional and serde-defaulted so old `settings.toml` files load |
| 84 | unchanged, but nothing reads or writes it (#6533). Recent use is rebuilt at |
| 85 | startup, off the UI thread, from saved session metadata (provider identity, |
| 86 | model, `updated_at`) and each session's redacted `cost.route_receipts`; a |
| 87 | committed in-session switch adds to it live. No new store is written. |
| 88 | Wire spelling is preserved and model IDs are keyed case-insensitively. |
| 89 | |
| 90 | ## Persistence rule |
| 91 | |
| 92 | The ordinary chooser is `auto`, the current route/model, pins and Fleet |
| 93 | models, recently used routes, user-declared `[[models]]` rows, and one default |
| 94 | per credentialed route (the saved provider preference or `[providers.X].model` |
| 95 | only when that route is current or recently used; otherwise its built-in |
| 96 | default). Provider configuration alone never imports that provider's catalog. |
| 97 | Catalog search remains available even when the ordinary set contains only one |
| 98 | model. Cancel never writes. Successful apply writes the smallest |
| 99 | provider-qualified state needed to make the user's choice repeatable. |
| 100 |