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

125 lines
8.2 KiB
Markdown

# 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 layer**`pkg/skilltools` registry keeps its gating/audit
design; `gated_tool.go` re-bases `NewGatedTool[Args]` on
`DefineTool[Args]`; ~96 tools recompile against `llm.Tool`.
4. **Executors**`skillexec` + `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).