Claude API Multi-Turn Conversation Memory Explained
Claude's API has no built-in memory. Every request is stateless — there is no session ID, no server-side conversation store, and no "remember this user" flag. If you want multi-turn conversation memory, you build it yourself by sending the full message history back with every request. This is the single most common point of confusion for developers moving from a chat UI (which feels like it remembers you) to the raw API (which does not).
The good news is that implementing multi-turn memory is straightforward once you understand the mental model: you are the memory. Claude only knows what's in the messages array you send on each call. This article covers how that works, how to manage growing context, and the tradeoffs between naive history replay and smarter summarization.
How Conversation State Actually Works
Every call to the Messages endpoint takes an array of messages, each with a role (user or assistant) and content. To continue a conversation, you append the new user message to the array that already contains prior turns, and send the whole thing again:
curl https://api.anthropic.com/v1/messages \
-H "x-api-key: $ANTHROPIC_API_KEY" \
-H "anthropic-version: 2023-06-01" \
-H "content-type: application/json" \
-d '{
"model": "claude-3-5-sonnet-latest",
"max_tokens": 512,
"messages": [
{"role": "user", "content": "My name is Alex and I work at a logistics startup."},
{"role": "assistant", "content": "Nice to meet you, Alex! What can I help you with at your logistics startup?"},
{"role": "user", "content": "What was my name again?"}
]
}'
Claude answers "Alex" not because it remembers the earlier request, but because that turn is still physically present in this request's messages array. Drop the first two messages and Claude has no idea who Alex is. This is why "memory" in the Claude API is really context management — you decide what gets kept, trimmed, or summarized before each call.
Building a Simple Conversation Store
For most apps, the pattern is:
- Store each conversation's messages in a database, keyed by a conversation ID or session ID.
- On each new user turn, load the stored history, append the new message.
- Send the full array to the API.
- Append Claude's response to the stored history and save it back.
async function sendTurn(conversationId, userText) {
const history = await db.getMessages(conversationId); // [{role, content}, ...]
history.push({ role: "user", content: userText });
const res = await fetch("https://api.subtoapi.app/v1/messages", {
method: "POST",
headers: {
"Authorization": `Bearer ${process.env.SUBTOAPI_KEY}`,
"content-type": "application/json"
},
body: JSON.stringify({
model: "claude-3-5-sonnet-latest",
max_tokens: 1024,
messages: history
})
});
const data = await res.json();
const reply = data.content[0].text;
history.push({ role: "assistant", content: reply });
await db.saveMessages(conversationId, history);
return reply;
}
If you're routing requests through SubToAPI, this pattern doesn't change — you still own the history array, but you get a stable HTTPS endpoint, streaming, and per-key usage metadata on top of it, which is useful when several team members or app instances are generating conversations against the same underlying Claude access. See the quickstart and messages docs for the exact request shape.
Managing Context Growth
The obvious problem: conversations grow, and every turn resends the entire history, which costs tokens and eventually hits the model's context window limit. A few practical strategies:
- Sliding window: keep only the last N turns. Simple, cheap, but loses early context (a support bot forgetting the customer's original issue after 20 messages).
- Summarize-and-compress: periodically ask Claude to summarize older turns into a short paragraph, replace those messages with the summary, and keep recent turns verbatim. This preserves continuity while capping token growth.
- System prompt for durable facts: put stable information (user name, account tier, preferences) in the
systemparameter instead of re-deriving it from history each time. This is cheaper than relying on the model to re-find it buried in old turns. - External memory store: for facts that must persist across sessions (not just within one conversation), save structured data (name, preferences, past orders) in your own database and inject it into the system prompt or first message rather than depending on conversation history at all.
const summary = await summarizeOldTurns(history.slice(0, -10));
const trimmedHistory = [
{ role: "user", content: `Conversation summary so far: ${summary}` },
...history.slice(-10)
];
This keeps token usage predictable and avoids silent truncation errors when a conversation grows past the model's limit.
Streaming and Tool Use in Multi-Turn Contexts
Multi-turn memory interacts with two other features you'll likely need:
- Streaming: each turn can still stream token-by-token even though you're resending full history each time. See streaming docs for the event format.
- Tool use: if Claude calls a tool mid-conversation, the tool call and its result become part of the message history too — you must include the
tool_useandtool_resultblocks in subsequent turns, not just plain text, or Claude loses the context of what it asked for and what it got back. Details are in the tools docs.
Practical Checklist
- Store conversation history per user/session, not per request.
- Decide a trimming/summarization strategy before you ship, not after you hit context limits in production.
- Keep durable facts in the system prompt, not buried in old turns.
- Persist
tool_use/tool_resultblocks alongside text when using tools mid-conversation. - If multiple services or team members generate conversations against the same Claude access, a shared API layer with per-key usage tracking (like SubToAPI) makes it easier to see which conversations are consuming the most tokens.
questions
Does Claude remember previous conversations across sessions automatically? No. Claude has no server-side memory. Anything from a previous session must be re-sent as part of the messages array or stored separately in your own database and reinjected.
How many turns can I include before hitting context limits? It depends on the model's context window and the length of each message. Track cumulative token usage per conversation and start summarizing or trimming well before you approach the model's stated limit.
Should I store conversation history as plain text or structured JSON? Store it as structured JSON matching the API's message format (role + content, including any tool_use/tool_result blocks). This lets you resend it directly without reformatting and avoids losing tool-call context.