|
| 1 | +# Durable Execution with DBOS |
| 2 | + |
| 3 | +!!! note |
| 4 | + Durable execution support is in beta and the public interface is subject to change based on user feedback. We expect it to be stable by the release of Pydantic AI v1 at the end of August. Questions and feedback are welcome in [GitHub issues](https://github.com/pydantic/pydantic-ai/issues) and the [`#pydantic-ai` Slack channel](https://logfire.pydantic.dev/docs/join-slack/). |
| 5 | + |
| 6 | +Pydantic AI allows you to build durable agents that can preserve their progress across transient API failures and application errors or restarts, and handle long-running, asynchronous, and human-in-the-loop workflows with production-grade reliability. Durable agents have full support for [streaming](agents.md#streaming-all-events) and [MCP](mcp/client.md), with the added benefit of fault tolerance. |
| 7 | + |
| 8 | +[DBOS](https://www.dbos.dev/) is a lightweight [durable execution](https://docs.dbos.dev/architecture) library that's natively supported by Pydantic AI. |
| 9 | +The integration only uses Pydantic AI's public interface, so it can also serve as a reference for how to integrate with other durable execution systems. |
| 10 | + |
| 11 | +### Durable Execution |
| 12 | + |
| 13 | +In DBOS'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. |
| 14 | + |
| 15 | +DBOS relies primarily on a replay mechanism to recover from failures. |
| 16 | +As the program makes progress, DBOS saves key inputs and decisions, allowing a re-started program to pick up right where it left off. |
| 17 | + |
| 18 | +The key to making this work is to separate the application's repeatable (deterministic) and non-repeatable (non-deterministic) parts: |
| 19 | + |
| 20 | +1. Deterministic pieces, termed [**workflows**](https://docs.dbos.dev/python/tutorials/workflow-tutorial), execute the same way when re-run with the same inputs. |
| 21 | +2. Non-deterministic pieces, termed [**steps**](https://docs.dbos.dev/python/tutorials/step-tutorial), can run arbitrary code, performing I/O and any other operations. |
| 22 | + |
| 23 | +Workflow code can run for extended periods and, if interrupted, resume exactly where it left off. |
| 24 | +Critically, workflow code generally _cannot_ include any kind of I/O, over the network, disk, etc. |
| 25 | +Step code faces no restrictions on I/O or external interactions, but if a step fails part-way through it is restarted from the beginning. |
| 26 | + |
| 27 | + |
| 28 | +!!! note |
| 29 | + |
| 30 | + If you are familiar with celery, it may be helpful to think of DBOS steps 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. |
| 31 | + However, DBOS workflows and steps offer a great deal more flexibility and functionality than celery tasks. |
| 32 | + |
| 33 | + See the [DBOS documentation](https://docs.dbos.dev/architecture) for more information. |
| 34 | + |
| 35 | +In the case of Pydantic AI agents, integration with DBOS means that [model requests](models/index.md), [tool calls](tools.md) that may require I/O, and [MCP server communication](mcp/client.md) all need to be offloaded to DBOS steps 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 steps as needed. |
| 36 | + |
| 37 | +The diagram below shows the overall architecture of an agentic application in DBOS. |
| 38 | +DBOS is lightweight because it runs entirely in-process as a library, so your workflows and steps remain normal functions within your application that you can call from other application code. DBOS instruments them to checkpoint their state into a database (i.e., possibly replicated across cloud regions). |
| 39 | + |
| 40 | +```text |
| 41 | + Clients |
| 42 | + (HTTP, RPC, Kafka, etc.) |
| 43 | + | |
| 44 | + v |
| 45 | ++------------------------------------------------------+ |
| 46 | +| Application Servers | |
| 47 | +| | |
| 48 | +| +----------------------------------------------+ | |
| 49 | +| | Pydantic AI + DBOS Libraries | | |
| 50 | +| | | | |
| 51 | +| | [ Workflows (Agent Run Loop) ] | | |
| 52 | +| | [ Steps (Tool, MCP, Model) ] | | |
| 53 | +| | [ Queues ] [ Cron Jobs ] [ Messaging ] | | |
| 54 | +| +----------------------------------------------+ | |
| 55 | +| | |
| 56 | ++------------------------------------------------------+ |
| 57 | + | |
| 58 | + v |
| 59 | ++------------------------------------------------------+ |
| 60 | +| Database | |
| 61 | +| (Stores workflow and step state, schedules tasks) | |
| 62 | ++------------------------------------------------------+ |
| 63 | +``` |
| 64 | + |
| 65 | +See the [DBOS documentation](https://docs.dbos.dev/architecture) for more information. |
| 66 | + |
| 67 | +## Durable Agent |
| 68 | + |
| 69 | +Any agent can be wrapped in a [`DBOSAgent`][pydantic_ai.durable_exec.dbos.DBOSAgent] to get a durable agent, by automatically wrapping the agent run loop as a deterministic DBOS workflow and offloading work that requires I/O (namely model requests and MCP server communication) to non-deterministic steps. To make it flexible, `DBOSAgent` doesn't automatically wrap other tool functions, so you can decorate them as either DBOS workflows or steps as needed. |
| 70 | + |
| 71 | +At the time of wrapping, the agent's [model](models/index.md) and [MCP server communication](mcp/client.md) are wrapped as DBOS steps instead of directly invoking the original functions inside the workflow. The original agent can still be used as normal outside the DBOS workflow. |
| 72 | + |
| 73 | +Here is a simple but complete example of wrapping an agent for durable execution. All it requires is to install the DBOS [open-source library](https://github.com/dbos-inc/dbos-transact-py): |
| 74 | + |
| 75 | +```sh |
| 76 | +uv add pydantic-ai[dbos] |
| 77 | +``` |
| 78 | + |
| 79 | +or if you use pip: |
| 80 | +```sh |
| 81 | +pip install pydantic-ai[dbos] |
| 82 | +``` |
| 83 | + |
| 84 | +```python {title="dbos_agent.py" test="skip"} |
| 85 | +from dbos import DBOS, DBOSConfig |
| 86 | + |
| 87 | +from pydantic_ai import Agent |
| 88 | +from pydantic_ai.durable_exec.dbos import DBOSAgent |
| 89 | + |
| 90 | +dbos_config: DBOSConfig = { |
| 91 | + 'name': 'pydantic_dbos_agent', |
| 92 | + 'system_database_url': 'sqlite:///dbostest.sqlite', # (3)! |
| 93 | +} |
| 94 | +DBOS(config=dbos_config) |
| 95 | + |
| 96 | +agent = Agent( |
| 97 | + 'gpt-5', |
| 98 | + instructions="You're an expert in geography.", |
| 99 | + name='geography', # (4)! |
| 100 | +) |
| 101 | + |
| 102 | +dbos_agent = DBOSAgent(agent) # (1)! |
| 103 | + |
| 104 | +async def main(): |
| 105 | + DBOS.launch() |
| 106 | + result = await dbos_agent.run('What is the capital of Mexico?') # (2)! |
| 107 | + print(result.output) |
| 108 | + #> Mexico City (Ciudad de México, CDMX) |
| 109 | +``` |
| 110 | + |
| 111 | +1. The original `Agent` cannot be used inside a deterministic DBOS workflow, but the `DBOSAgent` can. Workflow function declarations and `DBOSAgent` creations needs to happen before calling `DBOS.launch()` because DBOS requires all workflows to be registered before launch so that recovery can correctly find all workflows. |
| 112 | +2. [`DBOSAgent.run()`][pydantic_ai.durable_exec.dbos.DBOSAgent.run] works like [`Agent.run()`][pydantic_ai.Agent.run], but runs inside a DBOS workflow and wraps model requests, decorated tool calls, and MCP communication as DBOS steps. |
| 113 | +3. This assumes DBOS is using SQLite. To deploy your agent to production, we recommend using a Postgres server. |
| 114 | +4. The agent's `name` is used to uniquely identify its workflows. |
| 115 | + |
| 116 | +_(This example is complete, it can be run "as is" — you'll need to add `asyncio.run(main())` to run `main`)_ |
| 117 | + |
| 118 | +Because DBOS workflows need to be defined before calling `DBOS.launch()` and the `DBOSAgent` instance automatically registers `run` and `run_sync` as workflows, it needs to be defined before calling `DBOS.launch()` as well. |
| 119 | + |
| 120 | +For more information on how to use DBOS in Python applications, see their [Python SDK guide](https://docs.dbos.dev/python/programming-guide). |
| 121 | + |
| 122 | +## DBOS Integration Considerations |
| 123 | + |
| 124 | +There are a few considerations specific to agents and toolsets when using DBOS for durable execution. These are important to understand to ensure that your agents and toolsets work correctly with DBOS's workflow and step model. |
| 125 | + |
| 126 | +### Agent and Toolset Requirements |
| 127 | + |
| 128 | +To ensure that DBOS knows what code to run when a workflow fails or is interrupted and then restarted, each agent instance needs to have a name that's unique. |
| 129 | + |
| 130 | +Other than that, any agent and toolset will just work! |
| 131 | + |
| 132 | +### Agent Run Context and Dependencies |
| 133 | + |
| 134 | +As DBOS checkpoints workflows and steps execution into a database, workflow inputs and outputs, and step outputs need to be serializable (JSON Pickleable). You may also want to keep the inputs and outputs small (usually less than 2MB). |
| 135 | + |
| 136 | +### Streaming |
| 137 | + |
| 138 | +Because DBOS steps cannot stream output directly to the step call site, [`Agent.run_stream()`][pydantic_ai.Agent.run_stream] is not supported. |
| 139 | + |
| 140 | +Instead, you can implement streaming by setting an [`event_stream_handler`][pydantic_ai.agent.EventStreamHandler] on the `Agent` or `DBOSAgent` instance and using [`DBOSAgent.run()`][pydantic_ai.durable_exec.dbos.DBOSAgent.run]. |
| 141 | +The event stream handler function will receive the agent [run context][pydantic_ai.tools.RunContext] 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](agents.md#streaming-all-events). |
| 142 | + |
| 143 | + |
| 144 | +## Step Configuration |
| 145 | + |
| 146 | +TBD |
| 147 | + |
| 148 | +## Step Retries |
| 149 | + |
| 150 | +TBD |
0 commit comments