Appearance
Multi-Turn Conversation Design
A single prompt gets a single answer. A conversation gets context, follow-ups, clarifications, and corrections, if you manage the message history correctly. This lesson covers building prompts for multi-turn interactions: tracking state, managing context growth, and handling the conversation as data.

What you'll learn
- Multi-turn prompts pass the full message history with each call, every previous exchange is context
- State tracking means extracting and updating structured data across turns: user preferences, task progress, collected information
- Context management prevents the conversation from growing past token limits while preserving what matters
The problem
A single-turn prompt is a function: input in, output out. A conversation is a state machine: each turn depends on every previous turn, the user's preferences accumulate, and the conversation grows until it hits the context limit. Without deliberate conversation design, your agent forgets what happened three turns ago, repeats itself, or loses track of what it was doing.
Build it
Step 1: Basic conversation loop
Implement a standard conversational loop to maintain continuity across multiple interactions. This foundational setup appends user and assistant messages sequentially.
python
messages = [{"role": "system", "content": "You are a helpful coding assistant."}]
while True:
user_input = input("> ")
messages.append({"role": "user", "content": user_input})
response = client.messages.create(
model="claude-sonnet-4-6",
max_tokens=1024,
system=messages[0]["content"],
messages=messages[1:] # system prompt passed separately in Anthropic
)
messages.append({"role": "assistant", "content": response.content[0].text})
print(response.content[0].text)Step 2: Track state across turns
Extract and track critical state variables outside the raw message history. This prevents the agent from losing track of tasks as the conversation context lengthens.
python
conversation_state = {
"task": None,
"files_modified": [],
"tests_run": False
}
def update_state(assistant_response, tool_calls):
# Parse the response and tool calls to update conversation state
if "file written" in assistant_response.lower():
conversation_state["files_modified"].append(extract_filename(assistant_response))
if "tests passed" in assistant_response.lower():
conversation_state["tests_run"] = TrueStep 3: Inject state into the system prompt
Inject the tracked state back into the system prompt dynamically. This ensures the model always has immediate access to the most current session context.
python
def build_system_prompt(base_prompt, state):
state_summary = f"""
Current task: {state['task'] or 'None'}
Files modified: {', '.join(state['files_modified']) or 'None'}
Tests run: {'Yes' if state['tests_run'] else 'No'}
"""
return base_prompt + "\n\n## Current Session State\n" + state_summaryStep 4: Manage context growth
Apply middle-truncation to prevent the conversation from exceeding token limits. This preserves the essential system instructions and the most recent context while pruning older exchanges.
python
MAX_MESSAGES = 20
def trim_history(messages, max_messages):
if len(messages) <= max_messages:
return messages
# Keep system prompt + first user message + last N messages
return [messages[0], messages[1]] + messages[-(max_messages - 2):]What goes wrong
| Mistake | How you notice it | The fix |
|---|---|---|
| Conversation grows past context limit | Errors or the model forgets early context | Trim history, summarize early turns, or use a sliding window |
| State drifts across turns | Agent contradicts itself or repeats completed steps | Explicitly track state in a structured object and inject a summary into each prompt |
| Assistant and user roles alternate incorrectly | API error about role ordering | Messages must alternate: user, assistant, user, assistant. Tool results count as user messages |
| System prompt included in message array | Duplicate instructions or API error | Anthropic's API takes system separately. OpenAI includes it as the first message with role "system" |
Confirm it worked
Run a simulated conversation to verify the agent accurately recalls information from earlier turns. This test confirms the context management logic works as expected.
python
# Run a 5-turn conversation and verify the agent remembers turn 1
messages = [{"role": "system", "content": "Remember the user's name."}]
messages.append({"role": "user", "content": "My name is Alice."})
# ... run turn ...
messages.append({"role": "user", "content": "What's my name?"})
response = client.messages.create(model="claude-sonnet-4-6", max_tokens=1024, system=messages[0]["content"], messages=messages[1:])
assert "Alice" in response.content[0].text