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
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())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.
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
By default, a deployed session ends on its own after 4 hours idle, or after 24 hours in total, whichever comes first:
| Limit | Default | Set with | At most |
|---|---|---|---|
| Idle | 4 hours | AGENTS_SDK_IDLE_TIMEOUT_SECONDS | 24 hours deployed, a year with agents serve |
| In total, from the session's first run | 24 hours | AGENTS_SDK_LIFETIME_LIMIT_SECONDS | a 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 serveWith 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 serveSessions 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
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
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)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
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
- Sessions: start sessions and send messages.
- Local, in-process: end local sessions, and keep them after the process exits.
- Agent lifecycle: update and shut down a deployed agent.