Feature highlights

Each card shows one feature in a few lines of Python, with the output of a real run.

To run the examples, install the SDK and paste each one into main() of the file below. Its tools return fixed text.

main.py
import asyncio

from pydantic import BaseModel

from mistralai import agents

@agents.tool(annotations=agents.ToolAnnotations(read_only_hint=True))
async def get_order(order_id: str) -> str:
    """Get the status of an order."""
    return f"Order {order_id}: delivered, 49 EUR"

@agents.tool(annotations=agents.ToolAnnotations(read_only_hint=True))
async def list_orders(country: str) -> list[str]:
    """List this week's orders for a country, or for every country with 'any'."""
    return [f"order {number} ({country})" for number in range(1040, 1045)]

@agents.tool
async def refund_order(order_id: str) -> str:
    """Refund an order."""
    return f"Order {order_id}: refunded"

agent = agents.Agent(
    model="mistral-large-4",
    name="support-agent",
    instructions="Answer questions about orders in one plain sentence.",
    tools=[get_order, list_orders, refund_order],
)

async def main() -> None:
    ...  # paste an example's code here

if __name__ == "__main__":
    asyncio.run(main())
THE TURNCompletedget_order("1042")refund_order("1042")time →ON_APPROVALread-onlyAllowApproval()not read-onlyKeepCallbackOpen()waitsA PERSONturn.answer(id, AllowApproval())agent: "Order 1042 is delivered, and it is now refunded."the model reads: "Tool call was rejected during approval."
Step 1 of 5. With permission_mode="ask", each tool call waits for an approval. The model calls get_order("1042"). on_approval runs your policy: get_order is read-only, so it returns AllowApproval(), and the call runs.
Approve tool calls

Approve tool calls

With permission_mode="ask", every tool call needs an approval. Your code can give it, or leave the call for a person to approve.

def policy(
    ctx: agents.ApprovalCallbackContext, request: agents.ApprovalRequest
) -> agents.AllowApproval | agents.KeepCallbackOpen:
    if request.tool.annotations.read_only_hint:
        return agents.AllowApproval()
    return agents.KeepCallbackOpen()  # anything else waits for a person

careful = agents.Agent(
    model="mistral-large-4",
    tools=[get_order, refund_order],
    permission_mode="ask",
)
session = await careful.start_session(on_approval=policy)
state = await session.run("Check the status of order 1042, then refund it.")
while isinstance(state, agents.Blocked):  # refund_order waits
    turn = session.turn(state.turn_id)
    for callback in state.callbacks:
        await turn.answer(callback.id, agents.AllowApproval())  # a person allows
    state = await turn.result(after=state)
if isinstance(state, agents.Completed):
    print(state.text)
await session.cancel()
Output
Done! Here's a summary:
• Status check: Order 1042 was delivered, amount 49 EUR.
• Refund: Order 1042 has been refunded ✅

The loop runs until the turn ends: turn.result(after=state) waits for the turn again, from where it stopped. See Approvals and Step through a session.

YOUTHE TURNtime →list_orders("any")list_orders("France")goes in before the next model callCompletedCONVERSATIONwhat the model readsyouList this week's orders, then count them.tool calllist_orders("any")tool result5 orders: 1040 … 1044 (any)tool calllist_orders("France")tool result5 orders: 1040 … 1044 (France)agentThere are 5 orders from France this week.steerOnly the orders from France.waiting
Step 1 of 5. send_message starts a turn. The model reads the conversation and calls list_orders("any").Each band is a model call.
Steer a turn while it runs

Steer a turn while it runs

Send a message to the agent while it works, to change what it does. on_event= shows the turn's events as they come.

def show(event: agents.Event) -> None:
    match event:
        case agents.EntryCompleted(entry=agents.MessageEntry(role="user") as message):
            print("user:", message.text)
        case agents.EntryCompleted(entry=agents.ToolCallEntry(name, arguments)):
            print("calling", name, arguments)
        case agents.TextDelta(text=text):
            print(text, end="", flush=True)  # the reply, while the model writes it

session = await agent.start_session()
turn = await session.send_message("List this week's orders, then count them.")
await turn.steer("Only the orders from France.")
state = await turn.result(on_event=show)
print()
await session.cancel()
Output
user: List this week's orders, then count them.
user: Only the orders from France.
calling client.list_orders {'country': 'France'}
This week's orders from France:
1. order 1040 (France)
2. order 1041 (France)
3. order 1042 (France)
4. order 1043 (France)
5. order 1044 (France)
Total: 5 orders

The steer joins the running turn. Here it arrives before the first tool call, so the agent lists only the orders from France. See Build a session app and Follow a turn while you wait.

YOUR CODEclass Order(BaseModel): order_id: str delivered: bool price_eur: intJSON SCHEMA"order_id": string"delivered": boolean"price_eur": integerstrictno output_type: no schemaMODELANSWER{"order_id": "1042", "delivered": true, "price_eur": 49}PYDANTICorder_id✓delivered✓price_eur✓STATE.OUTPUTorder_id='1042'delivered=Trueprice_eur=49an Order
Step 1 of 5. agent.run(…, output_type=Order) turns Order into a JSON schema. It goes with the model call as the answer format, in strict mode.Read the top row left to right, then the bottom row right to left.
Get a typed result in one call

Get a typed result in one call

run sends one message and waits for the answer. Pass a Pydantic model as output_type to get the answer as an object of that model.

class Order(BaseModel):
    order_id: str
    delivered: bool
    price_eur: int

typed = agents.Agent(model="mistral-medium-latest", tools=[get_order])
state = await typed.run("What is the status of order 1042?", output_type=Order)
if isinstance(state, agents.Completed):
    print(state.output)
Output
order_id='1042' delivered=True price_eur=49

See One call and Structured output.

MON 17:40OVERNIGHTTUE 09:10asklaptop closedfollow-upYOUR LAPTOPMonday's processclosed: the process is goneTuesday: a new processagent = agents.Agent.load_from(agent_name="payment-reviewer")session = await agent.start_session(on_approval=review_policy)await session.run("Summarise alert 4711: who paid whom, and why…")stored_id = session.id # keep it, in your databasenothing runs here overnightagent = agents.Agent.load_from(agent_name="payment-reviewer")session = agent.session(stored_id, on_approval=review_policy)await session.run( "Compare it with the customer's payments of the last 12 months.")THE SESSION, ON MISTRALstatus: runningstatus: waitingMonTueyouSummarise alert 4711: who paid whom, and why…tool callget_alert("4711")tool result€9,800, Mr Le Chonk → a new accountagent€9,800 to a new beneficiary in the UAE…youCompare it with the customer's payments of…tool callget_account_history(…)tool resultlast 12 months: largest payment €1,150agent€9,800 is above every payment in 12 months.Tuesday's call reads Monday's turn: get_alert does not run againwaiting for 0 h
Step 1 of 5. Monday 17:40. Your script loads the deployed payment-reviewer, starts a session and asks: "Summarise alert 4711: who paid whom, and why it was flagged." The agent calls get_alert and answers.
Interact with an agent

Interact with an agent

A session keeps everything the agent has done, so each message builds on the work before it. It can hold a quick exchange, work that runs for hours, or a follow-up the next day.

# payment-reviewer and review_policy come from the suspicious payment review example
agent = agents.Agent.load_from(agent_name="payment-reviewer")
session = await agent.start_session(on_approval=review_policy)
await session.run("Summarise alert 4711: who paid whom, and why it was flagged.")
stored_id = session.id  # keep it, in your database

# The next day, in a new process: the same session, by its id
agent = agents.Agent.load_from(agent_name="payment-reviewer")
session = agent.session(stored_id, on_approval=review_policy)
state = await session.run("Compare it with the customer's payments of the last 12 months.")
if isinstance(state, agents.Completed):
    print(state.text)
Output
€9,800 is above every payment of the last 12 months: the largest was €1,150.

See Sessions.

YOUR COMPUTERDEPLOYAT SCALEmain.pyagents chatagent = agents.Agent( model="mistral-large-4", name="support-agent", tools=[get_order, list_orders, refund_order], environment=agents.MistralSandbox(),)$ uv run --env-file .env agents chatTalking to the agent in this process(session 4236c157-…).> Has order 1042 shipped?→ get_order {"order_id": "1042"}← get_order doneYes, order 1042 was delivered.session 4236c157own sandboxone session, one sandbox:a container on Mistral CloudDEPLOY$ git add --all && git commit -m "Support agent"$ git push$ uv run --env-file .env agents deployDeploying support-agent to managed platform...Agent support-agent is live after 1m24s.Mistral builds thepushed commitsupport-agent: liveYOUR APP, THEN MISTRALsessions running: 0agent = agents.Agent.load_from(agent_name="support-agent")state = await agent.run(question) # each call starts a sessionsession 1own sandboxsession 2own sandboxsession 3own sandboxsession 4own sandboxsession 5own sandboxsession 6own sandboxsession 7own sandboxsession 8own sandbox
Step 1 of 4. On your computer: add environment=agents.MistralSandbox() to main.py, and agents chat runs the agent in this process. The session gets its own sandbox, a container on Mistral Cloud.
Deploy to Mistral

Deploy to Mistral

In a project made by agents init, with the main.py above (its agent is support-agent): chat with the agent in your terminal, then push and deploy it. The deploy builds the commit you pushed to GitHub, in the repository that Deploy your agent sets up:

uv run --env-file .env agents chat
git add --all && git commit -m "Support agent"
git push
uv run --env-file .env agents deploy

The figure adds environment=agents.MistralSandbox() to the agent in main.py: then each session gets its own container on Mistral, deleted when the session closes. Without it, the agent has no sandbox (NoSandbox, the default).

Your app loads the deployed agent by its name, then runs it like a local one:

agent = agents.Agent.load_from(agent_name="support-agent")
state = await agent.run("Has order 1042 shipped?")
if isinstance(state, agents.Completed):
    print(state.text)
Output
Yes, order 1042 was delivered.

See Deploy your agent and Deploy.

YOUR APPyour processexited: nothing of yours runslater: a new processagent = agents.Agent.load_from(agent_name="private-repository-cleanup-agent")session = await agent.start_session()turn = await session.send_message(cleanup_prompt())session_id, turn_id = session.id, turn.id # store them# then exit: the turn goes on, on Mistralagent = agents.Agent.load_from(agent_name="private-repository-cleanup-agent")session = agent.session(session_id)state = await session.turn(turn_id).result()print(state.text)await session.cancel()THE TURN ON MISTRAL, IN THE SESSION'S OWN SANDBOXTURNRunningCompletedelapsed 1 minmodel calls: 2file_system.bashgit clone github.com/your-org/your-private-repodonefile_system.read_fileCONTRIBUTING.mdfile_system.bashgit checkout -b agent-cleanup/…-tax-helpersdonefile_system.editsrc/billing/tax.pyfile_system.editsrc/billing/invoice.pyfile_system.bashpytest tests/billing2 failedfile_system.editsrc/billing/tax.pyfile_system.bashpytest tests/billing41 passedfile_system.bashgit push origin agent-cleanup/…-tax-helpersdonefile_system.bashopen the pull request on GitHub#123answerhttps://github.com/your-org/your-private-repo/pull/123your new process printshttps://github.com/your-org/your-private-repo/pull/123then session.cancel(): the session closes and its sandbox is deleted
Step 1 of 5. Your app loads the deployed cleanup agent, starts a session and sends the task. The agent starts in its own sandbox: it clones the repository.
Run in the background

Run in the background

Once you deploy with Studio, self-hosted or Mistral-managed, execution is durable and runs in the background. Your app can exit and get the result later. This example uses the agent from Deploy to Mistral. The figure shows the same calls with the coding agent of Private repository cleanup agent, which works for minutes in its own sandbox.

agent = agents.Agent.load_from(agent_name="support-agent")
session = await agent.start_session()
turn = await session.send_message("List this week's orders, then count them.")
session_id, turn_id = session.id, turn.id  # store them, then exit

# Later, after your app restarts:
agent = agents.Agent.load_from(agent_name="support-agent")
session = agent.session(session_id)
state = await session.turn(turn_id).result()
if isinstance(state, agents.Completed):
    print(state.text)
await session.cancel()
Output
There are 5 orders this week.

See Find a session again and Open a session by its id.

Next steps

Next steps

  • Quickstart: install the SDK, then build and chat with a first agent, step by step.
  • Deploy your agent: run it on Mistral and call it from your app.