Introducing Durable Execution
How we use Temporal to make Open Xtract extractions survive failures automatically.
Extracting data from a single PDF is fast. Extracting data from ten thousand of them is a different problem entirely. When you're processing documents at scale, things fail — network timeouts, model API rate limits, container restarts. The question isn't whether something will go wrong, it's what happens when it does.
That's why we added durable execution to Open Xtract.
The Problem with Long-Running Extractions
A standard extraction with Open Xtract is simple:
from pydantic import BaseModel
from open_xtract import extract
class PdfInfo(BaseModel):
summary: str
language: str
result = extract(
schema=PdfInfo,
model="anthropic:claude-sonnet-4-5",
url="https://example.com/document.pdf",
instructions="return a 2 sentence summary and the primary language of the document",
)
This works great for single documents. But when you're running extractions that take minutes or hours — processing large batches, handling big files, or chaining multiple extraction steps — a transient failure means starting over from scratch.
In enterprise environments, "just retry the whole thing" isn't acceptable. You need extractions that can pick up where they left off.
Temporal-Backed Durability
We chose Temporal as the backbone for durable execution. Temporal is a workflow orchestration engine that persists the state of every step in a workflow. If a process crashes mid-execution, it resumes from the last completed step — not from the beginning.
Enabling it is a single flag:
result = extract(
schema=PdfInfo,
model="anthropic:claude-sonnet-4-5",
url="https://example.com/document.pdf",
instructions="Extract document info",
durable=True,
)
When durable=True, Open Xtract:
- Starts a Temporal server automatically via Docker
- Persists workflow state in PostgreSQL so nothing is lost if the process dies
- Retries failed steps automatically with backoff, without re-running steps that already succeeded
- Recovers from crashes by resuming from the last checkpoint
No manual orchestration. No custom retry logic. No state management code.
Visibility into What's Running
Debugging a batch extraction that failed somewhere in the middle is painful without visibility. Temporal ships with a web UI, and Open Xtract exposes it with a single flag:
result = extract(
schema=PdfInfo,
model="anthropic:claude-sonnet-4-5",
url="https://example.com/document.pdf",
instructions="Extract document info",
durable=True,
temporal_ui=True, # starts UI on http://localhost:8080
)
The UI shows every workflow execution, its current state, which steps completed, which failed, and why. When something goes wrong at 3 AM, you don't have to grep through logs to figure out what happened.
When to Use It
Durable execution adds infrastructure overhead — it spins up Docker containers running Temporal and PostgreSQL. For a single quick extraction, that's unnecessary.
Use durable=True when:
- Processing large batches where partial failure shouldn't require a full restart
- Handling expensive extractions where re-running completed work wastes time and API credits
- Running in production pipelines where reliability matters more than simplicity
- Chaining extraction steps where later steps depend on earlier results
For one-off extractions and development, the default non-durable mode is the right choice.
Cleaning Up
When you're done with durable extractions, clean up the Docker containers:
from open_xtract import stop_temporal
stop_temporal()
Why This Matters
Most extraction tools treat reliability as someone else's problem. You're expected to build your own retry logic, your own state management, your own recovery mechanisms.
We think that's backwards. If the tool knows it's running a long extraction, it should handle failure recovery itself. Durable execution in Open Xtract means you write the same simple extraction code and let the framework worry about what happens when things go wrong.
Install the Temporal extra and try it out:
uv add open-xtract[temporal]
Check the docs for the full guide, or browse the source on GitHub.