# Handling Conversation Context and Message History with Claude: A Complete Guide

> Master conversation context and message history with Claude. Learn to accumulate message history in a Python list and pass it to the Claude API for seamless interactions. Get the complete guide.

- Repository: [Anthropic/prompt-eng-interactive-tutorial](https://github.com/anthropics/prompt-eng-interactive-tutorial)
- Tags: how-to-guide
- Published: 2026-03-09

---

**To maintain conversation context with Claude, accumulate a Python list of message dictionaries with `role` and `content` keys, passing the complete history to `client.messages.create()` on every API call.**

The `anthropics/prompt-eng-interactive-tutorial` repository demonstrates the canonical pattern for managing multi-turn dialogues with Anthropic's Claude API. This approach relies on **stateless API calls with stateful client-side message accumulation**, enabling complex tool-use workflows and contextual follow-ups without server-side session management.

## The Core Pattern: Role-Based Message Lists

The foundation of conversation management in Claude applications rests on a simple Python list containing dictionary objects. Each dictionary represents a single turn in the dialogue with two required fields:

- **`role`**: Identifies the speaker as `"user"` for human inputs, `"assistant"` for Claude's responses, or `"system"` for persistent instructions
- **`content`**: The raw text payload containing the actual message or markup

In `Anthropic 1P/10.1_Appendix_Chaining Prompts.ipynb`, this pattern enables sequential prompt chaining by appending new dictionaries to the list after each exchange. Because the Anthropic API is **stateless**, the client must preserve and resubmit the entire `messages` list with every request to maintain continuity across turns.

## Managing Tool Use with Conversation History

The tutorial's `Anthropic 1P/10.2_Appendix_Tool Use.ipynb` file extends the basic pattern to support function calling workflows. This implementation demonstrates how to interrupt Claude's response, execute local code, and resume the conversation with tool results injected into the message history.

### Step 1: Initialize with System Instructions

Begin by constructing a **system prompt** that defines available tools and their schemas. This prompt remains constant across all turns in the conversation.

```python
system_prompt_tools_general_explanation = """
You have access to a set of functions you can use to answer the user's question.
"""

system_prompt_tools_specific_tools = """
<tools>
  <tool_description>
    <tool_name>do_pairwise_arithmetic</tool_name>
    <description>Multiplies two large integers</description>
    <parameters>
      <parameter><name>first_operand</name><type>int</type></parameter>
      <parameter><name>second_operand</name><type>int</type></parameter>
      <parameter><name>operator</name><type>str</type></parameter>
    </parameters>
  </tool_description>
</tools>
"""

```

### Step 2: Send Initial User Message with Stop Sequences

Create the first user message and pass it to `client.messages.create()` with **stop sequences** that halt generation when Claude attempts a function call.

```python
user_msg = {"role": "user", "content": "Multiply 1,984,135 by 9,343,116"}

response = get_completion(
    [user_msg],
    system_prompt=system_prompt,
    stop_sequences=["</function_calls>"]
)

```

The `stop_sequences=["</function_calls>"]` parameter forces the API to return immediately when Claude generates the closing XML tag, allowing the client to intercept the function call before Claude proceeds to generate a final answer.

### Step 3: Extract Parameters Using find_parameter

Parse the partial assistant output to extract tool arguments using the `find_parameter` helper function.

```python
def find_parameter(message, name):
    start_tag = f'name="{name}">'
    start = message.find(start_tag)
    if start == -1:
        return None
    start += len(start_tag)
    end = message.find("<", start)
    return message[start:end]

first_operand = find_parameter(response, "first_operand")
second_operand = find_parameter(response, "second_operand")
operator = find_parameter(response, "operator")

```

### Step 4: Execute Tool and Format Results

Execute the requested operation locally using `do_pairwise_arithmetic`, then format the result using `construct_successful_function_run_injection_prompt`.

```python
result = do_pairwise_arithmetic(int(first_operand), int(second_operand), operator)

function_results = construct_successful_function_run_injection_prompt(
    [{'tool_name': 'do_pairwise_arithmetic', 'tool_result': result}]
)

```

### Step 5: Reconstruct Message History with Tool Results

Append both the assistant's partial response and a new user message containing the tool output to the conversation history.

```python
full_first_response = response + "</function_calls>"

messages = [
    user_msg,
    {"role": "assistant", "content": full_first_response},
    {"role": "user", "content": function_results}
]

final_answer = get_completion(messages, system_prompt=system_prompt)

```

This reconstruction preserves the full context: the original query, Claude's intent to use a tool, and the actual tool execution result.

## Complete Working Example

The following self-contained script demonstrates the end-to-end flow for handling conversation context with tool integration:

```python
import anthropic

# Initialize client

client = anthropic.Anthropic(api_key="your-api-key")

def get_completion(messages, system_prompt="", stop_sequences=None):
    """Helper wrapper for Anthropic API calls."""
    resp = client.messages.create(
        model="claude-3-sonnet-20240229",
        max_tokens=2000,
        temperature=0.0,
        system=system_prompt,
        messages=messages,
        stop_sequences=stop_sequences,
    )
    return resp.content[0].text

def find_parameter(message, name):
    """Extract parameter values from XML-like function call markup."""
    start_tag = f'name="{name}">'
    start = message.find(start_tag)
    if start == -1:
        return None
    start += len(start_tag)
    end = message.find("<", start)
    return message[start:end]

def do_pairwise_arithmetic(a, b, op):
    """Local tool implementation."""
    if op == "*":
        return a * b
    elif op == "+":
        return a + b
    return None

def construct_successful_function_run_injection_prompt(tool_results):
    """Format tool results for Claude's expected markup."""
    results_xml = "<function_results>\n"
    for item in tool_results:
        results_xml += "<result>\n"
        results_xml += f"<tool_name>{item['tool_name']}</tool_name>\n"
        results_xml += f"<stdout>\n{item['tool_result']}\n</stdout>\n"
        results_xml += "</result>\n"
    results_xml += "</function_results>"
    return results_xml

# System prompt defining tool capabilities

system_prompt = """
You have access to a set of functions you can use to answer the user's question.
<tools>
  <tool_description>
    <tool_name>do_pairwise_arithmetic</tool_name>
    <description>Performs arithmetic operations</description>
    <parameters>
      <parameter><name>first_operand</name><type>int</type></parameter>
      <parameter><name>second_operand</name><type>int</type></parameter>
      <parameter><name>operator</name><type>str</type></parameter>
    </parameters>
  </tool_description>
</tools>
"""

# Turn 1: User asks a question requiring calculation

user_message = {"role": "user", "content": "Multiply 1,984,135 by 9,343,116"}
partial_reply = get_completion(
    [user_message],
    system_prompt=system_prompt,
    stop_sequences=["</function_calls>"]
)

# Extract and execute

first = int(find_parameter(partial_reply, "first_operand"))
second = int(find_parameter(partial_reply, "second_operand"))
op = find_parameter(partial_reply, "operator")
calc_result = do_pairwise_arithmetic(first, second, op)

# Construct tool result message

tool_output = construct_successful_function_run_injection_prompt(
    [{'tool_name': 'do_pairwise_arithmetic', 'tool_result': calc_result}]
)

# Turn 2: Submit full history including tool result

messages = [
    user_message,
    {"role": "assistant", "content": partial_reply + "</function_calls>"},
    {"role": "user", "content": tool_output}
]

final_answer = get_completion(messages, system_prompt=system_prompt)
print(final_answer)

```

## Key Implementation Files

The tutorial repository contains several critical files demonstrating conversation context patterns:

- **`Anthropic 1P/10.1_Appendix_Chaining Prompts.ipynb`**: Demonstrates basic message list accumulation for sequential prompting without tools
- **`Anthropic 1P/10.2_Appendix_Tool Use.ipynb`**: Contains the complete tool-use workflow with `stop_sequences` and message reconstruction
- **`Anthropic 1P/hints.py`**: Provides reference documentation for the role-based message structure and XML markup contracts
- **[`AmazonBedrock/utils/hints.py`](https://github.com/anthropics/prompt-eng-interactive-tutorial/blob/main/AmazonBedrock/utils/hints.py)**: Adapts the same patterns for AWS Bedrock runtime environments

## Summary

- **Accumulate message history** in a Python list of dictionaries with `role` and `content` keys
- **Pass the complete list** to `client.messages.create()` on every turn to maintain context
- **Use `stop_sequences`** to interrupt Claude for tool execution, then append results as new user messages
- **Preserve system prompts** consistently across all turns to maintain task definition
- **Format tool results** using the XML-like markup structure expected by Claude with `construct_successful_function_run_injection_prompt`

## Frequently Asked Questions

### How does Claude maintain context across multiple API calls?

Claude does not maintain server-side session state. According to the `anthropics/prompt-eng-interactive-tutorial` source code, the client application must preserve conversation history by accumulating all previous messages in a list and passing the complete list to the `messages` parameter of `client.messages.create()` on every request. This stateless architecture ensures each API call contains the full context required for coherent multi-turn dialogue.

### What is the correct format for message history entries?

Each entry in the message history must be a Python dictionary containing exactly two keys: `role` (a string set to `"user"`, `"assistant"`, or `"system"`) and `content` (a string containing the message text). As implemented in `10.2_Appendix_Tool Use.ipynb`, the content field may contain plain text or structured XML-like markup for tool interactions.

### How do you handle tool results in the conversation flow?

When Claude generates a function call, the client intercepts the partial output using `stop_sequences`, executes the requested tool locally with `do_pairwise_arithmetic`, then formats the result using `construct_successful_function_run_injection_prompt`. This formatted output, along with the assistant's partial response, is appended to the history before requesting a final completion.

### Can conversation history be truncated to save tokens?

Yes, while the tutorial demonstrates passing complete histories, production applications may implement sliding window approaches to limit context length. However, any truncation must preserve the alternating user/assistant structure and retain critical system prompts to maintain conversation coherence, as the API requires valid message sequences for proper context processing.