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