---
title: 'Durable AI Agents: What They Are and How They Work'
published date: '2026-10-11'
author: 'Illiana Reed'
title-image: 'https://camelai.com/blog/assets/images/covers/durable-ai-agents.png'
---

**TL;DR:** Durable AI agents keep their history, files and unfinished work outside the process that runs them. They recover recorded progress after deploys and crashes, wait days for a person or a schedule without holding compute, and resume from another process. Two common approaches are to write the agent loop as a workflow on a durable execution engine such as Temporal or Inngest, or use a durable agent runtime that owns the loop and the state, such as camelRun or Claude Managed Agents. Neither makes external side effects happen exactly once; for that you need idempotency keys and a plan for "outcome unknown."

---

## What are durable AI agents?

An agent is a loop: send the conversation to a model, run the tools it asks for, append the results, and repeat until it answers. (Our guide on [how to build an AI agent](https://camelai.com/blog/how-to-build-an-ai-agent) covers the parts.) In a prototype, the loop and the conversation live in one process's memory, which works until that process stops.

A **durable AI agent** keeps its state outside whichever process happens to be running it. That buys you four things:

- **It survives deploys, crashes and restarts mid-turn.** Progress is recorded as it happens, so an interrupted turn is continued, or at least reported honestly, instead of vanishing.
- **It keeps its history and files** for days or months, not just for one connection.
- **It waits without holding compute,** for an approval or a scheduled wake-up, while nothing runs.
- **It resumes from any process.** You address it by an id, not by the machine that started it.

### Stateful, long-running and durable are not the same

- A **stateful AI agent** remembers earlier turns. Saving the chat log to Postgres makes an agent stateful, but it doesn't save the turn that was running when the process died.
- A **long-running AI agent** works or waits for minutes, hours or days. Without durability that's fragile: the longer a turn runs, the likelier a deploy lands in the middle of it.
- A **durable** agent is both, plus recovery: its state and its pending work outlive any one process.

## What goes wrong without durability

1. **A deploy kills a turn halfway.** The model asked for three tool calls and two ran. The turn lived in memory, so the user waits for a reply that never comes.
2. **A blind retry repeats a side effect.** The user or your job queue retries, the model plans again, and `send_invoice` runs twice.
3. **A wait holds a process.** Waiting for a manager's approval means keeping a request, socket or serverless function open until something times out.
4. **The agent is pinned to one machine.** The next message lands on an instance that has never seen the conversation.
5. **Follow-ups disappear.** "Check the build again in an hour" was a timer in memory.

## Durable execution for AI agents: how it works

Every durable system records progress somewhere that outlives the process, and after a failure rebuilds from that record instead of starting over. There are two common ways to rebuild.

**Replay.** Durable execution engines record the result of each step (a model call, a tool call) in an event history or journal. After a crash they run your code again from the top and hand back recorded results for the steps that already finished, so the code reaches the same point without redoing them. [Temporal](https://docs.temporal.io/workflows) does this instead of restoring a memory snapshot. The price is **determinism**: given the same recorded results, code outside steps must make the same calls in the same order. Temporal and [DBOS](https://docs.dbos.dev/architecture) both require it, and [Inngest](https://www.inngest.com/docs/learn/how-functions-are-executed) warns that code outside a step can run again on every replay.

**Resume from saved state.** Much of an agent loop's state is its conversation, alongside pending work and control state. If each model response and tool result is saved as it's produced, another process can load that state and carry on. Managed agent runtimes handle this recovery without requiring you to write a replayable workflow; your tools still need to handle retries and interrupted calls. ([Trigger.dev](https://trigger.dev/docs/how-it-works) takes a third route: during long waits it checkpoints the whole running process and restores it later.)

Recorded model outputs should be reused during recovery: a model can answer the same prompt differently. If a response or tool result was never committed, recovery still needs to handle that uncertainty before repeating an action.

## Two approaches: durable workflow engines vs durable agent runtimes

### Durable workflow engines

You write the agent loop as a workflow. Each model call and tool call becomes a step (an "activity" in Temporal), which the engine checkpoints and replays.

- **[Temporal](https://docs.temporal.io/workflows)** replays an event history. Its [OpenAI Agents SDK integration](https://temporal.io/blog/announcing-openai-agents-sdk-integration) for Python has been generally available since March 23, 2026 and runs each agent invocation as an Activity.
- **[Inngest](https://www.inngest.com/docs/learn/how-functions-are-executed)** checkpoints each `step.run()` result and replays the handler around them.
- **[Restate](https://docs.restate.dev/concepts/durable_execution)** journals every step and deduplicates requests that carry an idempotency key.
- **[DBOS](https://docs.dbos.dev/architecture)** is a library that checkpoints workflows and steps to Postgres, with no separate orchestration server.
- **[Vercel Workflows](https://vercel.com/docs/workflows)** uses `'use workflow'` and `'use step'` directives, and runs can pause for minutes to months. The AI SDK's `WorkflowAgent` [makes your agent a workflow and your tools steps](https://workflow-sdk.dev/docs/ai).
- **[Trigger.dev](https://trigger.dev/docs/wait)** pauses tasks that wait for more than a few seconds.

**You get** control of the loop, retries and observability, and agents that sit inside larger business processes on the same engine. **You still provide or integrate** the loop, context management, conversation storage across workflow runs, the approval inbox and a way to address one agent per user. Engines that replay workflow code also require it to be deterministic.

### Durable agent runtimes

Managed agent runtimes own the loop and the state. You supply a model, instructions and tools; they run each turn and retain state between turns. Features such as file persistence, scheduled wake-ups and approvals vary. Programmable runtimes such as Cloudflare Agents supply durable primitives and let you choose or write the harness.

- **camelRun.** Agents are keyed by your own ids, and the same key is the same agent from any process. History, files, configuration and pending work live in the runtime, not your process. Schedules, channels (Slack, Telegram, Discord) or your own code can wake it. Runs have no timeout. When your app deploys, closing its agents on SIGTERM drains running tool calls for a bounded period and hands new ones to the next process. Calls that outlast the drain can still have an unknown outcome.
- **[Cloudflare Agents](https://developers.cloudflare.com/agents/concepts/agent-class/).** Each agent is a Durable Object with its own SQL storage and scheduling, and it hibernates when inactive. [Fibers](https://developers.cloudflare.com/agents/runtime/execution/durable-execution/) checkpoint work to SQLite; after an eviction, your `onFiberRecovered` hook decides how to recover. The Project Think harness on top is [experimental](https://blog.cloudflare.com/project-think/).
- **[Letta](https://docs.letta.com/concepts/stateful-agents).** Stateful agents whose memories, messages, reasoning and tool calls are all persisted in a database.
- **[Claude Managed Agents](https://platform.claude.com/docs/en/managed-agents/overview)** (beta). Sessions that resume cleanly after pauses, with history, sandbox state and outputs stored server-side.
- **[OpenAI's Agents API](https://developers.openai.com/api/docs/guides/agents-api/overview).** OpenAI manages sessions, orchestration, context compaction and recovery; your application provides the tools.

**With a managed harness, you get** the loop and its state handling without writing them. When camelAI moved camelCode's agents onto camelRun, the team deleted about 72,000 lines of code (the [camelRun intro post](https://camelai.com/blog/the-web-wasnt-built-for-agents) explains why). **You give up** control of that harness's internals, and you work within its models, sandbox and limits. Programmable runtimes leave more of those choices to you. For a product-by-product view, see [AI agent runtimes and hosting platforms compared](https://camelai.com/blog/ai-agent-runtimes-hosting-platforms).

### Side by side

| | Durable workflow engine | Durable agent runtime |
|---|---|---|
| **Examples** | Temporal, Inngest, Restate, DBOS, Vercel Workflows, Trigger.dev | camelRun, Cloudflare Agents, Letta, Claude Managed Agents, OpenAI Agents API |
| **Who writes the loop** | You, or a workflow-compatible agent library | A bundled harness, or your code on a programmable runtime |
| **What gets recorded** | Each step's result (model call, tool call) | The agent: history, files, config, pending work |
| **Recovery** | Most replay your code against recorded results, so it must be deterministic | The runtime reloads the agent's state; how an interrupted turn resumes varies by product |
| **Waiting on a person** | A primitive (signal, event, hook, token); you build the inbox and UI | Varies; camelRun has built-in approvals that suspend the run |
| **A lost tool result** | Usually retried (at-least-once), so steps must be idempotent | Varies; camelRun never re-sends the call and tells the model the outcome is unknown |
| **Best fit** | Agents inside larger business processes; full control of the loop | Many long-lived agents per user, ticket or thread, mostly asleep |

## What durability doesn't guarantee: exactly-once side effects

Durable history keeps the agent's own state safe. It doesn't make the outside world transactional. Take one tool call:

1. The model asks to refund order 123.
2. Your tool sends the refund to the payment provider, which refunds it.
3. Before the response is recorded, the process dies, the connection drops or the call's deadline passes.

Whatever recovers the agent now holds a call with no result, and the fact it needs (did the refund happen?) lives in someone else's system. It can send the call again (**at-least-once**: you may refund twice) or not (**at-most-once**: the refund may never happen). No durability layer can close that gap from its side. An external effect happens exactly once only when the receiving system deduplicates, which is what idempotency keys are for.

Systems pick different sides:

- **Workflow engines generally retry.** Temporal's docs say [activities may run more than once](https://docs.temporal.io/activity-definition) (say, a worker finishes one but crashes before reporting it) and suggest keying them by workflow run ID plus activity ID. DBOS asks for idempotent steps, and Vercel's `WorkflowAgent` retries failed tool calls up to 3 times by default.
- **camelRun never re-sends a lost call.** When a tool call's answer is lost (the connection dropped or its deadline passed), the model is told the outcome is unknown so it can check before repeating anything, and the run lists the call in `toolErrors`. Every tool call also carries `context.idempotencyKey`, which stays the same across attempts of that call, such as when it runs again after a person answers.

### Key every side effect

- **If the downstream API accepts an idempotency key,** pass yours through.
- **If it doesn't, record the key yourself** under a unique constraint, in the same transaction as the change, so a repeat becomes a no-op.
- **Prefer naturally idempotent operations.** "Set ticket 123's status to closed" is safe to repeat; "append another closure event" needs deduplication.

This tool from camelRun's human input guide shows the pattern. `context.confirm` pauses the call for a person; once they answer, the runtime calls the tool again with the same arguments, so everything before the ask runs twice. Ask first, act after, and key the side effect:

```ts
const deleteApp = tool({
  description: "Delete an app", input: schema.Object({ app: schema.String() }),
  execute: async ({ app }, context) => {
    if (!await context.confirm(`Delete ${app}? Its URL stops working.`)) return { cancelled: true };
    return apps.delete(app, { idempotencyKey: context.idempotencyKey });
  },
});
```

### Plan for "outcome unknown"

- **Give the model a way to check.** A read tool such as `get_refund_status(orderId)` lets it confirm before trying again; say in its instructions that an unknown outcome means check first. Deduplicate on the business key (order 123, refund) too, so a second attempt the model decides on is also safe.
- **Handle uncertainty in your code.** In camelRun, `run.toolErrors` lists tool calls that didn't complete, and `run()` throws a `RunError` with `uncertain` set when a restart cut the run short. Find out what happened before you retry.
- **Retry requests, not runs.** Reuse the run's `idempotencyKey` when you retry after a timeout, and you get the original run back instead of a second one.
- **Reply from the result, not the stream.** camelRun records a run's result durably, and it arrives even if your process restarts. The event stream is for showing progress.

## Waiting days for a person

On a workflow engine you build approvals from a primitive: a [Temporal signal](https://docs.temporal.io/encyclopedia/workflow-message-passing), Inngest's [`step.waitForEvent()`](https://www.inngest.com/docs/reference/functions/step-wait-for-event), a Restate [awakeable](https://docs.restate.dev/foundations/actions), a Vercel Workflows hook or a Trigger.dev [waitpoint token](https://trigger.dev/docs/wait-for-token). The run pauses while your app stores the request, shows it to someone and sends the answer back. Waits are cheap: Inngest doesn't run your compute during one, Restate can suspend a function on a FaaS platform so you don't pay for the wait, and Trigger.dev doesn't count timed waits over 5 seconds toward compute.

In camelRun, approvals are part of the runtime. Mark a tool `needsApproval: true` (or give a definition's tool sources an approval policy) and the run suspends before the call: it ends with `status: "input_required"`, the agent sleeps, and no run time is billed while it waits. When someone answers, minutes or days later and from any process, the turn resumes, and the model sees an ordinary tool result saying who answered and how long it took.

```ts
let run = await agent.run("Delete the staging database", { user: "alice" });
while (run.status === "input_required") {
  const input = run.inputs[0];
  console.log(input.kind, input.message);          // "approval", "Allow db__drop to run?"
  run = await input.answer(true, { from: "alice" }); // resolves with the resumed run
}
console.log(run.text);
```

Details that matter in production:

- **The approver sees the real call:** the tool, its source and its arguments. An approved call runs with exactly those arguments; a declined call never runs.
- **Waits are bounded.** Inputs expire after 7 days by default, configurable from 60 seconds to 30 days but never past the agent's own expiry.
- **A new message replaces a waiting input**, and `agent.abort()` cancels it.
- **Approvals can go to an inbox.** `agents.runtime.inbox("pending")` lists waiting inputs across all your agents, and the `input.requested` webhook announces new ones.
- **Sandboxed code can't wait for days.** A call from the model's JavaScript that would ask a person fails, so tools that ask are always offered to the model directly.

*Pricing and limits change often; check each vendor's pricing page before deciding.*

## When to choose a workflow engine or an agent runtime

### Choose a durable workflow engine when…

- The agent is one step in a larger business process you already run, and you want both on one engine.
- You want to own every line of the loop: custom planning, multi-agent graphs, your own retry rules.
- Your team already runs Temporal, Inngest, Restate, DBOS, Vercel Workflows or Trigger.dev.

### Choose a durable agent runtime when…

- You want many long-lived agents, one per customer, ticket, thread or pull request, most of them asleep at any moment. Our guide to [multi-tenant AI agents](https://camelai.com/blog/multi-tenant-ai-agents) covers that architecture.
- You'd rather not build history storage, wake-ups, approvals and channels yourself.
- Your agent's tools are your own app's APIs.

### Where camelRun fits, and where it doesn't

camelRun is a durable agent runtime for agents inside a product. It works with any model and keeps your tools in your own code, with your credentials. The runtime is open source (AGPL-3.0), and you can self-host it with one Docker container and Postgres.

It isn't a Linux box. Its code sandbox runs JavaScript only: no shell, no Python, no package installs, no network. (Your tools run in your own code, so those limits don't apply to them.) If your agent needs to build and test code or run arbitrary CLIs, use a product with a full sandbox, such as Claude Managed Agents, or a coding harness in your own container.

### When you don't need durability

- A single model call that finishes in seconds and is safe to run again.
- A prototype, where losing a turn costs you one retry.

## Try camelRun

camelRun is camelAI's hosted runtime for durable agents. You define each agent's model, instructions, tools and channels; camelRun runs the loop, keeps each agent's history and files, and wakes agents when there's work. It costs $0.01 per agent-hour of active run time, plus model usage, and sleeping agents cost only storage. Start with the [quickstart](https://camelai.com/docs/camelrun/quickstart), or read more on the [camelRun product page](https://camelai.com/run).
