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-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
:highdialect 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) backsmajordomo.Parsefor 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 providerm1, modelrichardyoung/qwen3-14b-abliterated:q4_K_M.- A bare token that is a provider name yields a targeted error ("use openai/").
- 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.