Build/Observability

Tracing

OpenTelemetry spans for performance debugging and operation tracking

Use ctx.tracer in agents and c.var.tracer in routes to create OpenTelemetry spans. Spans help you understand timing, track operations through your system, and debug performance issues.

Basic Span Pattern

Wrap operations in spans to track their duration and status:

import { createAgent } from '@agentuity/runtime';
import { SpanStatusCode } from '@opentelemetry/api';
import { generateText } from 'ai';
import { openai } from '@ai-sdk/openai';
 
const agent = createAgent('TracingExample', {
  handler: async (ctx, input) => {
    return ctx.tracer.startActiveSpan('generate-response', async (span) => {
      try {
        span.setAttribute('model', 'gpt-5-mini');
        span.setAttribute('promptLength', input.prompt.length);
 
        const { text, usage } = await generateText({
          model: openai('gpt-5-mini'),
          prompt: input.prompt,
        });
 
        span.setAttribute('outputLength', text.length);
        span.setAttribute('totalTokens', usage.totalTokens);
        span.setStatus({ code: SpanStatusCode.OK });
 
        return { response: text };
      } catch (error) {
        span.setStatus({ code: SpanStatusCode.ERROR });
        ctx.logger.error('Generation failed', error);
        throw error;
      }
    });
  },
});

Spans automatically end when the handler completes. Always set the status to OK or ERROR.

Adding Context with Attributes

Use setAttribute to add searchable metadata to spans:

return ctx.tracer.startActiveSpan('user-lookup', async (span) => {
  span.setAttribute('userId', input.userId);
  span.setAttribute('source', 'api');
  span.setAttribute('cached', false);
 
  const user = await fetchUser(input.userId);
 
  span.setAttribute('userFound', !!user);
  span.setAttribute('accountType', user?.type ?? 'unknown');
 
  span.setStatus({ code: SpanStatusCode.OK });
  return user;
});

Common attributes: IDs, counts, categories, boolean flags. These appear in trace views and can be filtered.

Recording Events

Use addEvent to mark significant moments within a span:

return ctx.tracer.startActiveSpan('data-pipeline', async (span) => {
  span.addEvent('pipeline-started', { inputSize: data.length });
 
  const validated = await validateData(data);
  span.addEvent('validation-complete', { validRecords: validated.length });
 
  const enriched = await enrichData(validated);
  span.addEvent('enrichment-complete', { enrichedFields: 5 });
 
  const stored = await storeData(enriched);
  span.addEvent('storage-complete', { recordsStored: stored.count });
 
  span.setStatus({ code: SpanStatusCode.OK });
  return { processed: stored.count };
});

Events create a timeline within the span, useful for understanding where time is spent.

Nested Spans

Create child spans for multi-step operations:

return ctx.tracer.startActiveSpan('rag-pipeline', async (parentSpan) => {
  try {
    parentSpan.setAttribute('query', input.query);
 
    // Retrieve relevant context
    const context = await ctx.tracer.startActiveSpan('retrieve-context', async (span) => {
      span.setAttribute('index', 'knowledge-base');
      const results = await ctx.vector.search('docs', { query: input.query, limit: 5 });
      span.setAttribute('resultsFound', results.length);
      span.setStatus({ code: SpanStatusCode.OK });
      return results;
    });
 
    // Rerank results
    const ranked = await ctx.tracer.startActiveSpan('rerank-results', async (span) => {
      span.setAttribute('inputCount', context.length);
      const reranked = await rerankByRelevance(context, input.query);
      span.setAttribute('outputCount', reranked.length);
      span.setStatus({ code: SpanStatusCode.OK });
      return reranked;
    });
 
    // Generate answer
    const answer = await ctx.tracer.startActiveSpan('generate-answer', async (span) => {
      span.setAttribute('model', 'gpt-5-mini');
      span.setAttribute('contextChunks', ranked.length);
      const response = await generateWithContext(input.query, ranked);
      span.setAttribute('responseLength', response.length);
      span.setStatus({ code: SpanStatusCode.OK });
      return response;
    });
 
    parentSpan.setStatus({ code: SpanStatusCode.OK });
    return { answer, sources: ranked.map(r => r.id) };
  } catch (error) {
    parentSpan.setStatus({ code: SpanStatusCode.ERROR });
    throw error;
  }
});

Nested spans create a parent-child hierarchy in trace views, showing how operations relate.

Tracing in Routes

Routes access the tracer via c.var.tracer:

import { createRouter } from '@agentuity/runtime';
import { SpanStatusCode } from '@opentelemetry/api';
 
const router = createRouter();
 
// import notificationSender from '@agent/notification-sender';
 
router.post('/customers/:id/notify', async (c) => {
  return c.var.tracer.startActiveSpan('send-notification', async (span) => {
    try {
      const customerId = c.req.param('id');
      span.setAttribute('customerId', customerId);
 
      const body = await c.req.json();
      span.setAttribute('notificationType', body.type);
      span.setAttribute('channel', body.channel);
 
      const result = await notificationSender.run({
        customerId,
        ...body,
      });
 
      span.setAttribute('delivered', result.success);
      span.setStatus({ code: SpanStatusCode.OK });
      return c.json(result);
    } catch (error) {
      span.setStatus({ code: SpanStatusCode.ERROR });
      throw error;
    }
  });
});
 
export default router;

Viewing Traces

View traces for a session using the CLI:

# Get session details including trace timeline
agentuity cloud session get sess_abc123xyz

Traces are also visible in the Agentuity Console, showing the full span hierarchy with timing. See CLI Reference for more trace viewing options.

When to Use Tracing

ScenarioApproach
Simple operationsLogging is sufficient
Multi-step workflowsCreate spans for each step
Performance debuggingAdd spans to identify bottlenecks
External API callsWrap in spans to track latency
Agent-to-agent callsSpans automatically propagate context

Best Practices

  • Name spans descriptively: generate-summary not step-2
  • Always set status: SpanStatusCode.OK or SpanStatusCode.ERROR
  • Add relevant attributes: IDs, counts, and categories for filtering
  • Use events for milestones: Mark significant points within long operations
  • Keep spans focused: One span per logical operation

Next Steps

Need Help?

Join our DiscordCommunity for assistance or just to hang with other humans building agents.

Send us an email at hi@agentuity.com if you'd like to get in touch.

Please Follow us on

If you haven't already, please Signup for your free account now and start building your first agent!