Files
majordomo/docs/adr/0012-agent-loop.md
T
steve 7dab4112ff feat: agent run loop, Generate[T], reflect-derived schemas
Phase 5:
- agent/: model + system prompt + toolboxes composition; bounded
  tool-dispatch loop (default 10 steps); panic-proof tool execution;
  unknown-tool and duplicate-name handling; history continuation; step
  observers; partial results on ErrMaxSteps/errors (ADR-0012)
- llm.SchemaFor[T]: strict-compatible JSON schemas from Go types
  (nullable pointers, description/enum tags, recursion rejected)
- majordomo.Generate[T]: typed structured output with fence-stripping
  decode and model-naming errors
- README agents/structured-output sections + matrix synced

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

2.8 KiB

ADR-0012: Agent run loop

Status: Accepted — 2026-06-10

Context

Agents are the consumer-facing composition: Model + system prompt + toolboxes (+ skills), run as a tool-dispatch loop. mort's agents need multi-step tool use, bounded loops, per-step observation (tracing/usage recording), conversation continuation, and a loop that survives every tool failure.

Decision

  • agent.New(model, system, opts...) with options WithToolbox, WithTools, WithSkill, WithMaxSteps (default 10), WithRequestOptions (per-step generation knobs), WithStepObserver. AddSkill/AddToolbox extend an agent after construction (configure-then-share concurrency contract).
  • Run(ctx, input, opts...): history seeds via WithHistory (a previous Result.Messages round-trips), per-run request options, per-run OnStep callbacks. The loop: Generate with the merged toolset → no tool calls = final answer → otherwise execute calls sequentially, append the assistant turn + one RoleTool message, repeat.
  • Never panics, never stalls on tool failure: handlers run through llm.ExecuteTool (panic-recovering, errors → IsError results); unknown tool names come back as IsError results the model can react to; observer panics are swallowed. Only model errors (already retried/failed-over by the chain) and ctx cancellation abort the run — and even then the partial Result (transcript, steps, usage) is returned alongside the error, as it is on ErrMaxSteps.
  • Duplicate tool names across toolboxes/skills fail loudly at Run — a silently shadowed tool is a debugging trap.
  • Skills compose additively (the agent.Skill interface lives in this package so agent does not import skill): Instructions() append to the system prompt in attachment order; Tools() join the merged toolset.
  • Intermediate-step streaming = step observers (agent-level and per-run), invoked synchronously after every step with the response and tool results. Token-level streaming inside the loop was deliberately deferred: tool-heavy steps end on buffered tool calls anyway, and mort's observers (trace/usage recorders) want completed steps. Model.Stream remains available for direct streaming outside the loop.
  • Generate[T] + llm.SchemaFor[T] give typed structured output: reflect-derived schemas (strict-compatible: all properties required, additionalProperties:false, pointers → nullable anyOf), markdown-fence stripping as a decode fallback, errors that name the serving model.

Consequences

  • An agent over a failover chain inherits retry/backoff/media normalization transparently — no agent-level error handling for transient provider trouble.
  • Sequential tool execution is deterministic and trace-friendly; parallel dispatch can be added later behind an option without API change.