# How to Deploy Durable Agents with Pi Durable

Source: https://www.omnara.com/guides/deploy-pi-durable-agents
Author: Kartik Sarangmath
Published: 2026-10-16

[Pi Durable](https://github.com/earendil-works/pi/tree/main/packages/durable) is Earendil's library for agents that keep their state through crashes, restarts, and redeploys. It's built by the team behind [Pi](https://pi.dev), the open-source coding agent, and it's for agents that live longer than one request: they take new messages while they work, run on a schedule, and pick up where they left off.

The library doesn't decide where your agent runs. This guide covers the three ways to deploy it today (Cloudflare Durable Objects, Vercel, and your own servers), what it costs on Cloudflare, and what you still have to build around it. It also compares it with [Omnara](https://github.com/omnara-ai/omnara), the open-source agent platform we build, which solves the same problem with a different design. Each fits different jobs.

Versions and prices are as of October 16, 2026. Pi Durable is on 1.3.0 and is marked experimental.

## The Short Version

| If you want | Use |
| --- | --- |
| A custom agent app where you own the loop, the state, and your own resumable workflow steps | Pi Durable |
| Many agents that sit idle most of the time and pay nothing for awake time while they sleep | [Pi Durable on Cloudflare](#on-cloudflare-durable-objects) |
| A Next.js or Vercel app, or Pi Durable on your own Postgres | [Pi Durable with Vercel's durable-harness](#on-vercel-with-durable-harness) |
| One small app on one server | [Pi Durable on Node with SQLite](#on-your-own-servers) |
| An agent API for your customers or teams, with organizations, roles, approvals, schedules, and webhooks already built | [Omnara](#how-omnara-handles-the-same-job) |
| Agents in your own cloud, across several servers, without building the host | [Omnara self-hosted](#how-omnara-handles-the-same-job) |

## What Pi Durable Does

Pi Durable is a TypeScript library, MIT licensed, with [a long README and a full spec](https://github.com/earendil-works/pi/tree/main/packages/durable). Earendil [launched it on October 1](https://earendil.com/posts/pi-durable/) alongside Pi 1.0. It isn't a hosted service. You open a `Harness` on a storage backend, and everything the agent does is saved there as it happens.

A session can hold many conversations, and you can fork any of them from a saved message. While the agent is busy, a new message can steer the current turn, wait as a follow-up, or be turned away, and a `requestId` makes a retried submission safe. Long conversations are summarized for the model, while the full history stays in storage.

Two things set it apart. The first is how it handles tools: Pi saves each tool call before running it, and after a crash the tool runs again only if you marked it `replay: "safe"`. Otherwise the model gets an "interrupted" result and decides what to do. The second is that your own app state can live inside the same transactions as the conversation. Typed documents and multi-step tasks commit together with the agent's messages, so your app's records and the conversation are always saved together, and Code Mode replays model-written JavaScript from a journal after a restart, so finished steps don't run twice.

It ships with four storage backends: in-memory, Node SQLite, JSONL, and SQLite inside a Cloudflare Durable Object. Earendil says a Postgres or key-value backend would be easy to write, but Pi itself doesn't ship one yet. Vercel's durable-harness, [below](#on-vercel-with-durable-harness), is one way to keep Pi's state in Postgres.

The rule that matters most when you deploy it is this line from [the README](https://github.com/earendil-works/pi/blob/7ec1f648e9b21391ab2a886d3ef8325945660d6d/packages/durable/README.md#L688): "One process owns a storage at a time; there is no cross-process locking." Pi keeps your agent's state correct, but something else has to make sure only one copy of the agent is running, wake it up for scheduled work, and restart it when it fails. Your deployment has to handle those.

## Three Ways to Deploy Pi Durable

### On Cloudflare Durable Objects

A [Durable Object](https://developers.cloudflare.com/durable-objects/) is a small stateful server Cloudflare runs for you, with its own SQLite database and a single owner at any time. It sleeps when nothing is happening and wakes on a request, a WebSocket message, or an alarm. That covers most of what Pi needs from a host, which is why Earendil [wrote up running Pi Durable on Cloudflare](https://earendil.com/posts/pi-durable-on-cloudflare/) and published [a sample app](https://github.com/earendil-works/pi-durable-cloudflare).

The sample's setup, shortened. The sample pins Pi Durable 1.2.0; [1.3.0](https://github.com/earendil-works/pi/blob/7ec1f648e9b21391ab2a886d3ef8325945660d6d/packages/durable/CHANGELOG.md) fixes two Durable Object issues, so use the newer version:

1. **Declare a SQLite-backed Durable Object** in `wrangler.jsonc`:

   ```jsonc
   "durable_objects": { "bindings": [{ "name": "SESSIONS", "class_name": "Session" }] },
   "exports": { "Session": { "type": "durable-object", "storage": "sqlite" } },
   ```

2. **Route each session to its object** from your Worker with `env.SESSIONS.getByName(sessionId).fetch(request)`.

3. **Open the harness when the object starts**, and reset the object if storage fails, so the next event reopens it:

   ```ts
   import { DurableObject } from "cloudflare:workers";
   import { BACKGROUND_CONTEXT } from "@earendil-works/chord/context";
   import { Harness } from "@earendil-works/pi-durable";
   import { openDurableObjectSqliteStorage } from "@earendil-works/pi-durable/storage/sqlite/cloudflare";

   export class Session extends DurableObject<Env> {
     #harness!: Harness;

     constructor(ctx: DurableObjectState, env: Env) {
       super(ctx, env);
       void ctx.blockConcurrencyWhile(() => this.#initialize());
     }

     async #initialize(): Promise<void> {
       const storage = await openDurableObjectSqliteStorage(this.ctx.storage);
       this.#harness = await Harness.open(storage, harnessOptions, BACKGROUND_CONTEXT);
       void this.#harness.closed.then((end) => {
         if (end.reason === "failed") this.ctx.abort("Harness failed");
       });
     }
   }
   ```

   `harnessOptions` holds your models, tools, and settings; the sample builds them in [`chat.ts`](https://github.com/earendil-works/pi-durable-cloudflare/blob/dcb56ce2d174babb3f00112ac5b8cd597e58ab45/src/chat.ts).

4. **Set alarms for scheduled and unfinished work.** Pi keeps its timers in memory, so a sleeping task [won't wake on its own](https://github.com/earendil-works/pi/issues/10857) after the object restarts. The sample's `alarm()` handler reads the harness's tasks, sets the next alarm for the earliest deadline, and sets a 60-second recovery alarm while any work is still running. Alarms can fire more than once, so keep the handler idempotent.

The sample connects clients over hibernatable WebSockets, so the object can sleep while they stay connected. Before you deploy, add authentication and rate limiting: the sample ships with neither, and its README warns, "Anyone who can reach this sample can create sessions that use your model credentials."

Cloudflare also has its own integration, [`PiHarness`](https://developers.cloudflare.com/agents/harnesses/pi/) in the Agents SDK, which handles the alarms for you. It's in beta, and its docs list what it doesn't do yet: tool approvals, replaying missed events to a client, and deleting conversations. A single model call longer than 15 minutes can be cut off. The two also split conversations differently. Earendil's sample puts many conversations in one object, so they can share state and search each other. Cloudflare recommends one object per conversation, because Pi chooses conversation IDs itself. That spreads the load, but anything shared across conversations then needs its own storage.

### On Vercel with durable-harness

Vercel's [`durable-harness`](https://vercel.com/docs/agent-harnesses/pi) runs Pi Durable on its [Workflow SDK](https://workflow-sdk.dev). Each of Pi's commits becomes an event in a workflow log, and Vercel Queues run each turn in a function. If a function crashes, another one picks up the turn from the log. It also handles ownership: when a new request opens a session, the old request's next write is refused, so two copies can't both write to the same agent.

You keep your Pi code and swap the storage for `openWorldStorage(sessionId)`. A ready-made chat route streams turns to the browser, and in production it refuses requests unless you pass an `authorize` function. You still implement the login and who-owns-which-session check yourself.

The same package runs off Vercel too. Point it at a [Postgres "World"](https://workflow-sdk.dev/worlds) on your own servers, and your sessions live in your database. It's new: the [repository](https://github.com/vercel-labs/durable-harness) went public on October 16 at version 0.0.x, and its API may change. Each step waits for the log, so turns are slower than with Vercel's lighter `durable-harness/pi` variant, which runs only Pi's agent loop.

### On your own servers

The simplest setup is Pi Durable on Node with the SQLite backend, on one server. It needs no cloud services, and it's a reasonable choice for a small internal agent. You're now the host, though, so the jobs Cloudflare would do are yours. Only one process can open a session's database, so stop the old process before starting its replacement. Run it under a supervisor that reopens the harness when it closes, and keep the process running so in-memory timers fire. And back up the database: Pi's [README](https://github.com/earendil-works/pi/blob/7ec1f648e9b21391ab2a886d3ef8325945660d6d/packages/durable/README.md#L667) says SQLite "commits survive process crashes; the newest may be lost on power or host failure."

Running across several servers means adding routing, locking, and takeover yourself. A few projects are building hosts for this, like Deno's [celld](https://github.com/denoland/celld) (beta) and [Rivet Agents](https://github.com/rivet-dev/agents). If you're already on Postgres, durable-harness with a Postgres World is the most direct path today.

## Cloudflare Durable Objects Pricing for Pi Agents

Durable Objects are billed for requests, storage, and the time each object is awake. On the [$5 Workers Paid plan](https://developers.cloudflare.com/workers/platform/pricing/), [time awake costs](https://developers.cloudflare.com/durable-objects/platform/pricing/) $12.50 per million GB-seconds after 400,000 included, and every awake object counts as 128 MB, however much memory it uses. Requests cost $0.15 per million after the first million, rows written cost $1.00 per million after 50 million, and stored data costs $0.20 per GB-month after 5 GB. The Free plan also runs SQLite-backed objects, within daily limits.

The time an object is awake is what drives the bill, and an object stays awake while it waits on a model. A pending outbound request keeps it in memory, and Cloudflare [bills that time](https://developers.cloudflare.com/durable-objects/concepts/durable-object-lifecycle/) even though the CPU is idle. Pi's in-memory sleep timers [also keep it awake](https://github.com/earendil-works/pi/issues/10325). Objects only sleep for free when nothing is in flight.

These examples assume one Durable Object per agent and a 30-day month.

| Example | Awake time per month | Cost of time awake |
| --- | --- | --- |
| One agent awake the whole month | 331,776 GB-seconds | Within the allowance, or about $4.15 once you're past it |
| 1,000 agents, each working one hour a day | 13.8 million GB-seconds | About $175 |
| 1,000 agents awake the whole month | 331.8 million GB-seconds | About $4,150 |

Requests, rows written, storage, and model tokens are on top, and rows written can cost more than time awake. Pi saves streaming output as often as every [100 milliseconds](https://github.com/earendil-works/pi/blob/7ec1f648e9b21391ab2a886d3ef8325945660d6d/packages/durable/README.md#L406) while a model answers. 1,000 agents streaming an hour a day could commit about a billion times a month, which is over $1,000 at one row per commit. `settings.progress` makes it commit less often.

The cost of awake objects has drawn attention. Wordware's Robert Chandler [packed 1,000 awake objects into containers](https://github.com/nyacg/do-on-container) and estimated $22 a month instead of $4,050 (his cost model counts 0.125 GB per object). His test objects were simple timers, not Pi agents. The pool also saves state to storage every 30 seconds, and in his hard-kill test, the 5 writes made since the last save were lost. He [says it himself](https://x.com/bertie_ai/status/2110818493360947391): "for anything with money in it, keep real DOs." If your agents mostly sleep, plain Durable Objects are already cheap.

Durable Objects also have [limits](https://developers.cloudflare.com/durable-objects/platform/limits/) worth planning around. Each object gets 10 GB of SQLite on the Paid plan (1 GB on Free), shared by every conversation, task, and document in it, and writes fail when it's full. Pi can't delete conversations [yet](https://github.com/earendil-works/pi/issues/11026), so that store only grows. A single row or value can be at most 2 MB, so large tool outputs and files belong in something like R2. Memory is 128 MB per isolate, shared by everything running in it, including large contexts and images. And each object runs on one thread, so many busy conversations in one object share its CPU. The 30-second CPU limit per request, configurable up to 5 minutes, counts CPU time, not time spent waiting on a model.

## What You Still Build Around Pi Durable

Pi Durable gives you a durable agent, not the product around it. Whichever host you pick, you'll still need:

- **Access control.** Authentication, rate limits, and who can create agents, read their history, change their tools, and use which credentials. Earendil's sample has none of this, and Vercel's chat route refuses requests until you pass an `authorize` function, but you still write the login.
- **Approvals.** Pi's `beforeTool` hook can wait for a person to approve a tool call and save the answer, but the wait lives in memory, so a restart loses it. A pause that's saved to storage is [an open proposal](https://github.com/earendil-works/pi/issues/10852).
- **Fleet visibility.** On Cloudflare, every object has its own database, so questions like "which agents failed today" mean exporting events somewhere central. Pi's maintainer [sketched one way](https://news.ycombinator.com/item?id=49927546): a background task that copies entries to Postgres.
- **Retention and webhooks.** Deleting data for privacy requests and storage limits, and telling other systems what your agents did.
- **Machines.** Pi's `pi-env` reaches machines through Node subprocesses and SSH, so it [doesn't run inside a Worker](https://github.com/earendil-works/pi/issues/10846) yet. You'd use Cloudflare's sandboxes, Vercel Sandbox, or your own bridge.

You'd need the same list for any agent you put in front of users.

## How Omnara Handles the Same Job

Omnara is an open-source (Apache 2.0) agent platform. Instead of a library you host, it's a service with an API: you create agents, send them messages, and read their events over HTTP, from the [CLI](https://www.npmjs.com/package/omnara), or from the [TypeScript SDK](https://www.npmjs.com/package/@omnara/sdk).

The design is different from Pi on Cloudflare. Agent state lives mainly in [one Postgres database](https://docs.omnara.com/self-hosting/architecture), with files and tool outputs in blob storage, and a pool of stateless workers takes turns running whichever agents have work. An idle agent is a row in Postgres, not a running process, so it doesn't tie up a worker, and a worker waiting on a model can run other agents' turns at the same time, up to 4 per worker by default.

Ownership and crashes work through leases. A worker claims an agent with a lease, [90 seconds by default](https://github.com/omnara-ai/omnara/blob/d570d1f0f12c1e19632280bf1d5536749bd43f26/internal/executionpolicy/policy.go), and renews it while it works. A worker that loses its lease can't save anything more for that agent. If a worker dies, its lease expires and another worker picks the agent up from Postgres. A model call that was cut off is retried from the start, up to [8 times](https://github.com/omnara-ai/omnara/blob/d570d1f0f12c1e19632280bf1d5536749bd43f26/internal/executionpolicy/policy.go). A tool call the worker was running is never re-run: it's marked failed with "its external outcome is unknown," and the model decides what to do. Custom tool calls waiting on your own system stay pending until it answers.

The pieces you'd otherwise build come with it:

- [Queued or steering messages](https://docs.omnara.com/events/sending-input), with an `Idempotency-Key` so retried requests are safe
- [Approvals](https://docs.omnara.com/tools/permissions) that wait, saved in Postgres, until someone answers in the dashboard, Slack, Discord, or the API
- [Custom tools](https://docs.omnara.com/tools/custom) held durably until your code posts a result, plus [MCP servers](https://docs.omnara.com/tools/mcp) and built-in tools for commands, files, and the web
- [Cron triggers](https://docs.omnara.com/api-reference/endpoints/configs-and-profiles/create-cron-trigger) for schedules
- Saved events as a [stream](https://docs.omnara.com/events/streaming) you can resume, or as [webhooks](https://docs.omnara.com/events/webhooks) with retries
- [Organizations, projects, and roles](https://docs.omnara.com/organization/members), API keys, and [encrypted secrets](https://docs.omnara.com/organization/secrets)
- Commands on [your own Linux or macOS machines](https://docs.omnara.com/machines/overview) through a daemon that connects out, or in [sandboxes from nine providers](https://docs.omnara.com/machines/providers/overview)

On [Omnara Cloud](https://www.omnara.com/pricing), there's nothing to run, and the platform pieces are already built. The platform is free with your own model keys and machines, and Omnara-managed models are billed at the provider's token rates. There's no charge for the time an agent spends waiting on a model; you pay for tokens and any Omnara-managed machines. Compare that with Pi on Cloudflare, where Cloudflare runs the servers but you still set up the host, or use `PiHarness`, and build everything around it.

The bigger difference is running it in your own cloud. You [self-host Omnara](https://docs.omnara.com/self-hosting/deployment) with Docker Compose on standard infrastructure: Postgres 18, Redis or Valkey, S3-compatible storage, and a shared filesystem for agent memory. Workers are stateless, so you add more to handle more agents, and ownership, worker failover, and wake-ups already work.

Cloudflare's Durable Objects service can't be self-hosted. Its open-source runtime, workerd, can run Durable Objects on your own server, but keeps each one on a single machine ([source](https://github.com/cloudflare/workerd/blob/01bb2e6deca73934885507d25ba105a0dfe202e6/src/workerd/server/workerd.capnp#L793-L806)). So to run Pi across several servers in your own cloud, you build ownership, routing, and wake-ups yourself, or use an early project like durable-harness on Postgres or Deno's celld.

Pi is more flexible in two ways. Omnara tracks each custom tool call and its result, but the steps inside your tool are your code's job, while Pi's durable tasks and Code Mode can save and resume your own workflow steps in the same transaction as the conversation. Omnara also has its own API and data model, and a library you compile into your app is easier to bend around something unusual.

For the smallest setups, Pi is lighter: one Node process with SQLite is less to run than Omnara's four services, as long as one server is enough. All of Omnara's agents share Postgres, so its capacity and uptime are yours to plan for when you self-host. Each turn also rebuilds the model's context from Postgres, starting at the latest summary. Provider prompt caching cuts the cost of resending it to the model, but a Pi agent that stays awake reads less from storage.

## Pi Durable vs Omnara

| | Pi Durable | Omnara |
| --- | --- | --- |
| What it is | A TypeScript library you host | An agent platform with an API, self-hosted or on Omnara Cloud |
| Where state lives | One SQLite database per session, or a Workflow SDK log | One Postgres database for every agent, plus blob storage for files |
| Who runs the agent | The host: one Durable Object, one function at a time, or your process | A shared pool of workers, each holding a lease |
| Custom workflow steps | Durable tasks, documents, and Code Mode, saved with the conversation | Custom tools held durably; the steps inside them are your code's job |
| Approvals | A `beforeTool` hook can wait, but the wait doesn't survive a restart | Built in; the agent waits, saved, until someone answers |
| Scheduling | You set alarms or timers in the host | Cron triggers |
| Teams, roles, and secrets | You build them | Built in |
| Hosted | Cloudflare or Vercel run the servers; you set up the host and build the product around it | Omnara Cloud runs everything |
| In your own cloud | One Node process for a single server; across servers, you build ownership, routing, and wake-ups, or use durable-harness on Postgres | Docker Compose on Postgres, Redis, and S3-compatible storage; add workers to scale |
| What costs grow with | Time each object is awake (Cloudflare), or your servers | Active work (Cloud), or your servers and database |
| Status | Experimental library; Cloudflare's integration is in beta | [v0.2.6](https://github.com/omnara-ai/omnara/releases) |

## What "Durable" Does and Doesn't Promise

Both recover from the common failures in similar ways. If the host dies after saving progress, Pi's session reopens on the next request or alarm, or Vercel hands the turn to another function. In Omnara, the lease expires and another worker continues from Postgres. A model answer that's cut off mid-stream is retried from the start on both, up to 8 times in Omnara, and neither picks up mid-answer. A client that sends the same message twice gets one copy, through `requestId` in Pi and an `Idempotency-Key` in Omnara. And neither can bring back data the storage itself lost.

They differ in how they handle tools, which matters most for tools that change something outside the agent, like sending an email. If a tool ran but its result wasn't saved, Pi runs it again only if you marked it `replay: "safe"` and otherwise tells the model the call was interrupted. Omnara doesn't run a tool again after its worker dies and tells the model its outcome is unknown. Either way, the model may simply call the tool again. In [a test by the One CLI team](https://www.withone.ai/blog/pi-durable-crash-safe-one-cli), Pi correctly reported a Gmail call as interrupted, and Haiku called the tool again, leaving two drafts. Their fix combined a key from the tool-call ID, a check in Gmail for that key, and marking the tool safe to replay. A key from the tool-call ID alone only catches reruns of the same call, not a new call from the model. For anything with side effects, give the tool a way to check whether the action already happened, on either platform.

## Which One to Pick

Pick Pi Durable when you're building your own agent product and want to own every piece, or when your agents need custom multi-step workflows that resume midway. On Cloudflare, it's a great fit for many independent agents that sleep most of the time. For a small internal agent, Node with SQLite and good backups is enough, and if you're on Vercel or want Pi's state in your own Postgres, use durable-harness.

Pick Omnara when you're building agents for customers or teams and want roles, approvals, schedules, and webhooks on day one, or when many agents are busy at once and spend most of their time waiting on models, where a shared pool of workers handles the waiting. If you need it in your own cloud, not on Cloudflare or Vercel, Omnara self-hosted gets you there without building a host.

For the wider checklist of running agents for real users, see [How to Deploy an AI Agent to Production](https://www.omnara.com/guides/deploy-ai-agents-to-production). For the difference between a harness like Pi and a runtime that hosts it, see [Four Ways to Build an AI Agent](https://www.omnara.com/guides/agent-framework-vs-harness-vs-runtime).

## FAQ

### What is Pi Durable?

Pi Durable is an open-source TypeScript library from Earendil, the team behind the Pi coding agent, for building agents that save every step as they work and recover after a crash. It holds conversations, tasks, and your own app state in one store, and it runs on Node, Bun, Cloudflare Durable Objects, or Vercel through durable-harness.

### Is Pi Durable production ready?

Earendil labels it experimental: "The API changes without notice between releases." Cloudflare's `PiHarness` is in beta, Vercel's durable-harness is on 0.0.x, and Earendil calls its Cloudflare example "a sample, not a production service." People are building on it, but expect to keep up with changes.

### How much does it cost to run Pi agents on Cloudflare?

On the $5 Workers Paid plan, you pay for Durable Objects usage on top. An object that's awake all month costs about $4.15 in duration once you're past the 400,000 GB-seconds included, and an object that's asleep costs nothing for duration. Waiting on a model counts as awake. Model tokens, requests, rows written, and storage are extra.

### Can I run Pi Durable without Cloudflare?

Yes. It runs on Node with SQLite or JSONL storage, and on Vercel or your own Postgres through durable-harness. Off Cloudflare, your host has to make sure only one process owns a session, restart it when it fails, and wake it for scheduled work.

### Does Pi Durable work with Postgres?

Not directly. Pi ships in-memory, SQLite, JSONL, and Durable Object storage. Vercel's durable-harness can keep Pi's commits in a Postgres-backed Workflow SDK World, and you could write your own backend against Pi's storage interface.

### What's the difference between Pi Durable and Cloudflare's PiHarness?

Pi Durable is the library. `PiHarness` is Cloudflare's wrapper that runs it inside a Durable Object and handles the alarms that keep its work moving. You can use the wrapper, or extend a Durable Object yourself like Earendil's sample.

### Is Omnara built on Pi?

No. Omnara runs its own agent loop and keeps its state in Postgres. It doesn't use Pi or Pi Durable.
