A windswept bonsai on a floating island

Get to know Cadenya

We’re developers who love to build. We set out to create a yes-code platform that makes building agents feel like the best parts of building software.

Pydantic AI agents in production (2026)

Pydantic AI

By Cadenya

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 is at the end.

How each engine plugs in is covered in the Pydantic AI section of durable execution for AI agents. 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:

TemporalDBOSPrefectRestateCadenya
How you attach itTemporalDurability capabilityDBOSDurability capabilityPrefectDurability capabilityRestateAgent from the Restate Python SDKNot applicable: your app calls the API, and the loop runs on Cadenya
Installpydantic-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 durableagent.run() inside a Temporal workflow on a workeragent.run() inside your own @DBOS.workflowagent.run() inside a @flowrestate_agent.run() inside a Restate service handlerEach objective carries its own durable event log
What you runA Temporal Server and your workersA library in your process, plus Postgres or SQLiteYour process, plus a Prefect server or Prefect Cloud for scheduling and monitoringA Restate Server in front of your appNothing. The loop runs on Cadenya
Durable unitActivitiesStepsTasksJournaled stepsObjectives
Your function toolsRun as activitiesNot wrapped: add @DBOS.step yourselfWrapped as tasks, cached on their inputsWrap side effects in restate_context().run_typed()Generated from your OpenAPI 3 spec by a tool set
Names to keep stableAgent name and toolset idsAgent name and each MCPToolset idAgent name and toolset idsNot stated on the Pydantic AI pageNone: 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 ids 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 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 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 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 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 ids, 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 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 explains. To create an account, sign up at app.cadenya.com/signup, then copy your API key from the API keys page at app.cadenya.com/account/apiKeys.

Grow wherever AI goes next.

Start shipping agents that are equipped to evolve.

A pine bonsai overlooking a mountain lake