How to Set Up OpenTelemetry Observability and Tracing for Copilot SDK: A Complete Guide

The Copilot SDK enables OpenTelemetry observability by accepting a TelemetryConfig object that it translates into environment variables for the spawned Copilot runtime, with optional TraceContextProvider support for distributed tracing.

The Copilot SDK provides a flexible, environment-variable-based approach to OpenTelemetry (OTEL) observability. Rather than bundling OpenTelemetry libraries directly, the SDK externalizes telemetry configuration—allowing you to inject OTLP endpoints, exporters, and trace context through the telemetry option in CopilotClientOptions.

Core Architecture: How Telemetry Flows Through the SDK

The SDK's telemetry system rests on three foundational types defined in nodejs/src/types.ts:

  • TelemetryConfig — configuration object describing exporters, endpoints, and capture settings (source)
  • CopilotClientOptions.telemetry — optional field passed to the CopilotClient constructor (source)
  • TraceContextProvider — callback interface for propagating external W3C trace context (source)

When you call client.start(), the SDK's buildRuntimeEnv() method in nodejs/src/client.ts (source) transforms your TelemetryConfig into the following environment variables:

Environment Variable Source in TelemetryConfig Purpose
COPILOT_OTEL_ENABLED always "true" Activates the runtime's OTEL exporter
OTEL_EXPORTER_OTLP_ENDPOINT otlpEndpoint OTLP collector URL
OTEL_EXPORTER_OTLP_PROTOCOL otlpProtocol "http/json" or "http/protobuf"
COPILOT_OTEL_FILE_EXPORTER_PATH filePath JSON-lines output for local debugging
COPILOT_OTEL_EXPORTER_TYPE exporterType "otlp-http" or "file"
COPILOT_OTEL_SOURCE_NAME sourceName Instrumentation scope name
OTEL_INSTRUMENTATION_GENAI_CAPTURE_MESSAGE_CONTENT captureContent Include prompt/response payloads

Setting Up OTLP HTTP Exporter

The most common production configuration streams traces to an OTLP collector. Pass a TelemetryConfig with exporterType: "otlp-http" when constructing your client:

import { CopilotClient, RuntimeConnection } from "copilot-sdk";

const client = new CopilotClient({
  telemetry: {
    otlpEndpoint: "http://localhost:4318",
    otlpProtocol: "http/json",
    exporterType: "otlp-http",
    sourceName: "my-backend-service",
    captureContent: true,
  },
  connection: RuntimeConnection.forTcp({ port: 0 })
});

await client.start();

The buildRuntimeEnv() method populates OTEL_EXPORTER_OTLP_ENDPOINT and related variables before spawning the Copilot CLI process. The runtime reads these on startup and configures its internal exporter accordingly.

Using File-Based Exporter for Local Development

For debugging without a collector, write traces to a JSON-lines file:

import { CopilotClient } from "copilot-sdk";

const client = new CopilotClient({
  telemetry: {
    filePath: "./copilot-traces.jsonl",
    exporterType: "file",
    captureContent: false,
  },
});

await client.start();

This sets COPILOT_OTEL_FILE_EXPORTER_PATH (source), causing the runtime to append traces as newline-delimited JSON objects.

Implementing Distributed Tracing with TraceContextProvider

To make Copilot runtime spans children of your existing traces, provide a TraceContextProvider via onGetTraceContext:

import { propagation, context } from "@opentelemetry/api";
import { CopilotClient } from "copilot-sdk";

const client = new CopilotClient({
  telemetry: {
    otlpEndpoint: "http://localhost:4318",
    otlpProtocol: "http/json",
    exporterType: "otlp-http",
  },
  onGetTraceContext: () => {
    const carrier: Record<string, string> = {};
    propagation.inject(context.active(), carrier);
    return carrier; // { traceparent, tracestate }
  },
});

await client.start();

The SDK calls this provider before each RPC (session.create, session.send, etc.) via the getTraceContext helper in nodejs/src/telemetry.ts (source), injecting the returned headers into the request.

Complete End-to-End Example

Combine OTLP export with custom span propagation:

import { trace, context, propagation } from "@opentelemetry/api";
import { CopilotClient, RuntimeConnection } from "copilot-sdk";

const tracer = trace.getTracer("my-app");

const client = new CopilotClient({
  telemetry: {
    otlpEndpoint: "http://localhost:4318",
    otlpProtocol: "http/json",
    exporterType: "otlp-http",
    sourceName: "my-app",
    captureContent: true,
  },
  onGetTraceContext: () => {
    const carrier: Record<string, string> = {};
    propagation.inject(context.active(), carrier);
    return carrier;
  },
  connection: RuntimeConnection.forTcp({ port: 0 }),
});

await client.start();

await tracer.startActiveSpan("copilot-session", async (span) => {
  const session = await client.session.create();
  const response = await session.send("Refactor this function to use async/await");
});

All Copilot CLI spans become descendants of your "copilot-session" span, with full trace continuity to your OTLP collector.

Important Constraints and Limitations

In-process connections lack per-client telemetry. The RuntimeConnection.forInProcess() transport inherits the host process's environment unconditionally. If you use this connection type, set OTEL environment variables directly before constructing the client—the telemetry option has no effect.

Configuration is immutable after construction. Changes to TelemetryConfig after calling client.start() do not propagate; the SDK reads these values once during buildRuntimeEnv().

Zero OpenTelemetry dependencies. The SDK does not import @opentelemetry/* packages, leaving you free to use any API version in your application code.

Summary

  • The Copilot SDK implements OpenTelemetry observability through environment variable injection, not bundled libraries
  • Configure via TelemetryConfig in CopilotClientOptions.telemetry to set OTLP endpoints, protocols, and exporters
  • The buildRuntimeEnv() method in nodejs/src/client.ts translates configuration into COPILOT_OTEL_* and OTEL_EXPORTER_* variables
  • Use TraceContextProvider (onGetTraceContext) to propagate external trace context for distributed tracing
  • Choose OTLP HTTP for production collectors or file export for local debugging
  • Avoid forInProcess connections when telemetry isolation is required

Frequently Asked Questions

Does the Copilot SDK include OpenTelemetry libraries?

No. The SDK deliberately excludes @opentelemetry dependencies. It writes configuration to environment variables that the spawned Copilot runtime reads, keeping your dependency tree clean and version-flexible.

What happens if I don't provide a telemetry option?

The Copilot runtime runs without OpenTelemetry export. No traces are collected, and no performance overhead from telemetry processing occurs.

How do I correlate Copilot spans with my existing distributed traces?

Implement onGetTraceContext with a function that calls propagation.inject(context.active(), carrier). The SDK invokes this before each RPC, injecting traceparent and tracestate so Copilot spans attach to your current trace.

Can I change telemetry settings after creating the client?

No. The SDK evaluates TelemetryConfig once during start() via buildRuntimeEnv(). To modify settings, construct a new CopilotClient instance with updated configuration.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →