Integrations
Everything imports from one place:
from pyrula.agents.integrations import ( from_langchain_tool, durable_chat_model, as_langgraph_node,)PyrulaDurability needs pip install "pyrula[agents,pydantic-ai]" and is imported on its own:
from pyrula.agents.integrations import PyrulaDurabilityPatterns
Section titled “Patterns”- Tool adapters:
from_langchain_toolwraps a LangChain tool as a Pyrula@tool, anddurable_chat_modeljournals a LangChain chat model. See LangChain tools. - Durable nodes:
as_langgraph_nodemakes one node of an existing graph a journaled, resumable Pyrula run. See LangGraph. - Tool servers:
build_mcp_serverserves Pyrula tools to Claude Code, Cursor or any MCP host. See Serving tools over MCP; Calling MCP servers is the other direction. - Capabilities:
PyrulaDurabilityjournals a Pydantic AI agent’s model and tool calls. See Pydantic AI. - Recipes: the OpenAI Agents SDK runs on the journaled
AsyncOpenAIwith no adapter. See OpenAI Agents SDK.
Which side owns the journal decides what is durable: What’s durable, and what isn’t.
| Integration | Install |
|---|---|
| LangChain tools | pip install "pyrula[agents,langchain]" |
| LangGraph | pip install "pyrula[agents,langgraph]" |
| Pydantic AI | pip install "pyrula[agents,pydantic-ai]" |
| OpenAI Agents SDK | pip install "pyrula[agents]" openai-agents (in an environment without pyrula[litellm]) |
| MCP tool server and MCP client | pip install "pyrula[agents]" |
Custom integrations
Section titled “Custom integrations”run_and_await submits a workflow idempotently and waits for its result, for frameworks without an adapter:
from pyrula.workflows import run_and_await
result = await run_and_await( store, "charge_order", {"order": "o-1"}, idempotency_key="charge:o-1", # becomes the run id; a retry re-attaches timeout=60,)It raises DurableInvocationError if the run fails, DurableInvocationTimeout past timeout, and DurableInvocationCancelled if cancelled. A worker serving the workflow must be running.
Pass on_interrupt= to answer a ctx.interrupt from the caller. Without it the interrupt surfaces as DurableInterrupt. The handler’s return value is the answer, or InterruptResponse(action, value) with action one of approve, reject, respond or edit (anything else raises ValueError). A handler that raises or returns a bad action ends the run with interrupt_handler_failed; raise DurableInterrupt to leave the run suspended. On an approval interrupt (expects="approval") the bare string "approve" or "reject" is shorthand, and any other return value is a respond answer. To respond with the literal text “approve”, return InterruptResponse("respond", "approve").