OpenAI SDK: Conversation state & memory
The model is stateless (part 01), so a chat must supply earlier turns on every call.
In this post
What this post covers
The model is stateless (part 01), so a chat must supply earlier turns on every call. There are three
ways to do that: resend the history yourself, chain responses with
previous_response_id, or use a Conversation object that OpenAI
stores. All three bill the earlier turns as input tokens on every call. Long chats need a plan for
trimming or summarising.
1. The three approaches
| Where history lives | You send each turn | Good for | |
|---|---|---|---|
| Manual history | your database | the full list of items | full control, any provider, store=False privacy |
previous_response_id | OpenAI (stored responses) | only the new input + the id | simple chains, tool loops (part 07) |
| Conversations API | OpenAI (a conversation object) | new input + conversation id | long-lived threads you resume later |
2. Manual history
from fake_openai import fake_client
client = fake_client(script=["Hi Asha! How can I help?", "Your name is Asha.", "You live in Kochi."])
history = [] # stored in YOUR database per chat session
def chat(user_text):
history.append({"role": "user", "content": user_text})
r = client.responses.create(model="gpt-5.6-luna", instructions="Be brief.",
input=history, store=False)
history.append({"role": "assistant", "content": r.output_text})
return r
for text in ["Hi, I'm Asha from Kochi.", "What's my name?", "Where do I live?"]:
r = chat(text)
print(f"{r.output_text:28} | items sent this turn: {len(client.fake.requests[-1]['body']['input'])}")
Output
Hi Asha! How can I help? | items sent this turn: 1
Your name is Asha. | items sent this turn: 3
You live in Kochi. | items sent this turn: 5
The input grows every turn: 1, 3, then 5 items. Turn 50 resends 99 items, and every one of them is billed. That growth is the core cost problem of chat applications.
3. previous_response_id
from fake_openai import fake_client
client = fake_client(script=["Noted: Asha, Kochi.", "Your name is Asha."])
r1 = client.responses.create(model="gpt-5.6-luna", input="I'm Asha from Kochi.")
r2 = client.responses.create(model="gpt-5.6-luna", input="What's my name?",
previous_response_id=r1.id) # continue from r1
print(r2.output_text)
print("second request body:", client.fake.requests[1]["body"])
Output
Your name is Asha.
second request body: {'input': "What's my name?", 'model': 'gpt-5.6-luna', 'previous_response_id': 'resp_2'}
Your request is small, but the API still processes and bills the earlier turns as input, and it
requires the previous response to have been stored (store=True, the default for the Responses
API). Remember that instructions are not inherited, so send them with every call.
4. Conversations
real API
conv = client.conversations.create(metadata={"user": "u_123", "channel": "web"})
client.responses.create(model="gpt-5.6-luna", conversation=conv.id, input="I'm Asha from Kochi.")
r = client.responses.create(model="gpt-5.6-luna", conversation=conv.id, input="What's my name?")
print(r.output_text)
# days later: continue the same thread, or list its items to show the history in your UI
items = client.conversations.items.list(conv.id)
5. Keeping long conversations affordable
window + running summary
from fake_openai import fake_client
client = fake_client(script=["Asha from Kochi ordered a desk lamp (ORD-7731) that arrived cracked; wants a refund."])
history = [{"role": "user", "content": f"message {i}"} for i in range(1, 21)] # 20 old turns
KEEP = 6
old, recent = history[:-KEEP], history[-KEEP:]
summary = client.responses.create(
model="gpt-5.6-luna",
instructions="Summarise the conversation so far in 2 sentences, keeping names, ids and requests.",
input=old, max_output_tokens=120,
).output_text
compact = [{"role": "developer", "content": f"Earlier in this chat: {summary}"}] + recent
print(len(history), "items →", len(compact), "items")
print(compact[0]["content"])
Output
20 items → 7 items
Earlier in this chat: Asha from Kochi ordered a desk lamp (ORD-7731) that arrived cracked; wants a refund.
Recap
- Memory is always resent input, whichever approach you use, and it is billed each turn.
- Manual history gives full control (and works with
store=False);previous_response_idis the simplest chain; Conversations suit long-lived threads. - Resend
instructionsevery call; they are not inherited. - Window, summarise, extract facts to keep long chats fast and cheap.
Get the free RAG production checklist
25 checks to run before real users see your RAG feature: exact-match misses, invented citations, prompt injection through documents, quality that slips after a prompt change. Confirm your email and the PDF comes with the welcome email, plus each new experiment.