Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/platforms/javascript/common/agent-tracing/index.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -81,6 +81,11 @@ As of SDK version `11.0.0`, Sentry auto-instruments the most popular AI SDKs and
title: "TypeSafe",
icon: "typesafe-ai",
},
{
to: "/configuration/integrations/pi-durable/",
title: "pi-durable",
icon: "default",
},
{
to: "/agent-tracing/mastra/",
href: "/platforms/javascript/guides/mastra/",
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,156 @@
---
title: pi-durable
description: "Adds instrumentation for pi-durable. (default)"
supportedCategories:
- server
- serverless
notSupported:
- javascript.mastra
---

<AvailableSince version="11.6.0" />

_Import name: `Sentry.piDurableIntegration`_

The `piDurableIntegration` adds instrumentation for [`@earendil-works/pi-durable`](https://www.npmjs.com/package/@earendil-works/pi-durable), Earendil's durable agent harness, to capture <PlatformLink to="/agent-tracing/">agent tracing</PlatformLink> `gen_ai` spans for agent runs, model requests, and tool calls.

<PlatformSection notSupported={["javascript.cloudflare"]}>

This integration is enabled by default when <PlatformLink to="/tracing/">tracing</PlatformLink> is enabled. To customize it, see [Modifying Default Integrations](./../#modifying-default-integrations).

```javascript
Sentry.init({
dsn: "___PUBLIC_DSN___",
// Tracing must be enabled for agent tracing to work
tracesSampleRate: 1.0,
integrations: [Sentry.piDurableIntegration()],
});
```

</PlatformSection>

<PlatformSection supported={["javascript.deno"]}>

On Deno, preload Sentry's import hook so pi-durable can be instrumented:

```bash
deno run --allow-net --allow-env --allow-read --allow-sys --allow-write --preload=npm:@sentry/deno/import app.ts
```

</PlatformSection>

<PlatformSection supported={["javascript.bun", "javascript.elysia"]}>

<PlatformSection supported={["javascript.elysia"]}>

With the Node.js adapter, no build step is needed. The integration is enabled by default when tracing is enabled.

On Bun, add `sentryBunPlugin()` to your build so the plugin can instrument the bundled pi-durable package:

</PlatformSection>

<PlatformSection notSupported={["javascript.elysia"]}>

On Bun, add `sentryBunPlugin()` to your build so the plugin can instrument the bundled pi-durable package:

</PlatformSection>

```typescript {filename:build.ts}
import { sentryBunPlugin } from "@sentry/bun/plugin";

await Bun.build({
entrypoints: ["./src/index.ts"],
outdir: "./dist",
target: "bun",
plugins: [sentryBunPlugin()],
});
```

pi-durable must be part of the bundle. The plugin only runs during a build, so an application started with `bun run` without a build gets no pi-durable spans.

</PlatformSection>

<PlatformSection supported={["javascript.cloudflare"]}>

On Cloudflare, the <PlatformLink to="/install/vite-plugin/">Sentry Cloudflare Vite plugin</PlatformLink> adds this integration when your Worker bundles pi-durable. This includes pi-durable in a Durable Object hosted by the <PlatformLink to="/agent-tracing/agents-sdk/">Agents SDK</PlatformLink> `PiHarness`. You don't have to add the integration yourself.

```typescript {filename:vite.config.mts}
import { cloudflare } from "@cloudflare/vite-plugin";
import { sentryCloudflareVitePlugin } from "@sentry/cloudflare/vite";
import { defineConfig } from "vite";

export default defineConfig({
plugins: [cloudflare(), sentryCloudflareVitePlugin()],
});
```

Without the plugin, or with `buildTimeInstrumentation: false`, the SDK doesn't instrument pi-durable. To choose whether the SDK records prompts and responses, use the <PlatformLink to="/configuration/options/#dataCollection">`dataCollection`</PlatformLink> option.

pi-durable in the Agents SDK keeps its state in SQLite tables with the `pi_` prefix. Their statements don't create spans. See <PlatformLink to="/features/durableobject/#durable-object-sql-spans">Durable Object SQL Spans</PlatformLink>.

</PlatformSection>

## Supported Operations

- **Agent Runs** (`gen_ai.invoke_agent`): One span for each run. A run lasts until pi-durable settles the input of the conversation, so it covers every model request and tool round in between.
- **Model Requests** (`gen_ai.chat`): One span for each request that pi-ai sends, with the model, token usage, and finish reason.
- **Tool Calls** (`gen_ai.execute_tool`): One span for each tool call. The span records the result that the model receives, after `afterTool` hooks and output limits.

Each run starts a new trace. pi-durable runs the work in background tasks, which can continue after a restart, so a run isn't part of the trace of the request that submitted the input. When a tool call creates a conversation for a subagent, the first run of that conversation is a child of the tool call span.

All spans of a run get a `gen_ai.conversation.id` in the form `<harness id>:<conversation id>`, so the runs of one conversation group in [Conversations](/product/agents/conversations/). The SDK creates a new harness ID each time `Harness.open()` runs. After a restart, a pi-durable conversation therefore continues with a new ID.

## Captured Errors

The integration captures:

- Errors that a tool throws. pi-durable turns them into an error result for the model, so your code can't catch them.
- Errors that pi-durable passes to `onReport`, such as a hook that throws. If you pass your own `onReport`, the SDK still captures them and marks them as handled.
- Errors that a task phase throws.

The SDK doesn't capture errors of the built-in coding tools (`bash`, `read`, `edit`, and `write`), because these tools throw for expected failures, such as a command that exits with a non-zero code. Their spans still get an error status.

## Model Requests of Provider SDKs

pi-ai sends its model requests through the `openai`, `@anthropic-ai/sdk`, and `@google/genai` clients, or on Cloudflare through the Workers AI binding. To report each request only once, the OpenAI, Anthropic, Google Gen AI, and Workers AI integrations stop creating spans from the first pi-durable model request on. This applies to the whole process, so model calls that your code makes with these clients outside of pi-durable also get no spans.

The AWS integration still creates spans for Amazon Bedrock requests, so Sentry reports pi-durable model requests to Bedrock twice.

## Supported Versions

- `@earendil-works/pi-durable`: `>=1.0.0 <2.0.0`

<PlatformSection notSupported={["javascript.cloudflare"]}>

## Options

### `recordInputs`

_Type: `boolean` (optional)_

Records the system instructions, input messages, and tool definitions of model requests, and the arguments of tool calls.

Defaults to `true` if `dataCollection.genAI.inputs` is `true`, which is the default.

### `recordOutputs`

_Type: `boolean` (optional)_

Records the output messages of model requests and the results of tool calls.

Defaults to `true` if `dataCollection.genAI.outputs` is `true`, which is the default.

```javascript
Sentry.init({
dsn: "___PUBLIC_DSN___",
tracesSampleRate: 1.0,
integrations: [
Sentry.piDurableIntegration({
recordInputs: false,
recordOutputs: false,
}),
],
});
```

</PlatformSection>
8 changes: 8 additions & 0 deletions docs/platforms/javascript/common/configuration/options.mdx
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,14 @@ Propagates trace context over every RPC call on `env`, including bindings whose

</SdkOption>

<SdkOption name="durableObjectSqlSpanAllowlist" type='Array<string | RegExp>' defaultValue='[]' availableSince='10.67.0'>

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Bug: durableObjectSqlSpanAllowlist is documented with availableSince='10.67.0', but it likely became available in SDK version 11.6.0 alongside pi_ prefix filtering.
Severity: LOW

Suggested Fix

Verify the SDK release version for durableObjectSqlSpanAllowlist and update availableSince to '11.6.0' if the option was introduced in that version.

Prompt for AI Agent
Review the code at the location below. A potential bug has been identified by an AI
agent. Verify if this is a real issue. If it is, propose a fix; if not, explain why it's
not valid.

Location: docs/platforms/javascript/common/configuration/options.mdx#L579

Potential issue: The `durableObjectSqlSpanAllowlist` option documentation specifies
`availableSince='10.67.0'`, but its description covers both `cf_` and `pi_` prefix
filtering. The `pi_` prefix filtering and associated integration were introduced in SDK
version 11.6.0. Users running versions between 10.67.0 and 11.5.x who attempt to
configure this option according to the docs will find it non-functional.

Did we get this right? 👍 / 👎 to inform future reviews.


Names of Durable Object SQL tables whose statements create `db.query` spans, even though the name starts with a prefix for internal tables. By default, statements on tables with the `cf_` prefix (Agents SDK, PartyServer) or the `pi_` prefix (pi-durable in the Agents SDK `PiHarness`) create no spans.

String entries must match a table name exactly. Use a regular expression for pattern matching, such as `/^pi_/` to see the statements of pi-durable. See <PlatformLink to="/features/durableobject/#durable-object-sql-spans">Durable Object SQL Spans</PlatformLink>.

</SdkOption>

</PlatformSection>

<SdkOption name="strictTraceContinuation" type='boolean' defaultValue='false' categorySupported={["server", "serverless"]}>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,10 @@ export class MyAgent extends Agent<Env> {

To continue a trace that a Worker sends to the agent over RPC, see <PlatformLink to="/tracing/distributed-tracing/#rpc-trace-propagation">RPC Trace Propagation</PlatformLink>. With the Vite plugin, bindings to agents in the same Worker are propagated automatically.

## pi-durable Runs

If your agent hosts pi-durable with `PiHarness` from `agents/harness/pi`, the <PlatformLink to="/configuration/integrations/pi-durable/">pi-durable integration</PlatformLink> traces its runs, model requests, and tool calls (SDK version 11.6.0 or higher). Each run is a separate trace with the conversation ID of pi-durable, so `Sentry.setConversationId()` in your agent doesn't apply to these spans.

## Conversation IDs

The automatic conversation ID defaults to the **agent instance name**, which is correct when one agent instance is one chat session (for example `useAgent({ name: chatSessionId })`). When the chat is cleared (`clearHistory()` from `useAgentChat`, or anything that emits the [`message:clear` observability event](https://developers.cloudflare.com/agents/runtime/operations/observability/#channels)), the SDK rotates to a fresh conversation ID, so a reset chat groups as a new conversation.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,27 @@ The SDK automatically instruments Durable Object storage operations (`get`, `put

The SDK also filters out framework-internal storage operations (version 10.69.0 or higher): keys prefixed with `cf_`, `cf:`, `__ps_`, or `/` (used internally by the Agents SDK and PartyServer) don't create spans, so your traces show only your own storage access.

## Durable Object SQL Spans

The SDK creates a `db.query` span for each statement that a SQLite-backed Durable Object runs with `ctx.storage.sql.exec()` (version 10.61.0 or higher). The span name is the query summary, such as `SELECT orders`.

Frameworks built on Durable Objects keep their own state in SQLite tables, and a single request can run hundreds of statements against them. Statements on these tables don't create spans:

- Tables with the `cf_` prefix, used by the Agents SDK and PartyServer (version 10.67.0 or higher)
- Tables with the `pi_` prefix, used by pi-durable in the Agents SDK `PiHarness` (version 11.6.0 or higher)

A table of your own with one of these prefixes also gets no spans. To keep its spans, add it to <PlatformLink to="/configuration/options/#durableObjectSqlSpanAllowlist">`durableObjectSqlSpanAllowlist`</PlatformLink>. A string keeps the table with exactly that name, and a regular expression keeps every table whose name it matches. To see the statements of pi-durable, add `/^pi_/`:

```typescript {filename:src/instrument.server.ts}
import * as Sentry from "@sentry/cloudflare";

export default Sentry.defineCloudflareOptions((env) => ({
dsn: "___PUBLIC_DSN___",
tracesSampleRate: 1.0,
durableObjectSqlSpanAllowlist: ["cf_my_table", /^pi_/],
}));
```

## RPC Trace Propagation

To enable distributed tracing across RPC calls to your Durable Objects, see <PlatformLink to="/tracing/distributed-tracing/#rpc-trace-propagation">Distributed Tracing</PlatformLink>.
Original file line number Diff line number Diff line change
Expand Up @@ -94,6 +94,7 @@ Depending on whether an integration enhances the functionality of a particular r
| [`groqIntegration`](./groq/) | ✓ | | ✓ | |
| [`togetherAIIntegration`](./together-ai/) | ✓ | | ✓ | |
| [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | |
| [`piDurableIntegration`](./pi-durable/) | ✓ | ✓ | ✓ | |
| [`firebaseIntegration`](./firebase/) | ✓ | | ✓ | |
| [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ |
| [`pinoIntegration`](./pino/) | | ✓ | | |
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
| [`groqIntegration`](./groq/) | ✓ | | ✓ | |
| [`togetherAIIntegration`](./together-ai/) | ✓ | | ✓ | |
| [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | |
| [`piDurableIntegration`](./pi-durable/) | ✓ | ✓ | ✓ | |
| [`amqplibIntegration`](./amqplib/) | | | ✓ | |
| [`anrIntegration`](./anr/) | | ✓ | | |
| [`captureConsoleIntegration`](./captureconsole/) | | | | ✓ |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
| [`groqIntegration`](./groq/) | ✓ | | ✓ | |
| [`togetherAIIntegration`](./together-ai/) | ✓ | | ✓ | |
| [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | |
| [`piDurableIntegration`](./pi-durable/) | ✓ | ✓ | ✓ | |
| [`firebaseIntegration`](./firebase/) | ✓ | | ✓ | |
| [`bunRuntimeMetricsIntegration`](./bunruntimemetrics/) | | | | ✓ |
| [`captureConsoleIntegration`](./captureconsole/) | | | | ✓ |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,4 +17,6 @@

[Mistral AI](./mistral/) is automatically instrumented when built with the Sentry Cloudflare Vite plugin. Without the plugin, use `instrumentMistralAiClient`.

[pi-durable](./pi-durable/) is automatically instrumented when built with the Sentry Cloudflare Vite plugin.

The [`prismaIntegration`](./prisma/) needs the `nodejs_compat` compatibility flag on your Worker.
Original file line number Diff line number Diff line change
Expand Up @@ -22,3 +22,4 @@
| [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ |
| [`mistralAIIntegration`](./mistral/) | ✓ | | ✓ | | |
| [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | | |
| [`piDurableIntegration`](./pi-durable/) | ✓ | ✓ | ✓ | | |
Original file line number Diff line number Diff line change
Expand Up @@ -39,6 +39,7 @@
| [`groqIntegration`](./groq/) | ✓ | | ✓ | |
| [`togetherAIIntegration`](./together-ai/) | ✓ | | ✓ | |
| [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | |
| [`piDurableIntegration`](./pi-durable/) | ✓ | ✓ | ✓ | |
| [`anrIntegration`](./anr/) | | ✓ | | |
| [`captureConsoleIntegration`](./captureconsole/) | | | | ✓ |
| [`eventLoopBlockIntegration`](./event-loop-block/) | | ✓ | | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
| [`groqIntegration`](./groq/) | ✓ | | ✓ | |
| [`togetherAIIntegration`](./together-ai/) | ✓ | | ✓ | |
| [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | |
| [`piDurableIntegration`](./pi-durable/) | ✓ | ✓ | ✓ | |
| [`firebaseIntegration`](./firebase/) | ✓ | | ✓ | |
| [`amqplibIntegration`](./amqplib/) | | | ✓ | |
| [`anrIntegration`](./anr/) | | ✓ | | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
| [`groqIntegration`](./groq/) | ✓ | | ✓ | |
| [`togetherAIIntegration`](./together-ai/) | ✓ | | ✓ | |
| [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | |
| [`piDurableIntegration`](./pi-durable/) | ✓ | ✓ | ✓ | |
| [`anrIntegration`](./anr/) | | ✓ | | |
| [`captureConsoleIntegration`](./captureconsole/) | | | | ✓ |
| [`eventLoopBlockIntegration`](./event-loop-block/) | | ✓ | | |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,5 +53,6 @@
| [`groqIntegration`](./groq/) | ✓ | | ✓ | |
| [`togetherAIIntegration`](./together-ai/) | ✓ | | ✓ | |
| [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | |
| [`piDurableIntegration`](./pi-durable/) | ✓ | ✓ | ✓ | |
| [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ |
| [`pinoIntegration`](./pino/) | | ✓ | | |
Original file line number Diff line number Diff line change
Expand Up @@ -101,6 +101,7 @@ Depending on whether an integration enhances the functionality of a particular r
| [`groqIntegration`](./groq/) | ✓ | | ✓ | |
| [`togetherAIIntegration`](./together-ai/) | ✓ | | ✓ | |
| [`typesafeIntegration`](./typesafe/) | ✓ | | ✓ | |
| [`piDurableIntegration`](./pi-durable/) | ✓ | ✓ | ✓ | |
| [`zodErrorsIntegration`](./zodErrors/) | | | | ✓ |
| [`pinoIntegration`](./pino/) | | ✓ | | |

Expand Down
Loading
Loading