Session lifecycle and listing

A session starts with agent.start_session() or agent.run(), takes turns, and ends when you cancel it, when it reaches a limit, or, for a local agent.run(), when its turn is done. Its status says where it is, and you find it again by its id or in the list of your sessions.

You need an agent that runs in your process, or one deployed with Studio, through agents serve or agents deploy.

Session statuses

Session statuses

A session is running while a turn is active, waiting when no turn is and it takes a new message, and terminated once it has ended: see SessionInfo. The turn record says how a turn ended, and what you can do next.

Here a local session takes a turn, is cancelled, and refuses a new message, while its turns stay readable:

import asyncio

from mistralai import agents

agent = agents.Agent(model="mistral-large-4", name="greeter", instructions="Answer in one word.")

async def main() -> None:
    session = await agent.start_session()
    state = await session.run("Say hi.")
    async for info in agents.list_sessions(host="local"):
        print(info.status, info.termination)

    await session.cancel()
    async for info in agents.list_sessions(host="local"):
        print(info.status, info.termination)

    try:
        await session.run("Say bye.")
    except agents.SessionTerminatedError as error:
        print(type(error).__name__, error)
    if isinstance(state, agents.Completed):
        turn_state = await session.turn(state.turn_id).get()
        print(type(turn_state).__name__, turn_state.text)

asyncio.run(main())
Output
waiting None
terminated CancelledTermination(cause=None)
SessionTerminatedError The local Session has ended
Completed Hi
End a session

End a session

await session.cancel() ends the session: it stops the running turn, if any, and the session's subagents. In your process, it returns once the session has ended and its sandbox is free. Deployed, it returns once the agent has accepted the cancel. The ended session is terminated, its termination is CancelledTermination(cause=None), and you can still read its turns. A new message to it raises agents.SessionTerminatedError.

On a deployed agent, a session stays open until you cancel it, even after the call that started it returns. agent.run() is no exception: its session stays waiting after the turn, so end it by id:

state = await agent.run("Review alert A-1042")
await agent.session(state.session_id).cancel()

In your process, a session that agent.run() starts without idempotency_key= closes once its turn and its subagents are done, and its turns stay readable. Inside an async with agents.local_runtime() block, it stays open until the block exits or you cancel it.

Cancel each session you start once you no longer need it. Otherwise a deployed session ends only at a session limit, and a local one when the event loop stops or the async with agents.local_runtime() block it ran in exits: see Local session lifecycle.

To stop only the running turn and keep the session, call await turn.interrupt(): see Turn.interrupt.

Warning

Dropping a handle does not end a session

A session handle holds nothing open, so dropping it, or leaving the call that made it, changes nothing. A deployed session, and its sandbox, stay open until a session limit ends them. To end a session, call await session.cancel().

Session limits

Session limits

By default, a deployed session ends on its own after 4 hours idle, or after 24 hours in total, whichever comes first:

LimitDefaultSet withAt most
Idle4 hoursAGENTS_SDK_IDLE_TIMEOUT_SECONDS24 hours deployed, a year with agents serve
In total, from the session's first run24 hoursAGENTS_SDK_LIFETIME_LIMIT_SECONDSa week deployed, a year with agents serve

A session is idle when no turn is working, or when the running turn only waits for the user or your app, for example on a tool approval. It is not idle while one of its subagents works for it.

At a limit, the session stops as if it had been cancelled: a running turn returns agents.Interrupted, and its subagents stop too. Its termination is an agents.CancelledTermination, with cause set to "lifetime_limit" or "idle_limit". If the session does not stop on its own within 5 minutes, it is force-ended. A session ended by a limit cannot resume: start a new one.

You set the limits, in seconds, where you deploy the agent, for all its sessions. With agents serve, set them in its environment:

export AGENTS_SDK_IDLE_TIMEOUT_SECONDS=3600      # end a session after 1 hour idle
export AGENTS_SDK_LIFETIME_LIMIT_SECONDS=28800   # and after 8 hours in total
agents serve

With agents deploy, set them with ENV in the Dockerfile, within the maxima above: see Session limits.

To raise the total limit to a week, or beyond with agents serve, raise AGENTS_SDK_EXECUTION_TIMEOUT_SECONDS too. It is a week by default, and must stay more than the total limit plus 5 minutes: otherwise agents serve or the deployed agent fails at start. For sessions of up to a year with agents serve:

export AGENTS_SDK_LIFETIME_LIMIT_SECONDS=31536000      # a year in total
export AGENTS_SDK_IDLE_TIMEOUT_SECONDS=31536000        # and a year idle
export AGENTS_SDK_EXECUTION_TIMEOUT_SECONDS=31622400   # a year and a day
agents serve

Sessions already running keep the execution timeout they started with. A MistralSandbox has its own lifetime, 24 hours by default: raise its ttl_seconds for longer sessions.

Open a session by its id

Open a session by its id

A session outlives the call that started it, so store its id and open it again later. For a deployed agent, load the agent by its name, then bind a handle to the session, from any process:

from mistralai import agents

agent = agents.Agent.load_from(agent_name="hello-agent")
session = agent.session(stored_id)
state = await session.run("Where were we?")

An in-process session opens only inside the event loop that started it: each event loop has its own local runtime. To continue one in a later process, save it with the experimental journal: see JsonFileSessionJournal.

agent.session(id) sends nothing. An id that no session has is found out by the first request, which raises agents.SessionNotFoundError. See Session handles.

List sessions

List sessions

agents.list_sessions() iterates over your root sessions, newest first, as agents.SessionInfo records. To drive a listed session, for example to cancel it, bind a handle with agent.session(info.id):

async for info in agents.list_sessions(status="waiting", limit=2):
    print(info.id, info.status, info.created_at)
Output
01a18cb6-01c2-75dd-baba-b9d46f52d640 waiting 2026-10-30 12:08:20.554000+00:00
01a18cb5-5c3e-72b4-a90f-63a92036b762 waiting 2026-10-30 12:07:38.207000+00:00

Once a session ends, its termination says how. Match on the class, and keep a case for UnknownTermination, so your code still runs on an outcome added after your SDK version:

match info.termination:
    case agents.CompletedTermination(stop_reason=reason):
        print("completed", reason)
    case agents.CancelledTermination(cause=cause):
        print("cancelled", cause)
    case agents.FailedTermination(error=error):
        print("failed", error.message)
    case agents.UnknownTermination(outcome=outcome, raw=raw):
        print("ended:", outcome)  # e.g. "future_outcome"
    case None:
        print("still running")

A deployed session's record always has stop_reason=None. The result of its last turn has it: stop_reason on the Completed that turn.result() returns.

Every field, keyword and termination class, the subagent scopes, and the agents.Client for another API key or cloud: Listing and client.

List local sessions

List local sessions

agents.list_sessions(host="local") lists the in-process sessions of the running event loop instead, with the same keywords. Iterate inside the event loop that started the sessions: each asyncio.run has its own runtime. An in-process session that ended is listed as "terminated" until the event loop stops.

Next steps

Next steps