Skip to main content
Catalyst instruments the Cursor SDK in TypeScript. Initialize tracing before calling Agent.create() so the Agent static methods and the returned SDKAgent / Run objects are patched before any run is observed. Use this guide for Node applications that run programmatic Cursor agents with @cursor/sdk. Cursor publishes a TypeScript SDK only, so there is no Python equivalent for this integration.
Cursor’s SDK streams runs over HTTP/2 via @connectrpc/connect-node. Run Catalyst-traced Cursor apps under Node (>= 22). Bun’s HTTP/2 client currently emits NGHTTP2_FRAME_SIZE_ERROR mid-stream against api.cursor.com.

What Is Captured

  • One AGENT span named Cursor Agent Run per observed run, started lazily when run.stream(), run.wait(), run.conversation(), or run.cancel() is first called
  • cursor.agent_id, cursor.run_id, cursor.run_status, cursor.duration_ms, request IDs, and model metadata (llm.model_name, llm.invocation_parameters)
  • input.value from agent.send() and output.value from streamed assistant text, run.wait() results, or run.conversation() turns
  • TOOL child spans for streamed tool_call events, including tool.name, tool_call.id, JSON arguments, and tool results
  • Aggregate counts: agent.tool_call_count, agent.llm_call_count
  • Error status and exception details when run streaming, waiting, or cancellation fails

Install

bun add @inference/tracing @cursor/sdk

TypeScript Cursor Agent Run

Initialize tracing before creating Cursor agents. Auto-instrumentation detects @cursor/sdk when it is installed in the project, so the smoothest path is just setup().
TypeScript
import { Agent } from "@cursor/sdk";
import { setup } from "@inference/tracing";

const tracing = await setup({
  serviceName: "cursor-agent-runner",
});

const agent = await Agent.create({
  apiKey: process.env.CURSOR_API_KEY!,
  model: { id: "composer-2" },
  local: { cwd: process.cwd() },
});

try {
  const run = await agent.send("Summarize what this repository does");

  for await (const event of run.stream()) {
    console.log(event.type);
  }

  const result = await run.wait();
  console.log(`run ${run.id} ${result.status}`);
} finally {
  await agent[Symbol.asyncDispose]();
  await tracing.shutdown();
}
For manual initialization, pass the SDK namespace into the granular entry point:
TypeScript
import * as CursorSdk from "@cursor/sdk";
import { setup } from "@inference/tracing";
import { instrumentCursorSdk } from "@inference/tracing/cursor-sdk";

const tracing = await setup({ autoInstrument: false });
instrumentCursorSdk(CursorSdk, tracing);

Lazy Span Lifecycle

The AGENT span starts when the application first observes a run through run.stream(), run.wait(), run.conversation(), or run.cancel(), not when agent.send() returns. This avoids leaking open spans for fire-and-handoff workflows where one process kicks off a Cursor run and another process observes it later. Each unique observed run produces one AGENT span; observing the same run twice in the same process does not double-emit.

Stable Agent Identity

Cursor’s SDK exposes its own run and agent identifiers, which Catalyst preserves as cursor.run_id and cursor.agent_id. For the canonical Agents dashboard grouping key, wrap the Cursor run you operate with agentSpan() and pass your stable product ID as agentId.
TypeScript
import { agentSpan } from "@inference/tracing";

await agentSpan(
  {
    agentId: "cursor-repo-maintainer",
    agentName: "Cursor Repo Maintainer",
    spanName: "cursor-repo-maintainer.run",
    sessionId: "conversation-repo-tour",
    role: "code-maintenance",
    system: "cursor",
  },
  async (span) => {
    const input = "Summarize what this repository does";
    span.setInput(input);
    const run = await agent.send(input);
    const result = await run.wait();
    span.setOutput(result);
  },
);

Verify In Catalyst

Filter traces by your service.name (for example cursor-agent-runner). A successful run should show one Cursor Agent Run AGENT span with cursor.run_id and the model name, plus nested TOOL spans for each tool call the agent produced. For short-lived scripts, always call tracing.shutdown() before process exit so batched spans are flushed to Catalyst.