Tracing
A Shiro trace is a structured record of one run: provider calls, tool spans, handoffs, approvals, memory operations, timing, usage, and outcomes. Studio is a UI on top of that record. Logs are not a substitute.
Why traces exist#
A log line says something failed. A trace answers where in the run:
- Provider input
- Tool request + arguments
- Tool result / error
- Handoff decision
- Approval state
- Memory reads
- Output validation
Debug in that order unless you already know the failing stage.
Wiring a TraceManager#
import { ConsoleTraceExporter, Engine, JsonTraceExporter, TraceManager } from "@shiro-sdk/core";
const traces = new TraceManager();
const engine = new Engine({ events: traces });
// ... plugins, agent ...
await engine.execute(agent, input);
await traces.export(new ConsoleTraceExporter());
const json = await traces.export(new JsonTraceExporter());Pass the same traces instance (or event bus) you want Studio and exporters to share. See
Engine.
What to export#
| Destination | Use |
|---|---|
| Console | Local smoke tests |
| JSON | Bug reports, fixtures, Studio replay |
| Custom exporter | Your observability pipeline |
Studio can load exported JSON / replay mock traces while you iterate. Live attachment to a running engine uses the same event shapes — keep panel code stream-oriented.
Events vs spans#
- Events — points in time (
tool.started,approval.granted) - Spans — intervals with duration (provider call, tool execution)
Studio’s timeline prefers spans; the trace viewer shows both.
Best practices#
- Trace successes and failures
- Keep
runIdin application error responses - Attach JSON exports to issues instead of screenshots of logs
- Open Studio while building new tools — catch bad args early
Common mistakes#
- Logging only the final string answer
- Dropping tool arguments from exports
- Treating provider latency as total run latency
- Calling tools outside the runner and wondering why traces are empty