Skip to main content

Command Palette

Search for a command to run...

BDK

Observe an agent with OpenTelemetry

OpenTelemetry connects an agent turn to the observability system your team already uses. Once you configure an OTLP collector, each request, model turn, tool call, and subagent appears alongside token, tool, and cost metrics from the serve process.

Export is opt-in. Nothing leaves the process until you configure a destination.

Connect an OTLP collector

Set the collector endpoint, start the agent, and send it a message. You should see an agent_sdk.http request span and an agent_sdk.turn trace, with any tool or subagent work nested under the turn.

export OTEL_EXPORTER_OTLP_ENDPOINT=https://otel.example.comexport OTEL_EXPORTER_OTLP_HEADERS="Authorization=Bearer $OTEL_TOKEN"export OTEL_SERVICE_NAME=incident-agentbdk serve --dir . --dev

The default protocol is OTLP/HTTP protobuf. If you already call serve() from TypeScript, you can configure the same destination in code:

import { serve } from "@cursor/bdk";await serve(".", {  otel: {    serviceName: "incident-agent",    exporters: [      {        url: "https://otel.example.com",        headers: {          Authorization: `Bearer ${process.env.OTEL_TOKEN ?? ""}`,        },      },    ],  },});

Use environment variables when you start the agent with bdk serve. Use serve({ otel }) when your TypeScript application starts the server itself. When that application already uses OpenTelemetry, BDK sends its traces, metrics, and logs to the collector configured here.

Follow one conversation

Copy the complete session ID from the playground or session API, then filter your observability system on either agent_sdk.session_id or cursor.conversation.id. Every turn in that conversation carries the same value, including follow-ups and tool work.

The trace shape stays small:

request: agent_sdk.httpturn: agent_sdk.turn      |- agent_sdk.tool      `- agent_sdk.subagent         `- agent_sdk.tool

A conversation can produce several turn traces, so use the session ID as the join key instead of expecting one long-lived session span.

Subagents

A subagent's tool calls are agent_sdk.tool spans under its agent_sdk.subagent span. Its reasoning.completed and message.completed events sit on that span too, so the subagent span shows where its time went. Time between two tool spans is the model choosing the next step. A subagent that fails or is cancelled ends its span with ERROR status when it stops.

Computer use

Each computer use action is an agent_sdk.tool span with cursor.tool.name set to computerUse, under the computer use subagent's span. A failed action has ERROR status. These attributes describe the action without typed text, coordinates, or screenshots:

AttributeValue
agent_sdk.computer.actionsThe action type, such as click, type, or screenshot
agent_sdk.computer.executor_duration_msTime the desktop took to run the action and capture the screen
agent_sdk.computer.settle_msPart of that time spent waiting for the screen to settle

The two durations appear only on actions that succeed.

To export the full actions, turn on tool content as shown in Control what leaves the process. Tool content includes the text the agent typed, which can contain passwords.

Add domain telemetry

Use ctx.host.otel when your dashboard should answer questions the built-in telemetry cannot, such as which incident a turn handled or how long approval took. Attributes appear on the active turn and tool spans, while counters and histograms give you metrics to graph and alert on.

ctx.host.otel.setAttributes({  "support.ticket_id": "INC-123",  "support.queue": "p1",});ctx.host.otel.increment("support.ticket.resolved", {  outcome: "fixed",});ctx.host.otel.record("support.approval.duration_ms", 1420, {  outcome: "approved",});

This API is available in tools, hooks, and channel handlers. A session-bound context adds cursor.conversation.id and agent_sdk.agent automatically, so custom measurements join the first-party telemetry without extra plumbing.

Prefix custom names with your team or agent. First-party metric names and join keys are reserved, and this surface does not create custom spans.

Choose signals

Once export has a destination, traces and metrics are on by default; logs remain off until you opt in.

SignalWhat appears
TracesHTTP requests, turns, tools, and subagents
MetricsToken usage, tool calls, cost, and BDK session or turn counts
LogsSession events, after you enable log export

The first metrics to look for are cursor.token.usage, cursor.tool.calls, and cursor.cost.usage. Configure traces: false, metrics: false, or logs: true under serve({ otel }) when you need a different signal mix.

Control what leaves the process

Traces include structure, tool names, usage metrics, and span status. A failed tool's status includes a short failure reason. A tool or subagent that is still open when the turn ends gets ERROR status with the reason no result. User prompts, tool arguments, and full tool results stay off the wire until you opt in.

Turn on logs first, then opt into prompt or tool content separately:

export OTEL_LOGS_EXPORTER=otlpexport OTEL_LOG_USER_PROMPTS=1export OTEL_LOG_TOOL_CONTENT=1

OTEL_LOG_USER_PROMPTS exports user text. OTEL_LOG_TOOL_CONTENT exports tool inputs, outputs, and failure details. Enable either only when the collector's access and retention policies are appropriate for that data.

The same controls are available as logs: { userPrompts, toolContent } under serve({ otel }).