Runner
A Runner owns one execution. Application code rarely constructs it directly —
engine.execute() creates it, injects shared services, and returns the result.
Think of the runner as the object you would want in a debugger: every transition for this run lives here.
Why it exists#
Agent execution is stateful. If provider calls, tool calls, approvals, and handoffs are
coordinated by separate application paths, you cannot reconstruct the run later. The runner
keeps those transitions ordered and attributable to a single runId.
The runner answers: what happened in this execution, and in what order?
How you use it#
const result = await engine.execute(agent, "Should we refund order ord_7741?", {
sessionId: "refund_441",
});
console.log(result.runId);
console.log(result.output);RunResult includes the run id, output, messages, finish reason, and run context. Observability
comes from the engine’s event / trace pipeline — subscribe to events or export traces rather
than expecting a hidden result.trace field.
What it coordinates#
run.started
→ session / memory load
→ provider.call
→ tool.execute (optional approval pause)
→ handoff (optional)
→ provider.call
→ output.validated
run.completed | run.failedExact stage order is documented in Execution Pipeline.
Runner vs Engine#
| Engine | Runner | |
|---|---|---|
| Lifetime | Process | One run |
| Creates | Runners | — |
| Owns | Shared services | Execution state |
| Scale | One (typically) | Many concurrent |
See Mental Model.
Observing a run#
Attach an event bus or TraceManager to the engine before executing. Studio and exporters
consume those events:
import { JsonTraceExporter, TraceManager } from "@shiro-sdk/core";
const traces = new TraceManager();
const engine = new Engine({ events: traces });
await engine.execute(agent, input);
const json = await traces.export(new JsonTraceExporter());Open the export in Studio, or keep it for a bug report. See Tracing.
Best practices#
- Let the engine create runners
- Pass
sessionIdwhen the conversation continues - Treat approval rejection as a normal outcome, not a mysterious crash
- Keep mutating tools idempotent so a retried runner step is safe
Common mistakes#
- Reimplementing the loop with manual provider + tool calls
- Continuing a paused approval outside the runtime
- Mixing unstructured application logs with “the run failed” and calling it done
- Creating runners without going through a ready engine