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

2.4 KiB

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: production credential resolution should be explicit about where secrets come from and how they flow into subprocesses.