The first multi-turn conversation I ever built with Claude looked perfect in the playground and fell apart the moment I moved it into real code. I had carefully written a system prompt, added two or three user messages, and watched the model stay on topic. Then I copied the same messages into a Python script, ran it in a loop, and after the fifth turn the model started inventing constraints that had never been stated. When I opened the request payload I realized the problem immediately: I had been appending only the latest user message and discarding the earlier turns. The model had no memory of the conversation because I had never given it one.
That afternoon taught me the difference between “the playground remembers things for you” and “you are responsible for the entire context window.” This post exists to make that responsibility concrete. We will look at the exact shape of the Messages API, how system instructions actually work, what a context window really contains, and the practical failure modes that appear the moment you leave the single-turn examples.
The method stays the same:
Build the smallest useful version, then test what breaks.
What You Will Understand by the End
The precise structure of a messages list and why order matters
How the system parameter differs from a message with role "system"
What “context window” means in tokens, not just in marketing copy
How conversation history is (and is not) preserved across turns
Three realistic ways context can silently degrade and how to notice early
No tools, no structured output, no agents yet. Just the request shape that every later tutorial on this site will assume you already understand.
The Messages List Is the Entire Conversation
In the Anthropic Messages API the conversation lives in one place: the messages parameter. It is always a list of objects. Each object has at least two fields:
role: either "user" or "assistant"
content: the text (or later, a list of content blocks)
A minimal single-turn request looks like this:
Python
from anthropic import Anthropic
from dotenv import load_dotenv
import os
load_dotenv()
client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
response = client.messages.create(
model="claude-sonnet-5", # verify current ID in official docs
max_tokens=256,
messages=[
{
"role": "user",
"content": "Explain what a context window is in one short paragraph."
}
]
)
print(response.content[0].text)That is the entire conversation the model sees. There is no hidden state on the server that remembers previous calls. Every time you call messages.create you must send the full history you want the model to consider.
Adding Turns Correctly
When the model replies, you receive an assistant message. To continue the conversation you append both the previous assistant reply and the new user message:
Python
messages = [
{
"role": "user",
"content": "Explain what a context window is in one short paragraph."
}
]
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=256,
messages=messages
)
# Append the assistant turn
messages.append({
"role": "assistant",
"content": response.content[0].text
})
# Append the next user turn
messages.append({
"role": "user",
"content": "Now give me a concrete example of how a context window can fill up in a long chat."
})
response2 = client.messages.create(
model="claude-sonnet-5",
max_tokens=256,
messages=messages
)
print(response2.content[0].text)Notice the pattern: user → assistant → user → assistant. The list must alternate roles. Sending two user messages in a row or two assistant messages in a row will produce an error. The API enforces this ordering because the underlying training data expects a strict turn-taking structure.
I still catch myself occasionally appending only the new user message and wondering why the model has “forgotten” the earlier context. The model did not forget. I simply never sent the earlier turns again.

System Instructions Live Outside the Messages List
The system parameter is the correct place for durable instructions about role, tone, format, and constraints. It is not a message with role="system". The Anthropic API treats system content specially: it is applied to the entire request but does not appear as a turn in the conversation history.
Python
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=256,
system=(
"You are a precise technical writer. "
"Use short paragraphs. "
"Never use marketing language or superlatives. "
"If you are unsure, say so."
),
messages=[
{
"role": "user",
"content": "Explain what a context window is in one short paragraph."
}
]
)I keep system prompts short in early experiments. Long system prompts consume tokens that could otherwise be used for conversation history or document content. When a system prompt grows past a few hundred tokens I start asking whether every sentence is still earning its place.
A useful mental model: the system prompt is the standing order you give the model before the conversation begins. The messages list is the conversation itself.
What a Context Window Actually Contains
The context window is the total number of tokens the model can consider in a single request. That total includes:
The system prompt
Every message in the messages list (both user and assistant turns)
Any tool definitions or other structured content you later add
A small amount of overhead the API itself consumes
It does not include previous API calls that you never sent again. There is no persistent memory on the server side for ordinary Messages API requests.
When the combined size of system + messages approaches the model’s limit, you have three practical options:
Summarize or drop older turns
Move durable information into the system prompt or an external store
Switch to a model with a larger window (still verifying the current limits in the official docs)
I have watched teams discover this limit the hard way: a long-running support agent that worked beautifully for the first twenty turns and then began ignoring the original customer constraints because the early messages had been truncated. The failure looked like a reasoning error. It was actually a context-management error.
A Small Experiment You Can Run
Here is a short script that makes the growth of the context window visible:
Python
from anthropic import Anthropic
from dotenv import load_dotenv
import os
load_dotenv()
client = Anthropic(api_key=os.getenv("ANTHROPIC_API_KEY"))
messages = []
system = "You are a helpful assistant that answers briefly."
for i in range(1, 6):
messages.append({
"role": "user",
"content": f"This is turn {i}. Please reply with the turn number and a one-sentence summary of what we have discussed so far."
})
response = client.messages.create(
model="claude-sonnet-5",
max_tokens=128,
system=system,
messages=messages
)
assistant_text = response.content[0].text
messages.append({
"role": "assistant",
"content": assistant_text
})
print(f"--- After turn {i} ---")
print(f"Input tokens: {response.usage.input_tokens}")
print(f"Output tokens: {response.usage.output_tokens}")
print(assistant_text)
print()Watch the input_tokens number climb with each turn. That climb is the context window filling up in real time. When the number gets large enough relative to the model’s limit, you will eventually hit an error or forced truncation. Seeing the growth early makes the later design decisions much easier.

Three Ways Context Quietly Goes Wrong
1. Forgetting to Append the Assistant Turn
If you only keep user messages, the model never sees its own previous answers. It may contradict itself or re-ask for information it already provided. The fix is mechanical: always append both sides of the turn.
2. Letting the History Grow Without a Policy
A chat interface that stores every message forever will eventually exceed the context window. Decide in advance how you will summarize, drop, or archive older turns. The decision can be simple (keep the last N turns) or more sophisticated (summarize every 10 turns). The important part is that the policy exists before the window fills.
3. Putting Volatile Instructions in the System Prompt
If you change the system prompt on every request, you lose the ability to reason about what the model “knows.” Keep the system prompt stable and put turn-specific instructions in the user messages. Stability makes debugging possible.
I still keep a private note titled “context failures I have caused.” The three entries above appear in it more than once. Writing them down once has saved me from repeating the same mistakes inside larger systems.
Practical Rules I Use Every Day
Alternate roles strictly: user, assistant, user, assistant.
Treat the system prompt as standing orders, not as conversation.
Measure input tokens on every experimental request until the numbers feel intuitive.
Decide on a history policy before the conversation can grow long.
When something feels like a reasoning failure, first check whether the relevant earlier turns are still present in the messages list.
These rules are deliberately boring. Boring rules are the ones that survive contact with production traffic.
What This Post Does Not Cover
We have not yet discussed:
Content blocks beyond plain text (images, documents, tool results)
Prompt caching and its effect on cost and latency
How tool-use turns appear inside the messages list
Automatic context management features that newer models may offer
Those topics belong to later posts. The goal today is a clear mental model of the basic request shape so that every subsequent tutorial can build on solid ground.
Verification Checklist
Run a two-turn conversation and confirm the second reply correctly refers to information from the first turn.
Intentionally omit the assistant message and confirm the model loses the earlier context.
Print response.usage.input_tokens after several turns and watch the number increase.
Move a constraint from the system prompt into a user message and observe the difference in behavior.
Confirm that sending two user messages in a row produces a clear API error.
If all five steps behave as expected, you understand the foundation.
Update Note
The Messages API shape (roles, system parameter, content blocks) has been stable for a long time. Model context-window sizes and exact token-counting behavior can change with new releases. Always verify the current limits and recommended model IDs in the official Anthropic documentation before you rely on specific numbers in production code.
Looking Ahead
The next posts leave the Foundations category and begin building the first tool-using agent. Everything we have covered so far—authentication, the messages list, system instructions, and the reality of the context window—becomes the substrate those agents run on. A clean foundation makes the later work far less mysterious.
Keep the small multi-turn script. When an agent later “forgets” a constraint, you will already know the first place to look.
Build the smallest useful version, then test what breaks.
— Caleb Fletcher
Boise, Idaho
No feedback yet — submit the first.