Session and Turn
The handles of a session and of its turns, and what a turn takes as input.
| Name | What it is |
|---|---|
Session | A session, named by its ID. |
Turn | A turn of a session, named by its ID. |
SessionInput | A value you pass when you start a session, which tools and hooks can read. |
Depends | Mark an Annotated parameter as a session input: Annotated[T, agents.Depends()]. |
HistoryImportEntry | An entry of a history that seeds a new session, as history= takes it: a message, the model's reasoning, a tool call, a tool result or a snapshot. |
HistoryImportMessage | A message of the history: what the user, the model or the system said. |
HistoryImportReasoning | The model's reasoning, as a history entry. |
HistoryImportToolCall | A tool call the model made, as a history entry. |
HistoryImportToolResult | The result of a tool call, as a history entry: result or error, not both. |
HistoryImportSnapshot | Data saved in the history that the model does not see. |
TextContentBlock | A block of text. |
ImageContentBlock | An image, by URI. |
AudioContentBlock | An audio clip, by URI. |
ResourceLinkContentBlock | A link to a resource, such as a file, that the block does not hold. |
EmbeddedResourceContentBlock | A resource, such as a file, held in the block as text or as base64 data. |
UnknownContentBlock | A content block of a type added after your SDK version. |
ContentAnnotations | MCP block annotations: who a block is for and how much it matters. |
SessionSession
class SessionA session, named by its ID. Binding one with agent.session(session_id) sends no request.
Get one with await agent.start_session(), or bind a stored ID with
agent.session(session_id). Both keep the agent's output_type for run, and for the
get and result of the session's turns.
on_approval answers the approvals of the session and its subagents while session.run or
turn.result waits, with AllowApproval, DenyApproval or agents.KeepCallbackOpen().
on_sign_in answers their sign-ins with a SignInAnswer. on_client_tool answers their
client tool calls, so the wait does not return Blocked on them. An approval or sign-in handler
that raises, or returns anything else, leaves its callback open, and the waiting call raises while
the turn keeps running. A client tool answer or on_client_tool handler that raises follows the
agent's tool_errors, as on agent.run: by default the model reads that the call failed
and the wait goes on. Two waits at once in one event loop share the answers: the answer runs once
per client tool call.
Two handles are equal when they name the same session in the same place: the same Mistral API for a deployed agent, however you loaded it, or the same local runtime. Keep them in a set or as dictionary keys.
| Attribute | Type | Description |
|---|---|---|
id | str | The session's ID. |
Session.turnSession.turn
def turn(turn_id: str) -> TurnReturn the handle of a turn of this session. Sends no request.
Session.send_messageSession.send_message
async def send_message(
content: str | Sequence[TextContentBlock | ImageContentBlock | Mapping[str, Any]],
*,
idempotency_key: str | None = None,
) -> TurnSend a message: the session starts a turn, or adds the message to the running one.
turn.started says which: True for a new turn, False for the running one.
Raises agents.SessionBusyError when the session still cannot take the message after its retries;
nothing was applied.
| Parameter | Type | Default | Description |
|---|---|---|---|
content | str | Sequence[TextContentBlock | ImageContentBlock | Mapping[str, Any]] | required | The message: a string or content blocks. |
idempotency_key | str | None | None | Makes the send safe to retry: the same key returns the same turn, and reusing it for another message raises agents.SessionIdempotencyConflictError. |
Session.start_turnSession.start_turn
async def start_turn(
content: str | Sequence[TextContentBlock | ImageContentBlock | Mapping[str, Any]],
*,
idempotency_key: str | None = None,
) -> Turn | BusyStart a new turn. Returns Busy, with the running turn in busy.turn, when one is active.
The same idempotency_key returns the turn that key started; reusing it for another
message raises agents.SessionIdempotencyConflictError while the session runs. A retry
with a key refused with Busy starts a turn once the running one ends. A start never
joins a running turn, so turn.started is True.
Raises SessionBusyError when the session cannot take a start for another reason, such
as a limit on running subagents; nothing was applied.
Session.runSession.run
async def run(
content: str | Sequence[TextContentBlock | ImageContentBlock | Mapping[str, Any]],
*,
output_type: type[BaseModel] | None = None,
idempotency_key: str | None = None,
timeout: float | None = None,
on_event: Callable[[Event], Awaitable[None] | None] | None = None,
) -> TurnState[Any] | BusyStart a turn and wait for it to end, or to wait on a callback; return its record.
Returns Busy when another turn is active. It is start_turn, then
turn.result(timeout=, on_event=): leaving the wait does not stop the turn. With the
handle's on_client_tool, it raises what Turn.result raises for a client tool answer.
| Parameter | Type | Default | Description |
|---|---|---|---|
content | str | Sequence[TextContentBlock | ImageContentBlock | Mapping[str, Any]] | required | The message that starts the turn: a string or content blocks. |
output_type | type[BaseModel] | None | None | A Pydantic model that validates the output of a Completed record (see Turn.result). Without it, the handle's agent's output_type does, if it has one. |
idempotency_key | str | None | None | Names the start, so a retry with it returns the turn it started. |
timeout | float | None | None | The most seconds to wait for the turn, not counting the start. An on_approval or on_sign_in handler's time counts; your client-tool code does not. None waits with no limit. |
on_event | Callable[[Event], Awaitable[None] | None] | None | None | A plain or async function called with each of the turn's events while the call waits. When the turn has ended, the last event is TurnEnded. |
Session.iter_eventsSession.iter_events
def iter_events(*, after: TurnState[object] | str | None = None) -> AsyncIterator[Event]Yield the session's events after after, a record or a cursor, or from its start.
It follows the session until the session ends, and never changes anything. It reads each
ended turn, so each TurnEnded carries its record.
| Raises | |
|---|---|
ObservationLost | The session's events could not be read again; continue after its cursor. |
InvalidCursorError | At the first iteration, after is a record or a cursor of another session, or a cursor that is not valid. |
Session.cancelSession.cancel
async def cancel() -> NoneEnd the session and its subagents. A session that already ended is left as it is.
Running turns are interrupted, and the session takes no new command: a later send raises
SessionTerminatedError. Its history stays readable. A deployed cancel returns once the
deployment accepts it; a local one returns once the session has ended and released its
sandbox. A subagent cannot be cancelled on its own. Cancel its root session instead.
Session.subagentsSession.subagents
async def subagents() -> list[Session]Return the handles of this session's direct subagents, newest first.
A subagent's handle reads, and its turns take interrupt: send_message, start_turn,
run and cancel on the handle, and steer on its turns, raise
UnsupportedSessionProcedureError. To redirect a subagent, message or steer its parent;
to stop it, cancel its root session. It has this handle's on_approval, on_sign_in and
on_client_tool.
TurnTurn
class TurnA turn of a session, named by its ID. Binding one with session.turn(turn_id) sends no request.
Get one with session.turn(turn_id), or from a send. Two handles of the same turn of the
same session are equal.
| Attribute | Type | Description |
|---|---|---|
id | str | The turn's ID. |
started | bool | None | True for a turn a send started, False for a running turn a send joined. None for a handle that did not come from a send, such as one bound by ID. |
Turn.getTurn.get
async def get() -> TurnState[Any]Read this turn's state now. Never waits and never changes the turn.
Returns Running, Blocked (the turn waits on a callback), or the record of how the
turn ended: Completed, Interrupted, Failed or UnknownOutcome. Completed.output
is the turn's text, or, when the handle's agent has an output_type, that text parsed as on
result(output_type=...). The
record's cursor is the session's last event ID when the record was read. The session's
active turn is Blocked also while one of its subagents, at any depth, waits on a
callback, such as an approval: Blocked.callbacks lists it with the subagent's IDs.
A turn of a session that ended is never Running. When the session did not report how the
turn ended, the turn reads as the session ended: for a cancelled session, Interrupted with
cause="cancelled".
A turn a send returned is Running until the session lists it: a deployed agent answers the
send before it does.
| Raises | |
|---|---|
TurnNotFoundError | The session has no turn with this ID. Never for a turn a send returned, nor for busy.turn. |
Turn.resultTurn.result
async def result(
*,
output_type: type[BaseModel] | None = None,
after: TurnState[object] | str | None = None,
timeout: float | None = None,
on_event: Callable[[Event], Awaitable[None] | None] | None = None,
) -> TurnState[Any]Wait for the turn to end, or to wait on a callback, and return its record.
Returns Completed, Interrupted, Failed or UnknownOutcome when the turn ends,
and Blocked when it waits on a callback opened after after that is still open. At the
deadline, it returns the turn's record at that moment, Running or Blocked, and raises
nothing.
Leaving the wait sends nothing, and the turn keeps running. A cancel, such as
asyncio.wait_for or Ctrl-C, gets a note with the IDs and how to stop the turn.
Without after, a turn that waits on any callback returns at once. Pass this turn's last
record as after to wait past the callbacks you already handled.
| Parameter | Type | Default | Description |
|---|---|---|---|
output_type | type[BaseModel] | None | None | A Pydantic model that the turn's text is parsed into, as Completed.output. Defaults to the handle's agent's output_type. |
after | TurnState[object] | str | None | None | A record of this session, or its cursor, to read the turn's events after. Defaults to this turn's start: callbacks of earlier turns don't count. Pass this turn's record, or one read before this turn started. Another turn's record, read while this turn ran, can miss some of this turn's events. |
timeout | float | None | None | The most seconds to wait for the turn. An on_approval or on_sign_in handler's time counts; your client-tool code does not. None waits as long as the turn does. |
on_event | Callable[[Event], Awaitable[None] | None] | None | None | A plain or async function called with each of the turn's events, in order, while the call waits. When the turn has ended, the last event is TurnEnded. |
| Raises | |
|---|---|
ObservationLost | The session's events, or its subagents' callbacks, could not be read; the turn keeps running. |
TurnNotFoundError | The session has no turn with this ID. Never for a turn a send returned, nor for busy.turn: until the session lists it, the wait follows its events. |
InvalidCursorError | after is a record or a cursor of another session, raised before any read, or a cursor the session never had, raised only when the wait reads the events. |
ClientToolAnswerError | The handle's on_client_tool answered a call, and the answer could not be sent; the call stays open. |
Exception | With tool_errors="raise", the exception a client tool answer or on_client_tool handler raised; its call stays open. |
Turn.iter_eventsTurn.iter_events
def iter_events(*, after: TurnState[object] | str | None = None) -> AsyncIterator[Event]Yield this turn's events after after, or from its start, up to its TurnEnded.
It yields the events the session has, then the live ones, with no time limit: a turn that
waits on an approval can send no event for hours. When the turn ended, the last is
TurnEnded, with its record, and a position at or after its end yields nothing, at once.
It changes nothing.
| Raises | |
|---|---|
ObservationLost | The session's events could not be read again; continue after its cursor. |
TurnNotFoundError | At the first iteration, the session has no turn with this ID. Never for a turn a send returned, nor for busy.turn. |
InvalidCursorError | At the first iteration, after is a record or a cursor of another session, or a cursor that is not valid. |
Turn.steerTurn.steer
async def steer(
content: str | Sequence[TextContentBlock | ImageContentBlock | Mapping[str, Any]],
) -> Turn | TurnConflictAdd a message to this turn while it runs.
Returns TurnConflict when this turn is no longer the active one: the message is not added.
Turn.answerTurn.answer
async def answer(callback_id: str, answer: object) -> Answered | AlreadySettled | NotFoundAnswer any open callback of this turn's session or of its subagents, from any turn, by its callback ID. The turn's ID is not used: a callback's ID names it in its session.
An approval (OpenApproval) takes AllowApproval or DenyApproval. A sign-in
(OpenSignIn) takes a SignInAnswer that names each source it asks for, and only
those. A client tool call (OpenClientToolCall) takes the tool's result: what an answer
function would return, such as a model, a dict or a ToolResult. When the stub is in the
tools= of the agent or of its subagents, the result is checked against the stub's return
type and sent as its answer is. Otherwise it is sent as JSON, unchecked: a tool built with
output_schema=, a hook's tool, a tool ID that two different stubs share, or a handle
from Agent.load_from().
Reads the session once, then answers. Returns Answered when the callback was open,
AlreadySettled when it is no longer open (another client answered it first, or it
closed), and NotFound when the session never opened it. A subagent's open callback,
listed in the root's Blocked.callbacks, is answered on the subagent's session, and the
record names that session; once it is no longer open, the root says NotFound and the
subagent's own session, agent.session(callback.session_id), says AlreadySettled.
For an approval, Answered.answer is the answer that counted: two clients that answer
at the same time both get Answered, with the answer of the one that came first, so
compare it with yours. It is None when the answer that counted could not be read in time;
read the turn to see the outcome. For a client tool call and a sign-in it is None: the
first result sent counts. Local and deployed sessions do the same.
An approval's answer is AllowApproval or DenyApproval, never a bool, a dict or
None. KeepCallbackOpen() is a handler's return, never an answer: to leave a
callback open, do not answer it.
| Parameter | Type | Default | Description |
|---|---|---|---|
callback_id | str | required | The ID of the open callback, as in Blocked.callbacks. |
answer | object | required | The answer of the callback's kind: AllowApproval or DenyApproval, a SignInAnswer, or a client tool's result. |
| Raises | |
|---|---|
ValueError | callback_id is not a UUID (nothing is sent). |
SdkError | answer is KeepCallbackOpen() or does not fit the callback, or two subagents have a callback open with that ID. Also when answer allows a hook's approval for the whole session (AllowApproval(scope="session")), or the callback is a client tool call of a tool the platform provides, such as one in the connectors namespace. Nothing is sent. A sign-in arrives as OpenSignIn and takes a SignInAnswer. |
ObservationLost | The callback is open nowhere, and the session's events could not be read. |
Turn.interruptTurn.interrupt
async def interrupt() -> Turn | TurnConflictStop this turn; the session stays open for new turns.
Returns TurnConflict when this turn is no longer the active one.
Input
SessionInputSessionInput
class SessionInput(pydantic.BaseModel)A value you pass when you start a session, which tools and hooks can read.
Subclass it with the fields you need, such as a repository or an organization, and set
input_id. A tool, a hook or a sandbox hook reads it with a parameter annotated
Annotated[YourInput, agents.Depends()]; the model never sees it. Never put a credential in
it: the session keeps it for its whole lifetime.
A rule that only a validator expresses is checked where the value is used, and a rejected value fails that use. Never put the value in a validator's message, since the message reaches turn results and the model.
A session keeps its inputs for its whole lifetime, across new deployments of the agent, so a change to a class must keep accepting the values its previous version accepted.
| Attribute | Type | Description |
|---|---|---|
input_id | str | The input's stable name, which a session uses to find it. It is not part of the value. |
DependsDepends
class Depends()Mark an Annotated parameter as a session input: Annotated[T, agents.Depends()].
The annotated type T is the input class, declared once.
HistoryImportEntryHistoryImportEntry
type HistoryImportEntry = (
HistoryImportMessage
| HistoryImportReasoning
| HistoryImportToolCall
| HistoryImportToolResult
| HistoryImportSnapshot
)An entry of a history that seeds a new session, as history= takes it: a message, the model's reasoning, a tool call, a tool result or a snapshot. Give each entry as one of the classes below, or as a dict of the same fields, in snake case or camelCase, with its type: "message", "reasoning", "tool_call", "tool_result" or "snapshot".
HistoryImportMessageHistoryImportMessage
class HistoryImportMessage(
*,
role: Literal['system', 'user', 'assistant'],
content: list[ContentBlock] = [],
)A message of the history: what the user, the model or the system said.
| Attribute | Type | Description |
|---|---|---|
type | Literal['message'] | Always "message". |
HistoryImportReasoningHistoryImportReasoning
class HistoryImportReasoning(*, text: str)The model's reasoning, as a history entry.
| Attribute | Type | Description |
|---|---|---|
type | Literal['reasoning'] | Always "reasoning". |
HistoryImportToolCallHistoryImportToolCall
class HistoryImportToolCall(
*,
tool_call_id: str,
tool_kind: Literal['builtin', 'local', 'mcp', 'client', 'plugin', 'connector'] = 'local',
name: str,
arguments: JsonValue = None,
)A tool call the model made, as a history entry.
| Attribute | Type | Description |
|---|---|---|
type | Literal['tool_call'] | Always "tool_call". |
HistoryImportToolResultHistoryImportToolResult
class HistoryImportToolResult(
*,
tool_call_id: str,
name: str | None = None,
result: JsonValue = None,
error: ProtocolError | None = None,
annotations: dict[str, JsonValue] = {},
)The result of a tool call, as a history entry: result or error, not both.
| Attribute | Type | Description |
|---|---|---|
type | Literal['tool_result'] | Always "tool_result". |
HistoryImportSnapshotHistoryImportSnapshot
class HistoryImportSnapshot(*, task_name: str, snapshot_type: str, value: JsonValue = None)Data saved in the history that the model does not see. The session's history shows it as an agents.UnknownEntry of type "checkpoint": raw["message"] is task_name:snapshot_type, and raw["details"] is value.
| Attribute | Type | Description |
|---|---|---|
type | Literal['snapshot'] | Always "snapshot". |
TextContentBlockTextContentBlock
class TextContentBlock(
*,
annotations: ContentAnnotations | None = None,
meta: dict[str, JsonValue] | None = None,
text: str = '',
)A block of text.
| Attribute | Type | Description |
|---|---|---|
type | Literal['text'] | Always "text". |
ImageContentBlockImageContentBlock
class ImageContentBlock(
*,
annotations: ContentAnnotations | None = None,
meta: dict[str, JsonValue] | None = None,
uri: str,
media_type: str | None = None,
alt_text: str | None = None,
)An image, by URI.
| Attribute | Type | Description |
|---|---|---|
type | Literal['image'] | Always "image". |
ImageContentBlock.from_bytesImageContentBlock.from_bytes
@classmethod
def from_bytes(
data: bytes,
media_type: str | None = None,
*,
alt_text: str | None = None,
annotations: ContentAnnotations | None = None,
) -> ImageContentBlockAn inline image; the media type is read from PNG, JPEG, GIF or WebP bytes.
ImageContentBlock.from_pathImageContentBlock.from_path
@classmethod
def from_path(
path: str | PathLike[str],
media_type: str | None = None,
*,
alt_text: str | None = None,
annotations: ContentAnnotations | None = None,
) -> ImageContentBlockAn inline image read from a local file. Its base64 is at most 10,000,000 characters, a file of about 7.5 MB; a larger file raises ValueError.
AudioContentBlockAudioContentBlock
class AudioContentBlock(
*,
annotations: ContentAnnotations | None = None,
meta: dict[str, JsonValue] | None = None,
uri: str,
media_type: str | None = None,
)An audio clip, by URI.
| Attribute | Type | Description |
|---|---|---|
type | Literal['audio'] | Always "audio". |
ResourceLinkContentBlockResourceLinkContentBlock
class ResourceLinkContentBlock(
*,
annotations: ContentAnnotations | None = None,
meta: dict[str, JsonValue] | None = None,
uri: str,
name: str | None = None,
title: str | None = None,
description: str | None = None,
media_type: str | None = None,
size: int | None = None,
)A link to a resource, such as a file, that the block does not hold.
| Attribute | Type | Description |
|---|---|---|
type | Literal['resource_link'] | Always "resource_link". |
EmbeddedResourceContentBlockEmbeddedResourceContentBlock
class EmbeddedResourceContentBlock(
*,
annotations: ContentAnnotations | None = None,
meta: dict[str, JsonValue] | None = None,
uri: str,
media_type: str | None = None,
text: str | None = None,
blob: str | None = None,
)A resource, such as a file, held in the block as text or as base64 data.
| Attribute | Type | Description |
|---|---|---|
type | Literal['embedded_resource'] | Always "embedded_resource". |
UnknownContentBlockUnknownContentBlock
class UnknownContentBlock(*, original_type: str)A content block of a type added after your SDK version. Its type is unknown, so a match on type still works for the known blocks; original_type names its type, and its other fields are kept as they came. A turn's input cannot hold one.
| Attribute | Type | Description |
|---|---|---|
type | Literal['unknown'] | Always "unknown". |
ContentAnnotationsContentAnnotations
class ContentAnnotations(
*,
audience: list[Literal['user', 'assistant'] | str] | None = None,
priority: float | None = None,
last_modified: str | None = None,
)MCP block annotations: who a block is for and how much it matters.
A tool result block whose audience excludes "assistant" is never sent to the model; a client may hide a block whose audience excludes "user". priority must be between 0 and 1.