Tracing
Built-in span-based tracing for observability
Stratus includes an opt-in tracing system that records spans for model calls, tool executions, handoffs, subagents, and guardrails. Tracing uses AsyncLocalStorage for zero-overhead when inactive.
Basic Usage
Wrap your agent call with withTrace() to capture a trace:
import { withTrace, run, Agent } from "@usestratus/sdk/core";
const agent = new Agent({ name: "assistant", model, tools: [getWeather] });
const { result, trace } = await withTrace("weather_request", async () => {
return run(agent, "What's the weather in NYC?");
});
console.log(trace.name); // "weather_request"
console.log(trace.duration); // Total duration in ms
console.log(trace.spans); // Array of recorded spansTrace Structure
interface Trace {
id: string; // Unique trace ID
name: string; // Name passed to withTrace()
startTime: number; // Start timestamp
endTime?: number; // End timestamp
duration?: number; // Duration in ms
spans: Span[]; // Recorded spans
}Span Types
Each span captures a specific operation:
interface Span {
name: string;
type:
| "model_call"
| "tool_execution"
| "handoff"
| "guardrail"
| "subagent"
| "custom";
startTime: number;
endTime: number;
duration: number;
metadata?: Record<string, unknown>;
children: Span[];
}| Span Type | What It Captures |
|---|---|
model_call | An LLM API call (includes agent name, turn number, usage, tool call count) |
tool_execution | A tool's execute function (includes tool name) |
handoff | An agent-to-agent handoff (includes from/to agent names) |
guardrail | Guardrail execution (input or output) |
subagent | A sub-agent execution (includes child agent name) |
custom | Custom spans you create manually |
Custom Spans
Access the current trace context to record your own spans:
import { getCurrentTrace } from "@usestratus/sdk/core";
const myTool = tool({
name: "search",
description: "Search docs",
parameters: z.object({ query: z.string() }),
execute: async (_ctx, { query }) => {
const trace = getCurrentTrace();
const span = trace?.startSpan("vector_search", "custom", { query });
try {
const results = await vectorSearch(query);
return JSON.stringify(results);
} finally {
if (span) trace?.endSpan(span);
}
},
});Inspecting Traces
const { result, trace } = await withTrace("my_trace", async () => {
return run(agent, "Hello");
});
for (const span of trace.spans) {
console.log(`${span.type}: ${span.name} (${span.duration}ms)`);
if (span.metadata) {
console.log(" metadata:", span.metadata);
}
}Exporting Traces
Register trace processors to export every completed trace:
import { addTraceProcessor, withTrace } from "@usestratus/sdk/core";
addTraceProcessor({
async exportTrace(trace) {
await fetch("https://telemetry.example.com/traces", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(trace),
});
},
});
await withTrace("support_request", async () => {
return run(agent, "Help this customer");
});Processor failures are caught and logged to console.warn so telemetry outages don't fail agent runs.
| Function | Description |
|---|---|
addTraceProcessor(processor) | Append a processor |
setTraceProcessors(processors) | Replace all processors |
clearTraceProcessors() | Remove all processors |
Azure Monitor
Use the built-in Azure Monitor exporter to send trace and span events to Application Insights:
import {
addTraceProcessor,
createAzureMonitorTraceExporter,
withTrace,
} from "@usestratus/sdk/core";
addTraceProcessor(
createAzureMonitorTraceExporter({
connectionString: process.env.APPLICATIONINSIGHTS_CONNECTION_STRING,
serviceName: "support-agent",
}),
);
const { result, trace } = await withTrace("support_request", async () => {
return run(agent, "Look up order 123");
});If connectionString is omitted, the exporter reads APPLICATIONINSIGHTS_CONNECTION_STRING. If serviceName is omitted, it uses OTEL_SERVICE_NAME and falls back to "stratus-agent".
The exporter emits Application Insights event envelopes:
| Event | Description |
|---|---|
stratus.trace | One event per trace with trace name and total duration |
stratus.span | One event per span with span name, type, duration, trace ID, and parent span ID |
Zero Overhead
When withTrace() is not used, getCurrentTrace() returns undefined and
all tracing code paths are skipped. There is no performance cost for tracing
when it's not active.
Last updated on