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

61 lines
2.6 KiB
Markdown

# 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_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 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.