ADR-0005: Models are interchangeable — provider abstraction with fallback
Status
Proposed — awaiting approval from Alex Place.
Context
The North Star is explicit: models are replaceable; never hardcode a provider — the Convergence Core must never assume a specific LLM. Reasons: providers change pricing and availability, the project runs a local Σ₀/Ouro model that must slot in as a peer, and vendor lock-in would couple the loop's Reason stage to one company's uptime.
Without abstraction, every call site would require('@anthropic-ai/...') directly and a single outage or key problem would break chat ("all providers failed").
Decision
Providers are data, not code. The declared providers, fallback order, and default models live in the PCSF layer: data/pcsf/provider.pcsf.json (the registry — a gitignored runtime file, documented in PROVIDERS.md:17) plus the committed model roster data/pcsf/model.pcsf.json (default + available models per provider). The server reads .env for keys at startup and supports hot reload via POST /api/settings/providers without a restart (PROVIDERS.md:17-22).
All LLM calls route through a selector (provider-router.js / selectProvider, imported by dream-chat.js) with a defined fallback chain: Gemini → Claude → OpenAI → Ollama (local Σ₀/Ouro at http://127.0.0.1:11434, default ouro:latest) (PROVIDERS.md:156-161, dream-chat.js:430-431). No module hardcodes a single provider as a hard dependency.
Options Considered
Option A: Config-driven registry + fallback chain (chosen)
Pros: swap models without code changes; local model is a first-class peer; one outage degrades gracefully; keys hot-reload. Cons: an indirection layer to maintain; declared-but-unwired providers can imply capability the code lacks (see debt).
Option B: Direct SDK calls per site (rejected)
Cons: violates the North Star; lock-in; brittle to outages; the local model can't substitute cleanly.
Trade-off Analysis
The abstraction costs a small indirection and the risk of config/code drift. In exchange the project gets the single most load-bearing North Star property — interchangeability — which is what lets the local Σ₀ model and any future model plug in without touching the loop.
Consequences
- Positive: models swap via config; graceful fallback; local-first inference is a peer, not a
special case.
- Negative / trade-offs: the registry can list providers that aren't actually wired.
- Follow-ups: ARCHITECTURE.md §9.4 — Grok,
Mistral, Cohere, Perplexity are declared in PCSF but not in the fallback chain (PROVIDERS.md:167-170); either wire them or mark them clearly as future.
Alternatives considered
See Options. "Do nothing" (hardcode the current best model) is the lock-in the North Star forbids.
Evidence
| Claim | Evidence (file:line / commit / PR) | Confidence | Source |
|---|---|---|---|
| Providers declared as data (PCSF) | data/pcsf/provider.pcsf.json (gitignored runtime) + committed model.pcsf.json; PROVIDERS.md:17 |
High | code + doc |
| Calls route through a selector | provider-router.js import in dream-chat.js:9 |
High | code |
| Fallback chain Gemini→Claude→OpenAI→Ollama | PROVIDERS.md:156-161 | High | doc |
| Local model is a peer in the chain | dream-chat.js:430-431 |
High | code |
| Keys hot-reload without restart | PROVIDERS.md:22 | Medium | doc |
| Never hardcode a provider | CONVERGANCE-SIGMA0-BRIEFING.md #3 | High | project doc |