Add Durable Session Storage to the OpenAI Agents SDK
August 18, 2026

Most of what makes an agent feel intelligent is that it seems to be paying attention. It holds the thread of a conversation, remembers what you asked it for, reaches for a tool when it needs one, and builds on what came before instead of starting cold. That sense of continuity is most of the distance between an assistant and a glorified search box, and while you are building it feels almost free. The OpenAI Agents SDK gives you an in-memory session store, MemorySession, that keeps the entire conversation in your application’s memory, so every run picks up right where the last one left off.
The catch is hiding in that word. Everything the agent knows about a user lives inside a running process, so it lasts exactly as long as that process does. The SDK is candid about this (its docs describe MemorySession as intended for local development), and the limit is structural: deploy a new version, spread the load across a second instance, or let a serverless function go cold, and that history evaporates along with it. The agent that felt so attentive while you were building it becomes forgetful the moment real traffic arrives, meeting each returning user as though they had never spoken before.
The obvious answer is to write the conversation somewhere permanent, and that is where a lot of first attempts quietly come apart. A run does not hand back a tidy transcript of who said what. It hands back the whole record of how the agent got to its answer: the messages, certainly, but also the tools it called, the results those calls returned, and the points where it passed control to another agent. Flatten all of that into plain text and you have kept the words while throwing away the work, and the SDK will refuse to replay the thinned-out history you try to feed it.
This post follows a small example you can clone and run that keeps that record intact from one end to the other: the history loads before the run, the agent runs exactly as it already does, and only the new items from each turn are saved, tool calls and all. Those runs live in DialogueDB. It’s a conversation database, so the complete structured history of each run stays intact instead of flattened into text. Once that history is durable, three things an in-memory session could never offer come for free: memory that follows a user from one session into the next, search across past conversations by meaning rather than exact wording, and shared context that travels on its own when one agent hands off to another.
What durable session storage adds to an agent
The integration is a round trip with three legs. Before the run, the stored dialogue is loaded and mapped back into run items. The run itself happens exactly as it does today. After it resolves, the items it added are written back. Every leg is explicit, and nothing about the agent, its tools, or its handoffs changes.
How an agent turn round-trips
Persisting a turn
Seeding the next turn
MemorySession keeps these items inside the process that created them, so a restart wipes the conversation. Persisted in DialogueDB, the same items seed the next run in whichever process picks it up, tool calls included.
A restart clears whatever MemorySession was holding, but with the items in DialogueDB the next turn rebuilds the same history in whichever process handles it, so the agent continues instead of starting over. And because those runs now live in DialogueDB, three more capabilities come from the same data:
- Memory that carries between sessions: Keep facts about a user separate from the message list and recall them on the next visit.
- Search across past runs by meaning: Find where a topic came up without matching exact words, and without running a vector database.
- Per-user isolation: Every call carries a namespace, so one user’s agent never reads another user’s history.
How the integration works
The integration is deliberately explicit: a load before run() and a save after, wired around the call rather than tucked inside a drop-in Session class. That is the design, and it pays off twice. The wiring is a handful of lines you can read in one sitting, and it leaves you in full control of exactly what gets stored and when.
The load and the save meet at one mapping problem. An AgentInputItem is a large discriminated union, and only the three message variants carry a role. Tool calls, tool results, and reasoning items have none. Flatten items to { role, content: string } and every tool call is silently destroyed. Worse, the assistant message variant is rejected on reload, because its content must be an array and its status field is required. The fix is to store each item verbatim as structured content and let the SDK’s own schema validate it on the way back:
// persist.ts
import { protocol, type AgentInputItem } from '@openai/agents'
export const toStoredMessages = (items: AgentInputItem[]) =>
items.map((item) => ({
role: 'role' in item ? item.role : 'assistant', // storage label only; replay never reads it
content: item, // stored verbatim, so tool calls survive
}))
export const fromStoredMessages = (rows: readonly { content: unknown }[]) =>
rows.map((row) => protocol.ModelItem.parse(row.content))
DialogueDB’s message content accepts a structured object as readily as a string, which is what makes the verbatim store possible: no stringify, no lossy re-parse, no type assertion on the way out. protocol.ModelItem.parse is the SDK’s own Zod schema, so anything that comes back is exactly what run() expects.
With the mapper in place, one function runs a fully persisted turn. See how each step maps to the code that runs it:
Hover a step to see the lines it maps to.
Streaming changes nothing structural. A streamed run is run(agent, input, { stream: true }). Forward the events to your UI, wait for stream.completed, and persist the same slice afterward:
const stream = await run(agent, [...history, userMessage], { stream: true })
// forward stream events to your UI as they arrive
await stream.completed
await dialogue.saveMessages(toStoredMessages(stream.history.slice(history.length)))
See the full OpenAI Agents SDK integration
The whole pattern step by step: sessions, memory, semantic search, handoffs, and exactly what DialogueDB sees.
See the integrationWhy a conversation database
The example stores its runs in DialogueDB, a conversation database: one client that persists structured message history and, over that same data, provides memory, semantic search, threading, and per-user isolation, all scoped by a namespace and one API key. The point is not that agent items need a special store. It is that the moment runs are durable, the next three feature requests (remember this user, find that past conversation, keep tenants apart) are already served by the data you just persisted.
Memory in DialogueDB is caller-controlled. No background process reads transcripts and guesses at facts. The application calls createMemory with the value worth keeping, and searchMemories recalls it by meaning on a later turn, which is exactly what step 2 of the walkthrough above does. Multi-Agent Memory Architecture covers how to decide what each agent should be allowed to recall.
Handoffs are where the namespace model earns its place. The Agents SDK moves control between agents cleanly, but the receiving agent still needs the context of turns it was not present for. When every agent in the app loads and saves against the same namespace, a handoff target starts with the full dialogue history and the same memories as the agent that handed off. There is no first-class handoff API in DialogueDB, and none is needed. Shared context falls out of shared storage.
What a handoff inherits
namespace: "user_123"
Dialogue history
every message and tool call
Memories
facts saved across sessions
Both agents load and save against the same namespace, so the billing agent starts with the full history and the same memories. The handoff carries context because the storage is shared, not because either agent forwarded it.
Roll-your-own vs DialogueDB for agent sessions
None of this requires DialogueDB. The SDK’s sessions guide points at OpenAIConversationsSession for hosted persistence, and plenty of teams write agent items into SQLite or Postgres themselves. The storage is the easy half. The difference is how much sits around the storage before the result matches what the example already does:
| You handle | SQLite or roll-your-own | DialogueDB |
|---|---|---|
| Item schema and migrations | Design and maintain | Provided |
| Structured items (tool calls) | Serialize and rebuild | Stored as structured content |
| Redeploys and multiple instances | A database server to run | Managed |
| Per-user isolation | Query code on every read | namespace on every call |
| Memory across sessions | A second store to build | Same client |
| Search past runs by meaning | Add and sync a vector store | Built in |
A SQLite file is a fine local start, and it fails for the same reason MemorySession does: serverless filesystems are ephemeral, and two instances never share a disk. Postgres fixes durability and adds a schema to design, a message format to keep stable, and a vector layer to bolt on once search comes up. DialogueDB is managed, so the storage runs and scales without a server for you to keep healthy, and messages, memory, and search arrive as one integration. For the wider survey of the options, Where Should Your Agent’s Memory Live? compares storage backends, and Conversation Persistence in TypeScript AI Agent Frameworks traces this same message-shape problem across SDKs.
Frequently asked questions
Run the example
Clone the example, add your DialogueDB and OpenAI keys, and start it:
git clone https://github.com/dialoguedb/examples
cd examples/openai-agents-sdk
npm install
cp .env.example .env
npm start
The run plays out in three acts: turn one makes two real tool calls, a cold reload prints the function_call and function_call_result items coming back from DialogueDB intact, and turn two answers from that reloaded history plus a recalled memory. From there, the same persist.ts and turn() drop into your own app. The integration page walks through the whole pattern, and the quickstart gets you an API key.
Ready to Build Better Conversations?
Get started with DialogueDB in minutes. Free tier included.
Get Your API Key