Skip to content

Observability ​

Conceptual guide: Observability.

ObservabilityOptions ​

typescript
interface ObservabilityOptions {
  level?: EventLevel; // default "info"
  log?: LogFn;
  trace?: Tracer | Span;
}

type EventLevel = "trace" | "debug" | "info" | "warn" | "error";

Supply log and Axle creates and owns a tracer, flushing it after each operation. Supply trace to bring your own — Axle attaches spans but never ends or flushes it. level governs only the tracer Axle creates from log.

Tracer ​

typescript
class Tracer {
  constructor(options?: TracerOptions);
  get minLevel(): EventLevel;
  set minLevel(level: EventLevel);
  addWriter(writer: TraceWriter): void;
  removeWriter(writer: TraceWriter): void;
  startSpan(name: string, options?: SpanOptions): Span;
  flush(): Promise<void>;
}

interface TracerOptions {
  minLevel?: EventLevel;
  writers?: TraceWriter[];
}

Span ​

typescript
interface Span {
  startSpan(name: string, options?: SpanOptions): Span;
  end(status?: SpanStatus): void;

  trace(message: string, attributes?: Record<string, unknown>): void;
  debug(message: string, attributes?: Record<string, unknown>): void;
  info(message: string, attributes?: Record<string, unknown>): void;
  warn(message: string, attributes?: Record<string, unknown>): void;
  error(message: string, attributes?: Record<string, unknown>): void;

  setAttribute(key: string, value: unknown): void;
  setAttributes(attributes: Record<string, unknown>): void;
  setResult(result: SpanResult): void;
}

type SpanStatus = "ok" | "error" | "cancelled";
interface SpanOptions { type?: SpanType; attributes?: Record<string, unknown> }
type SpanType = string; // conventions: "workflow" | "llm" | "tool" | "action" | "internal"

end() defaults to "ok".

SpanData ​

What writers receive.

typescript
interface SpanData {
  traceId: string;
  spanId: string;
  parentSpanId?: string;
  name: string;
  type?: SpanType;
  startTime: number;
  endTime?: number;
  status: SpanStatus;
  attributes: Record<string, unknown>;
  events: SpanEvent[];
  result?: SpanResult;
}

interface SpanEvent {
  name: string;
  timestamp: number;
  level: EventLevel;
  attributes?: Record<string, unknown>;
}

SpanResult ​

typescript
type SpanResult = LLMResult | ToolResult;

interface LLMResult {
  kind: "llm";
  model: string;
  request: { messages: unknown[]; system?: string; tools?: unknown[] };
  response: { content: unknown };
  usage?: TokenUsage;
  finishReason?: string;
}

interface ToolResult {
  kind: "tool";
  name: string;
  input: unknown;
  output: unknown;
}

interface TokenUsage {
  inputTokens?: number;
  outputTokens?: number;
  totalTokens?: number;
  cachedInputTokens?: number;
  cacheWriteInputTokens?: number;
  reasoningOutputTokens?: number;
}

TokenUsage is the observability shape; Stats is the result shape. They carry the same numbers under different names.

TraceWriter ​

typescript
interface TraceWriter {
  onSpanStart(span: SpanData): void;
  onSpanUpdate?(span: SpanData): void;
  onSpanEnd(span: SpanData): void;
  onEvent?(span: SpanData, event: SpanEvent): void;
  flush?(): Promise<void>;
}

LogWriter ​

typescript
class LogWriter implements TraceWriter {
  constructor(log: LogFn);
}

type LogFn = (entry: LogEntry) => void;

interface LogEntry {
  level: EventLevel;
  message: string;
  fields?: Record<string, unknown>;
}

Projects the span tree into flat, correlated log entries. Each completed span becomes one line whose message is the span name and whose fields carry the span attributes plus type, status, traceId, spanId, parentSpanId, and durationMs.

Level mapping for completed spans:

SpanLevel
status === "error"error
type is "tool" or "workflow"info
everything elsedebug

So the info stream reads as a narrative — the run and its tool calls — while the full tree is available at debug.

SimpleWriter ​

typescript
class SimpleWriter implements TraceWriter {
  constructor(options?: SimpleWriterOptions);
}
typescript
interface SimpleWriterOptions {
  minLevel?: EventLevel; // default "info"
  showInternal?: boolean; // default false
  showTimestamp?: boolean; // default true
  showDuration?: boolean; // default true
  markdown?: boolean; // default false — renders events flagged markdown: true
  output?: (line: string) => void; // default console.log
}

Human-readable console output for development. showInternal: true is the equivalent of a --debug flag.

Span names and attributes ​

SpanTypeAttributes
agent.sendworkflowsessionId, agentName?, finishReason?, inputTokens, outputTokens
agent.compactworkflowsessionId, trigger, agentName?, outcome, beforeTokens?, afterTokens?
streaminternal—
step-NllmSet by the provider adapter; carries an LLMResult.
<tool name>toolCarries a ToolResult. Available in execute as ctx.span.
mcp:connectinternal—

agent.compact sets outcome to complete, skipped, or error.