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

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.

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.

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.

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.

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.

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:

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: 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.

Have a question about this repo?

These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:

Share the following with your agent to get started:
curl -s "https://instagit.com/install.md"

Works with
Claude Codex Cursor VS Code OpenClaw Any MCP Client

Maintain an open-source project? Get it listed too →