Files
majordomo/docs/adr/0004-env-dsn-providers.md
T
Steve Dudenhoeffer 3e81fbd540
CI / Tidy (push) Successful in 9m39s
CI / Build & Test (push) Successful in 10m21s
docs: public-readiness — vibe-coded disclosure + genericize internal hosts
- README + CLAUDE.md: upfront "this is a vibe-coded project" disclosure for
  going public.
- Replace internal LAN hostnames (*.orgrimmar.dudenhoeffer.casa) with
  example.com across README, ADR-0004, the envproviders example, and env_test.go
  (assertions updated together; suite still green). Token was already a
  "change-me" placeholder, not a real secret.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-25 19:25:58 -04:00

2.6 KiB

ADR-0004: LLM_* env-DSN provider definitions (go-llm parity, plus eager load)

Status: Accepted — 2026-06-10

Context

Steve's deployments define providers via env vars that must keep working unchanged:

LLM_M1=foreman://token@foreman-m1.example.com
LLM_M5=foreman://token@foreman-m5.example.com

go-llm (v2/parse.go) implements this lazily only: Parse("m5/x") misses the registry, computes LLM_ + UPPER(name) with -_, reads exactly that var, parses scheme://[token@]host[/path] by plain string splits, requires the scheme to be a registered provider, and dials https:// + host. There is no environment scan. The kickoff additionally requires New() to load LLM_* providers eagerly and a testable LoadEnv(map).

Decision

Implement both paths over one DSN parser (byte-for-byte go-llm semantics — :// split, first-@ split, trailing-/ trim, ErrInvalidDSN on missing scheme/host, base URL always https://host[/path]):

  • Eager: New() scans the process environment for LLM_<NAME> and registers each as provider lower(<NAME>) (underscores preserved: LLM_MY_BOXmy_box). LoadEnv(map[string]string) is the explicit, testable entry. Malformed entries never fail construction: they are recorded per-name, returned joined from LoadEnv, and surface from Parse only when that name is actually referenced (matching go-llm's fail-on-use behavior).
  • Lazy (go-llm parity): an unknown provider name in Parse falls back to LLM_{UPPER(name, - → _)}, so hyphenated spec names (my-prov/xLLM_MY_PROV) work exactly as in go-llm. Lazily resolved providers are cached in the registry.
  • The DSN scheme selects a SchemeFactory (foreman, ollama, ollama-cloud, openai, anthropic, google, gemini; extensible via RegisterScheme). The factory receives the registry name and the parsed DSN (token = credential, https://host = base URL).

Consequences

  • Existing muscle memory carries over: every go-llm-resolvable LLM_* var resolves identically here.
  • Eager loading additionally makes env providers visible to discovery (Provider(name)) before first use.
  • An env DSN cannot express plain-http endpoints (https is forced) — same limitation as go-llm, kept deliberately for parity; local Ollama uses the ollama provider's own default (http://localhost:11434) rather than a DSN.

Alternatives considered

  • url.Parse-based DSN parsing: subtly different (percent-decoding, userinfo passwords). Parity wins. Rejected.
  • Failing New() on malformed LLM_* vars: one stray var would break every consumer at startup. Rejected.