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,57 @@
|
||||
# ADR-0003: Parse grammar — verbatim model ids, inline alias expansion, chains
|
||||
|
||||
**Status:** Accepted — 2026-06-10
|
||||
|
||||
## Context
|
||||
|
||||
Callers (mort first) address models by string: single targets, tier aliases,
|
||||
and comma-separated failover chains, with custom and env-defined providers as
|
||||
first-class elements. go-llm's grammar is close but nests alias-chains as
|
||||
composite Models and strips `:low/:medium/:high` reasoning suffixes, which
|
||||
collides with Ollama-style tags (`minimax-m3:cloud`) and Google-style ids.
|
||||
|
||||
## Decision
|
||||
|
||||
Grammar (binding, from the kickoff):
|
||||
|
||||
```
|
||||
spec := element ("," element)*
|
||||
element := target | alias
|
||||
target := provider "/" model # model = everything after the FIRST "/",
|
||||
# up to the next comma, passed VERBATIM
|
||||
alias := bare token, no slash
|
||||
```
|
||||
|
||||
- Provider resolution order per target: registered providers (built-ins,
|
||||
RegisterProvider, eagerly env-loaded) → lazy `LLM_{UPPER(name)}` env DSN
|
||||
(ADR-0004) → error naming both places checked.
|
||||
- Aliases expand **inline** wherever they appear (head/middle/tail),
|
||||
recursively, into the flat element list. Cycles are detected via the
|
||||
expansion stack and return `ErrAliasCycle` — never a hang. Inline (not
|
||||
nested-Model, as in go-llm) expansion keeps one flat chain so health
|
||||
skipping and error reporting see every element uniformly.
|
||||
- Duplicate elements after expansion are dropped (first occurrence wins):
|
||||
retrying an already-failed target in the same pass is never useful.
|
||||
- A single element and a multi-element chain return the same `Model`
|
||||
(a chain of one) — identical retry/health semantics, callers never branch.
|
||||
- **No reasoning-suffix stripping.** mort's `:high` dialect is handled by
|
||||
mort's spec layer during migration; majordomo will expose reasoning effort
|
||||
as an explicit request option instead.
|
||||
- The package-level `Default()` registry (lazy, loads process env) backs
|
||||
`majordomo.Parse` for go-llm-style one-call ergonomics; `New()` builds
|
||||
isolated registries for tests/multi-tenant use.
|
||||
|
||||
## Consequences
|
||||
|
||||
- `m1/richardyoung/qwen3-14b-abliterated:q4_K_M` (a real mort tier value)
|
||||
parses as provider `m1`, model `richardyoung/qwen3-14b-abliterated:q4_K_M`.
|
||||
- A bare token that is a provider name yields a targeted error
|
||||
("use openai/<model-id>").
|
||||
- Alias updates after Parse don't affect already-built Models (expansion is
|
||||
at Parse time). mort re-parses per request, so DB-tier edits still apply.
|
||||
|
||||
## Alternatives considered
|
||||
|
||||
- Nested alias expansion (go-llm): opaque chains inside chains; health
|
||||
skipping can't see the elements. Rejected.
|
||||
- Reasoning suffixes in the grammar: breaks verbatim ids. Rejected.
|
||||
Reference in New Issue
Block a user