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

# Sentry for Pi Durable (Bun)

Runtime

* [Node.js](https://docs.sentry.io/platforms/javascript/guides/pi-durable.md)
* [Cloudflare](https://docs.sentry.io/platforms/javascript/guides/pi-durable/cloudflare.md)
* [Deno](https://docs.sentry.io/platforms/javascript/guides/pi-durable/deno.md)
* [Bun (current page)](https://docs.sentry.io/platforms/javascript/guides/pi-durable/bun.md)

[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 Bun app that runs Pi Durable, 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.

## [Prerequisites](https://docs.sentry.io/platforms/javascript/guides/pi-durable/bun.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 Bun application using `@earendil-works/pi-durable` version `1.0.0` or later.
* `@sentry/bun` version `11.6.0` or later.

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

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

Error Monitoring\[ ]Tracing

Install the Sentry Bun SDK:

```bash
bun add @sentry/bun@^11.6.0
```

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

Create an `instrument.ts` file in the root of your project. The Pi Durable integration is enabled by default, so you don't add it to `integrations`:

```typescript
import * as Sentry from "@sentry/bun";

Sentry.init({
  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.
  // Learn more at
  // https://docs.sentry.io/platforms/javascript/guides/bun/configuration/options/#tracesSampleRate
  tracesSampleRate: 1.0,
  // ___PRODUCT_OPTION_END___ performance
});
```

Bun has no runtime hook that Sentry can use to instrument Pi Durable, so build your app with `sentryBunPlugin` from `@sentry/bun/plugin`. The plugin instruments `Harness.open()` while `Bun.build()` bundles `@earendil-works/pi-durable`:

```typescript
import { sentryBunPlugin } from "@sentry/bun/plugin";

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

Build your app, then run the output with the instrument file preloaded:

```bash
bun build.ts
bun --preload ./instrument.ts dist/app.js
```

If you run your source directly, for example with `bun app.ts`, the plugin doesn't run, and Pi Durable isn't traced.

Your Pi Durable code doesn't change. Every Harness your app opens is traced, whichever storage it uses.

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/bun.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/bun.md#control-the-data-you-send-to-sentry-optional)

By default, the SDK sends user identity data (IP address, ID, and similar) and other data like HTTP bodies and URL query parameters. This will give you rich debugging context.

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. 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).

```javascript
Sentry.init({
  dsn: "https://<key>@o<orgId>.ingest.sentry.io/<projectId>",
  dataCollection: {
    userInfo: false,
    // other categories
  },
});
```

To change recording for Pi Durable only, pass `recordInputs` and `recordOutputs` to the integration. They take precedence over `dataCollection`:

```typescript
Sentry.init({
  dsn: "https://<key>@o<orgId>.ingest.sentry.io/<projectId>",
  integrations: [
    Sentry.piDurableIntegration({
      recordInputs: false,
      recordOutputs: false,
    }),
  ],
});
```

### [Link Conversations](https://docs.sentry.io/platforms/javascript/guides/pi-durable/bun.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/bun.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/bun.md#troubleshooting)

* **No AI spans at all.** Confirm that `sentryBunPlugin()` is in your `Bun.build()` call and that you run the built output, not your source files.

## [Next Steps](https://docs.sentry.io/platforms/javascript/guides/pi-durable/bun.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/bun.md#supported-versions)

* `@sentry/bun`: `>=11.6.0`
* `@earendil-works/pi-durable`: `>=1.0.0 <2.0.0`
