[ Skip to content ](https://ai.pydantic.dev/durable_execution/temporal/#durable-execution-with-temporal)
**Join us at the inaugural PyAI Conf in San Francisco on March 10th![Learn More](https://pyai.events/?utm_source=pydantic-ai) **
[ ![logo](https://ai.pydantic.dev/img/logo-white.svg) ](https://ai.pydantic.dev/ "Pydantic AI")
Pydantic AI
Temporal
Type to start searching
[ pydantic/pydantic-ai
  * v1.63.0
  * 15.1k
  * 1.7k

](https://github.com/pydantic/pydantic-ai "Go to repository")
[ ![logo](https://ai.pydantic.dev/img/logo-white.svg) ](https://ai.pydantic.dev/ "Pydantic AI") Pydantic AI
[ pydantic/pydantic-ai
  * v1.63.0
  * 15.1k
  * 1.7k

](https://github.com/pydantic/pydantic-ai "Go to repository")
  * [ Pydantic AI  ](https://ai.pydantic.dev/)
  * [ Installation  ](https://ai.pydantic.dev/install/)
  * [ Getting Help  ](https://ai.pydantic.dev/help/)
  * [ Troubleshooting  ](https://ai.pydantic.dev/troubleshooting/)
  * [ Pydantic AI Gateway  ](https://ai.pydantic.dev/gateway/)
  * Documentation
    * Core Concepts
      * [ Agents  ](https://ai.pydantic.dev/agent/)
      * [ Dependencies  ](https://ai.pydantic.dev/dependencies/)
      * [ Function Tools  ](https://ai.pydantic.dev/tools/)
      * [ Output  ](https://ai.pydantic.dev/output/)
      * [ Messages and chat history  ](https://ai.pydantic.dev/message-history/)
      * [ Direct Model Requests  ](https://ai.pydantic.dev/direct/)
    * Models & Providers
      * [ Overview  ](https://ai.pydantic.dev/models/overview/)
      * [ OpenAI  ](https://ai.pydantic.dev/models/openai/)
      * [ Anthropic  ](https://ai.pydantic.dev/models/anthropic/)
      * [ Google  ](https://ai.pydantic.dev/models/google/)
      * [ xAI  ](https://ai.pydantic.dev/models/xai/)
      * [ Bedrock  ](https://ai.pydantic.dev/models/bedrock/)
      * [ Cerebras  ](https://ai.pydantic.dev/models/cerebras/)
      * [ Cohere  ](https://ai.pydantic.dev/models/cohere/)
      * [ Groq  ](https://ai.pydantic.dev/models/groq/)
      * [ Hugging Face  ](https://ai.pydantic.dev/models/huggingface/)
      * [ Mistral  ](https://ai.pydantic.dev/models/mistral/)
      * [ OpenRouter  ](https://ai.pydantic.dev/models/openrouter/)
      * [ Outlines  ](https://ai.pydantic.dev/models/outlines/)
    * Tools & Toolsets
      * [ Function Tools  ](https://ai.pydantic.dev/tools/)
      * [ Advanced Tool Features  ](https://ai.pydantic.dev/tools-advanced/)
      * [ Toolsets  ](https://ai.pydantic.dev/toolsets/)
      * [ Deferred Tools  ](https://ai.pydantic.dev/deferred-tools/)
      * [ Built-in Tools  ](https://ai.pydantic.dev/builtin-tools/)
      * [ Common Tools  ](https://ai.pydantic.dev/common-tools/)
      * [ Third-Party Tools  ](https://ai.pydantic.dev/third-party-tools/)
    * Advanced Features
      * [ Image, Audio, Video & Document Input  ](https://ai.pydantic.dev/input/)
      * [ Thinking  ](https://ai.pydantic.dev/thinking/)
      * [ HTTP Request Retries  ](https://ai.pydantic.dev/retries/)
    * MCP
      * [ Overview  ](https://ai.pydantic.dev/mcp/overview/)
      * [ Client  ](https://ai.pydantic.dev/mcp/client/)
      * [ FastMCP Client  ](https://ai.pydantic.dev/mcp/fastmcp-client/)
      * [ Server  ](https://ai.pydantic.dev/mcp/server/)
    * [ Multi-Agent Patterns  ](https://ai.pydantic.dev/multi-agent-applications/)
    * [ Web Chat UI  ](https://ai.pydantic.dev/web/)
    * [ Embeddings  ](https://ai.pydantic.dev/embeddings/)
    * [ Testing  ](https://ai.pydantic.dev/testing/)
  * Pydantic Evals
    * [ Overview  ](https://ai.pydantic.dev/evals/)
    * Getting Started
      * [ Quick Start  ](https://ai.pydantic.dev/evals/quick-start/)
      * [ Core Concepts  ](https://ai.pydantic.dev/evals/core-concepts/)
    * Evaluators
      * [ Overview  ](https://ai.pydantic.dev/evals/evaluators/overview/)
      * [ Built-in Evaluators  ](https://ai.pydantic.dev/evals/evaluators/built-in/)
      * [ LLM Judge  ](https://ai.pydantic.dev/evals/evaluators/llm-judge/)
      * [ Custom Evaluators  ](https://ai.pydantic.dev/evals/evaluators/custom/)
      * [ Report Evaluators  ](https://ai.pydantic.dev/evals/evaluators/report-evaluators/)
      * [ Span-Based  ](https://ai.pydantic.dev/evals/evaluators/span-based/)
    * How-To Guides
      * [ Logfire Integration  ](https://ai.pydantic.dev/evals/how-to/logfire-integration/)
      * [ Dataset Management  ](https://ai.pydantic.dev/evals/how-to/dataset-management/)
      * [ Dataset Serialization  ](https://ai.pydantic.dev/evals/how-to/dataset-serialization/)
      * [ Concurrency & Performance  ](https://ai.pydantic.dev/evals/how-to/concurrency/)
      * [ Multi-Run Evaluation  ](https://ai.pydantic.dev/evals/how-to/multi-run/)
      * [ Retry Strategies  ](https://ai.pydantic.dev/evals/how-to/retry-strategies/)
      * [ Metrics & Attributes  ](https://ai.pydantic.dev/evals/how-to/metrics-attributes/)
    * Examples
      * [ Simple Validation  ](https://ai.pydantic.dev/evals/examples/simple-validation/)
  * Pydantic Graph
    * [ Overview  ](https://ai.pydantic.dev/graph/)
    * [ Beta API  ](https://ai.pydantic.dev/graph/beta/)
      * [ Steps  ](https://ai.pydantic.dev/graph/beta/steps/)
      * [ Joins & Reducers  ](https://ai.pydantic.dev/graph/beta/joins/)
      * [ Decisions  ](https://ai.pydantic.dev/graph/beta/decisions/)
      * [ Parallel Execution  ](https://ai.pydantic.dev/graph/beta/parallel/)
  * Integrations
    * [ Debugging & Monitoring with Pydantic Logfire  ](https://ai.pydantic.dev/logfire/)
    * Durable Execution
      * [ Overview  ](https://ai.pydantic.dev/durable_execution/overview/)
      * Temporal  [ Temporal  ](https://ai.pydantic.dev/durable_execution/temporal/)
        * [ Durable Execution  ](https://ai.pydantic.dev/durable_execution/temporal/#durable-execution)
        * [ Durable Agent  ](https://ai.pydantic.dev/durable_execution/temporal/#durable-agent)
        * [ Temporal Integration Considerations  ](https://ai.pydantic.dev/durable_execution/temporal/#temporal-integration-considerations)
          * [ Agent Names and Toolset IDs  ](https://ai.pydantic.dev/durable_execution/temporal/#agent-names-and-toolset-ids)
          * [ Agent Run Context and Dependencies  ](https://ai.pydantic.dev/durable_execution/temporal/#agent-run-context-and-dependencies)
          * [ Streaming  ](https://ai.pydantic.dev/durable_execution/temporal/#streaming)
          * [ Model Selection at Runtime  ](https://ai.pydantic.dev/durable_execution/temporal/#model-selection-at-runtime)
        * [ Activity Configuration  ](https://ai.pydantic.dev/durable_execution/temporal/#activity-configuration)
        * [ Activity Retries  ](https://ai.pydantic.dev/durable_execution/temporal/#activity-retries)
        * [ Observability with Logfire  ](https://ai.pydantic.dev/durable_execution/temporal/#observability-with-logfire)
        * [ Known Issues  ](https://ai.pydantic.dev/durable_execution/temporal/#known-issues)
          * [ Pandas  ](https://ai.pydantic.dev/durable_execution/temporal/#pandas)
      * [ DBOS  ](https://ai.pydantic.dev/durable_execution/dbos/)
      * [ Prefect  ](https://ai.pydantic.dev/durable_execution/prefect/)
    * UI Event Streams
      * [ Overview  ](https://ai.pydantic.dev/ui/overview/)
      * [ AG-UI  ](https://ai.pydantic.dev/ui/ag-ui/)
      * [ Vercel AI  ](https://ai.pydantic.dev/ui/vercel-ai/)
    * [ Agent2Agent (A2A)  ](https://ai.pydantic.dev/a2a/)
  * Related Packages
    * [ clai  ](https://ai.pydantic.dev/cli/)
  * Examples
    * [ Setup  ](https://ai.pydantic.dev/examples/setup/)
    * Getting Started
      * [ Pydantic Model  ](https://ai.pydantic.dev/examples/pydantic-model/)
      * [ Weather agent  ](https://ai.pydantic.dev/examples/weather-agent/)
    * Conversational Agents
      * [ Chat App with FastAPI  ](https://ai.pydantic.dev/examples/chat-app/)
      * [ Bank support  ](https://ai.pydantic.dev/examples/bank-support/)
    * Data & Analytics
      * [ SQL Generation  ](https://ai.pydantic.dev/examples/sql-gen/)
      * [ Data Analyst  ](https://ai.pydantic.dev/examples/data-analyst/)
      * [ RAG  ](https://ai.pydantic.dev/examples/rag/)
    * Streaming
      * [ Stream markdown  ](https://ai.pydantic.dev/examples/stream-markdown/)
      * [ Stream whales  ](https://ai.pydantic.dev/examples/stream-whales/)
    * Complex Workflows
      * [ Flight booking  ](https://ai.pydantic.dev/examples/flight-booking/)
      * [ Question Graph  ](https://ai.pydantic.dev/examples/question-graph/)
    * Business Applications
      * [ Slack Lead Qualifier with Modal  ](https://ai.pydantic.dev/examples/slack-lead-qualifier/)
    * UI Examples
      * [ Agent User Interaction (AG-UI)  ](https://ai.pydantic.dev/examples/ag-ui/)
  * API Reference
    * pydantic_ai
      * [ pydantic_ai.ag_ui  ](https://ai.pydantic.dev/api/ag_ui/)
      * [ pydantic_ai.agent  ](https://ai.pydantic.dev/api/agent/)
      * [ pydantic_ai.builtin_tools  ](https://ai.pydantic.dev/api/builtin_tools/)
      * [ pydantic_ai.common_tools  ](https://ai.pydantic.dev/api/common_tools/)
      * [ pydantic_ai — Concurrency  ](https://ai.pydantic.dev/api/concurrency/)
      * [ pydantic_ai.direct  ](https://ai.pydantic.dev/api/direct/)
      * [ pydantic_ai.durable_exec  ](https://ai.pydantic.dev/api/durable_exec/)
      * [ pydantic_ai.embeddings  ](https://ai.pydantic.dev/api/embeddings/)
      * [ pydantic_ai.exceptions  ](https://ai.pydantic.dev/api/exceptions/)
      * [ pydantic_ai.ext  ](https://ai.pydantic.dev/api/ext/)
      * [ pydantic_ai.format_prompt  ](https://ai.pydantic.dev/api/format_prompt/)
      * [ pydantic_ai.mcp  ](https://ai.pydantic.dev/api/mcp/)
      * [ pydantic_ai.messages  ](https://ai.pydantic.dev/api/messages/)
      * [ pydantic_ai.models.anthropic  ](https://ai.pydantic.dev/api/models/anthropic/)
      * [ pydantic_ai.models  ](https://ai.pydantic.dev/api/models/base/)
      * [ pydantic_ai.models.bedrock  ](https://ai.pydantic.dev/api/models/bedrock/)
      * [ pydantic_ai.models.cerebras  ](https://ai.pydantic.dev/api/models/cerebras/)
      * [ pydantic_ai.models.cohere  ](https://ai.pydantic.dev/api/models/cohere/)
      * [ pydantic_ai.models.fallback  ](https://ai.pydantic.dev/api/models/fallback/)
      * [ pydantic_ai.models.function  ](https://ai.pydantic.dev/api/models/function/)
      * [ pydantic_ai.models.google  ](https://ai.pydantic.dev/api/models/google/)
      * [ pydantic_ai.models.xai  ](https://ai.pydantic.dev/api/models/xai/)
      * [ pydantic_ai.models.groq  ](https://ai.pydantic.dev/api/models/groq/)
      * [ pydantic_ai.models.huggingface  ](https://ai.pydantic.dev/api/models/huggingface/)
      * [ pydantic_ai.models.instrumented  ](https://ai.pydantic.dev/api/models/instrumented/)
      * [ pydantic_ai.models.mcp_sampling  ](https://ai.pydantic.dev/api/models/mcp-sampling/)
      * [ pydantic_ai.models.mistral  ](https://ai.pydantic.dev/api/models/mistral/)
      * [ pydantic_ai.models.openai  ](https://ai.pydantic.dev/api/models/openai/)
      * [ pydantic_ai.models.openrouter  ](https://ai.pydantic.dev/api/models/openrouter/)
      * [ pydantic_ai.models.outlines  ](https://ai.pydantic.dev/api/models/outlines/)
      * [ pydantic_ai.models.test  ](https://ai.pydantic.dev/api/models/test/)
      * [ pydantic_ai.models.wrapper  ](https://ai.pydantic.dev/api/models/wrapper/)
      * [ pydantic_ai.output  ](https://ai.pydantic.dev/api/output/)
      * [ pydantic_ai.profiles  ](https://ai.pydantic.dev/api/profiles/)
      * [ pydantic_ai.providers  ](https://ai.pydantic.dev/api/providers/)
      * [ pydantic_ai.result  ](https://ai.pydantic.dev/api/result/)
      * [ pydantic_ai.retries  ](https://ai.pydantic.dev/api/retries/)
      * [ pydantic_ai.run  ](https://ai.pydantic.dev/api/run/)
      * [ pydantic_ai.settings  ](https://ai.pydantic.dev/api/settings/)
      * [ pydantic_ai.tools  ](https://ai.pydantic.dev/api/tools/)
      * [ pydantic_ai.toolsets  ](https://ai.pydantic.dev/api/toolsets/)
      * [ pydantic_ai.ui.ag_ui  ](https://ai.pydantic.dev/api/ui/ag_ui/)
      * [ pydantic_ai.ui  ](https://ai.pydantic.dev/api/ui/base/)
      * [ pydantic_ai.ui.vercel_ai  ](https://ai.pydantic.dev/api/ui/vercel_ai/)
      * [ pydantic_ai.usage  ](https://ai.pydantic.dev/api/usage/)
    * pydantic_evals
      * [ pydantic_evals.dataset  ](https://ai.pydantic.dev/api/pydantic_evals/dataset/)
      * [ pydantic_evals.evaluators  ](https://ai.pydantic.dev/api/pydantic_evals/evaluators/)
      * [ pydantic_evals.reporting  ](https://ai.pydantic.dev/api/pydantic_evals/reporting/)
      * [ pydantic_evals.otel  ](https://ai.pydantic.dev/api/pydantic_evals/otel/)
      * [ pydantic_evals.generation  ](https://ai.pydantic.dev/api/pydantic_evals/generation/)
    * pydantic_graph
      * [ pydantic_graph  ](https://ai.pydantic.dev/api/pydantic_graph/graph/)
      * [ pydantic_graph.nodes  ](https://ai.pydantic.dev/api/pydantic_graph/nodes/)
      * [ pydantic_graph.persistence  ](https://ai.pydantic.dev/api/pydantic_graph/persistence/)
      * [ pydantic_graph.mermaid  ](https://ai.pydantic.dev/api/pydantic_graph/mermaid/)
      * [ pydantic_graph.exceptions  ](https://ai.pydantic.dev/api/pydantic_graph/exceptions/)
      * Beta API
        * [ pydantic_graph.beta  ](https://ai.pydantic.dev/api/pydantic_graph/beta/)
        * [ pydantic_graph.beta.graph  ](https://ai.pydantic.dev/api/pydantic_graph/beta_graph/)
        * [ pydantic_graph.beta.graph_builder  ](https://ai.pydantic.dev/api/pydantic_graph/beta_graph_builder/)
        * [ pydantic_graph.beta.step  ](https://ai.pydantic.dev/api/pydantic_graph/beta_step/)
        * [ pydantic_graph.beta.join  ](https://ai.pydantic.dev/api/pydantic_graph/beta_join/)
        * [ pydantic_graph.beta.decision  ](https://ai.pydantic.dev/api/pydantic_graph/beta_decision/)
        * [ pydantic_graph.beta.node  ](https://ai.pydantic.dev/api/pydantic_graph/beta_node/)
    * fasta2a
      * [ fasta2a  ](https://ai.pydantic.dev/api/fasta2a/)
  * Project
    * [ Contributing  ](https://ai.pydantic.dev/contributing/)
    * [ Upgrade Guide  ](https://ai.pydantic.dev/changelog/)
    * [ Version policy  ](https://ai.pydantic.dev/version-policy/)


  * [ Durable Execution  ](https://ai.pydantic.dev/durable_execution/temporal/#durable-execution)
  * [ Durable Agent  ](https://ai.pydantic.dev/durable_execution/temporal/#durable-agent)
  * [ Temporal Integration Considerations  ](https://ai.pydantic.dev/durable_execution/temporal/#temporal-integration-considerations)
    * [ Agent Names and Toolset IDs  ](https://ai.pydantic.dev/durable_execution/temporal/#agent-names-and-toolset-ids)
    * [ Agent Run Context and Dependencies  ](https://ai.pydantic.dev/durable_execution/temporal/#agent-run-context-and-dependencies)
    * [ Streaming  ](https://ai.pydantic.dev/durable_execution/temporal/#streaming)
    * [ Model Selection at Runtime  ](https://ai.pydantic.dev/durable_execution/temporal/#model-selection-at-runtime)
  * [ Activity Configuration  ](https://ai.pydantic.dev/durable_execution/temporal/#activity-configuration)
  * [ Activity Retries  ](https://ai.pydantic.dev/durable_execution/temporal/#activity-retries)
  * [ Observability with Logfire  ](https://ai.pydantic.dev/durable_execution/temporal/#observability-with-logfire)
  * [ Known Issues  ](https://ai.pydantic.dev/durable_execution/temporal/#known-issues)
    * [ Pandas  ](https://ai.pydantic.dev/durable_execution/temporal/#pandas)


  1. [ Pydantic AI  ](https://ai.pydantic.dev/)
  2. [ Integrations  ](https://ai.pydantic.dev/logfire/)
  3. [ Durable Execution  ](https://ai.pydantic.dev/durable_execution/overview/)


# Durable Execution with Temporal
[Temporal](https://temporal.io) is a popular [durable execution](https://docs.temporal.io/evaluate/understanding-temporal#durable-execution) platform that's natively supported by Pydantic AI.
## Durable Execution
In Temporal's durable execution implementation, a program that crashes or encounters an exception while interacting with a model or API will retry until it can successfully complete.
Temporal relies primarily on a replay mechanism to recover from failures. As the program makes progress, Temporal saves key inputs and decisions, allowing a re-started program to pick up right where it left off.
The key to making this work is to separate the application's repeatable (deterministic) and non-repeatable (non-deterministic) parts:
  1. Deterministic pieces, termed [**workflows**](https://docs.temporal.io/workflow-definition), execute the same way when re-run with the same inputs.
  2. Non-deterministic pieces, termed [**activities**](https://docs.temporal.io/activities), can run arbitrary code, performing I/O and any other operations.


Workflow code can run for extended periods and, if interrupted, resume exactly where it left off. Critically, workflow code generally _cannot_ include any kind of I/O, over the network, disk, etc. Activity code faces no restrictions on I/O or external interactions, but if an activity fails part-way through it is restarted from the beginning.
Note
If you are familiar with celery, it may be helpful to think of Temporal activities as similar to celery tasks, but where you wait for the task to complete and obtain its result before proceeding to the next step in the workflow. However, Temporal workflows and activities offer a great deal more flexibility and functionality than celery tasks.
See the [Temporal documentation](https://docs.temporal.io/evaluate/understanding-temporal#temporal-application-the-building-blocks) for more information
In the case of Pydantic AI agents, integration with Temporal means that [model requests](https://ai.pydantic.dev/models/overview/), [tool calls](https://ai.pydantic.dev/tools/) that may require I/O, and [MCP server communication](https://ai.pydantic.dev/mcp/client/) all need to be offloaded to Temporal activities due to their I/O requirements, while the logic that coordinates them (i.e. the agent run) lives in the workflow. Code that handles a scheduled job or web request can then execute the workflow, which will in turn execute the activities as needed.
The diagram below shows the overall architecture of an agentic application in Temporal. The Temporal Server is responsible for tracking program execution and making sure the associated state is preserved reliably (i.e., stored to an internal database, and possibly replicated across cloud regions). Temporal Server manages data in encrypted form, so all data processing occurs on the Worker, which runs the workflow and activities.
```
            +---------------------+
            |   Temporal Server   |      (Stores workflow state,
            +---------------------+       schedules activities,
                     ^                    persists progress)
                     |
        Save state,  |   Schedule Tasks,
        progress,    |   load state on resume
        timeouts     |
                     |
+------------------------------------------------------+
|                      Worker                          |
|   +----------------------------------------------+   |
|   |              Workflow Code                   |   |
|   |       (Agent Run Loop)                       |   |
|   +----------------------------------------------+   |
|          |          |                |               |
|          v          v                v               |
|   +-----------+ +------------+ +-------------+       |
|   | Activity  | | Activity   | |  Activity   |       |
|   | (Tool)    | | (MCP Tool) | | (Model API) |       |
|   +-----------+ +------------+ +-------------+       |
|         |           |                |               |
+------------------------------------------------------+
          |           |                |
          v           v                v
      [External APIs, services, databases, etc.]

```

See the [Temporal documentation](https://docs.temporal.io/evaluate/understanding-temporal#temporal-application-the-building-blocks) for more information.
## Durable Agent
Any agent can be wrapped in a [`TemporalAgent`](https://ai.pydantic.dev/api/durable_exec/#pydantic_ai.durable_exec.temporal.TemporalAgent "TemporalAgent") to get a durable agent that can be used inside a deterministic Temporal workflow, by automatically offloading all work that requires I/O (namely model requests, tool calls, and MCP server communication) to non-deterministic activities.
At the time of wrapping, the agent's [model](https://ai.pydantic.dev/models/overview/) and [toolsets](https://ai.pydantic.dev/toolsets/) (including function tools registered on the agent and MCP servers) are frozen, activities are dynamically created for each, and the original model and toolsets are wrapped to call on the worker to execute the corresponding activities instead of directly performing the actions inside the workflow. The original agent can still be used as normal outside the Temporal workflow, but any changes to its model or toolsets after wrapping will not be reflected in the durable agent.
Here is a simple but complete example of wrapping an agent for durable execution, creating a Temporal workflow with durable execution logic, connecting to a Temporal server, and running the workflow from non-durable code. All it requires is a Temporal server to be [running locally](https://github.com/temporalio/temporal#download-and-start-temporal-server-locally):
```
brew install temporal
temporal server start-dev

```

[With Pydantic AI Gateway](https://ai.pydantic.dev/durable_execution/temporal/#__tabbed_1_1)[Directly to Provider API](https://ai.pydantic.dev/durable_execution/temporal/#__tabbed_1_2)
[Learn about Gateway](https://ai.pydantic.dev/gateway) temporal_agent.py```
import uuid

from temporalio import workflow
from temporalio.client import Client
from temporalio.worker import Worker

from pydantic_ai import Agent
from pydantic_ai.durable_exec.temporal import (
    PydanticAIPlugin,
    PydanticAIWorkflow,
    TemporalAgent,
)

agent = Agent(
    'gateway/openai:gpt-5.2',
    instructions="You're an expert in geography.",
    name='geography',

[](https://ai.pydantic.dev/durable_execution/temporal/#__code_2_annotation_10)
)

temporal_agent = TemporalAgent(agent)  [](https://ai.pydantic.dev/durable_execution/temporal/#__code_2_annotation_1)


@workflow.defn
class GeographyWorkflow(PydanticAIWorkflow):  [](https://ai.pydantic.dev/durable_execution/temporal/#__code_2_annotation_2)
    __pydantic_ai_agents__ = [temporal_agent]  [](https://ai.pydantic.dev/durable_execution/temporal/#__code_2_annotation_3)

    @workflow.run
    async def run(self, prompt: str) -> str:
        result = await temporal_agent.run(prompt)  [](https://ai.pydantic.dev/durable_execution/temporal/#__code_2_annotation_4)
        return result.output


async def main():
    client = await Client.connect(  [](https://ai.pydantic.dev/durable_execution/temporal/#__code_2_annotation_5)
        'localhost:7233',  [](https://ai.pydantic.dev/durable_execution/temporal/#__code_2_annotation_6)
        plugins=[PydanticAIPlugin()],  [](https://ai.pydantic.dev/durable_execution/temporal/#__code_2_annotation_7)
    )

    async with Worker(  [](https://ai.pydantic.dev/durable_execution/temporal/#__code_2_annotation_8)
        client,
        task_queue='geography',
        workflows=[GeographyWorkflow],
    ):
        output = await client.execute_workflow(  [](https://ai.pydantic.dev/durable_execution/temporal/#__code_2_annotation_10)
            GeographyWorkflow.run,
            args=['What is the capital of Mexico?'],
            id=f'geography-{uuid.uuid4()}',
            task_queue='geography',
        )
        print(output)
        #> Mexico City (Ciudad de México, CDMX)

```

  1. The agent's `name` is used to uniquely identify its activities.


temporal_agent.py```
import uuid

from temporalio import workflow
from temporalio.client import Client
from temporalio.worker import Worker

from pydantic_ai import Agent
from pydantic_ai.durable_exec.temporal import (
    PydanticAIPlugin,
    PydanticAIWorkflow,
    TemporalAgent,
)

agent = Agent(
    'openai:gpt-5.2',
    instructions="You're an expert in geography.",
    name='geography',

[](https://ai.pydantic.dev/durable_execution/temporal/#__code_3_annotation_10)
)

temporal_agent = TemporalAgent(agent)

[](https://ai.pydantic.dev/durable_execution/temporal/#__code_3_annotation_1)


@workflow.defn
class GeographyWorkflow(PydanticAIWorkflow):

[](https://ai.pydantic.dev/durable_execution/temporal/#__code_3_annotation_2)
    __pydantic_ai_agents__ = [temporal_agent]

[](https://ai.pydantic.dev/durable_execution/temporal/#__code_3_annotation_3)

    @workflow.run
    async def run(self, prompt: str) -> str:
        result = await temporal_agent.run(prompt)

[](https://ai.pydantic.dev/durable_execution/temporal/#__code_3_annotation_4)
        return result.output


async def main():
    client = await Client.connect(

[](https://ai.pydantic.dev/durable_execution/temporal/#__code_3_annotation_5)
        'localhost:7233',

[](https://ai.pydantic.dev/durable_execution/temporal/#__code_3_annotation_6)
        plugins=[PydanticAIPlugin()],

[](https://ai.pydantic.dev/durable_execution/temporal/#__code_3_annotation_7)
    )

    async with Worker(

[](https://ai.pydantic.dev/durable_execution/temporal/#__code_3_annotation_8)
        client,
        task_queue='geography',
        workflows=[GeographyWorkflow],
    ):
        output = await client.execute_workflow(

[](https://ai.pydantic.dev/durable_execution/temporal/#__code_3_annotation_10)
            GeographyWorkflow.run,
            args=['What is the capital of Mexico?'],
            id=f'geography-{uuid.uuid4()}',
            task_queue='geography',
        )
        print(output)
        #> Mexico City (Ciudad de México, CDMX)

```

  1. The original `Agent` cannot be used inside a deterministic Temporal workflow, but the `TemporalAgent` can.
  2. As explained above, the workflow represents a deterministic piece of code that can use non-deterministic activities for operations that require I/O. Subclassing [`PydanticAIWorkflow`](https://ai.pydantic.dev/api/durable_exec/#pydantic_ai.durable_exec.temporal.PydanticAIWorkflow "PydanticAIWorkflow") is optional but provides proper typing for the `__pydantic_ai_agents__` class variable.
  3. List the `TemporalAgent`s used by this workflow. The [`PydanticAIPlugin`](https://ai.pydantic.dev/api/durable_exec/#pydantic_ai.durable_exec.temporal.PydanticAIPlugin "PydanticAIPlugin") will automatically register their activities with the worker. Alternatively, if modifying the worker initialization is easier than the workflow class, you can use [`AgentPlugin`](https://ai.pydantic.dev/api/durable_exec/#pydantic_ai.durable_exec.temporal.AgentPlugin "AgentPlugin") to register agents directly on the worker.
  4. [`TemporalAgent.run()`](https://ai.pydantic.dev/api/durable_exec/#pydantic_ai.durable_exec.temporal.TemporalAgent.run "run



      async
  ") works just like [`Agent.run()`](https://ai.pydantic.dev/api/agent/#pydantic_ai.agent.AbstractAgent.run "run



      async
  "), but it will automatically offload model requests, tool calls, and MCP server communication to Temporal activities.
  5. We connect to the Temporal server which keeps track of workflow and activity execution.
  6. This assumes the Temporal server is [running locally](https://github.com/temporalio/temporal#download-and-start-temporal-server-locally).
  7. The [`PydanticAIPlugin`](https://ai.pydantic.dev/api/durable_exec/#pydantic_ai.durable_exec.temporal.PydanticAIPlugin "PydanticAIPlugin") tells Temporal to use Pydantic for serialization and deserialization, treats [`UserError`](https://ai.pydantic.dev/api/exceptions/#pydantic_ai.exceptions.UserError "UserError") exceptions as non-retryable, and automatically registers activities for agents listed in `__pydantic_ai_agents__`.
  8. We start the worker that will listen on the specified task queue and run workflows and activities. In a real world application, this might be run in a separate service.
  9. The agent's `name` is used to uniquely identify its activities.
  10. We call on the server to execute the workflow on a worker that's listening on the specified task queue.


_(This example is complete, it can be run "as is" — you'll need to add`asyncio.run(main())` to run `main`)_
In a real world application, the agent, workflow, and worker are typically defined separately from the code that calls for a workflow to be executed. Because Temporal workflows need to be defined at the top level of the file and the `TemporalAgent` instance is needed inside the workflow and when starting the worker (to register the activities), it needs to be defined at the top level of the file as well.
For more information on how to use Temporal in Python applications, see their [Python SDK guide](https://docs.temporal.io/develop/python).
## Temporal Integration Considerations
There are a few considerations specific to agents and toolsets when using Temporal for durable execution. These are important to understand to ensure that your agents and toolsets work correctly with Temporal's workflow and activity model.
### Agent Names and Toolset IDs
To ensure that Temporal knows what code to run when an activity fails or is interrupted and then restarted, even if your code is changed in between, each activity needs to have a name that's stable and unique.
When `TemporalAgent` dynamically creates activities for the wrapped agent's model requests and toolsets (specifically those that implement their own tool listing and calling, i.e. [`FunctionToolset`](https://ai.pydantic.dev/api/toolsets/#pydantic_ai.toolsets.FunctionToolset "FunctionToolset") and [`MCPServer`](https://ai.pydantic.dev/api/mcp/#pydantic_ai.mcp.MCPServer "MCPServer")), their names are derived from the agent's [`name`](https://ai.pydantic.dev/api/agent/#pydantic_ai.agent.AbstractAgent.name "name



      abstractmethod
      property
      writable
  ") and the toolsets' [`id`s](https://ai.pydantic.dev/api/toolsets/#pydantic_ai.toolsets.AbstractToolset.id "id



      abstractmethod
      property
  "). These fields are normally optional, but are required to be set when using Temporal. They should not be changed once the durable agent has been deployed to production as this would break active workflows.
For dynamic toolsets created with the [`@agent.toolset`](https://ai.pydantic.dev/api/agent/#pydantic_ai.agent.Agent.toolset "toolset") decorator, the `id` parameter must be set explicitly. Note that with Temporal, `per_run_step=False` is not respected, as the toolset always needs to be created on-the-fly in the activity.
Other than that, any agent and toolset will just work!
### Agent Run Context and Dependencies
As workflows and activities run in separate processes, any values passed between them need to be serializable. As these payloads are stored in the workflow execution event history, Temporal limits their size to 2MB.
To account for these limitations, tool functions and the [event stream handler](https://ai.pydantic.dev/durable_execution/temporal/#streaming) running inside activities receive a limited version of the agent's [`RunContext`](https://ai.pydantic.dev/api/tools/#pydantic_ai.tools.RunContext "RunContext



      dataclass
  "), and it's your responsibility to make sure that the [dependencies](https://ai.pydantic.dev/dependencies/) object provided to [`TemporalAgent.run()`](https://ai.pydantic.dev/api/durable_exec/#pydantic_ai.durable_exec.temporal.TemporalAgent.run "run



      async
  ") can be serialized using Pydantic.
Specifically, only the `deps`, `run_id`, `metadata`, `retries`, `tool_call_id`, `tool_name`, `tool_call_approved`, `tool_call_metadata`, `retry`, `max_retries`, `run_step`, `usage`, and `partial_output` fields are available by default, and trying to access `model`, `prompt`, `messages`, or `tracer` will raise an error. If you need one or more of these attributes to be available inside activities, you can create a [`TemporalRunContext`](https://ai.pydantic.dev/api/durable_exec/#pydantic_ai.durable_exec.temporal.TemporalRunContext "TemporalRunContext") subclass with custom `serialize_run_context` and `deserialize_run_context` class methods and pass it to [`TemporalAgent`](https://ai.pydantic.dev/api/durable_exec/#pydantic_ai.durable_exec.temporal.TemporalAgent "TemporalAgent") as `run_context_type`.
### Streaming
Because Temporal activities cannot stream output directly to the activity call site, [`Agent.run_stream()`](https://ai.pydantic.dev/api/agent/#pydantic_ai.agent.AbstractAgent.run_stream "run_stream



      async
  "), [`Agent.run_stream_events()`](https://ai.pydantic.dev/api/agent/#pydantic_ai.agent.AbstractAgent.run_stream_events "run_stream_events"), and [`Agent.iter()`](https://ai.pydantic.dev/api/agent/#pydantic_ai.agent.Agent.iter "iter



      async
  ") are not supported.
Instead, you can implement streaming by setting an [`event_stream_handler`](https://ai.pydantic.dev/api/agent/#pydantic_ai.agent.EventStreamHandler "EventStreamHandler



      module-attribute
  ") on the `Agent` or `TemporalAgent` instance and using [`TemporalAgent.run()`](https://ai.pydantic.dev/api/durable_exec/#pydantic_ai.durable_exec.temporal.TemporalAgent.run "run



      async
  ") inside the workflow. The event stream handler function will receive the agent [run context](https://ai.pydantic.dev/api/tools/#pydantic_ai.tools.RunContext "RunContext



      dataclass
  ") and an async iterable of events from the model's streaming response and the agent's execution of tools. For examples, see the [streaming docs](https://ai.pydantic.dev/agent/#streaming-all-events).
As the streaming model request activity, workflow, and workflow execution call all take place in separate processes, passing data between them requires some care:
  * To get data from the workflow call site or workflow to the event stream handler, you can use a [dependencies object](https://ai.pydantic.dev/durable_execution/temporal/#agent-run-context-and-dependencies).
  * To get data from the event stream handler to the workflow, workflow call site, or a frontend, you need to use an external system that the event stream handler can write to and the event consumer can read from, like a message queue. You can use the dependency object to make sure the same connection string or other unique ID is available in all the places that need it.


### Model Selection at Runtime
[`Agent.run(model=...)`](https://ai.pydantic.dev/api/agent/#pydantic_ai.agent.AbstractAgent.run "run



      async
  ") normally supports both model strings (like `'openai:gpt-5.2'`) and model instances. However, `TemporalAgent` does not support arbitrary model instances because they cannot be serialized for Temporal's replay mechanism.
To use model instances with `TemporalAgent`, you need to pre-register them by passing a dict of model instances to `TemporalAgent(models={...})`. You can then reference them by name or by passing the registered instance directly. If the wrapped agent doesn't have a model set, the first registered model will be used as the default.
Model strings work as expected. For scenarios where you need to customize the provider used by the model string (e.g., inject API keys from deps), you can pass a `provider_factory` to `TemporalAgent`, which is passed the [`RunContext`](https://ai.pydantic.dev/api/tools/#pydantic_ai.tools.RunContext "RunContext



      dataclass
  ") and provider name.
Here's an example showing how to pre-register and use multiple models:
multi_model_temporal.py```
from dataclasses import dataclass
from typing import Any

from temporalio import workflow

from pydantic_ai import Agent
from pydantic_ai.durable_exec.temporal import TemporalAgent
from pydantic_ai.models.anthropic import AnthropicModel
from pydantic_ai.models.google import GoogleModel
from pydantic_ai.models.openai import OpenAIResponsesModel
from pydantic_ai.providers import Provider
from pydantic_ai.tools import RunContext


@dataclass
class Deps:
    openai_api_key: str | None = None
    anthropic_api_key: str | None = None


# Create models from different providers
default_model = OpenAIResponsesModel('gpt-5.2')
fast_model = AnthropicModel('claude-sonnet-4-5')
reasoning_model = GoogleModel('gemini-3-pro-preview')


# Optional: provider factory for dynamic model configuration
def my_provider_factory(run_context: RunContext[Deps], provider_name: str) -> Provider[Any]:
    """Create providers with custom configuration based on run context."""
    if provider_name == 'openai':
        from pydantic_ai.providers.openai import OpenAIProvider

        return OpenAIProvider(api_key=run_context.deps.openai_api_key)
    elif provider_name == 'anthropic':
        from pydantic_ai.providers.anthropic import AnthropicProvider

        return AnthropicProvider(api_key=run_context.deps.anthropic_api_key)
    else:
        raise ValueError(f'Unknown provider: {provider_name}')


agent = Agent(default_model, name='multi_model_agent', deps_type=Deps)

temporal_agent = TemporalAgent(
    agent,
    models={
        'fast': fast_model,
        'reasoning': reasoning_model,
    },
    provider_factory=my_provider_factory,  # Optional
)


@workflow.defn
class MultiModelWorkflow:
    @workflow.run
    async def run(self, prompt: str, use_reasoning: bool, use_fast: bool) -> str:
        if use_reasoning:
            # Select by registered name
            result = await temporal_agent.run(prompt, model='reasoning')
        elif use_fast:
            # Or pass the registered instance directly
            result = await temporal_agent.run(prompt, model=fast_model)
        else:
            # Or pass a model string (uses provider_factory if set)
            result = await temporal_agent.run(prompt, model='openai:gpt-5-mini')
        return result.output

```

## Activity Configuration
Temporal activity configuration, like timeouts and retry policies, can be customized by passing [`temporalio.workflow.ActivityConfig`](https://python.temporal.io/temporalio.workflow.ActivityConfig.html) objects to the `TemporalAgent` constructor:
  * `activity_config`: The base Temporal activity config to use for all activities. If no config is provided, a `start_to_close_timeout` of 60 seconds is used.
  * `model_activity_config`: The Temporal activity config to use for model request activities. This is merged with the base activity config.
  * `toolset_activity_config`: The Temporal activity config to use for get-tools and call-tool activities for specific toolsets identified by ID. This is merged with the base activity config.
  * `tool_activity_config`: The Temporal activity config to use for specific tool call activities identified by toolset ID and tool name. This is merged with the base and toolset-specific activity configs.
If a tool does not use I/O, you can specify `False` to disable using an activity. Note that the tool is required to be defined as an `async` function as non-async tools are run in threads which are non-deterministic and thus not supported outside of activities.


## Activity Retries
On top of the automatic retries for request failures that Temporal will perform, Pydantic AI and various provider API clients also have their own request retry logic. Enabling these at the same time may cause the request to be retried more often than expected, with improper `Retry-After` handling.
When using Temporal, it's recommended to not use [HTTP Request Retries](https://ai.pydantic.dev/retries/) and to turn off your provider API client's own retry logic, for example by setting `max_retries=0` on a [custom `OpenAIProvider` API client](https://ai.pydantic.dev/models/openai/#custom-openai-client).
You can customize Temporal's retry policy using [activity configuration](https://ai.pydantic.dev/durable_execution/temporal/#activity-configuration).
## Observability with Logfire
Temporal generates telemetry events and metrics for each workflow and activity execution, and Pydantic AI generates events for each agent run, model request and tool call. These can be sent to [Pydantic Logfire](https://ai.pydantic.dev/logfire/) to get a complete picture of what's happening in your application.
To use Logfire with Temporal, you need to pass a [`LogfirePlugin`](https://ai.pydantic.dev/api/durable_exec/#pydantic_ai.durable_exec.temporal.LogfirePlugin "LogfirePlugin") object to Temporal's `Client.connect()`:
logfire_plugin.py```
from temporalio.client import Client

from pydantic_ai.durable_exec.temporal import LogfirePlugin, PydanticAIPlugin


async def main():
    client = await Client.connect(
        'localhost:7233',
        plugins=[PydanticAIPlugin(), LogfirePlugin()],
    )

```

By default, the `LogfirePlugin` will instrument Temporal (including metrics) and Pydantic AI and send all data to Logfire. To customize Logfire configuration and instrumentation, you can pass a `logfire_setup` function to the `LogfirePlugin` constructor and return a custom `Logfire` instance (i.e. the result of `logfire.configure()`). To disable sending Temporal metrics to Logfire, you can pass `metrics=False` to the `LogfirePlugin` constructor.
## Known Issues
### Pandas
When `logfire.info` is used inside an activity and the `pandas` package is among your project's dependencies, you may encounter the following error which seems to be the result of an import race condition:
```
AttributeError: partially initialized module 'pandas' has no attribute '_pandas_parser_CAPI' (most likely due to a circular import)

```

To fix this, you can use the [`temporalio.workflow.unsafe.imports_passed_through()`](https://python.temporal.io/temporalio.workflow.unsafe.html#imports_passed_through) context manager to proactively import the package and not have it be reloaded in the workflow sandbox:
temporal_activity.py```
from temporalio import workflow

with workflow.unsafe.imports_passed_through():
    import pandas

```

© Pydantic Services Inc. 2024 to present
