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 . --devThe 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.toolA 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:
| Attribute | Value |
|---|---|
agent_sdk.computer.actions | The action type, such as click, type, or screenshot |
agent_sdk.computer.executor_duration_ms | Time the desktop took to run the action and capture the screen |
agent_sdk.computer.settle_ms | Part 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.
| Signal | What appears |
|---|---|
| Traces | HTTP requests, turns, tools, and subagents |
| Metrics | Token usage, tool calls, cost, and BDK session or turn counts |
| Logs | Session 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=1OTEL_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 }).
Related
skills/otel/SKILL.md: compact configuration and custom-metric reference for coding agents- Hooks: emit domain telemetry from session events
- Deployment: set collector endpoints and secrets on hosted or self-hosted agents
- Cursor enterprise OpenTelemetry Export: send organization-wide Cursor telemetry to the same collector