Turn records
What a read or a wait of a turn returns: a TurnState, one of six records, and the values that the calls which start, steer or interrupt a turn return.
Guides: Turns and Session statuses.
| Name | What it is |
|---|---|
TurnState | A turn's state as of cursor: one of the six records Running, Blocked, Completed, Interrupted, Failed and UnknownOutcome. |
Running | The turn is working. |
Blocked | The turn waits for an answer to one or more callbacks. |
Completed | The turn completed, possibly early: see stop_reason. |
Interrupted | The turn stopped before it completed. |
Failed | The turn ended with an error, for example a refusal from the model provider. |
UnknownOutcome | The turn ended with an outcome added after your SDK version, such as "future_outcome". |
ProtocolError | An error from where the session runs, such as the one a turn failed with. |
InvalidOutput | The turn completed, but its output did not validate against the requested type. |
ToolCall | One tool call the model made during the turn. |
TokenUsage | The tokens one model call used, as the provider reports them. |
OpenCallback | A callback that the turn, or one of its subagents, waits on: an OpenApproval, an OpenSignIn, an OpenClientToolCall or an UnknownOpenCallback. |
OpenApproval | An approval the turn waits on. |
OpenClientToolCall | A client tool call the turn waits on. |
OpenSignIn | A sign-in the turn waits on: the agent asks the user to connect one or more sources, such as connectors, before it uses their tools. |
UnknownOpenCallback | A callback of a kind with no type of its own in your SDK version. |
Busy | A new turn was not started, because another turn of the session is active. |
TurnConflict | A request named a turn that is no longer the session's active turn. |
TurnStateTurnState
type TurnState[T] = Running | Blocked | Completed[T] | Interrupted | Failed | UnknownOutcomeA turn's state as of cursor: one of the six records Running, Blocked, Completed,
Interrupted, Failed and UnknownOutcome.
RunningRunning
class Running(*, session_id: str, turn_id: str, cursor: str, text: str | None)The turn is working. Nothing waits on you.
| Parameter | Type | Default | Description |
|---|---|---|---|
session_id | str | required | The ID of the turn's session. |
turn_id | str | required | The ID of the turn. |
cursor | str | required | The ID of the position in the session's log that the record was read at. For a Running or Blocked that a wait with on_event returns, it is the position the handler's events reached, when that differs. |
text | str | None | required | The turn's latest assistant text at that position, or None before the first one. |
BlockedBlocked
class Blocked(
*,
session_id: str,
turn_id: str,
cursor: str,
text: str | None,
callbacks: tuple[OpenCallback, ...],
)The turn waits for an answer to one or more callbacks.
| Parameter | Type | Default | Description |
|---|---|---|---|
session_id | str | required | The ID of the turn's session. |
turn_id | str | required | The ID of the turn. |
cursor | str | required | The ID of the position in the session's log that the record was read at. For a Running or Blocked that a wait with on_event returns, it is the position the handler's events reached, when that differs. |
text | str | None | required | The turn's latest assistant text at that position, or None before the first one. |
callbacks | tuple[OpenCallback, ...] | required | The callbacks the turn waits on. |
CompletedCompleted
class Completed[T](
*,
session_id: str,
turn_id: str,
cursor: str,
text: str | None,
output: T | InvalidOutput | None,
stop_reason: str | None = None,
tool_calls: tuple[ToolCall, ...] = (),
)The turn completed, possibly early: see stop_reason.
With output_type, output is None when the turn ended early, so there is no answer to
parse. Without output_type, it is the turn's text, also when the turn ended early (a skipped
turn's text is the hook's reason, a cut-off turn's text is the cut-off reply), or "" when there
is none.
| Parameter | Type | Default | Description |
|---|---|---|---|
session_id | str | required | The ID of the turn's session. |
turn_id | str | required | The ID of the turn. |
cursor | str | required | The ID of the position in the session's log that the record was read at. For a Running or Blocked that a wait with on_event returns, it is the position the handler's events reached, when that differs. |
text | str | None | required | The turn's latest assistant text at that position, or None before the first one. |
output | T | InvalidOutput | None | required | The turn's text; with output_type, that text parsed, InvalidOutput when it does not validate, or None when the turn ended early. |
stop_reason | str | None | None | "iteration_limit" (the turn reached its iteration limit), "skipped" (a hook skipped it), "rejected" (a hook rejected the reply), "output_limit" (the model's last reply stopped at the output token limit, after the agent asked it for a shorter reply up to 6 times), None when the turn ended normally, or a stop reason added after your SDK version. |
tool_calls | tuple[ToolCall, ...] | () | The turn's own tool calls, in the order the session history records them; a subagent's calls are not included. |
InterruptedInterrupted
class Interrupted(*, session_id: str, turn_id: str, cursor: str, text: str | None, cause: str)The turn stopped before it completed.
When no cause is given, the SDK reads "cancelled" if the turn ended with its cancelled session, else
"interrupted".
text is what the turn said before it stopped.
| Parameter | Type | Default | Description |
|---|---|---|---|
session_id | str | required | The ID of the turn's session. |
turn_id | str | required | The ID of the turn. |
cursor | str | required | The ID of the position in the session's log that the record was read at. For a Running or Blocked that a wait with on_event returns, it is the position the handler's events reached, when that differs. |
text | str | None | required | The turn's latest assistant text at that position, or None before the first one. |
cause | str | required | Why the turn stopped: "interrupted" (a client interrupted the turn), "cancelled" (a client cancelled or closed the turn's session), "lifetime_limit" or "idle_limit" (the session reached one of its limits), or a cause added after your SDK version. |
FailedFailed
class Failed(*, session_id: str, turn_id: str, cursor: str, text: str | None, error: ProtocolError)The turn ended with an error, for example a refusal from the model provider.
| Parameter | Type | Default | Description |
|---|---|---|---|
session_id | str | required | The ID of the turn's session. |
turn_id | str | required | The ID of the turn. |
cursor | str | required | The ID of the position in the session's log that the record was read at. For a Running or Blocked that a wait with on_event returns, it is the position the handler's events reached, when that differs. |
text | str | None | required | The turn's latest assistant text at that position, or None before the first one. |
error | ProtocolError | required | Why the turn failed, a ProtocolError. |
UnknownOutcomeUnknownOutcome
class UnknownOutcome(
*,
session_id: str,
turn_id: str,
cursor: str,
text: str | None,
outcome: str,
raw: dict[str, JsonValue] = {},
)The turn ended with an outcome added after your SDK version, such as "future_outcome".
Leave a case for this record, so your code still runs when outcomes are added.
| Parameter | Type | Default | Description |
|---|---|---|---|
session_id | str | required | The ID of the turn's session. |
turn_id | str | required | The ID of the turn. |
cursor | str | required | The ID of the position in the session's log that the record was read at. For a Running or Blocked that a wait with on_event returns, it is the position the handler's events reached, when that differs. |
text | str | None | required | The turn's latest assistant text at that position, or None before the first one. |
outcome | str | required | The outcome, such as "future_outcome". |
raw | dict[str, JsonValue] | {} | The turn's termination, unparsed. |
ProtocolErrorProtocolError
class ProtocolError(
*,
message: str,
code: str | None = None,
retryable: bool = False,
details: dict[str, JsonValue] | None = None,
)An error from where the session runs, such as the one a turn failed with.
The code of a failed turn's error says what failed, so you can match on it:
"completion_rejected": the model provider refused the request with an HTTP 4xx status other than 401, 403, 408, 409 and 429, for example an unknown model or an invalid parameter: fix the request."completion_unauthorized": the model provider refused the credentials (HTTP 401 or 403). Not retryable."completion_rate_limited": more requests than your rate limit allows (HTTP 429 with reasonrate_limited). Retryable."completion_unavailable": the model provider could not serve the request: no model capacity (HTTP 409, or any other 429), an outage or a server error. Retryable for a transient cause, such as HTTP 502, 503 or 504."completion_timed_out": the provider answered HTTP 408, the model stopped producing output, or the call ran past its time limit. Retryable, except for a call past its time limit."completion_context_too_large": the request is larger than the model's context window. Not retryable, except when the provider refuses it again right after a context compaction, or refuses even the smallest compaction request (details.reasonis"after_compaction"or"minimal_summary_request")."completion_billing_denied": billing refused the request, for example because the budget is spent. Not retryable."completion_failed": the model call failed without an HTTP status, for example a lost connection or a stream that ended early. Retryable."completion_failed_permanently": the model call failed without an HTTP status and a retry will not help, for example a reply that reports no token usage: an OpenAI-compatible server must supportstream_options.include_usage. Not retryable."remote_session_failed": a served or deployed session failed or reached its time limit. The session has ended: start a new one."capability_execution_failed": a hook other thanpre_tool_callorpost_tool_callraised an exception, or its saved state was too large. The message ends with the exception's text. Not retryable."tool_raised": a tool raised an exception other thanagents.ToolErroron an agent withtool_errors="raise". The message names the tool and the exception's type. Not retryable."tool_output_schema_mismatch": a tool's result did not match itsoutput_schema=on an agent withtool_errors="raise". Not retryable."invalid_compaction_summary": a context compaction failed: the model's summary was empty, had malformed<summary>delimiters, called tools, or reached the output token limit. Not retryable."compaction_request_too_large": a context compaction could not start, because the context it must send does not fit under the compaction threshold. Not retryable."compaction_replacement_too_large": the summary and the context the turn needs do not fit under the compaction threshold. Not retryable."turn_failed": a served agent's error that has no code of its own.
InvalidOutputInvalidOutput
class InvalidOutput(*, raw: JsonValue, error: str)The turn completed, but its output did not validate against the requested type.
ToolCallToolCall
class ToolCall(name: str, arguments: JsonValue)One tool call the model made during the turn.
name is the tool's full ID, such as client.get_weather, not the name the model calls.
A call to a tool that does not exist keeps the model's name.
In the tool_calls of a post_llm_call or post_agent_turn hook, the call is not
resolved yet, so name is the name the model called, such as get_weather.
A call that did not run is listed too: a permission rule denied it, a user rejected it at
approval, its approval timed out, a hook skipped it or failed on it, the agent refused it
(invalid_tool_call: arguments that are not valid JSON or that the schema rejects, an unknown
tool, wait beside other calls), or, inside run_typescript, its arguments did not match the
tool's input schema or the program's time budget stopped it before it started.
arguments is the call's arguments: in Completed.tool_calls, the ones a pre_tool_call hook
set, else the model's; in a hook's tool_calls, the model's. Arguments that are not valid JSON are
the raw string, and a session of a deployed agent may report any JSON value.
| Parameter | Type | Default | Description |
|---|---|---|---|
name | str | required | The tool's full ID, such as client.get_weather, not the name the model calls. A call to a tool that does not exist keeps the model's name. |
arguments | JsonValue | required | The call's arguments: in Completed.tool_calls, the ones a pre_tool_call hook set, else the model's; in a hook's tool_calls, the model's. Arguments that are not valid JSON are the raw string, and a session of a deployed agent may report any JSON value. |
TokenUsageTokenUsage
class TokenUsage(
*,
input_tokens: int,
output_tokens: int,
total_tokens: int,
cached_input_tokens: int = 0,
cache_write_tokens: int = 0,
reasoning_tokens: int = 0,
)The tokens one model call used, as the provider reports them.
total_tokens is input_tokens plus output_tokens. cached_input_tokens counts the input
tokens read from the provider's cache, and is part of input_tokens.
Open callbacks
OpenCallbackOpenCallback
class OpenCallback(*, id: str, session_id: str, turn_id: str, kind: str, title: str | None)A callback that the turn, or one of its subagents, waits on: an OpenApproval, an
OpenSignIn, an OpenClientToolCall or an UnknownOpenCallback.
turn.answer takes its id on any turn of its session or of a session above it in the tree.
A subagent's callback has the subagent's IDs, its own turn included.
OpenApprovalOpenApproval
class OpenApproval(
*,
id: str,
session_id: str,
turn_id: str,
kind: str,
title: str | None,
request: ApprovalRequest,
)An approval the turn waits on.
Inherits id, session_id, turn_id, kind and title from OpenCallback.
| Parameter | Type | Default | Description |
|---|---|---|---|
request | ApprovalRequest | required | The tool call that the approval is for: the same ApprovalRequest an on_approval handler gets. |
OpenClientToolCallOpenClientToolCall
class OpenClientToolCall[P, R](
*,
id: str,
session_id: str,
turn_id: str,
kind: str,
title: str | None,
call: ClientToolCall,
)A client tool call the turn waits on.
Check which tool it calls with <stub>.called(callback), then answer it with
turn.answer(callback.id, await callback.run(fn)).
Inherits id, session_id, turn_id, kind and title from OpenCallback.
| Parameter | Type | Default | Description |
|---|---|---|---|
call | ClientToolCall | required | The call to answer: the same ClientToolCall an on_client_tool handler gets. |
OpenClientToolCall.runOpenClientToolCall.run
async def run(
fn: Callable[P, R | ToolResult[Any] | Awaitable[R | ToolResult[Any]]],
/,
) -> R | ToolResult[Any]Call fn with the call's arguments, validated by the tool's stub, and return its result:
what turn.answer(callback.id, ...) takes for this call.
fn takes the stub's parameters, without a context, and returns the stub's return type
or an agents.ToolResult, synchronously or not. A synchronous fn runs in a tool
thread. An exception fn raises propagates: tool_errors does not apply.
When the model's arguments do not match the stub, fn is not called: the result is an
error agents.ToolResult saying why, so that answering it lets the model correct the call.
| Raises | |
|---|---|
SdkError | No stub for this tool in the agent's tools, guardrails, hooks or subagents (as on a handle from Agent.load_from()), or two stubs share its ID. Read call.arguments instead. |
OpenSignInOpenSignIn
class OpenSignIn(
*,
id: str,
session_id: str,
turn_id: str,
kind: str,
title: str | None,
requests: tuple[SignInRequest, ...],
)A sign-in the turn waits on: the agent asks the user to connect one or more sources, such as connectors, before it uses their tools.
Answer it with turn.answer(callback.id, agents.SignInAnswer(connected=..., denied=...)),
naming each source by its id, or with an on_sign_in= handler, which gets this record.
kind is "sign_in". on_client_tool never gets a sign-in.
Inherits id, session_id, turn_id, kind and title from OpenCallback.
| Parameter | Type | Default | Description |
|---|---|---|---|
requests | tuple[SignInRequest, ...] | required | The sources to connect, one SignInRequest each. |
UnknownOpenCallbackUnknownOpenCallback
class UnknownOpenCallback(
*,
id: str,
session_id: str,
turn_id: str,
kind: str,
title: str | None,
raw: dict[str, JsonValue] = {},
)A callback of a kind with no type of its own in your SDK version.
A later SDK version can give a kind a type of its own. Match on kind, and leave open the kinds
that you do not know.
Inherits id, session_id, turn_id, kind and title from OpenCallback.
Refusals
BusyBusy
class Busy(*, session_id: str, active_turn_id: str, turn: Turn)A new turn was not started, because another turn of the session is active.
This is not a turn state: no turn was started.
TurnConflictTurnConflict
class TurnConflict(*, session_id: str, expected_turn_id: str, active_turn_id: str | None)A request named a turn that is no longer the session's active turn. Nothing was applied: the session refused the request.