fabro/docs/internal/llm-client-resolution.md
Bryan Helmkamp 3510d5081d
Implement the lithos CredentialProvider trait directly
fabro-auth defined its own `CredentialSource` trait beside the lithos
`CredentialProvider`, with a parallel `ResolveError` and an adapter between
them, because lithos had no way to ask which providers a store can serve
right now. It does now: `credentials::readiness`, `ClientBuilder::build_ready`,
and `CredentialError::Unusable`.

- The vault, SQL vault, API-key, and extra-headers stores implement
  `CredentialProvider` directly. Material that is present but unusable (an
  expired token with no refresh, a wrong-typed vault entry, a header secret
  that did not resolve, a store read failure) is `CredentialError::Unusable`
  with the operator-facing reason; its `Display` replaces
  `auth_issue_message`. `is_configured` is the cheap presence check.
- `fabro_llm::build_client` calls `build_ready`; `FabroClient::auth_issues`
  carries `CredentialError`. `fabro_llm::configured_providers` replaces the
  per-store `configured_providers` method.
- `CredentialSource`, `ResolvedCredentials`, `lithos_credentials`,
  `ResolveError`, and `auth_issue_message` are deleted. Twenty files that
  held `Arc<dyn CredentialSource>` hold `Arc<dyn CredentialProvider>`.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-10 09:16:39 -06:00

34 lines
2.4 KiB
Markdown

# LLM Client Resolution
This document defines how Fabro resolves LLM credentials and constructs `fabro-llm` clients.
## Core Rules
- The lithos `CredentialProvider` trait is the credential authority; Fabro's vault, SQL secret store, and API-key stores implement it directly.
- Long-lived runtime contexts store `Arc<dyn CredentialProvider>` and `Arc<Catalog>`, not `Client`.
- Call `fabro_llm::client::Client::from_source(&source, catalog).await?` at the point of use.
- Standalone setup and tests that use default settings build a default `Arc<Catalog>` locally, then pass it explicitly.
- `GenerateParams::new(model, client)` always receives an explicit `Arc<Client>`.
- When a caller needs diagnostics in runtime request-serving paths, read `FabroClient::ready` and `auth_issues` (from `ClientBuilder::build_ready`), or call `lithos_llm::credentials::readiness` directly.
- `VaultCredentialSource` is the normal source for vault-backed runtime contexts; `VaultCredentialSource::environment_only()` serves env-only or no-vault contexts.
## Why
- Rebuilding a client from the source at point of use preserves OAuth refresh behavior on long-running processes.
- Holding the source on contexts avoids process-global installs and cross-context leakage.
- Threading the catalog into credential resolution keeps custom providers, aliases, header-only providers, and API model IDs consistent across auth, client registration, and request translation.
- Requiring an explicit client on `GenerateParams` makes the old silent fallback bug unrepresentable.
## Application
- Workflow state lives on `RunServices.llm_source` and `RunServices.catalog`.
- Server state lives on `AppState.llm_source` and `AppState.catalog()`.
- Hooks and other long-lived executors receive a source plus catalog and derive clients when they actually generate.
- One-shot CLI commands may resolve a source locally, build a default settings catalog if they do not load runtime catalog settings, then derive a client once for that operation.
## Enforcement
- Do not add new `Client::from_env`-style shortcuts in production paths.
- Do not cache a long-lived `Client` where OAuth refresh or storage-dir rebinding matters.
- Do not construct LLM clients or resolve credentials without an explicit `Arc<Catalog>` or `&Catalog`.
- Mirror [server-secrets-strategy.md](server-secrets-strategy.md): production credential resolution should be explicit about where secrets come from and how they flow into subprocesses.