Files
majordomo/docs/mort-migration.md
steve 97513141dc docs: examples for every hard requirement + mort migration blueprint
Phase 7: nine runnable examples/ programs (parse, failover chains with
trailing alias, tiers, LLM_* env providers, multimodal, tool loop,
Generate[T], agent, skills); docs/mort-migration.md mapping mort's
go-llm/go-agentkit usage onto majordomo APIs with the planned additive
library extensions and conversion order; README finalized with the
complete matrix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-10 13:17:20 +02:00

8.2 KiB

mort → majordomo migration blueprint

The plan for converting mort off go-llm/go-agentkit and onto majordomo, executed as the final phase of the build run. Inventory source: a full sweep of mort's 160 importing files (2026-06-10).

Mandate (Steve, mid-run): this is not a 1:1 port. mort's agent / LLM / tool code may be rewritten wholesale into a properly designed, extensible system — the constraint is user-facing behavior: .query still does quick research, .research / .deepresearch keep their roles, chatbot and general agents keep broadly the same functionality.

Layering: what moves, what stays

mort features (.query, chatbot, cookbook, scaddy, ...)        — stay, re-wired
mort orchestration (lanes, run-critic, budgets, delivery,
  encryption, compaction policy, tracing/usage recording)     — stay, re-based
go-llm + go-agentkit                                          — DELETED
majordomo (Parse/tiers/chains/health, providers, media,
  tools, agents, skills)                                      — the new floor

mort-side systems survive because majordomo's seams are public: providers and models are small interfaces (lane wrapping = an llm.Provider decorator; the foreman 30-minute timeout = a Model decorator), the agent loop exposes observers, and the registry accepts any provider.

Core mappings

mort today (go-llm / agentkit) majordomo
llm.Parse(spec) / llms.ParseModelRequest Registry.Parse (same grammar; chains, aliases, LLM_* lazily)
tier convars + llm.RegisterResolver RegisterResolver (library addition №1) backed by the same convar reads
legacy aliases (sonnet, haiku, ...) RegisterAlias table, unchanged strings
comma failover chains + bench-on-failure chain executor + health tracker (ADR-0006/0008)
llm.SetFailoverDefaults/Observer, failoverlog WithHealthConfig/WithChainConfig + ChainConfig.Observer (addition №7) feeding the same llm_failover_events store
.failover bench/unbench/list health.Tracker Bench/Unbench/Snapshot (addition №6)
llm.Client + provider constructors + OPENAI_KEY etc. built-ins + env keys; mort's nonstandard OPENAI_KEY/GOOGLE_GEMINI_API_KEY bridged via WithAPIKey at wiring
deepseek/moonshot/xai/groq clients openai.New(WithName(...), WithBaseURL(...)) compat presets registered at boot
LLM_M1=foreman://... env DSN identical (ADR-0004 parity); foreman timeout via a Model-wrapping decorator
llm.Define[Args] / DefineSimple (60+ sites) DefineTool[Args] (addition №2, schema via SchemaFor)
llm.NewToolBox / ToolBox.Execute/AllTools llm.NewToolbox / Execute / Tools
llm.Message/UserMessage/SystemMessage/UserMessageWithImages Message constructors + UserParts(Text, Image...)
llm.Image{Base64/URL} ImagePart (bytes; URL images fetched by mort's imageutil at the edge — it already downloads Discord attachments)
model.Complete(ctx, req) Model.Generate
llms.GenerateWith[T] (hidden tool trick) majordomo.Generate[T] (native structured output; flexBool-style tolerance stays mort-side in the few schemas that need it)
llm.WithPromptCaching/WithMaxTokens/WithTemperature request options; prompt caching = addition №4 (Anthropic cache_control)
reasoning :high suffixes in specs stripped by mort's spec layer → WithReasoningEffort (ADR-0003)
usage detail keys (cached/reasoning tokens) Usage detail fields (addition №3)
agentkit.Agent{...}.Run agent.New(...).Run
Observer/FuncObserver/MultiObserver WithStepObserver / OnStep (multiple registrations compose)
MaxIter / MaxIterFunc (critic adjust_budget) WithMaxSteps / WithMaxStepsFunc (addition №5a)
Steer mailbox (critic nudges) WithSteer per-step message injection (addition №5b)
ContextCompactor WithCompactor pre-step hook (addition №5c); mort keeps its summarize-the-middle policy
MaxConsecutiveToolErrors / MaxSameToolCallRepeats loop guards (addition №5d)
ErrMaxIterExhausted agent.ErrMaxSteps
SessionTools{Tools,PostRun,Cleanup} / AttachImages mort-side session-tool layer over AddToolbox + run history; artifacts/delivery were always mort code
AgentDefinition/PhaseDefinition/RunPipeline mort-side phase runner: sequential agent.Run calls with per-phase model/tools/system + template vars (mort's executor owns this today anyway)
skill manifests (skills/*/skill.yml) unchanged YAML → skill.New(name, WithInstructions(sys), WithToolbox(...)) + mort's executor bounds
llm.Transcription types move into pkg/logic/transcribe (out of majordomo's scope)
lane wrapping of provider.Provider laneProvider implementing llm.Provider, registered via RegisterProvider — no fork needed (this was inventory pain point №6)

Library additions planned during conversion

Each is additive, general-purpose, and lands in majordomo main with tests and an ADR before mort consumes it:

  1. Registry.RegisterResolver(Resolver) — dynamic alias resolution (DB-backed tiers); checked after static aliases during expansion, output re-expanded recursively with the same cycle guard.
  2. DefineTool[Args](name, desc, fn) — typed tools: schema from SchemaFor[Args], arguments unmarshaled before the handler runs.
  3. Usage{CacheReadTokens, CacheWriteTokens, ReasoningTokens} populated by the providers that report them (Anthropic, OpenAI, Google).
  4. WithPromptCaching() → Anthropic cache_control (system + last turn); no-op elsewhere.
  5. Agent loop hooks: (a) WithMaxStepsFunc, (b) WithSteer, (c) WithCompactor, (d) WithToolErrorLimits.
  6. health.Tracker.Bench(key, until) / Unbench(key) / Snapshot() for the .failover admin surface and web UI.
  7. ChainConfig.Observer — one callback per failover decision (attempt, classification, bench) feeding mort's event log.

Conversion order (Phase 9 execution plan)

  1. Library additions above (majordomo main, gated, committed).
  2. pkg/logic/llms rebuild — the choke point. One mort package that owns: a majordomo.Registry with mort's env keys, compat providers, lane-wrapped registration, the convar-backed tier resolver, legacy aliases, the foreman timeout decorator, health observer wiring, and the ParseModelRequest/ParseModelForContext/GenerateWith/CallAndExecute/ SimpleCall mort-facing API (now thin wrappers over majordomo). The reasoning-suffix dialect is stripped here.
  3. Tool layerpkg/skilltools registry keeps its gating/audit design; gated_tool.go re-bases NewGatedTool[Args] on DefineTool[Args]; ~96 tools recompile against llm.Tool.
  4. Executorsskillexec + agentexec re-based on agent.Agent (loop guards, compactor, steer, observers from the additions). The no-tools direct path falls out naturally (empty toolset sends no tools array). Phased agents = mort-side sequential runner.
  5. Call sites — chatbot, cookbook, recipe, summary, standingquery, tasks, fitness, drawbot, lottery, reminder, epon, scaddy, clipper, transcribe: mechanical re-pointing to the new llms wrappers and canonical types.
  6. Delete go-llm + go-agentkit from go.mod; repo-wide grep must be clean; go build ./... + tests green.
  7. Branch, push, PR (never touching mort's main).

Behavioral deltas to verify after conversion

  • Default chain knobs differ (go-llm: 3 retries/5m flat cooldown/jitter; majordomo: 1 retry/threshold 2/5s→5m exponential). mort's convars (llms.failover.max_retries, llms.failover.cooldown_seconds) map onto ChainConfig.TransientRetries + health.Config so operators keep their dials; semantics ADR-0006 apply.
  • go-llm benched-everything chains ignore cooldowns; majordomo returns the joined exhaustion error. mort's executor already retries failed runs.
  • ErrRequestSpecific (400/413/422) failed over WITHOUT benching in go-llm; majordomo fails fast by default — mort sets AdvanceOnPermanent: true to preserve availability behavior.
  • Image URLs: go-llm providers fetched llm.Image{URL}; majordomo is bytes-only — imageutil.FromTextURLs now downloads (it already has the HTTP plumbing and the 50MB cap).