Skip to main content

⚡ pydantic-ai-durable

Crash-proof, stateful, and malleable agents for PydanticAI.
Complete Python implementation of Earendil's @earendil-works/pi-durable architecture.


🧠 The Problem vs The Fix

Without pydantic-ai-durable 💥 With pydantic-ai-durable 🛡️
Process restarts or crashes? Everything is lost. Turns auto-resume from their last checkpoint.
Crashes during a tool call? Retrying double-charges cards. Replay safety: Safe tools re-fetch; mutating tools never duplicate.
Exploring "what-if" speculations? Re-run entire history. Fork instantly in O(1) without copying database rows.
Distributed multi-step task fails? Dirty resources leaked. Durable Sagas: Automatic reverse rollback compensation.
AI wants to run a risky action? Hard to intercept cleanly. Approval Gates: Pauses run for human review & argument override.

🚀 30-Second Quickstart

uv pip install pydantic-ai-durable   # or: pip install pydantic-ai-durable

# Latest from GitHub (works before/without a PyPI release)
uv pip install "git+https://github.com/matheusbuniotto/pydantic-ai-durable"

Both package imports work interchangeably:

import pydantic_ai_durable as pad
# or
import pya_durable as pad

Complete Bootstrap in 15 Lines

import asyncio
from pydantic_ai_durable import (
    Harness,
    SqliteStorage,
    create_models,
    openrouter_provider,
)

async def main():
    # 1. Pluggable Auth & Providers (OpenRouter, OpenAI, Anthropic, Gemini, Groq, Ollama)
    models = create_models()
    models.set_provider(openrouter_provider(api_key="sk-or-..."))
    # or models.set_provider("openai", api_key="sk-...")

    # 2. Open durable harness over SQLite WAL storage
    harness = await Harness.open(SqliteStorage("agent.db"), models=models)

    # 3. Resume any unfinished work from previous runs
    await harness.resume()

    # 4. Open root channel (shortcuts like "gpt-luna 6", dict specs, or Agent instances)
    root = await harness.root({"agent": {"model": "openrouter:openai/gpt-6-luna"}})

    # 5. Submit with idempotent requestId (safe against network retries)
    result = await root.submit({"content": "Audit cluster health", "requestId": "job-001"})
    print("Agent:", result.output)

    await harness.close()

asyncio.run(main())

🎯 Cheat Sheet: Which Feature Do I Need?

If you want to... Use this What it does
Prevent double-charging on crash @durable_tool(replay="unsafe") Halts duplicate runs; preserves partial output
Auto-rerun safe lookups on crash @durable_tool(replay="safe") Re-runs read-only tools without user prompt
Ask a human before dropping a table @approval_gate Pauses turn, generates challenge token
Roll back multi-step cloud steps @define_saga Fan-out child tasks + reverse rollback
Branch into a "what-if" experiment conv.fork() Zero-copy transcript DAG off any message
Inject mid-turn steering message harness.steer("...") Prioritizes instructions in next step
Build a live dashboard or TUI harness.view_state() Emits real-time reactive transcript & tasks
Store shared typed state define_doc(...) Atomic document state alongside transcript

🧱 Core Patterns (Copy & Paste)

1. 🛡️ Durable Tools: Safe Replay vs Unsafe Mutating Guard

Declare tool replay safety so crashes never cause accidental side-effects:

from pydantic_ai_durable import durable_tool, set_tool_partial_output

# SAFE: Read-only operations automatically rerun on crash recovery
@durable_tool(replay="safe")
async def search_database(query: str) -> str:
    return f"Results for: {query}"

# UNSAFE: Mutating calls are NEVER repeated automatically
@durable_tool(replay="unsafe")
async def charge_customer(amount: int, customer_id: str) -> str:
    set_tool_partial_output("Connecting to payment gateway...")
    # charge logic...
    return f"Charged ${amount} to {customer_id}"

2. ⏸️ Human Approval Gates & Argument Overrides

Pause turn execution for sensitive operations and wait for human operator intervention:

from pydantic_ai_durable import approval_gate, DurableHarness

# Intercept sensitive actions and generate challenge token
@approval_gate(description="Approve database migration on production")
async def drop_production_table(table_name: str) -> str:
    return f"Dropped {table_name}"

# The agent turn pauses with TaskStatus.WAITING ("waiting_approval").
# External operator reviews challenge token and approves (with optional argument rewrite):
await harness.resolve_approval(
    token=challenge_token,
    approved=True,
    override_args={"table_name": "users_backup"},  # Redirect to safe table
)

3. 🔁 Durable Task Sagas with Reverse Compensation

Orchestrate complex distributed workflows with parallel fan-out and automatic reverse rollback:

from pydantic_ai_durable import define_saga, SagaContext, SagaResult, SagaPolicy

@define_saga(name="cloud.provision_cluster", initial_step="reserve_nodes")
class ProvisionClusterSaga:
    async def reserve_nodes(self, ctx: SagaContext) -> SagaResult:
        # Parallel fan-out across multiple child tasks
        w1 = await ctx.spawn_child("cloud.reserve", {"type": "gpu-h100"})
        w2 = await ctx.spawn_child("cloud.reserve", {"type": "cpu-m6i"})
        
        # Await with fail-fast cascade
        await ctx.wait_for_children(policy=SagaPolicy.FAIL_FAST)
        return ctx.transition_to("configure_network")

    async def configure_network(self, ctx: SagaContext) -> SagaResult:
        # If this step raises an error, compensate_reserve_nodes runs automatically!
        ctx.state["cluster_ready"] = True
        return ctx.complete(result="Cluster active")

    async def compensate_reserve_nodes(self, ctx: SagaContext) -> None:
        # Releases reserved compute instances bottom-up
        print("Rollback: Releasing reserved nodes...")

4. 🌲 Zero-Copy Transcript Branching (Forking)

Branch a conversation off any historical message in O(1) time without duplicating database rows:

# Fork off specific answer entry
thread = await conv.fork(
    entry_id=entry.entry_id,
    new_conversation_id="conv-speculation-1",
)

# Both proceed concurrently with zero interference
await asyncio.gather(
    conv.run("Proceed with standard rollout"),
    thread.run("What happens if we roll back immediately?"),
)

5. 👥 Real-Time Multiplayer Streaming & View State

Connect TUIs, web dashboards, and mobile clients with reactive state subscriptions:

# Obtain reactive view state observable
view = await harness.view_state()
print("Current turn count:", len(view.value.transcript))

# Subscribe to real-time incremental updates
unsubscribe = view.subscribe(lambda state: print("Updated active tasks:", state.active_tasks))

# Or consume high-level agent event stream
async for event in harness.watch_events():
    print(f"[{event.event_type}] {event.payload}")

6. 🗄️ Pluggable Storage Backends

Switch backends effortlessly based on your deployment tier:

from pydantic_ai_durable import SqliteStorage, MemoryStorage, JsonlStorage

# 1. Production SQLite with WAL mode (Recommended default)
storage = SqliteStorage("agent.sqlite")

# 2. Zero-disk in-memory storage for lightning-fast tests
test_storage = MemoryStorage()

# 3. Append-only JSONL ledgers for streaming audit logs
audit_storage = JsonlStorage("audit_trail.jsonl")

🏛️ Architecture

                      ┌────────────────────────────────────────────────────────┐
                      │                     DURABLE HARNESS                    │
                      │  Storage: SQLite WAL / In-Memory / Append-Only JSONL   │
                      │  Extension Registry: Tools, Hooks, Prompt Sections     │
                      │  Multiplayer Hub: Presence, View State, Event Stream   │
                      └───────────────────────────┬────────────────────────────┘
                                                  │
                      ┌───────────────────────────┴────────────────────────────┐
                      │                                                        │
             ┌────────▼────────┐                                      ┌────────▼────────┐
             │  Conversation 1 │ (Root Channel)                       │  Conversation 2 │ (Fork / Thread)
             │  - Agent Config │                                      │  - Shared Tree  │ (O(1) Branching)
             │  - Transcript   │ (Parent-pointer DAG)                 │  - Sandbox Env  │
             └────────┬────────┘                                      └─────────────────┘
                      │
            ┌─────────┴──────────────────────────────────────┐
            │ Tasks & Ownership Hierarchy                    │
            ├───────────────────────┬────────────────────────┤
            │ Model Turn Task       │ Durable Replay Tool    │
            │ Multi-Step Saga Task  │ Child Subagent Run     │
            │ Background Compaction │ Human Approval Gate    │
            └───────────────────────┴────────────────────────┘

⚖️ Full Pi Durable Parity Table

pydantic-ai-durable brings 100% of Earendil's TypeScript framework to Python and PydanticAI:

Architectural Pillar Pi Durable (TypeScript) PyA Durable (Python) Status
Model Providers & Auth createModels() + setProvider() create_models() + *_provider() Complete
Harness Bootstrap Harness.open() + resume() Harness.open() + resume() Complete
Storage Engine Node SQLite WAL / DO SqliteStorage (WAL mode) Complete
Zero-Disk Storage MemoryStorage MemoryStorage Complete
Append-Only Ledger JsonlStorage JsonlStorage Complete
Turn Execution Loop Durable Engine Loop DurableStepEngine on Agent.iter() Complete
Replay Policies safe / unsafe @durable_tool(replay="safe") Complete
Hierarchical Tasks defineTask + ownership define_task + TaskRunner Complete
Task Trees & Aborts Bottom-up cascade abort_task bottom-up cascade Complete
Subagents Tool-owned conversations create_handoff_tool / Subagents Complete
Branching (Forking) Non-copying parent DAG DurableConversation.fork() Complete
Active Steering whenBusy: "steer" when_busy="steer" + SteeringQueue Complete
Context Compaction Background summarization BackgroundCompactor + search_history Complete
Extensions Registry defineExtension define_extension + ExtensionRegistry Complete
Prompt Sections section(...) dynamic eval define_section(...) dynamic eval Complete
Tool Wrapping & Hooks wrapTool, beforeTool, afterTool wrap_tool, LifecycleHook Complete
Execution Envs NodeExecutionEnv + cwd LocalExecutionEnv, IsolatedSandboxEnv Complete
Multiplayer Sync viewState + watchEvents ReactiveViewState + watch_events Complete
Durable Documents defineDoc (scope="conversation") define_doc + ReactiveDocumentState Complete
Human Approvals Challenge tokens @approval_gate + ApprovalManager Complete
Durable Sagas Parallel wait + abort rollback define_saga + reverse compensation Complete

🧪 Testing & Verification

Every pull request is verified across unit test suites, and real AI provider evaluations:

# Run 148 unit & behavioral tests
uv run pytest

# Run the 9 core behavioral evaluations (high-fidelity fallback)
uv run python evals/run_evals.py

# Run evaluations with OpenRouter & GPT-6 Luna
OPENROUTER_API_KEY=sk-or-v1-... uv run python evals/run_evals.py

# Or specify model shortcuts directly
uv run python evals/run_evals.py --model "gpt-luna 6"

License

MIT License. Designed with human-first clarity, pragmatism, and high focus.

Metadata

Release files for pydantic-ai-durable 0.1.0

For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.

Source distribution (sdist)

Source distribution for pydantic-ai-durable 0.1.0
File Size Uploaded Provenance
pydantic_ai_durable-0.1.0.tar.gz 304.7 kB Without provenance Details

Built distribution (wheel)

Table of built distributions (wheels) for pydantic-ai-durable 0.1.0
File Interpreter ABI Platform
pydantic_ai_durable-0.1.0-py3-none-any.whl Python 3 none any Details

Total release size: 383.0 kB

Release files / pydantic_ai_durable-0.1.0.tar.gz

Download URL pydantic_ai_durable-0.1.0.tar.gz
Size 304.7 kB
Tags Source
SHA-256 checksum
How to use checksums
85e8c37ee48c7a8a18bb2e9529214127ef3641046d51ee0a6ae0180509abb3f1
BLAKE2b-256 checksum
How to use checksums
587ca737efcb0bd39812e9a5c2881ceebb0f410d912cffd26b2e744381330102
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Release files / pydantic_ai_durable-0.1.0-py3-none-any.whl

Download URL pydantic_ai_durable-0.1.0-py3-none-any.whl
Size 78.3 kB
Tags Python 3
SHA-256 checksum
How to use checksums
37fd85ddb87c6bd0e904aeb72b3a46081b789eba383e2fc69fb44c487fef155e
BLAKE2b-256 checksum
How to use checksums
a9c61da2e03e0148da21ab7177b153910d17893debd5db3a4fef6a39307ea5ed
Upload date
Uploaded using Trusted Publishing?
What is trusted publishing?
Yes
Uploaded via uv/0.13.5 {"installer":{"name":"uv","version":"0.13.5","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}

Security for pydantic-ai-durable 0.1.0

Provenance

Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.

No provenance found

None of the files in this release were uploaded with provenance metadata.

A note on package security

PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.

To learn more about protecting yourself from malicious actors, visit our package security documentation.

Release history Release notifications | RSS feed

This release

0.1.0 This release

2 release files No provenance

Anthropic, PBC Visionary sponsor Bloomberg Visionary sponsor Hudson River Trading Visionary sponsor Meta Visionary sponsor NVIDIA Visionary sponsor Microsoft Sustainability sponsor Depot Continuous Integration AWS Cloud computing and Security Sponsor Datadog Monitoring Fastly CDN Google Download Analytics Sentry Error logging StatusPage Status page