---
title: "Cloudflare Quick Start"
description: "Learn how to send Pi Durable agent runs, model requests, tool calls, and errors to Sentry from Cloudflare Durable Objects."
url: https://docs.sentry.io/platforms/javascript/guides/pi-durable/cloudflare/
---

# Cloudflare Quick Start for Pi Durable

[Pi Durable](https://earendil.com/posts/pi-durable/) is Earendil's durable agent harness, built on the same foundations as the Pi coding agent. This guide sets up the Sentry SDK in a Worker that runs Pi Durable in a Durable Object, for example through the Agents SDK `PiHarness`, so agent runs, model requests, tool calls, and errors flow into [Sentry Agent Tracing](https://docs.sentry.io/product/agents.md).

Pi Durable is experimental, and its API can change between releases. The Sentry integration supports `@earendil-works/pi-durable` `>=1.0.0 <2.0.0` and requires JavaScript SDK version `11.6.0` or later.

Running Pi Durable on Node.js? Follow the [Pi Durable Quick Start](https://docs.sentry.io/platforms/javascript/guides/pi-durable.md) instead.

## [Prerequisites](https://docs.sentry.io/platforms/javascript/guides/pi-durable/cloudflare.md#prerequisites)

Before you begin, you need:

* A Sentry [account](https://sentry.io/signup/) and [project](https://docs.sentry.io/product/projects.md). The project's DSN tells the SDK where to send data.
* A Worker using `@earendil-works/pi-durable` version `1.0.0` or later, built with Vite and `@cloudflare/vite-plugin`. To host Pi Durable with `PiHarness`, you need the Agents SDK (`agents`) version `0.26.0` or later.
* `@sentry/cloudflare` version `11.6.0` or later.

## [Install](https://docs.sentry.io/platforms/javascript/guides/pi-durable/cloudflare.md#install)

Choose the features you want to configure, and this guide will show you how:

Error Monitoring\[ ]Tracing

Install the Sentry Cloudflare SDK:

```bash
npm install @sentry/cloudflare@^11.6.0
```

*Other available variations of the above snippet: yarn, pnpm*

## [Configure](https://docs.sentry.io/platforms/javascript/guides/pi-durable/cloudflare.md#configure)

Add the Sentry Vite plugin to your Vite config:

```typescript
import { cloudflare } from "@cloudflare/vite-plugin";
import { sentryCloudflareVitePlugin } from "@sentry/cloudflare/vite";
import { defineConfig } from "vite";

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

The plugin instruments `Harness.open()` at build time, so every Harness your Worker opens is traced. It also wraps your Worker entry, your Durable Object classes, and your Agents SDK classes with Sentry, so `Sentry.init()` runs in the Durable Object that hosts Pi Durable without any changes to your code.

Create an `instrument.server.ts` file next to your Worker entry (the file named in Wrangler's `main`). The plugin picks it up by convention and uses its default export as the Sentry options:

```typescript
import { defineCloudflareOptions } from "@sentry/cloudflare";

export default defineCloudflareOptions((env: Env) => ({
  dsn: "https://<key>@o<orgId>.ingest.sentry.io/<projectId>",
  // ___PRODUCT_OPTION_START___ performance

  // Set tracesSampleRate to 1.0 to capture 100%
  // of spans for tracing.
  // We recommend adjusting this value in production.
  tracesSampleRate: 1.0,
  // ___PRODUCT_OPTION_END___ performance
}));
```

Enable Node.js compatibility in your Wrangler config so the SDK can run on Workers:

```toml
main = "src/index.ts"
compatibility_flags = ["nodejs_compat"]
```

Your Pi Durable code doesn't change. For example, an Agents SDK class that hosts Pi Durable with `PiHarness` is traced as is:

```typescript
import { createModels } from "@earendil-works/pi-ai/models";
import { openaiProvider } from "@earendil-works/pi-ai/providers/openai";
import { createRegistry, Harness } from "@earendil-works/pi-durable";
import { Agent } from "agents";
import { PiHarness } from "agents/harness/pi";

export class Assistant extends Agent<Env> {
  registry = createRegistry();

  harness = new PiHarness({
    harness: ({ storage, context }) => {
      // pi-ai reads provider keys from `process.env` by default, so hand over the Worker's secret.
      const models = createModels({
        authContext: {
          env: async (name) =>
            name === "OPENAI_API_KEY"
              ? this.env.OPENAI_API_KEY
              : undefined,
          fileExists: async () => false,
        },
      });
      models.setProvider(openaiProvider());
      return Harness.open(
        storage,
        { models, registry: this.registry },
        context,
      );
    },
    defaults: { model: { provider: "openai", id: "gpt-6.1-sol" } },
  });

  constructor(ctx: DurableObjectState, env: Env) {
    super(ctx, env);
    this.lifecycle.use(this.harness);
  }
}
```

With this setup, Sentry captures errors thrown by your tools and extensions, and AI spans for every run (agent runs, model requests, tool calls, token usage, and latency). Manual spans you start inside a tool nest under that tool's span. Prompts and model outputs are recorded on your AI spans by default.

To review what's captured and turn recording of prompts and responses off, see [Control the Data You Send to Sentry](https://docs.sentry.io/platforms/javascript/guides/pi-durable/cloudflare.md#control-the-data-you-send-to-sentry-optional) below.

### [Control the Data You Send to Sentry (Optional)](https://docs.sentry.io/platforms/javascript/guides/pi-durable/cloudflare.md#control-the-data-you-send-to-sentry-optional)

By default, the SDK sends the inputs and outputs of your model requests and tool calls, such as prompts, responses, and tool arguments. This will give you rich debugging context. Review the data your agent handles before you deploy to production.

The SDK always filters sensitive values whose keys match a built-in denylist, such as `auth` or `password`, and sends `[Filtered]` instead.

To send less data, turn off the categories you don't need in the `dataCollection` option:

```typescript
import { defineCloudflareOptions } from "@sentry/cloudflare";

export default defineCloudflareOptions((env: Env) => ({
  dsn: "https://<key>@o<orgId>.ingest.sentry.io/<projectId>",
  dataCollection: {
    genAI: { inputs: false, outputs: false },
  },
}));
```

For the full list of categories and their defaults, see the [`dataCollection` options](https://docs.sentry.io/platforms/javascript/guides/pi-durable/configuration/options.md#dataCollection).

### [Link Conversations](https://docs.sentry.io/platforms/javascript/guides/pi-durable/cloudflare.md#link-conversations)

Sentry groups the runs of a Pi Durable conversation into a single [Conversation](https://docs.sentry.io/product/agents/conversations.md) automatically — there's no Sentry-specific setup. The SDK sets `gen_ai.conversation.id` on the agent, model, and tool spans of every run to `<harness id>:<conversation id>`.

Pi Durable numbers conversations per storage, starting from 1, so the SDK prefixes a random id for each opened Harness to keep root conversations from different storages apart. This id changes when the Harness reopens, for example after a process restart or after a Durable Object restarts. Runs from before and after a restart then show up as separate conversations in Sentry.

A subagent runs in a conversation of its own. Its first run nests under the tool call that created it, so the subagent shows up inside the parent's trace.

## [Verify](https://docs.sentry.io/platforms/javascript/guides/pi-durable/cloudflare.md#verify)

Submit a message to one of your conversations, ideally one that calls a tool. Then open [**Explore → Agents**](https://sentry.io/orgredirect/organizations/:orgslug/explore/agents/) in Sentry. Select the conversation to open its detail. The timeline shows the agent run, model requests, tool calls, token usage, latency, and any errors your tools threw.

If no data appears, confirm that:

* The `dsn` belongs to the Sentry project you're viewing.
* `tracesSampleRate` is greater than `0`.
* Your app completed at least one run after you added the setup.

## [Troubleshooting](https://docs.sentry.io/platforms/javascript/guides/pi-durable/cloudflare.md#troubleshooting)

* **No AI spans at all.** Confirm `sentryCloudflareVitePlugin()` is in your Vite config and that you build and run the Worker through Vite. A Worker you deploy with `wrangler` directly, without the plugin, doesn't get the build-time instrumentation of `Harness.open()`.
* **Every restart of the Durable Object starts a new conversation in Sentry.** This is expected for now. See [Link Conversations](https://docs.sentry.io/platforms/javascript/guides/pi-durable/cloudflare.md#link-conversations).

## [Next Steps](https://docs.sentry.io/platforms/javascript/guides/pi-durable/cloudflare.md#next-steps)

* [Track AI agent spend with dashboards and alerts](https://sentry.io/cookbook/monitor-ai-agent-spend-with-dashboards-and-alerts/): build a dashboard for cost, tokens, models, and conversations.
* [Pi Durable's README](https://github.com/earendil-works/pi/tree/main/packages/durable): conversations, tasks, extensions, and storage, from the Pi side.
* Use the appropriate [JavaScript framework guide](https://docs.sentry.io/platforms/javascript.md) for application monitoring in a separate frontend or service.

What gets captured?

Sentry's Pi Durable integration maps Pi Durable's runtime operations to Sentry operations for the Agents dashboards:

| Pi Durable Operation            | Sentry Operation      |
| ------------------------------- | --------------------- |
| Run of a conversation           | `gen_ai.invoke_agent` |
| Model request (`pi.generation`) | `gen_ai.chat`         |
| Tool call (`pi.tool`)           | `gen_ai.execute_tool` |

Spans are tagged with `sentry.origin: auto.ai.pi_durable` and carry the model, token usage, and finish reasons for each request. Each run starts a trace of its own, separate from the request that submitted it, so concurrent runs never end up in each other's traces.

**Crash recovery.** When the process dies during a run, the spans that were open in that process are lost. Once a new process resumes the run, its spans arrive in a new trace that starts with the resumed step, such as the rerun of a tool call.

**Tool results.** A tool span records the result the model receives, including output streamed with `api.output()` and results an `afterTool` hook rewrote.

**Errors.** A tool that throws is captured as an error, and its span is marked failed. Pi Durable hands the throw back to the model as a tool result, so your app never sees it.

The built-in coding tools (`bash`, `read`, `edit`, `write`) are the exception: they throw to report expected failures, such as a command that exits with a non-zero code, so their span is marked failed but no error is captured.

Failures Pi Durable only passes to `onReport`, such as a hook or system prompt section that throws, are captured too. A failed model request isn't captured as an error; its `chat` span carries the error status.

**Provider SDKs.** Pi Durable sends model requests through `@earendil-works/pi-ai`, which calls the `openai`, `@anthropic-ai/sdk`, and `@google/genai` clients, or the Workers AI binding. From the first run on, the SDK turns off its OpenAI, Anthropic, Google Gen AI, and Workers AI integrations for the whole process, so these requests aren't counted twice. Calls your app makes with those clients outside Pi Durable aren't traced either. Amazon Bedrock requests are still reported a second time by the AWS integration.

**Not captured yet.** Tool calls that Pi Durable answers without running the tool, custom tasks you define with `defineTask`, agent names (Pi Durable has none), a link between a run and the request that submitted it, and a marker for compaction requests.

## [Supported Versions](https://docs.sentry.io/platforms/javascript/guides/pi-durable/cloudflare.md#supported-versions)

* `@sentry/cloudflare`: `>=11.6.0`
* `@earendil-works/pi-durable`: `>=1.0.0 <2.0.0`
* `agents`: `>=0.26.0`, when you host Pi Durable with `PiHarness`
