Files
majordomo/docs/adr/0003-parse-grammar.md
steve dcd004289f 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>
2026-06-10 12:35:34 +02:00

58 lines
2.6 KiB
Markdown

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