Turns and the turn record

A turn is the work the agent does to answer one message. The SDK runs it, and gives you its record: one value that says how the turn ended, or what it waits for. You start a turn, wait for it, and read its record. Three calls start a turn:

CallWhat it does
session.run(message)Starts a turn and waits for its record.
session.start_turn(message), then turn.result()Starts a turn and returns its handle at once. turn.result() waits for the record.
session.send_message(message)Starts a turn, or adds the message to the turn that runs.
Start a turn

Start a turn

reviewer.py is the payment reviewer of What an agent is. report shows what each record holds:

import asyncio

from mistralai import agents

from reviewer import payment_reviewer

def report(state: agents.TurnState) -> None:
    match state:
        case agents.Completed(tool_calls=calls):
            print("completed:", [call.name for call in calls])
        case agents.Blocked(callbacks=callbacks):
            print("waits for:", [callback.title for callback in callbacks])
        case agents.Interrupted(cause=cause):
            print("stopped:", cause)
        case agents.Failed(error=error):
            print("failed:", error.message)
        case agents.Running():
            print("still running")
        case _:  # agents.UnknownOutcome: an outcome added after your SDK version
            print("ended:", type(state).__name__)

async def main() -> None:
    session = await payment_reviewer.start_session()
    turn = await session.start_turn(
        "Review transaction monitoring alert 4711.",
        idempotency_key="alert:4711:review",
    )
    if isinstance(turn, agents.Busy):
        turn = turn.turn  # another turn runs: follow it
    state = await turn.result()
    report(state)

    state = await session.run("Which case did you open? Reply with the case id only.")
    if isinstance(state, agents.Completed):
        print(state.text)
    report(state)
    await session.cancel()

asyncio.run(main())
Output
completed: ['client.get_alert', 'client.open_case']
CASE-4711
completed: []

The second turn called no tool. A tool you wrote shows as client.<name>: see Tool namespaces.

When a turn already runs

When a turn already runs

A session runs one turn at a time. While a turn runs, start_turn() and session.run() start nothing and return agents.Busy, whose turn is the running turn's handle, as in main() above. send_message() adds the message to the running turn instead.

The turn record

The turn record

Every call that reads or waits for a turn returns the same record: turn.result(), turn.get(), session.run(), agent.run(), and the state of a TurnEnded event. Match on its type:

Runningthe turn worksBlockedwaits for an answerTHE TURN ENDSCompletedread output, textInterruptedsomeone stopped itFailederror, maybe retryablecallbackanswersession.start_turn(...)session.start_turn(...)→ agents.Busyerror.retryable True: send the message againone turn at a time: Busy.turn is the running turn
Step 1 of 6. session.start_turn() starts a turn. While it works, the record is Running.
RecordWhat happenedWhat you do next
agents.CompletedThe turn ended normally.Read output or text, and send the next message.
agents.BlockedThe turn waits for an answer, such as a tool approval.Answer each of its callbacks: see Approvals.
agents.RunningYour wait ended before the turn did.Wait again with turn.result(after=state).
agents.InterruptedSomeone or something stopped the turn.Send the next message, unless the session has ended.
agents.FailedThe turn ended with an error. It is returned, not raised.When error.retryable is True, send the message again: see When a turn fails.

Keep a case _: for agents.UnknownOutcome, an outcome added after your SDK version. Every record also has session_id, turn_id, cursor and text, the turn's latest answer text. Turn records lists the fields of each type, and the stop reasons of a Completed turn.

Wait with a timeout

Wait with a timeout

timeout= bounds your wait, not the turn. At the deadline, the call returns the record as it is then, often Running, and the turn keeps running. Wait again with turn.result(after=state): see Turn.result.

To stop a turn, call await turn.interrupt(). To end the session, call await session.cancel().

  • Events: follow a turn step by step while it runs.
  • History entries: read a turn's entries, or start a session from existing history.
  • Approvals: answer a Blocked turn's approvals.