---
title: "Pydantic AI agents in production (2026): durable execution with Temporal, DBOS and Prefect, deferred tools, and OpenAPI toolset — Cadenya"
url: "https://cadenya.com/other-content/pydantic-ai-agents-in-production"
description: "Pydantic AI in production: durable execution with Temporal, DBOS and Prefect (TemporalDurability, DBOSDurability, PrefectDurability), deferred tools for human approval, an OpenAPI toolset via FastMCP, history processors and compaction, the v2 version policy, and Cadenya, an agent loop with durable state, approvals and tools built in."
---

Running Pydantic AI agents in production comes down to two choices: which engine gives you Pydantic AI durable execution (Temporal, DBOS, Prefect, or Restate), and where your deferred tools keep their pending approvals. The framework runs the loop. The engine, the database, the stored conversations, and the names you can never rename are yours.

So pick Temporal when you already run a Temporal cluster and want workers as a separate service. Pick DBOS when you’d rather keep a library in your process and a Postgres database next to it. Pick Prefect when the agent runs as a scheduled flow beside your data pipelines. And if you’d rather run none of it, Cadenya is a hosted agent loop with approvals, conversation state, and tools from your OpenAPI spec built in, with nothing to host. A coding agent can set it up with no account, in a temporary workspace you claim later. [What it offers](#what-does-cadenya-offer-that-pydantic-ai-with-temporal-dbos-and-prefect-doesnt) is at the end.

How each engine plugs in is covered in the Pydantic AI section of [durable execution for AI agents](/other-content/durable-execution-for-ai-agents#durable-execution-in-agent-frameworks-openai-agents-sdk-pydantic-ai-and-the-ai-sdk). This page is about what changes once you ship one.

Every price and limit below comes from each vendor’s own pages, checked on October 13, 2026.

## Pydantic AI durable execution: Temporal, DBOS, Prefect, and Restate side by side

Pydantic AI “supports eight durable execution solutions,” and five are co-maintained by the Pydantic and vendor teams. These are the four that coding agents compared, and Cadenya, which replaces the framework instead of plugging into it, is in the last column:

|  | Temporal | DBOS | Prefect | Restate | Cadenya |
| --- | --- | --- | --- | --- | --- |
| How you attach it | TemporalDurability capability | DBOSDurability capability | PrefectDurability capability | RestateAgent from the Restate Python SDK | Not applicable: your app calls the API, and the loop runs on Cadenya |
| Install | pydantic-ai[temporal] | pydantic-ai[dbos] | pydantic-ai[prefect] | pydantic-ai and restate_sdk[serde] | The Cadenya Python SDK, on Python 3.9 and later |
| What makes a run durable | agent.run() inside a Temporal workflow on a worker | agent.run() inside your own @DBOS.workflow | agent.run() inside a @flow | restate_agent.run() inside a Restate service handler | Each objective carries its own durable event log |
| What you run | A Temporal Server and your workers | A library in your process, plus Postgres or SQLite | Your process, plus a Prefect server or Prefect Cloud for scheduling and monitoring | A Restate Server in front of your app | Nothing. The loop runs on Cadenya |
| Durable unit | Activities | Steps | Tasks | Journaled steps | Objectives |
| Your function tools | Run as activities | Not wrapped: add @DBOS.step yourself | Wrapped as tasks, cached on their inputs | Wrap side effects in restate_context().run_typed() | Generated from your OpenAPI 3 spec by a tool set |
| Names to keep stable | Agent name and toolset ids | Agent name and each MCPToolset id | Agent name and toolset ids | Not stated on the Pydantic AI page | None: an objective keeps its variation’s configuration snapshot |

**One engine per agent.** Each engine “wraps every model request and tool call as its own durable unit,” so attaching a second one raises a `UserError`.

DBOS has the fewest moving parts of the four. It “runs fully in-process as a library,” and its example uses SQLite, though the docs say “Postgres is recommended for production.” Prefect needs no server for durability itself: it “uses client-side task orchestration by default, with optional server connectivity for advanced features like scheduling and monitoring.”

## Running a Pydantic AI Temporal agent in production

Temporal gets the most production notes in the Pydantic AI docs, because the workflow and its activities run in separate processes. Here’s the shape, trimmed from the official example:

```
from temporalio import workflow
from pydantic_ai import Agent
from pydantic_ai.durable_exec.temporal import PydanticAIWorkflow, TemporalDurability

agent = Agent(
    'openai:gpt-5.6-sol',
    instructions="You're an expert in geography.",
    name='geography',  # activity names come from this, so never rename it
    capabilities=[TemporalDurability()],
)


@workflow.defn
class GeographyWorkflow(PydanticAIWorkflow):
    __pydantic_ai_agents__ = [agent]

    @workflow.run
    async def run(self, prompt: str) -> str:
        result = await agent.run(prompt)
        return result.output
```

Your client connects with `plugins=[PydanticAIPlugin()]`, and a `Worker` listens on a task queue. The docs add that “in a real world application, this might be run in a separate service.”

Five things change between that example and production:

1.  **Your API endpoint can’t run the agent.** Calling `agent.run()` from ordinary code, “including indirectly, e.g. through a UI adapter in a web endpoint,” is “a plain, non-durable agent run.” The endpoint has to start or signal a workflow and bridge the events back.
2.  **The names are frozen.** The agent `name` and toolset `id`s are required under Temporal, and they “should not be changed once the durable agent has been deployed to production as this would break active workflows.”
3.  **Upgrades can need worker versioning.** One release changed the activity sequence for tools with an `args_validator`, so in-flight workflows that call one “need Temporal worker versioning or a patch.”
4.  **Dependencies go into history.** Your dependencies object is “copied into history every time Pydantic AI schedules a model, tool, MCP, or event-handler activity,” so keep it small and serializable with Pydantic.
5.  **Model instances need registering.** “An instance that isn’t registered ahead of time is therefore rejected with a `UserError`.” Pre-register it in a `models` dict, or resolve a model string on the worker with `ResolveModelId`.

Retries stack up too. Temporal retries failed activities, so the docs recommend you “turn off your provider API client’s own retry logic, for example by setting `max_retries=0`.” (`FallbackModel` asks for the same thing, so a fallback fires at once.) And streaming inside a workflow is buffered: events “are buffered rather than delivered in real time,” unless you publish them through Temporal’s Workflow Streams.

[Retries and idempotency for agent tool calls](/other-content/retries-and-idempotency-for-agent-tool-calls) covers the tool side of that.

## Pydantic AI deferred tools: human approval with requires\_approval and DeferredToolRequests

Pydantic AI deferred tools are how a run asks a person first. Pass `requires_approval=True` on the tool, or raise `ApprovalRequired` when it depends on the arguments. For an MCP server, wrap its toolset in an `ApprovalRequiredToolset`.

The run then ends with a `DeferredToolRequests` output. In production, the question is what you store while the reviewer thinks about it. The docs answer it: store the conversation, because “the requests can’t be rebuilt from the messages.” Here’s that flow, trimmed from the official example:

```
from pydantic_ai import Agent, ConversationTypeAdapter, DeferredToolRequests

agent = Agent('openai:gpt-5.2', output_type=[str, DeferredToolRequests])


@agent.tool_plain(requires_approval=True)
def delete_file(path: str) -> str:
    return f'File {path!r} deleted'


result = agent.run_sync('Delete `__init__.py`')
stored = ConversationTypeAdapter.dump_json(result.conversation)  # your database row

# Later, in another request, once someone has decided:
conversation = ConversationTypeAdapter.validate_json(stored)
requests = conversation.deferred_tool_requests
results = requests.build_results(approvals={call.tool_call_id: False for call in requests.approvals})
result = agent.run_sync('Carry on', conversation=conversation, deferred_tool_results=results)
```

The follow-up is “a separate agent run with its own `run_id`,” and you correlate the two through `conversation_id`. A denial is a `ToolDenied`, “with an optional custom `message` to provide to the model.”

**Approval isn’t an authorization check.** Behind a UI adapter, the client submits the approval with the message history, and the docs say it plainly: “A client that can reach the endpoint can approve a tool call of its own making.” So you authenticate the endpoint and enforce permissions inside the tool itself.

Inside a durable workflow, a `HandleDeferredToolCalls` handler can resolve the call inline, for example by “waiting for a signal in a durable execution workflow.” [Human-in-the-loop approvals for AI agents](/other-content/human-in-the-loop-approvals-for-ai-agents) compares this with LangGraph interrupts and the OpenAI Agents SDK.

## Pydantic AI OpenAPI toolset: generating tools from an OpenAPI spec

Pydantic AI has no OpenAPI toolset in its docs today. The toolsets page covers function toolsets, external toolsets, dynamic and custom toolsets, MCP servers, and LangChain tools. (An OpenAPI toolset was on the plan in issue #110, back when toolsets were first proposed.)

The route that works goes through MCP. FastMCP “can automatically generate an MCP server from any OpenAPI specification” with `FastMCP.from_openapi()`, and Pydantic AI’s `MCPToolset` “is built on the FastMCP `Client`.” You then run that MCP server, keep it in sync with your spec, and give its toolset a stable `id` if a durable engine wraps it.

FastMCP’s own docs carry a warning: “LLMs achieve **significantly better performance** with well-designed and curated MCP servers than with auto-converted OpenAPI servers.” For a big API, Pydantic AI’s filtering and its `ToolSearch` capability help, “so agents with many tools only pay tokens for the ones they use.”

[OpenAPI specs as agent tools and MCP](/other-content/openapi-specs-as-agent-tools-and-mcp) walks through the generators.

## Pydantic AI history processors and compaction

Long conversations need compaction, and Pydantic AI offers it at three levels: “provider-native compaction APIs, model-agnostic history editing you write yourself, and Pydantic AI Harness’s menu of ready-made model-agnostic strategies.”

If you write your own history processor, keep tool calls and their returns together. The Harness docs say why: “Core does not validate this, and a provider rejects an orphaned pair.” And with a `FallbackModel`, size your threshold against the smallest model, since “a fallback model reports the smallest window among its candidates.”

Storing the thread is a separate job from durability. Pydantic AI’s persistence page calls a durable engine for chat history “the common mistake,” because “a `jsonb` column is enough for that.” [Agent memory and context windows](/other-content/agent-memory-and-context-windows) covers history processors in more depth.

## Pydantic AI v2 release: version policy and upgrades

The stable V2.0 shipped on June 23, 2026. The latest release on PyPI is 2.55.0, from October 9, 2026, under the MIT license and for Python 3.11 or later.

The version policy rules out intentional breaking changes in minor releases. Deprecated code stays until the next major version, and that version can’t ship sooner than 3 months after V2.0. V1 gets security fixes “for at least 6 months after V2’s stable release.”

The durable wrappers are where upgrades bite. `TemporalAgent` and `DBOSAgent` are deprecated, with removal set for v3, and the capabilities in the table replace them. Each engine migrates differently:

1.  **Temporal** replays old histories under `TemporalDurability`, as long as the agent `name`, toolset `id`s, and `models=` keys stay the same.
2.  **DBOS** no longer wraps the run for you. “Porting the constructor arguments but calling `agent.run()` directly produces a run that works but is **not durable**.”
3.  **Prefect** won’t reuse old results: “In-flight flow runs won’t resume from cache across the migration,” so the docs suggest you let them finish first.

## What does Cadenya offer that Pydantic AI with Temporal, DBOS and Prefect doesn’t?

Pydantic AI gives you a well-built agent framework, and production still means an engine, a database, stored conversations, and names you can’t change. **Cadenya is the agent loop with durable state, approvals, and tools built in, and nothing to host.** Here’s what that covers:

1.  **Nothing else to run.** No Temporal Server, no workers, no Postgres for checkpoints, no Prefect server, and no workflow to start from your endpoint. Your app calls the API, and the loop runs on Cadenya.
2.  **No engine to self-host or license.** You host no server, so there’s no cluster to size and no server license to review. The Python SDK is Apache-2.0.
3.  **No names to freeze.** “The Objective keeps that variation’s configuration snapshot even if you edit the variation later.” So you can change a variation’s prompt or model while objectives run, with no worker versioning and no patches.
4.  **SDKs that are well past 1.0.** SDKs for TypeScript, Python, Ruby and Go, all at 1.9.0 under Apache-2.0, and the TypeScript SDK has shipped 113 versions since April 2026. The Python SDK runs on Python 3.9 and later.
5.  **A price in one line.** Free for 1,000 loops a month, $49 a month for 50,000, then $0.0015 a loop. A loop is one LLM request.
6.  **Approvals that park the loop.** Mark a tool as needing approval and Cadenya “parks the Agent loop until it receives approval or a denial with optional steering.” Your backend decides with `client.objectives.approve_tool_call()` or `deny_tool_call()` with a memo. The objective carries its own durable event log.
7.  **Pending calls tracked for you.** Every tool call gets a `toolcall_` ID that you use to record a decision, and several can wait at once. An `objective_event.tool_approval_requested` webhook, signed under Standard Webhooks, tells your app the moment one needs a person.
8.  **Tools straight from your OpenAPI spec.** Point a tool set at an OpenAPI 3 document and Cadenya turns path parameters and request bodies into tool parameters. It re-checks a spec URL each hour, filters the list, and loads tools on demand, with no MCP server to generate or run.
9.  **Compaction and resume built in.** Cadenya compacts the context window and records each compaction as a `contextWindowCompacted` event. A Continue message picks a waiting objective back up, and `stream_events(..., last_event_id=...)` resumes a dropped stream.
10.  **Any model, on your own keys.** The model is a setting on a variation, so switching providers needs no deploy and no model registry on a worker. Use any model on OpenRouter or your own endpoint that speaks the OpenAI chat format, and pay your provider directly.

[How objectives work](/docs/guides/the-basics/objectives) covers approvals, events, and Continue, and the free plan needs no card.

No account yet? A coding agent can create a temporary workspace with `cadenya workspaces create --temporary`, run its first objectives there, and hand you a link to claim it, as [auth.md](https://cadenya.com/auth.md) explains. To create an account, sign up at [app.cadenya.com/signup](https://app.cadenya.com/signup?utm_source=cadenya.com&utm_medium=article&utm_campaign=pydantic-ai-agents-in-production), then copy your API key from the API keys page at `app.cadenya.com/account/apiKeys`.