> ## Documentation Index
> Fetch the complete documentation index at: https://docs.devin.ai/llms.txt
> Use this file to discover all available pages before exploring further.

# Trace Devin SDK sessions to Langfuse

> Record every Devin SDK session as OpenTelemetry traces: turns, model requests with token usage, and tool calls, for Langfuse or any OTLP backend.

The SDK can record every session's live events as OpenTelemetry spans. You turn it on once, on
the client, and it works for [local](https://docs.devin.ai/sdk/guides/local-sessions.md) and
[cloud](https://docs.devin.ai/sdk/guides/cloud-sessions.md) sessions with no CLI flag. The SDK only creates spans through
`@opentelemetry/api`. Your app sets up the tracer provider and exporter, so you can send traces to
[Langfuse](https://langfuse.com/integrations/native/opentelemetry) or any other OpenTelemetry
backend.

> [!NOTE]
>
> Cloud sessions currently report turns and tool calls but not per-request token usage or cost, so
> their traces have no `generation` spans. Local sessions report both.

## Install

`@opentelemetry/api` is an optional peer dependency of the SDK. For Langfuse, also install the
OpenTelemetry trace SDK and Langfuse's span processor:

```sh theme={null}
npm install @cognition-ai/sdk @opentelemetry/api @opentelemetry/sdk-trace-base @langfuse/otel
```

## Trace every session

Pass `tracing: traceSessions(...)` when you create the client. `createDevin`,
`LocalDevin.start`, `LocalDevin.connect` and `CloudDevin.connect` all accept it.

```ts theme={null}
import { LangfuseSpanProcessor } from "@langfuse/otel";
import { BasicTracerProvider } from "@opentelemetry/sdk-trace-base";
import { createDevin } from "@cognition-ai/sdk";
import { traceSessions } from "@cognition-ai/sdk/tracing";

// Reads LANGFUSE_PUBLIC_KEY, LANGFUSE_SECRET_KEY and LANGFUSE_BASE_URL.
const provider = new BasicTracerProvider({
  spanProcessors: [new LangfuseSpanProcessor()],
});

const devin = await createDevin({
  cwd: "/path/to/project", // leave out for a cloud client
  tracing: traceSessions({
    tracer: provider.getTracer("my-app"),
    userId: "user-123",
    tags: ["devin"],
  }),
});

const session = await devin.createSession({
  tracing: { metadata: { taskId: "task-42" } },
});
await session.run("Fix the failing test in src/utils");

devin.close(); // ends any unfinished turn
await provider.shutdown(); // flushes spans before the process exits
```

What gets traced:

* **Every session the client opens:** `createSession`, `loadSession`, `attach` and
  `launchSession`.
* **Every prompt:** `send`, `run`, `stream`, `streamUpdates` and `postMessage`, including images
  and other content blocks. The prompt is recorded as the turn's input.
* **Cloud messages from other clients:** a message sent from the Devin web app, Slack or another
  SDK process is traced as its own turn. Your own prompts are not counted twice.
* **Attach and reload:** history replayed by `loadSession` or `attach` is not exported again. A
  turn that is already running when you attach is traced from then on.

Tracing for a session ends when the session is released, the client closes or the connection
drops, and any unfinished turn is ended then.

A session's `tracing` option refines the client's: `metadata` is merged, `tags` are added, and
`name`, `userId` and `sessionId` replace the client's values. Pass `tracing: false` to leave one
session untraced.

If your app already registers a global provider (for example with `@opentelemetry/sdk-node`),
leave out `tracer` and spans use the global provider. Pass `parent` to nest turns under an
existing span, such as your harness's own request span. Set `recordContent: false` to keep
timing, token usage, cost, model and tool names but leave out prompt, reply and tool text.

## Trace one session

`traceSession` traces a single session you already hold, without a client-level option. It only
sees the session's incoming events, so record each prompt yourself before you send it:

```ts theme={null}
import { traceSession } from "@cognition-ai/sdk/tracing";

const tracer = traceSession(session, { tracer: provider.getTracer("my-app") });

const prompt = "Fix the failing test in src/utils";
tracer.recordPrompt(prompt);
await session.send(prompt);

await tracer.end(); // ends a turn that never finished
```

It takes the same options as `traceSessions`.

## What a trace contains

Each turn is its own trace unless it has a parent span. Langfuse groups the turns by session id,
which defaults to the Devin session's id, so a session reloaded in a new process shows up in the
same Langfuse session.

| Span | Langfuse type | Contents |
| - | - | - |
| `turn N` | agent | Prompt, final message, stop cause |
| `generation <model>` | generation | One per model request: uncached input, output, cache read and cache write tokens |
| `<tool name>` | tool | One per tool call: input and output |

The last generation of a turn also carries the turn's request id, time to first token, and ACU
cost when the agent reports it. Set `usdPerAcu` to also record that cost in dollars. Spans also
set `gen_ai.*` attributes for backends other than Langfuse.

Turns that stop on `error`, `quota_exhausted`, `auth_required` or `content_filter` are marked as
errors. A provider refusal ends the turn with `content_filter` and keeps any partial output; the
session can take the next prompt. Failed tool calls are errors, and rejected or cancelled ones
are warnings.

## Running it in production

* The process running the session needs the Langfuse endpoint, the public and secret keys, and
  network access to Langfuse.
* Batching, retries and export errors are handled by your span processor. A Langfuse outage
  never throws into the session.
* If the session's event stream fails, the open turn is marked `stream_error`, and the session
  reports the failure to your code as it normally would.
* Cost fields are only set when the agent reports them. If they are empty, compute cost from the
  token counts.


This documentation is built and hosted on [Mintlify](https://mintlify.com), a developer documentation platform.