The Resonate Python SDK enables developers to build reliable and scalable cloud applications across a wide variety of use cases.
- How to contribute to this SDK
- Evaluate Resonate for your next project
- Example application library
- Distributed Async Await — the concepts that power Resonate
- Join the Discord
- Subscribe to the Journal
- Follow on X
- Follow on LinkedIn
- Subscribe on YouTube
| Package | Module | What it is |
|---|---|---|
resonate-sdk |
resonate |
The SDK: durable functions, context, the HTTP/SSE/local transports. |
resonate-base |
resonate_base |
Connector seam: the Network/Source protocols and ConnectorError. Nothing else — a connector moves opaque strings and never parses ids or invents address formats. No third-party dependencies. |
resonate-nats |
resonate_nats |
NATS connector. Install with pip install resonate-nats. |
resonate-pg |
resonate_pg |
Postgres connector — the server is a resonate-pg schema, no server process. Install with pip install resonate-pg. |
resonate-sdk-aws |
resonate_aws |
AWS Lambda worker shim. |
Connectors depend on resonate-base alone, never on resonate-sdk — and they
are not extras of resonate-sdk. Installing one is pip install resonate-nats or pip install resonate-pg, so the SDK's dependency metadata
never names a connector and the arrow only ever points one way.
- Python ≥3.12
- A Resonate server: either the Resonate server (v0.9.x and up), or resonate-pg — one SQL file on Postgres 16+ — via
resonate-pg.
- Install the Resonate Server & CLI
brew install resonatehq/tap/resonate- Install the Resonate SDK
pip install resonate-sdk- Write your first Resonate Function
A countdown as a loop. Simple, but the function can run for minutes, hours, or days, despite restarts.
import asyncio
from datetime import timedelta
from resonate.context import Context
from resonate.resonate import Resonate
async def countdown(ctx: Context, count: int, delay: int) -> None:
for i in range(count, 0, -1):
# Run a function durably, awaiting its persisted result
await ctx.run(ntfy, i)
# Sleep durably -- the worker holds no state while suspended
await ctx.sleep(timedelta(seconds=delay))
print("Done!")
async def ntfy(ctx: Context, i: int) -> None:
print(f"Countdown: {i}")
async def main() -> None:
# Connect to the Resonate server and register the functions
resonate = Resonate(url="http://localhost:8001")
resonate.register(countdown)
resonate.register(ntfy)
try:
# Invoke with execution id "countdown.1"; run() returns a handle
# immediately, result() awaits the durable outcome
handle = resonate.run("countdown.1", countdown, 5, 1)
await handle.result()
finally:
await resonate.stop()
if __name__ == "__main__":
asyncio.run(main())- Start the server
resonate dev- Run the worker
python countdown.pyResult
You will see the countdown in the terminal
python countdown.py
Countdown: 5
Countdown: 4
Countdown: 3
Countdown: 2
Countdown: 1
Done!What to try
While the function is running, inspect the current state of the execution using the resonate tree command. The tree command visualizes the call graph of the function execution as a graph of durable promises.
resonate tree countdown.1Now try killing the worker mid-countdown and restarting python countdown.py. Because the invocation id is the same (countdown.1), the worker reattaches to the existing durable promise and the countdown picks up right where it left off without missing a beat.
The examples/ directory contains runnable programs covering the
core patterns. Start a server (resonate dev) on localhost:8001, then run any
of them, for example:
uv run python examples/hello-world
uv run python examples/fibonacci --mode rpc --n 10| Example | What it shows |
|---|---|
hello-world |
A minimal ctx.run / ctx.rpc chain |
fibonacci |
Recursive durable invocations via run, rpc, or a mix |
pipeline |
A multi-stage DAG with stages running in parallel |
structured-concurrency |
The runtime never leaks an unawaited durable child |
recovery |
Typed serialize/deserialize across the durability boundary |
retries |
Resonate retrying a flaky leaf function until it succeeds |
error-handling |
How a failure crosses the boundary and is re-raised |
detached |
Fire-and-forget invocations decoupled from the parent |
human-in-the-loop |
Suspending on a promise an external party resolves |
polling |
Non-blocking progress tracking with handle.done() |
rpc |
One worker dispatching to another by group |
versioning |
Running several versions of one function side by side |
saga |
Compensating completed steps when a later step fails |
pydantic |
Pydantic BaseModel (de)serialization at the durability boundary |
pydantic-ai |
Durable Pydantic AI agent runs |
resonate.ext.pydantic_ai makes Pydantic AI agent
runs durable: wrap any agent in ResonateAgent and every run() executes as a
durable workflow, with model requests and MCP server communication checkpointed
as durable steps that are served from the journal on recovery instead of
re-hitting the provider.
pip install "resonate-sdk[pydantic-ai]"from pydantic_ai import Agent
from resonate.ext.pydantic_ai import ResonateAgent
from resonate.resonate import Resonate
resonate = Resonate(url="http://localhost:8001")
agent = Agent(
"openai:gpt-5.2",
instructions="You're an expert in geography.",
name="geography",
)
durable_agent = ResonateAgent(agent, resonate)
async def main():
result = await durable_agent.run("What is the capital of Mexico?", id="capital-1")
print(result.output)The optional id is the run's durable identity: retrying with the same id
after a crash (or calling it twice) performs the work exactly once and returns
the recovered result. Function tools run inline in the workflow body and are
re-executed on replay; model and MCP steps are not. Because a crashed run
recovers from its serialized parameters, run-time arguments that cannot be
rebuilt from JSON (model, toolsets, output_type, callables) must be
configured on the agent at construction time. Provider-level retries are best
disabled in favor of Resonate's step retry policies (model_retry_policy /
mcp_retry_policy).

