AI Engineer Code 2025
Building Durable, Production-Ready Agents with OpenAI SDK and Temporal
Read the talk
Building Durable, Production-Ready Agents with OpenAI SDK and Temporal
A weather agent makes the durability problem concrete: Temporal preserves execution across worker failures, while the OpenAI Agents SDK supplies the agent loop.
From a talk by Cornelia Davis
Before you start: Basic Python, async functions, and familiarity with LLM tool calling will help you follow the code and execution walkthroughs.
Who controls the next step?
What changes when an LLM decides which operation an application should perform next? The application still runs across processes, networks, and external services, but its path is no longer fully prescribed in advance. Cornelia Davis approaches that problem from distributed systems: she is a developer advocate at Temporal and previously worked at Pivotal on Cloud Foundry, whose VMware origins included Linux containers, orchestration, and eventual consistency before Docker and Kubernetes. This workshop moves from the OpenAI Agents SDK to Temporal, then combines them through an integration the two teams developed together.
The demonstrations use changes Davis made that morning, presented from branches rather than their repositories’ main branches. For a gentler entry point, she also describes Jupyter notebooks runnable in GitHub Codespaces: first an SDK agent, then a Temporal application, then the integration. Here, the more advanced path exposes the execution machinery before replacing much of it with the SDK.
Agency means the model participates in control flow. In Davis’s definition, an application becomes agentic when the LLM chooses what happens next. The OpenAI Agents SDK, available in Python and TypeScript, supplies the runtime for that interaction. An Agent can begin with a name and instructions, leaving other settings at their defaults; Davis does not identify a particular default model.
Each Runner.run invocation starts an agentic loop. A minimal Python example has very little application code:
python
import asyncio
from agents import Agent, Runner
async def main() -> None:
agent = Agent(
name="Helpful agent",
instructions="Answer in haikus.",
)
result = await Runner.run(agent, "Explain what an agent does.")
print(result.final_output)
if __name__ == "__main__":
asyncio.run(main())
Tools, guardrails, and handoffs configure what that loop can do. When the model requests a tool, the runtime invokes it and feeds its result back into the next model call. The loop continues until it produces a final output, subject to the SDK’s completion rules and configured limits. That loop boundary matters later when composing several agents.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Put execution history outside the process
Temporal predates the generative-AI boom and addresses the distributed-systems problems that agents inherit. Davis lists Snapchat, Airbnb bookings, Pizza Hut and Taco Bell orders as adoption examples, then OpenAI Codex, OpenAI Image Gen, and Lovable. Her framing is a backing service, analogous in deployment shape to a database, Redis, or Kafka: application code uses a separate service to obtain a capability it should not have to rebuild. Here, that capability is durable execution.
The developer expresses the business path—call the model, invoke an API, feed the result back—while Temporal supplies mechanisms for retrying failures and recovering execution. Rate limits, temporary downstream outages, and application crashes are all relevant. An SDK runs alongside the business logic; functions designated as durable units of work have their outcomes recorded by the service.
Davis makes the token-cost implication concrete with an imagined crash on the 1,352nd model turn. Recorded, completed calls can be reused during recovery instead of purchased again. This guarantee depends on a completion having been recorded: a request whose outcome remains unknown may need another attempt. The later retry discussion explains why that distinction matters for both cost and side effects.
At the time of the workshop, Davis describes seven formally supported languages, a recently released Swift SDK from Apple, and experimental Clojure work. Temporal grew from a fork of Uber’s Cadence; most of the project uses MIT licensing, with some Apache-licensed material in the Java SDK. The underlying objective is the same across languages: keep the business path readable while moving recovery machinery into the platform.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Activities do work; workflows coordinate it
An activity encapsulates work that can fail independently, such as an external API call, or expensive work whose result should survive a restart. Withdrawing from one account and depositing into another are Davis’s initial examples. These SDKs do more than wrap REST endpoints: they participate in execution and recovery. The service also does more than store bytes; it coordinates workflow state transitions, task processing, and timers.
A workflow orchestrates activities into business logic. In Python, decorators identify both kinds of code. Davis mentions standalone activities as an emerging capability, but the workshop concentrates on the combination of activities and workflows. Retry policies sit at activity invocation boundaries: exponential backoff, bounded or unlimited retries, and maximum retry intervals can be configured without implementing a retry loop around every call.
| Mechanism | What it provides |
|---|---|
| Retry policy | Another attempt after an eligible failure |
| Task queues | Delivery of workflow and activity work to workers |
| Event history | Recorded execution facts used to reconstruct state |
Code that reads like a sequence of local function calls is therefore executing through a distributed system. Workers receive work through service-managed queues, and additional worker instances can increase processing capacity. Davis recounts an unnamed startup’s estimate that time spent on business logic rose from 25% to 75% after it replaced its Kafka-based approach with Temporal Cloud. That is a customer anecdote about engineering effort; Temporal Cloud supplies the backing service as SaaS.
Temporal records activity scheduling and results in an event history. After a worker disappears, workflow replay uses that history to reconstruct execution state and reach the unfinished work. This is event sourcing applied to execution: the durable record lives in the service, rather than only in the worker’s memory.
The first audience question exposes a boundary: what about streaming agents? At the workshop, native streaming was not available, although customers had built production streaming on top of Temporal. Davis identifies streaming and large-payload storage as priorities; the latter means passing references to large objects instead of copying their contents through every transition. The current integration README, inspected in August 2026, documents experimental Runner.run_streamed support while excluding Realtime agents. That is newer guidance than the demonstration’s streaming discussion.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Build the loop explicitly
The first demonstration runs entirely against a local Temporal service. After installing the CLI through Homebrew or the provided installation script, the startup command is:
bash
temporal server start-dev
The applications connect to localhost; Temporal Cloud is optional. Davis uses the Temporal AI Cookbook in the temporalio organization, with her extended agentic-loop example on a workshop branch.
This version calls the OpenAI Responses API directly, without the Agents SDK. Its two activity boundaries are a model invocation and a tool invocation. The model activity wraps an ordinary Responses request containing the model, instructions, user input, and tool descriptions, with API timeout configuration. Adding @activity.defn identifies the function as an activity; the request itself remains recognizable application code.
The workflow is a class whose @workflow.run method supplies its entry point. Classes also provide a place for annotated signal, update, and query handlers: signals and updates let callers interact with running workflows, while queries read their state. The core agent behavior here is a while True loop that executes the model activity, inspects its response, and decides whether to invoke a tool. The instructions ask the model to choose tools when useful and otherwise respond in haikus.
The example assumes one tool call at a time. When the model requests a function, the application must preserve both sides of that interaction in its conversation history:
- Append the model’s function-call request.
- Execute the requested tool.
- Append the tool result, associated with that call.
- Send the extended history back to the model.
There is no additional context-management strategy: the history simply grows. Saving only the result would omit the model request that explains why that result is present.
This bookkeeping also reveals a portability cost. Davis has another version targeting Gemini, whose conversation-history JSON differs from OpenAI’s. She deliberately avoids LiteLLM here because she wants to understand those formats directly rather than target a common abstraction. After parsing the response, the workflow dispatches the tool activity by the name the model returned.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Keep tool dispatch separate from the loop
The loop should not need rewriting whenever its toolset changes. Temporal’s dynamic activity supplies one part of that separation: an activity can be requested by name and resolved by a handler at runtime. A worker’s dynamic handler receives activity types that do not have a separately registered implementation. The agent’s loop can therefore dispatch the model’s chosen name without containing a branch for every possible tool.
Dynamic activity dispatch is different from dynamically loading tool definitions. In this demonstration, the Tools module loads when the application starts. It can be replaced without changing the agentic code, but there is no live registry and no hot-loading mechanism. With @activity.defn(dynamic=True), the generic invoker can handle weather or random-number activity names without registering each name separately with the worker.
Two functions in tools/__init__ form the module’s interface:
get_tools()returns the JSON descriptions supplied to the model so it knows which tools exist and what arguments they accept.get_handler(name)resolves a selected name to the Python function that performs the work.
The workflow uses the first interface when asking the model what to do. The dynamic activity uses the second when carrying out that decision. The generic orchestration and generic invoker consequently do not need embedded weather-specific logic.
For the weather tool, a Pydantic request model and metadata provide the material for generating its schema. The handwritten demo uses an internal helper because Davis did not have a public Responses API schema-generation helper available. That dependency is explicitly marked in the example; the later SDK integration removes it. Handler lookup is implemented with straightforward name checks, and replacing the tools module requires restarting the Python process.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Ask for weather, then kill the worker
A worker is the application process that polls task queues and executes registered workflows and activities. One worker can process multiple tasks concurrently. Davis describes deployments using substantial concurrency, including hundreds of threads, but thread counts and concurrency configuration depend on the SDK and workload; that description is not a sizing prescription. She runs the worker in one terminal and starts a workflow from another.
The first request asks whether California has weather alerts. The model selects get_weather_alerts, the tool returns advisories, and the model turns them into a response. The live result includes dense fog, wind, high surf, and beach hazards. In the Temporal UI, Davis reads the activity sequence from bottom to top: the initial LLM call, the dynamically dispatched activity under its actual get_weather_alerts name, and the subsequent LLM call that consumes the result. Generic dispatch has preserved a specific, inspectable execution history.
The next request asks for alerts at the user’s current location. Three tools are available, but their sequence is not programmed into the workflow. The model composes them:
- Get the current computer’s IP address.
- Resolve that IP address to a state.
- Retrieve weather alerts for that state.
The live run resolves to New York and returns no weather alerts. The distinction from the California request is the model’s choice of intermediate operations, visible as named activities in the same UI.
Davis repeats the location request and interrupts the only worker with Control+C. The first model call and the IP-address tool call have completed; the next model activity is unfinished. No agent process remains alive, yet the Temporal UI still shows the workflow and its pending work. She describes cutting the network as another possible experiment, with repeated attempts visible in the UI, but does not perform that experiment here.
Restarting the worker lets the workflow continue. Since the original process was killed, its memory cannot be the source of continuity. Temporal reconstructs workflow state from the recorded event history and resumes unfinished execution. The workflow’s lifetime has become independent of the worker’s lifetime.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Recovery still needs safe side effects
The same activity boundary can invoke another agent. An agent can be exposed as a tool, an activity can call it, or a parent can use a child workflow for another unit of orchestration. Davis distinguishes these available composition techniques from native A2A protocol support, which she says was not yet present at the workshop.
A more consequential question is whether retries require idempotent activities. They should be designed that way, but Temporal does not inspect their internals or enforce idempotence. If no response arrives, the runtime cannot know whether the request never reached the activity or whether the downstream operation succeeded and its acknowledgment was lost. Retrying can therefore repeat a side effect. Replay of a recorded result and retry of an unknown outcome are different operations; the developer remains responsible for making the latter safe.
The onstage estimate for activity-service latency is tens of milliseconds, depending on server location. No measurement boundary or percentile is supplied. Davis considers that overhead acceptable for many agents running over minutes, hours, or days, especially when they also wait for human input, while acknowledging that some latency-sensitive applications may not fit.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Replace the manual loop with the Agents SDK
The second implementation keeps the same external services but changes who implements the loop. OpenAI’s models and API supply inference; Temporal supplies durable execution; the Agents SDK sits above them with agents, tools, guardrails, and tracing. Davis introduces SDK tracing as part of this integration, while the walkthrough’s visible execution evidence is the Temporal UI and console logs.
The same weather and location functions remain activities. Their signatures and docstrings describe their arguments, and the public activity_as_tool adapter turns each activity into an SDK tool with timeout configuration. The adapter both constructs the tool description and schedules a Temporal activity when invoked; the underlying activity must be registered with a worker. Enabling the integration plugin is also necessary—the Agents SDK does not use Temporal simply because it is installed.
The activity implementations become smaller. get_weather_alerts retains its activity decorator and National Weather Service call, but loses the custom schema-generation code. The location functions likewise need their activity decorators, arguments, and docstrings. In the workflow, an Agent receives those adapted tools and Runner.run replaces the handwritten model/tool loop.
| Responsibility | Handwritten version | Integrated version |
|---|---|---|
| Agent loop | Workflow’s explicit loop | SDK runner inside a workflow |
| Tool schema | Custom helper | activity_as_tool |
| Tool execution | Dynamic activity invoker | Adapted, registered activities |
| Model-call durability | Explicit model activity | Integration runner and plugin |
The important boundary remains the workflow. A plain in-memory SDK application would lose this demonstration’s execution state when its process died unless the application supplied its own recovery mechanism. Likewise, plain SDK deployments need their own instance management and work distribution; Temporal supplies queued execution that multiple workers can consume.
The worker configures the OpenAI Agents plugin, including retry behavior for model calls. This answers a gap in the shorter workflow: there is no longer a user-written activity for invoking the LLM, but those calls still need durable boundaries. Davis explains that OpenAI made the runner extensible through an abstract runner class, and Temporal provides its own implementation. The integration therefore reaches inside the agent loop to make model calls durable, alongside the activity-backed tools.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Run the same failure test, then wait for a human
With the integrated version, one terminal again runs the worker and another starts the workflow. The California query produces model and National Weather Service calls in the logs. A Tools Workflow appears in Temporal’s UI with the same activity-level visibility as the manual implementation. The current-location query again resolves to New York with no alerts. Davis interrupts another run with Control+C, and the workflow subsequently continues: replacing the loop implementation has retained its durability.
This leads to Davis’s broader programming model: treat a process as a logical execution, while Temporal maps that execution onto physical workers. Ordinarily, developers must reason about what happens when work moves between processes or when a process vanishes. Durable execution separates the business operation’s continuity from those placement decisions.
Human-in-the-loop applications make that separation especially useful. An agent may work briefly, then wait hours or days for approval. It should not need a dedicated process to remain alive throughout that wait. Davis describes waiting workflow state leaving active memory and eventually the worker cache; the exact eviction behavior is implementation-dependent. When input arrives, persisted history can reconstruct the state and continue execution, even weeks later. The same recovery model handles both an unexpected crash and an ordinary eviction of inactive work.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Separate runs and handoffs have different boundaries
Small, focused agents can be assembled into a larger workflow. Davis sketches triage, clarification, human input, instruction generation, and planning stages, followed by parallel work and result processing. Code can express fan-out and join, process results as they arrive, wait, or loop. In Temporal, that orchestration must remain deterministic and workflow-safe; external I/O belongs in activities. The outer structure can be fixed even when the model chooses the operations inside an individual agent loop.
Davis calls these focused components micro-agents, drawing on microservices and Unix’s preference for components that do one thing well. The first composition method is ordinary code: await one Runner.run, then pass its output into the next. For example, two focused agents can separate planning from writing:
python
from agents import Agent, Runner
async def plan_then_write(request: str) -> str:
planner = Agent(
name="Planner",
instructions="Produce a concise plan for answering the request.",
)
writer = Agent(
name="Writer",
instructions="Write an answer using the supplied plan.",
)
plan = await Runner.run(planner, request)
answer = await Runner.run(writer, str(plan.final_output))
return str(answer.final_output)
These are separate runner invocations. The surrounding code controls their ordering and could also introduce parallelism or iteration.
The second method is an SDK handoff. An agent definition lists other agents as possible handoff targets, each with its own instructions and potentially its own tools. Unlike starting another run in application code, a handoff changes the active agent and input within the existing runner loop. Davis describes this as changing the loop’s context or persona.
| Composition | What changes | Loop boundary |
|---|---|---|
| Code calls another agent | The application passes a result into another run | Separate Runner.run calls |
| SDK handoff | The active agent and its context change | The existing run continues |
A triage agent might route a question about the current temperature to a weather agent and a question about Costco’s opening hours to a local-information agent. The handoff changes which specialist handles the request. Davis confirms that the Temporal integration is handoff-aware, but does not give a live handoff demonstration.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Where the examples lead next
The Temporal Python SDK’s contrib directory contains the OpenAI Agents SDK integration and samples. Davis also points back to the AI Cookbook: her agentic-loop recipe was awaiting formal approval on its branch despite a favorable review, and the cookbook included an Agents SDK recipe. Those workshop branches explain the code shown in the recording; the repositories’ current main branches may have moved on.
The integration is part of a broader effort to add durable execution beneath agent frameworks. Davis points to an explanatory Temporal blog and says Pydantic subsequently built its own Temporal integration. She also mentions several other integrations in progress without naming them or committing to a precise count.
For deeper examples, Davis directs viewers to Temporal’s human-in-the-loop material and an advanced MCP demonstration: a durable MCP server supporting sampling and elicitation. She asks workshop attendees to suggest follow-up topics, then promotes Replay at Moscone in May. The conference slide includes Samuel Colvin and representatives from Replit and NVIDIA, alongside a workshop-specific registration discount. These are the recording’s community invitations, rather than current offers.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Persistence, stopping, and operational ownership
The final questions distinguish killing a worker from losing the backing service’s storage. A local development server can be configured for disk persistence, such as SQLite; Davis normally uses disposable local state. Self-hosted Temporal supports relational databases and Cassandra. Temporal Cloud adds managed persistence and multi-region namespaces. Davis describes an AWS footprint of 15 regions and tentatively estimates four to six Google Cloud regions at the workshop; these are historical availability figures, not deployment guidance. Self-hosting remains an explicit option.
Stopping a workflow is also different from stopping its worker. An asynchronous start returns a handle through which a caller can request that execution stop. Normal completion is simpler: application logic determines that the task is done and returns from the workflow. Durable execution preserves unfinished work; it does not remove the application’s responsibility to define completion.
A workflow need not represent a single short request. An entity workflow can represent a long-lived object—a digital twin of a loyalty customer, for example. Each checkout QR scan sends a signal into that customer’s workflow. It processes the event and returns to waiting, potentially over a lifetime measured in years. Waiting does not require continuous worker execution, although the durable history and state still occupy storage.
Durability also does not restart or monitor the customer’s infrastructure by itself. Asked about incident-management integration for a failed worker, Davis says customers build those integrations; she knows of no native Slack connector and is unsure about Cloud-specific options. Kubernetes alerts, dashboards, and autoscaling can handle the worker deployment. Temporal Cloud hosts the service, while customers host their workloads and workers. Hosted workers are mentioned only as an idea, with no roadmap commitment at the time.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
What the available examples do—and do not—cover
The remaining questions test how far the demonstrated pattern has spread. For voice agents, Davis cannot name an example offhand; another speaker describes experimentation, including internal work, but knows of no deployed examples. Asked about Claude Agent SDK templates, Davis says she has no Claude example available yet and that her Gemini example is almost complete. She welcomes contributions to the MIT-licensed cookbook. These answers describe the workshop’s available examples, not present-day provider support.
For Excel and PDF extraction, Davis likewise has no personal example to offer. She distinguishes the cookbook’s rigorously reviewed recipes from Code Exchange entries, which usually lead to community-owned GitHub repositories. She tentatively estimates that collection at 20–40 examples but does not confirm that it contains an extraction implementation. The useful distinction is ownership and review: a community example may be a starting point, while cookbook recipes are explicitly reviewed to demonstrate best practices.
The workshop closes with an invitation to help build the remaining capabilities: engineers interested in Temporal’s AI work are directed to Johan, and prospective developer advocates to Davis. The open questions are concrete extensions of the demonstrated system—new agent interfaces, new providers, and examples that preserve durable execution while doing more useful work.
Suggest correction
This note stays in this page until you copy or download it. Nothing is submitted; reloading clears the draft.
Resources
From the talk
Runnable examples of durable agent loops, tool calling, human approval, and MCP servers.
Integration setup and examples for running agents in workflows and exposing activities as tools, with current feature limitations.
Further reading
OpenAI's reference for runner execution, tool calls, handoffs, completion, and run configuration.
Explains why retryable activities need idempotency and shows patterns for preventing duplicate side effects.
Official guidance for adding Temporal-backed durable execution to Pydantic agents.
Read the transcript and explore slides
Loading transcript and slides…