Files
majordomo/CLAUDE.md
T
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

5.3 KiB
Raw Blame History

CLAUDE.md — majordomo operating manual

majordomo is a clean-slate Go substrate for LLM-backed agents: target-agnostic model access, a parseable model naming / failover / tiering system with health tracking, multimodality, tool calls, structured output, and agents composed from model + system prompt + toolboxes + skills.

North star: majordomo exists to re-architect mort's agentic layer. mort is the first consumer and the design's acceptance test — when a choice is a toss-up, pick what makes mort's tiers, failover chains, toolboxes, and skills cleanest to express. But majordomo itself stays general-purpose and mort-agnostic: no mort types, no Discord, no mort config.

Module & stack

  • Module: gitea.stevedudenhoeffer.com/steve/majordomo, Go 1.26.
  • Stdlib-first (ADR-0007): hand-rolled net/http clients for OpenAI(+compat), Anthropic(+compat), Ollama (cloud+local), foreman. The one approved dependency is google.golang.org/genai (Google provider). Anything else needs an ADR. No go-llm, no go-agentkit — importing either is an automatic failure.

Package map (ADR-0001)

majordomo        Registry, Parse, env-DSN loading, chain executor, re-exports
  llm/           canonical contract: Message/Part/Request/Response/Option,
                 Tool/Toolbox, Capabilities, Stream, Model, Provider, errors
  health/        clock-injected health tracker (bench/backoff)
  media/         image normalization to target capabilities   (Phase 3)
  provider/fake/ scriptable in-memory provider for hermetic tests
  provider/{openai,anthropic,ollama,google}/                  (Phases 3-4)
  agent/         Agent run loop                               (Phase 5)
  skill/         Skill interface + composition                (Phase 6)
  examples/      one runnable example per hard requirement    (Phase 7-8)

Canonical types live in leaf package llm; the root re-exports them via type aliases. Providers import llm, never each other, never the root.

Parse grammar (ADR-0003)

spec    := element ("," element)*       # ordered failover chain
element := target | alias
target  := provider "/" model           # model id VERBATIM after first "/"
alias   := bare token (no slash), expands INLINE, recursively, cycle-checked
  • Parse("ollama-cloud/minimax-m3:cloud,ollama-cloud/kimi-k2.6:cloud,anthropic/opus-4.8") → try head-to-tail. Appending ,thinking expands the registered alias in place at the tail.
  • Provider resolution: registry (built-ins, RegisterProvider, eager env) → lazy LLM_{UPPER(name)} env DSN → error.
  • Single element ≡ chain of one; same Model interface, same semantics.
  • No reasoning suffixes (:high etc. are NOT stripped — model ids are verbatim). Reasoning effort becomes a request option (provider phases).

LLM_* env-DSN providers (ADR-0004, go-llm parity)

LLM_<NAME>=scheme://[token@]host[/path] — e.g. LLM_M5=foreman://token@foreman-m5.example defines provider m5; then m5/qwen3:30b works in Parse, chains, and aliases. Scheme ∈ {foreman, ollama, ollama-cloud, openai, anthropic, google, gemini} RegisterScheme. Token = credential; base URL = https://host always. New() scans the process env eagerly; unknown names also resolve lazily at Parse time (my-provLLM_MY_PROV). Malformed entries fail on use, not at startup.

Health & failover (ADR-0006, ADR-0008)

  • Transient (408/429/5xx, timeouts, conn refused/reset, DNS, deadline) vs permanent (400/401/403/404/405/422, model-not-found, ctx.Canceled). Unknown → transient. Classifier overridable.
  • One transient error → retry same target (default 1 retry). Every failed attempt counts; at threshold (default 2 consecutive) the target is benched for base 5s × 2^n, capped 5m. Success fully resets. Chains skip benched targets; 404 advances penalty-free; auth/malformed fail fast (configurable); exhaustion returns a joined error naming every target.
  • Tracker is in-memory, process-local, clock-injected. No persistence.

House conventions (mirror foreman)

  • gofmt; check errors immediately and wrap with fmt.Errorf("%w: ..."); imports stdlib → third-party → internal; // Why: doc comments where rationale isn't obvious.
  • ADRs in docs/adr/, one decision each, append-only, indexed in its README. progress.md gets a dated entry per phase.
  • Conventional commits (feat:, test:, docs:, chore:, refactor:).
  • Tests are hermetic: fake provider + fake clock; provider clients test against httptest; no network or credentials in the default suite. Live tests sit behind //go:build live / examples/live/ and skip without their env vars.
  • .env holds live keys (gitignored, never committed/printed/quoted); .env.example carries placeholders.

Gates (every phase; what CI runs)

go build ./...
go vet ./...
go test -race -count=1 ./...
go mod tidy && git diff --exit-code go.mod go.sum

CI: .gitea/workflows/ci.yaml (Gitea Actions, mirrors foreman). README.md must match reality in the same commit that changes behavior — no aspirational docs; unbuilt features are marked pending in the matrix.

Out of scope (anti-creep)

No persistent store (health is in-memory behind the registry), no observability/metrics stack, no config-file framework beyond LLM_* env DSNs, no CLI beyond examples, no provider-specific features leaking into the canonical API, nothing mort-specific in the library.