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

Server vs. Client Execution

Lesson 14 of 18 Updated November 3, 2026 Developer
On this page
  1. Two Ways to Handle Handoffs
  2. Server Mode, the Default
  3. Client Mode, When You Want Control
  4. Choosing Between the Two
  5. Key Takeaways

Lesson updated November 3, 2026 based on mistralai SDK 3.7.0 and the Mistral Handoffs documentation: an agent.handoff entry identifies the target agent via next_agent_id (not to_agent), and conversations.append does not accept an agent_id parameter—older code would fail. Target agent resumption here occurs via a new conversation, to which you pass the context. Sources: Handoffs · client-python.

Two Ways to Handle Handoffs

When an agent transfers control to another agent, the Mistral API offers two execution modes: server (automatic) and client (manual). The setting is a single word, but it determines who actually drives the workflow. In server mode, you delegate the entire choreography to Mistral and receive the result. In client mode, you regain control between each step. Everything else—the agents, their tools, their handoff targets—remains exactly the same.

Server Mode, the Default

response = client.beta.conversations.start(
    agent_id=router_agent.id,
    inputs="Analyze and calculate market trends.",
    handoff_execution="server"  # Default — can be omitted
)

The flow is fully managed: the router receives the request, decides to transfer to the research agent, and Mistral executes the transfer; the research agent does its work then transfers to the calculator, and Mistral executes that as well; the calculator produces the result. You receive the final response, accompanied by all intermediate outputs—so you see what happened, but after the fact.

This mode has three qualities that explain why it is the default. It is simple, since you write no control logic on your side. It is fast, because the entire chain executes at Mistral without network round-trips at each relay. It is reliable, as error handling and timeouts between agents are handled for you.

Its limitations are the exact counterpart. You have no intermediate control: impossible to validate or modify a handoff while it occurs. Cost is opaque, in the sense that all agents in the chain execute without you being able to interrupt mid-flow a request heading in an unnecessary direction. And you cannot insert any custom logic between steps—no fine-grained logging, no validation, no business decisions.

Client Mode, When You Want Control

response = client.beta.conversations.start(
    agent_id=router_agent.id,
    inputs="Analyze the cloud market.",
    handoff_execution="client"
)

The router receives the request and decides to transfer, exactly as before. But instead of the transfer executing, you receive an agent.handoff event and the chain stops there. It is up to you to restart it by calling the target agent—which leaves you the latitude to validate, log, enrich the context, or simply refuse.

response = client.beta.conversations.start(
    agent_id=router_agent.id,
    inputs="Search for the latest tech news.",
    handoff_execution="client"
)

for entry in response.outputs:
    if entry.type == "agent.handoff":
        target_agent_id = entry.next_agent_id
        print(f"Handoff requested to: {target_agent_id}")

        # Validate, then hand over to the target agent, with useful context
        next_response = client.beta.conversations.start(
            agent_id=target_agent_id,
            inputs="Search for the latest tech news.",
        )

        # Process the target agent's response
        for next_entry in next_response.outputs:
            print(f"{next_entry.type}: {next_entry.content}")

One point deserves attention: Mistral’s documentation describes client mode—control returns to you at transfer time—but provides no method to have the target agent resume the same conversation, and conversations.append does not accept an agent_id parameter. The example therefore starts a conversation with the target agent, to whom you explicitly transmit what they need: the original request, and if applicable a summary of what has already been done. This is also an opportunity to transmit only what they must see.

The snippet above handles only a single relay. A real chain links several, which calls for a loop.

def execute_with_control(router_id, query):
    """Executes a multi-agent workflow with control at each step."""
    response = client.beta.conversations.start(
        agent_id=router_id,
        inputs=query,
        handoff_execution="client"
    )

    conversation_id = response.conversation_id
    results = []

    while True:
        handoff_found = False

        for entry in response.outputs:
            if entry.type == "agent.handoff":
                handoff_found = True
                target = entry.next_agent_id
                print(f"[LOG] Handoff to {target}")

                # Here you can add: validation, logging, metrics
                response = client.beta.conversations.start(
                    agent_id=target,
                    inputs=query,  # the original request, enriched if needed
                    handoff_execution="client",
                )
                conversation_id = response.conversation_id
                break

            elif entry.type == "message.output":
                results.append(entry.content)

        if not handoff_found:
            break

    return results

The structure is that of a pump: as long as a handoff appears in the outputs, you process it and restart; as soon as a loop iteration finds none, the chain is finished and you exit with the accumulated messages. The comment in the middle marks the location that justifies this entire mode: it is there, and only there, that your code interposes itself between two agents.

Choosing Between the Two

Server mode covers standard production. If your workflow is predictable, if you trust the agents you have written, or if you are prototyping and want to see results quickly, full automation is the right choice—and writing a control loop would be wasted effort.

Client mode is justified as soon as an external requirement enters the chain. Human validation, when an operator must approve a transfer to an agent that triggers sensitive actions. A need for logging and auditing, when compliance requires tracing each step with your own identifiers and your own timestamps. Conditional routing, when the decision depends on business rules the model cannot know—customer status, time of day, a quota. A cap, or rate limiting, when you want to bound the number of agents executed for a request. A fallback strategy, finally, when one agent’s failure must trigger something other than an error surfaced to the user.

Here is what the first of these situations looks like, reduced to its skeleton.

def workflow_with_validation(router_id, query):
    response = client.beta.conversations.start(
        agent_id=router_id,
        inputs=query,
        handoff_execution="client"
    )

    for entry in response.outputs:
        if entry.type == "agent.handoff":
            target = entry.next_agent_id
            approval = input(f"Authorize transfer to {target}? (y/n) ")

            if approval.lower() == "y":
                response = client.beta.conversations.start(
                    agent_id=target,
                    inputs=query,
                )
            else:
                print("Transfer refused.")
                break

The console input serves as a demonstration; in an application, it will be replaced by a notification to an operator and by workflow resumption when the response arrives. The principle remains the same: the transfer occurs only if your code triggers it.

The right approach is to start in server mode and switch only the day a specific constraint requires it. Switching to client mode “just in case” makes you pay immediately for the complexity of a loop and network round-trips, for control you are not yet using.

Key Takeaways

  • Server mode = fully automatic, simple and fast, no intermediate control
  • Client mode = you receive handoff events and decide what to do
  • Client mode enables validation, logging, conditional routing, and error handling
  • For most cases, server mode is sufficient—switch to client mode when you need control
  • Both modes support the same agent chains; only the control differs