Files
majordomo/docs/adr/0012-agent-loop.md
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

54 lines
2.8 KiB
Markdown

# 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.