Skip to main content
Lesson 5 of 18 · Mistral Agents: Building Autonomous AI

Persistent Conversations

Lesson 5 of 18 Updated November 3, 2026 Developer
On this page
  1. State That Persists Across Exchanges
  2. Starting a Conversation
  3. Continuing a Conversation
  4. The Immutability Mechanism
  5. Reading Conversation History
  6. Managing Storage
  7. Multi-Turn Conversation Pattern
  8. Key Takeaways

Lesson updated November 3, 2026 based on mistralai SDK 3.7.0: retrieve a conversation’s history (its entries) using client.beta.conversations.get_history; retrieve does not exist, and get returns only conversation metadata. Source: client-python.

State That Persists Across Exchanges

One of the major advantages of the Agents API over Chat Completions is conversation persistence. You no longer need to manually manage message history—Mistral handles it server-side. Specifically, the list of dictionaries you used to accumulate in a variable, truncate when context overflowed, and rebuild on every process restart disappears from your code. You only need to track one thing: an identifier. And that identifier follows a rule that trips up everyone on their first project.

Starting a Conversation

The conversations.start() method creates a new conversation and sends the first message. The following example also creates the agent that will run it, then gives it a project context you can query over multiple turns.

from mistralai.client import Mistral
import os

client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])

# Create an agent
agent = client.beta.agents.create(
    model="mistral-medium-latest",
    name="Project Assistant",
    description="Project management assistant.",
    instructions="You help organize and track projects. Be structured."
)

# Start the conversation
response = client.beta.conversations.start(
    agent_id=agent.id,
    inputs="I'm launching a new project to redesign our website. Budget: €50K, deadline: September 2026."
)

print(f"Conversation ID: {response.conversation_id}")

The returned conversation_id is your key to continuing this conversation later. Store it where your application can retrieve it: in a user session, a database, or the support ticket it relates to.

Continuing a Conversation

To add a message to an existing conversation, pass the conversation_id to conversations.append(). Follow the thread in this example: the second question doesn’t repeat the budget or deadline, and the third asks for a schedule without reminding the agent it’s a website redesign. Yet the agent has all that context.

# First exchange
response1 = client.beta.conversations.start(
    agent_id=agent.id,
    inputs="I'm launching a website redesign project. Budget €50K."
)

# Second exchange — the agent remembers the context
response2 = client.beta.conversations.append(
    conversation_id=response1.conversation_id,
    inputs="What are the priority steps?"
)

# Third exchange — still in the same context
response3 = client.beta.conversations.append(
    conversation_id=response2.conversation_id,
    inputs="Draft a schedule for the first 3 months."
)

Look closely at which identifier feeds each call: response2 starts from the ID of response1, response3 from that of response2. This isn’t a quirk; it’s the core mechanism of the API.

The Immutability Mechanism

Every call to conversations.append() returns a new conversation_id. The old one remains readable, but you must use the latest ID for the next append.

# Each append returns a new ID
id_1 = response1.conversation_id  # "conv-aaa"
id_2 = response2.conversation_id  # "conv-bbb"
id_3 = response3.conversation_id  # "conv-ccc"

# Always use the LATEST id to continue
response4 = client.beta.conversations.append(
    conversation_id=id_3,  # Not id_1 or id_2
    inputs="Add client validation milestones."
)

The classic mistake is saving the initial identifier in your database when the user opens the thread, then reusing it as-is for every message. Nothing crashes: the API responds normally, but each question restarts from the context of the first exchange, and your agent seems to have amnesia without any error alerting you. Always overwrite the stored identifier with the one returned in the response.

Reading Conversation History

You can read history at any time, which helps both with debugging and displaying a thread in your interface.

conversation = client.beta.conversations.get_history(
    conversation_id=response.conversation_id
)

for entry in conversation.entries:
    print(f"[{entry.role}] {entry.type}: {entry.content[:100]}...")

Managing Storage

By default, conversations are stored in the Mistral cloud. The store=False parameter disables this behavior for exchanges your internal policy forbids leaving on a third-party server.

# Ephemeral conversation — nothing is saved
response = client.beta.conversations.start(
    agent_id=agent.id,
    inputs="Confidential data to analyze...",
    store=False
)

The trade-off matches the goal: the conversation isn’t recoverable after the session, and data isn’t kept on Mistral servers. You lose the history playback described above, and your application must handle tracking exchanges itself if needed. Make this decision upfront, not as a last-minute checkbox.

Multi-Turn Conversation Pattern

The skeleton below applies everything above in an interactive loop. The key point is the conversation_id variable, initialized to None: it decides which call to make—start() on the first turn, append() afterward—and it gets reassigned after each response, following the immutability rule.

from mistralai.client import Mistral
import os

client = Mistral(api_key=os.environ["MISTRAL_API_KEY"])

agent = client.beta.agents.create(
    model="mistral-large-latest",
    name="Technical Coach",
    description="Coach for technical decisions.",
    instructions="You are a senior technical coach. Ask questions to understand the context before advising."
)

# Conversation loop
conversation_id = None

while True:
    user_input = input("You: ")
    if user_input.lower() in ["quit", "exit"]:
        break

    if conversation_id is None:
        response = client.beta.conversations.start(
            agent_id=agent.id,
            inputs=user_input
        )
    else:
        response = client.beta.conversations.append(
            conversation_id=conversation_id,
            inputs=user_input
        )

    conversation_id = response.conversation_id

    for entry in response.outputs:
        if hasattr(entry, "content"):
            print(f"Agent: {entry.content}")

Adapt it to your context by replacing input() with your actual message source—HTTP request, queue message, webhook event—and persisting conversation_id where your application can find it on the next turn.

Key Takeaways

  • conversations.start() creates a new conversation with a first message
  • conversations.append() adds a message to an existing conversation
  • Each append returns a new conversation_id—always use the latest one
  • store=False disables persistence on Mistral’s side for sensitive data
  • The agent retains the full conversation context without you needing to resend history