Files
majordomo/docs/adr/0001-package-layout.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

2.1 KiB

ADR-0001: Package layout — canonical types in a leaf llm package, root re-exports

Status: Accepted — 2026-06-10

Context

Provider implementations (openai, anthropic, google, ollama/foreman) must share the canonical types (Message, Request, Response, Capabilities, Model, Provider). If those types lived in the root majordomo package, the root could not also register built-in providers (root → provider/openai → root is an import cycle). go-llm solved this with a v2/provider leaf package; the kickoff sketch puts the Provider interface in provider/provider.go and the message types at root, which recreates the cycle.

Decision

  • All canonical contract types live in the leaf package majordomo/llm (Message, Part, Request, Response, Option, Tool, Toolbox, Capabilities, Stream, Model, Provider, error classification). It imports nothing else in the module.
  • The root majordomo package re-exports every canonical type via type aliases (plus constructor/option wrappers), so consumers write majordomo.Request, majordomo.UserText(...) and rarely import llm.
  • The root owns assembly: Registry, Parse, env-DSN loading, the chain executor, and (from Phase 3) registration of real provider clients.
  • The planned resolve/ package is folded into the root: the grammar needs registry state (aliases, providers, env fallback) at every expansion step, and a callback interface between two packages bought nothing but indirection.
  • health/, media/, provider/<impl>/, provider/fake/, agent/, and skill/ are subpackages importing llm (and never each other, except agent → skill).

Consequences

  • No import cycles; new providers are additive subpackages.
  • Consumers get the flat one-import API the kickoff sketches.
  • Type aliases (not wrappers) mean zero conversion cost and full interchangeability between majordomo.X and llm.X.

Alternatives considered

  • Everything in root. No cycles only if providers also live in root — a single giant package. Rejected.
  • Self-registering providers via package init() side effects. Hides wiring, breaks multi-registry isolation, surprises tests. Rejected.