Skip to main content

Tracing API Reference

The MutagenT SDK provides three levels of abstraction for manual tracing. Choose the one that fits your use case:
ApproachBest ForEffort
@trace() decoratorClass methods with automatic I/O captureLowest
withTrace() wrapperFunctions with manual enrichment via SpanHandleMedium
startSpan() / endSpan()Integration adapters, full manual controlHighest
All tracing functions are no-ops when tracing is not active, so you can safely leave instrumentation in place without runtime overhead when tracing is disabled. The @trace() decorator and withTrace() wrapper check for an existing tracing configuration only, while the low-level startSpan() / endSpan() API will additionally attempt lazy initialization from the MUTAGENT_API_KEY environment variable on first use.

Decorators

trace(options)

A method decorator that wraps class methods with automatic span creation. It captures arguments as input, the return value as output, and sets the span status based on success or failure. Import:
import { trace } from "@mutagent/sdk/tracing";
Options:
interface TraceDecoratorOptions {
  kind: SpanKind;       // Required -- the type of operation
  name?: string;        // Span name (defaults to the method name)
  attributes?: Record<string, unknown>;  // Custom attributes
}
Behavior:
  • Captures method arguments as input.raw
  • Captures the return value as output.raw
  • Sets status: "ok" on successful return
  • Sets status: "error" with the error message on throw (re-throws the error)
  • Works with both synchronous and asynchronous methods
  • Preserves this context
  • Runs the method inside a span context, so nested @trace() calls or getCurrentSpan() calls see the correct parent
  • No-op when tracing is not initialized
Example:
import { initTracing, trace, shutdownTracing } from "@mutagent/sdk/tracing";

initTracing({ apiKey: process.env.MUTAGENT_API_KEY! });

class ResearchAgent {
  @trace({ kind: "agent", name: "research-agent" })
  async run(query: string): Promise<string> {
    const docs = await this.search(query);
    return await this.summarize(docs);
  }

  @trace({ kind: "retrieval", name: "web-search" })
  async search(query: string): Promise<string[]> {
    // This span becomes a child of "research-agent"
    return await fetchSearchResults(query);
  }

  @trace({
    kind: "llm.chat",
    name: "summarize",
    attributes: { "gen_ai.model": "gpt-4o" },
  })
  async summarize(docs: string[]): Promise<string> {
    return await callLLM(docs.join("\n"));
  }
}

const agent = new ResearchAgent();
await agent.run("What is MutagenT?");

await shutdownTracing();
This produces a trace tree:
The @trace() decorator uses the legacy TypeScript decorator syntax (experimentalDecorators). Make sure your tsconfig.json includes "experimentalDecorators": true if you use this pattern.

Wrappers

withTrace(options, fn)

Wraps a function with a tracing span, providing a SpanHandle for manual enrichment of the span during execution. Import:
import { withTrace } from "@mutagent/sdk/tracing";
Signature:
function withTrace<T>(
  options: WithTraceOptions,
  fn: (span: SpanHandle) => T
): T;
Options:
interface WithTraceOptions {
  kind: SpanKind;                        // Required -- the type of operation
  name?: string;                         // Span name (defaults to kind)
  input?: SpanIO;                        // Structured input data
  attributes?: Record<string, unknown>;  // Custom attributes
}
SpanHandle methods:
MethodDescription
span.setAttributes(attrs)Merge custom key-value attributes into the span
span.addEvent(name, attrs?)Record a timestamped lifecycle event
span.setOutput(output)Set structured output data (SpanIO)
span.setInput(input)Set or override structured input data (SpanIO)
span.setMetrics(metrics)Set token counts, cost, model, provider (SpanMetrics)
span.spanIdRead-only span ID
span.traceIdRead-only trace ID
Behavior:
  • Sets status: "ok" on successful return
  • Sets status: "error" with the error message on throw (re-throws the error)
  • Works with both synchronous and asynchronous functions
  • Runs the function inside a span context for automatic parent-child linking
  • No-op when tracing is not initialized (provides a dummy SpanHandle)
Examples:
import { withTrace } from "@mutagent/sdk/tracing";

const answer = await withTrace(
  { kind: "chain", name: "rag-pipeline" },
  async (span) => {
    const docs = await retrieveDocs(query);
    const response = await generateAnswer(query, docs);

    span.setOutput({ text: response });
    span.setMetrics({
      model: "gpt-4o",
      provider: "openai",
      inputTokens: 1200,
      outputTokens: 350,
      totalTokens: 1550,
    });

    return response;
  }
);

Low-Level API

The low-level API gives you full control over span lifecycle. This is the layer that integration packages and advanced use cases build on.

startSpan(options)

Create and start a new span. If no trace is active, a new trace ID is generated. If a parent span exists in the current async context, the new span is automatically linked as a child. If tracing has not been explicitly initialized via initTracing(), startSpan() will attempt lazy initialization from the MUTAGENT_API_KEY environment variable (once per lifecycle). Import:
import { startSpan } from "@mutagent/sdk/tracing";
Signature:
function startSpan(options: SpanOptions): MutagentSpan | undefined;
Options:
interface SpanOptions {
  kind: SpanKind;                        // Required -- the type of operation
  name?: string;                         // Span name (defaults to kind)
  input?: SpanIO;                        // Structured input data
  attributes?: Record<string, unknown>;  // Custom attributes
  parentSpanId?: string;                 // Explicit parent (overrides context)
}
Returns undefined if tracing is not initialized.

endSpan(span, endOptions?)

End a span, computing its duration and delivering it to the batch collector. Import:
import { endSpan } from "@mutagent/sdk/tracing";
Signature:
function endSpan(span: MutagentSpan, endOptions?: SpanEndOptions): void;
End Options:
interface SpanEndOptions {
  status?: SpanStatus;                   // "ok" | "error" | "unset"
  statusMessage?: string;               // Error message or status description
  output?: SpanIO;                       // Structured output data
  metrics?: SpanMetrics;                 // Token counts, cost, latency
  attributes?: Record<string, unknown>;  // Additional attributes to merge
  events?: SpanEvent[];                  // Lifecycle events to append
}

getCurrentSpan()

Get the currently active span from the async context.
import { getCurrentSpan } from "@mutagent/sdk/tracing";

const span = getCurrentSpan();
if (span) {
  console.log(`Active span: ${span.name} (${span.kind})`);
}
Returns undefined if no span is active or tracing is not initialized.

getCurrentTraceId()

Get the trace ID of the currently active trace.
import { getCurrentTraceId } from "@mutagent/sdk/tracing";

const traceId = getCurrentTraceId();
// Useful for correlating logs with traces
console.log(`Processing request in trace: ${traceId}`);
Returns undefined if no trace is active or tracing is not initialized.

runInSpanContext(span, fn)

Run a function within the async context of a span. This makes the span visible to getCurrentSpan() and getCurrentTraceId() inside the function, and ensures any child spans created within are linked to this span as their parent.
import { startSpan, endSpan, runInSpanContext } from "@mutagent/sdk/tracing";

const span = startSpan({ kind: "agent", name: "my-agent" });
if (span) {
  const result = await runInSpanContext(span, async () => {
    // getCurrentSpan() returns `span` here
    // Any startSpan() calls here will have `span` as their parent
    return await doWork();
  });
  endSpan(span, { status: "ok", output: { raw: result } });
}
If tracing is not initialized, the function runs directly without any context wrapping.

Full Low-Level Example

import {
  initTracing,
  shutdownTracing,
  startSpan,
  endSpan,
  runInSpanContext,
} from "@mutagent/sdk/tracing";

initTracing({ apiKey: process.env.MUTAGENT_API_KEY! });

// Root span
const agentSpan = startSpan({ kind: "agent", name: "qa-agent" });

if (agentSpan) {
  await runInSpanContext(agentSpan, async () => {
    // Child span (auto-linked to agentSpan)
    const retrievalSpan = startSpan({
      kind: "retrieval",
      name: "vector-search",
      input: { text: "How does MutagenT work?" },
    });

    if (retrievalSpan) {
      const docs = await vectorStore.search("How does MutagenT work?");
      endSpan(retrievalSpan, {
        status: "ok",
        output: {
          documents: docs.map((d) => ({
            content: d.text,
            score: d.score,
            metadata: { source: d.source },
          })),
        },
      });
    }

    // Another child span
    const llmSpan = startSpan({
      kind: "llm.chat",
      name: "generate-answer",
      input: {
        messages: [
          { role: "user", content: "How does MutagenT work?" },
        ],
      },
    });

    if (llmSpan) {
      await runInSpanContext(llmSpan, async () => {
        const completion = await openai.chat.completions.create({
          model: "gpt-4o",
          messages: [{ role: "user", content: "How does MutagenT work?" }],
        });

        endSpan(llmSpan, {
          status: "ok",
          output: {
            messages: [
              {
                role: "assistant",
                content: completion.choices[0].message.content!,
              },
            ],
          },
          metrics: {
            model: "gpt-4o",
            provider: "openai",
            inputTokens: completion.usage?.prompt_tokens,
            outputTokens: completion.usage?.completion_tokens,
            totalTokens: completion.usage?.total_tokens,
          },
        });
      });
    }
  });

  endSpan(agentSpan, { status: "ok" });
}

await shutdownTracing();

Type Reference

SpanIO

Structured input and output data for spans. All fields are optional — use whichever matches your data shape.
interface SpanIO {
  messages?: Array<{
    role: "system" | "user" | "assistant" | "tool";
    content: string;
    name?: string;
    toolCallId?: string;
  }>;
  toolCalls?: Array<{
    id: string;
    name: string;
    arguments: Record<string, unknown>;
    result?: unknown;
  }>;
  documents?: Array<{
    content: string;
    metadata?: Record<string, unknown>;
    score?: number;
  }>;
  text?: string;
  raw?: unknown;
}
FieldUse When
messagesLLM chat completions (input prompts, output responses)
toolCallsTool/function calling (input calls, output results)
documentsRetrieval operations (retrieved chunks with scores)
textSimple text input/output
rawAnything else (serializable escape hatch)

SpanMetrics

Quantitative metrics for span performance and cost tracking.
interface SpanMetrics {
  model?: string;        // e.g., "gpt-4o", "claude-3-opus"
  provider?: string;     // e.g., "openai", "anthropic"
  inputTokens?: number;  // Prompt tokens
  outputTokens?: number; // Completion tokens
  totalTokens?: number;  // Total tokens
  costUsd?: number;      // Estimated cost in USD
  latencyMs?: number;    // Execution duration
  ttftMs?: number;       // Time to first token
}

SpanStatus

type SpanStatus = "ok" | "error" | "unset";
StatusMeaning
"ok"The operation completed successfully
"error"The operation failed
"unset"Default state — status not yet determined

SpanEvent

Timestamped lifecycle events that can be attached to spans.
interface SpanEvent {
  name: string;
  timestamp?: Date;                      // Defaults to now
  attributes?: Record<string, unknown>;  // Event metadata
}

SpanKind

All 15 span kinds as string literals:
type SpanKind =
  | "llm.chat" | "llm.completion" | "llm.embedding"   // Generation
  | "chain" | "agent" | "graph" | "node" | "edge"     // Orchestration
  | "workflow" | "middleware"
  | "tool"                                              // Tools
  | "retrieval" | "rerank"                              // Retrieval
  | "guardrail"                                         // Safety
  | "custom";                                           // Other
See the Tracing Overview for detailed descriptions of each kind.

MutagentSpan

The internal mutable span representation during its lifecycle. Returned by startSpan() and passed to endSpan() and runInSpanContext().
interface MutagentSpan {
  readonly id: string;
  readonly traceId: string;
  readonly parentSpanId: string | undefined;
  readonly kind: SpanKind;
  readonly name: string;
  readonly startedAt: Date;
  input: SpanIO | undefined;
  output: SpanIO | undefined;
  status: SpanStatus;
  statusMessage: string | undefined;
  metrics: SpanMetrics;
  attributes: Record<string, unknown>;
  events: SpanEvent[];
}

FinishedSpan

Immutable span representation after endSpan() is called. This is the shape serialized and sent to the API by the BatchCollector.
interface FinishedSpan {
  readonly spanId: string;
  readonly traceId: string;
  readonly parentSpanId: string | undefined;
  readonly name: string;
  readonly kind: SpanKind;
  readonly input: SpanIO | undefined;
  readonly output: SpanIO | undefined;
  readonly startTime: string;     // ISO 8601
  readonly endTime: string;       // ISO 8601
  readonly durationMs: number;
  readonly model: string | undefined;
  readonly provider: string | undefined;
  readonly inputTokens: number | undefined;
  readonly outputTokens: number | undefined;
  readonly totalTokens: number | undefined;
  readonly costUsd: number | undefined;
  readonly status: SpanStatus;
  readonly statusMessage: string | undefined;
  readonly attributes: Record<string, unknown>;
  readonly events: SerializedSpanEvent[];
  readonly metadata: Record<string, unknown> | undefined;
}

interface SerializedSpanEvent {
  readonly name: string;
  readonly timestamp: string;  // ISO 8601
  readonly attributes?: Record<string, unknown>;
}

TracePayload

The HTTP payload format sent to POST /api/traces. Spans are grouped by trace ID. You do not construct this manually — the BatchCollector builds it automatically.
interface TracePayload {
  traces: Array<{
    traceId: string;
    name?: string;
    sessionId?: string;
    source: string;
    environment?: string;
    spans: Array<{
      spanId: string;
      parentSpanId?: string;
      name: string;
      kind: SpanKind;
      input?: SpanIO;
      output?: SpanIO;
      startTime: string;        // ISO 8601
      endTime: string;          // ISO 8601
      durationMs: number;
      model?: string;
      provider?: string;
      inputTokens?: number;
      outputTokens?: number;
      totalTokens?: number;
      costUsd?: number;
      status: SpanStatus;
      statusMessage?: string;
      attributes?: Record<string, unknown>;
      events?: SerializedSpanEvent[];
      metadata?: Record<string, unknown>;
    }>;
  }>;
}

interface TraceHTTPResponse {
  accepted: number;
  rejected: number;
}

TracingConfig

Configuration object passed to initTracing(). See the Tracing Setup page for full details on each option.
interface TracingConfig {
  apiKey: string;              // Required
  endpoint?: string;           // Default: "https://api.mutagent.io"
  environment?: string;        // e.g., "production", "staging", "development"
  batchSize?: number;          // Default: 10
  flushIntervalMs?: number;    // Default: 5000
  debug?: boolean;             // Default: false
  source?: string;             // Default: "sdk"
}

When to Use Which Approach

  • You have a class-based architecture with methods that represent distinct operations
  • You want zero-effort input/output capture (arguments and return values are recorded automatically)
  • You do not need to set custom metrics or structured I/O during execution
class MyAgent {
  @trace({ kind: "agent" })
  async run(query: string) { /* ... */ }
}
  • You want to enrich the span during execution (set metrics, output, events)
  • You are working with standalone functions rather than class methods
  • You need the SpanHandle to record token usage, model info, or custom events
await withTrace({ kind: "llm.chat" }, async (span) => {
  span.setMetrics({ model: "gpt-4o", inputTokens: 500 });
  // ...
});
  • You are building a framework integration or adapter
  • Span start and end happen in different scopes or callbacks
  • You need to explicitly control when context propagation happens via runInSpanContext()
const span = startSpan({ kind: "tool" });
// ... later, in a callback ...
endSpan(span, { status: "ok" });

Server-Side Trace API

The MutagenT server exposes these REST endpoints for trace ingestion and retrieval. The SDK handles ingestion automatically via the BatchCollector, but you can also call these endpoints directly. All endpoints require workspace context via the x-workspace-id header and authentication via x-api-key (API key) or Authorization: Bearer (OAuth token).

Ingestion Endpoints

MethodPathDescription
POST /api/tracesIngest a single trace with spansIdempotent — same traceId updates existing
POST /api/traces/batchBatch ingest up to 100 tracesMore efficient for high-volume ingestion
POST /api/traces/otlpOTLP bridge (ExportTraceServiceRequest)Accepts OTel HTTP/JSON format

Retrieval Endpoints

MethodPathDescription
GET /api/tracesList traces with filteringSupports source, environment, status, sessionId, date range, pagination
GET /api/traces/:idGet trace by ID with all spansReturns full span tree
GET /api/traces/statsAggregated trace statsGroup by model, provider, kind, or source
DELETE /api/traces/:idDelete a trace and all its spansPermanent deletion

Query Filters for GET /api/traces

ParameterTypeDescription
sessionIdstringFilter by session ID
statusstringFilter by status: running, completed, error
sourcestringFilter by source: sdk, langchain, langgraph, vercel-ai, openai, otel, manual
environmentstringFilter by environment: production, staging, development
startAfterstringISO 8601 timestamp — traces starting after this time
startBeforestringISO 8601 timestamp — traces starting before this time
limitnumberPage size (1—100, default 20)
offsetnumberPagination offset (default 0)

Python API

The Python SDK provides the same low-level API with snake_case naming. Decorators and wrappers are not yet available in Python — use the low-level API directly.
from mutagent_tracing import (
    init_tracing,
    shutdown_tracing,
    start_span,
    end_span,
    get_current_span,
    get_current_trace_id,
    SpanOptions,
    SpanEndOptions,
    SpanIO,
    SpanMetrics,
    SpanKind,
    SpanStatus,
)

init_tracing(api_key="mt_xxxxxxxxxxxx")

# Create a span
span = start_span(SpanOptions(
    kind=SpanKind.CHAIN,
    name="rag-pipeline",
    input=SpanIO(text="What is MutagenT?"),
))

if span:
    # Do work...
    result = generate_answer("What is MutagenT?")

    # End the span
    end_span(span, SpanEndOptions(
        status=SpanStatus.OK,
        output=SpanIO(text=result),
        metrics=SpanMetrics(
            model="gpt-4o",
            provider="openai",
            input_tokens=800,
            output_tokens=200,
            total_tokens=1000,
        ),
    ))

shutdown_tracing()
Python context propagation uses contextvars, which works across async/await and threads. Parent-child linking is automatic when spans are nested within the same execution context.