feat: foundations — canonical types, Parse grammar, env DSNs, health, chains
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>
This commit is contained in:
@@ -0,0 +1,60 @@
|
||||
# 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 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.
|
||||
Reference in New Issue
Block a user