By Sagar Shankaran, Founder of CallSphere
Use the OpenAI Conversations API with conversations.create, previous_response_id chaining, and auto_previous_response_id for server-side history management in AI agents.
Key takeaways
The OpenAI Agents SDK supports two fundamentally different approaches to managing conversation history:
Client-side sessions: Your application stores and retrieves history using a session backend (SQLite, Redis, SQLAlchemy). The full history is sent with each API request.
Server-managed conversations: OpenAI's servers store the history. You reference it with an ID, and the server reconstructs the context. Your application only sends the new message.
Each approach has distinct tradeoffs. This post explores server-managed conversations and when they are the right choice.
With client-side sessions, every API call includes the full conversation history in the request payload. For a 50-turn conversation, you are sending all 50 turns every time.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
flowchart TD
MSG(["New message"])
WORKING["Working memory<br/>rolling window"]
EPISODIC[("Episodic memory<br/>past sessions")]
SEMANTIC[("Semantic memory<br/>facts and preferences")]
SUM["Summarizer<br/>compresses old turns"]
ROUTER{"Retrieve<br/>needed memories"}
PROMPT["Assembled context"]
LLM["LLM"]
UPD["Memory updater<br/>writes new facts"]
MSG --> WORKING --> ROUTER
ROUTER -->|Past sessions| EPISODIC
ROUTER -->|User facts| SEMANTIC
EPISODIC --> SUM --> PROMPT
SEMANTIC --> PROMPT
WORKING --> PROMPT --> LLM --> UPD
UPD --> EPISODIC
UPD --> SEMANTIC
style ROUTER fill:#4f46e5,stroke:#4338ca,color:#fff
style LLM fill:#f59e0b,stroke:#d97706,color:#1f2937
style EPISODIC fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style SEMANTIC fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
With server-managed conversations, OpenAI stores the conversation on their servers. Your API call includes only:
previous_response_id)The server reconstructs the full context internally. This reduces your request payload size dramatically and simplifies your client code.
The Conversations API lets you create a named conversation container:
from openai import AsyncOpenAI
client = AsyncOpenAI()
# Create a conversation
conversation = await client.conversations.create()
print(f"Conversation ID: {conversation.id}")
# Output: conv_abc123...
The conversation_id is a persistent handle for the conversation. You can use it across multiple requests to maintain continuity.
The core mechanism for server-managed multi-turn conversations is previous_response_id. Each response has a unique ID, and you pass it to the next request to chain them together.
from openai import AsyncOpenAI
client = AsyncOpenAI()
# Turn 1
response1 = await client.responses.create(
model="gpt-4o",
input="My name is Alice and I'm planning a trip to Japan.",
)
print(f"Turn 1: {response1.output_text}")
print(f"Response ID: {response1.id}")
# Turn 2 — chain to Turn 1
response2 = await client.responses.create(
model="gpt-4o",
input="What month should I visit?",
previous_response_id=response1.id,
)
print(f"Turn 2: {response2.output_text}")
# The model knows Alice is planning a Japan trip
# Turn 3 — chain to Turn 2 (which chains to Turn 1)
response3 = await client.responses.create(
model="gpt-4o",
input="And what's my name?",
previous_response_id=response2.id,
)
print(f"Turn 3: {response3.output_text}")
# Output: "Your name is Alice."
The chain is cumulative — response3 has context from all three turns because each response links back to its predecessor.
The Agents SDK integrates server-managed conversations through the auto_previous_response_id setting:
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
from agents import Agent, Runner
agent = Agent(
name="ServerMemoryAgent",
instructions="You are a helpful assistant with server-managed memory.",
)
# Turn 1
result1 = await Runner.run(agent, "I live in Tokyo and work as an engineer.")
response_id = result1.raw_responses[-1].id
# Turn 2 — pass previous_response_id
result2 = await Runner.run(
agent,
"What city do I live in?",
previous_response_id=response_id,
)
print(result2.final_output) # "You live in Tokyo."
To avoid manually tracking response IDs, enable automatic chaining:
from agents import Agent, Runner, RunConfig
agent = Agent(
name="AutoChainAgent",
instructions="You are a helpful assistant.",
)
config = RunConfig(auto_previous_response_id=True)
# The runner automatically chains responses
result1 = await Runner.run(agent, "My favorite language is Python.", run_config=config)
result2 = await Runner.run(agent, "What's my favorite language?", run_config=config)
print(result2.final_output) # "Your favorite language is Python."
With auto_previous_response_id=True, the runner tracks the last response ID and passes it automatically on the next call. No session backend needed, no history management code.
Here is a complete chatbot using server-managed conversations:
import asyncio
from agents import Agent, Runner
agent = Agent(
name="ChatBot",
instructions="""You are a friendly conversational assistant.
Remember everything the user tells you across the conversation.""",
)
class ServerManagedChat:
def __init__(self):
self.last_response_id: str | None = None
async def send_message(self, message: str) -> str:
"""Send a message and get a response, with automatic chaining."""
kwargs = {}
if self.last_response_id:
kwargs["previous_response_id"] = self.last_response_id
result = await Runner.run(agent, message, **kwargs)
# Store the response ID for the next turn
self.last_response_id = result.raw_responses[-1].id
return result.final_output
def reset(self):
"""Start a new conversation."""
self.last_response_id = None
async def main():
chat = ServerManagedChat()
print("Chat started. Type 'quit' to exit, 'reset' for new conversation.\n")
while True:
user_input = input("You: ").strip()
if user_input.lower() == "quit":
break
if user_input.lower() == "reset":
chat.reset()
print("Conversation reset.\n")
continue
response = await chat.send_message(user_input)
print(f"Bot: {response}\n")
asyncio.run(main())
You can use both approaches together. Server-managed conversations handle the immediate multi-turn context, while client-side sessions store long-term user data.
from agents import Agent, Runner
from agents.extensions.sessions import SQLiteSession
# Client-side: persistent user preferences
user_session = SQLiteSession(db_path="./user_profiles.db")
agent = Agent(
name="HybridAgent",
instructions="You are an assistant with both short-term and long-term memory.",
)
class HybridMemoryChat:
def __init__(self, user_id: str):
self.user_id = user_id
self.last_response_id: str | None = None
async def load_user_context(self) -> str:
"""Load persistent user context from client-side session."""
items = await user_session.get_items(f"profile:{self.user_id}")
if items:
return "User context: " + str(items[-1].get("content", ""))
return ""
async def send_message(self, message: str) -> str:
# Load persistent context
context = await self.load_user_context()
full_message = f"{context}\n\nUser: {message}" if context else message
kwargs = {}
if self.last_response_id:
kwargs["previous_response_id"] = self.last_response_id
result = await Runner.run(agent, full_message, **kwargs)
self.last_response_id = result.raw_responses[-1].id
return result.final_output
async def save_preference(self, preference: str):
"""Save a long-term preference to client-side session."""
await user_session.add_items(
f"profile:{self.user_id}",
[{"role": "system", "content": preference}],
)
For many production systems, the best approach is hybrid:
| Concern | Approach |
|---|---|
| Immediate conversation context | Server-managed (previous_response_id) |
| Long-term user preferences | Client-side session (SQLite/Redis) |
| Cross-conversation memory | Client-side session |
| Compliance and auditing | Client-side session |
| Quick prototyping | Server-managed |
The two approaches are not mutually exclusive. Use server-managed conversations for the easy case and layer in client-side sessions where you need more control.
Sources:

Written by
Sagar Shankaran· Founder, CallSphere
LinkedInSagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
Graphiti is the open-source temporal knowledge graph for AI agents in 2026. Learn how bi-temporal memory beats vector RAG for voice agents and long-running LLMs.
OpenAI's Frontier platform makes model-native orchestration the default. What that means for agent builders, voice/chat buyers, and the build-vs-buy decision.
The 2026 desktop AI agent landscape — ServiceNow Project Arc, Anthropic Claude offerings, OpenAI agents, and Google Mariner. A buyer's map.
May 2026's biggest agent-architecture shift: planning, tool selection, and self-correction move inside the model. Framework code shrinks. Here is what changes.
A three-way comparison of Gemini Enterprise, Anthropic managed agents and OpenAI Frontier Platform after Cloud Next 2026 — strengths, gaps, buyer fit.
Anthropic's May 2026 push positions Claude as a vertical platform for financial services. The strategic positioning versus OpenAI and Google.
© 2026 CallSphere Inc. All rights reserved.
Made within San Francisco
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI