dcd004289f
Phase 1 of the majordomo build: - llm/ canonical contract (messages, parts, tools, capabilities, streaming, Model/Provider, error classification) - health/ clock-injected tracker (threshold bench, exponential capped cooldown, reset-on-success) - root Registry + Parse (verbatim model ids, inline recursive alias expansion with cycle detection, chain dedup), LLM_* env-DSN providers (go-llm parity: lazy fallback + eager LoadEnv), health-aware chain executor behind the Model interface - provider/fake scriptable test provider; hermetic test suite incl. the trailing-thinking chain and foreman:// env loading - ADRs 0001-0008, CLAUDE.md, README (honest matrix), CI workflow, docs/phase-1-design.md Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2.6 KiB
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.orgrimmar.dudenhoeffer.casa
LLM_M5=foreman://token@foreman-m5.orgrimmar.dudenhoeffer.casa
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 forLLM_<NAME>and registers each as providerlower(<NAME>)(underscores preserved:LLM_MY_BOX→my_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/x→LLM_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 viaRegisterScheme). 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
ollamaprovider'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.