Approvals and sign-ins

How a program answers the approvals and the connector sign-ins of a turn: the handler, the request it gets, the answers, and what turn.answer returns.

Guide: Approvals.

NameWhat it is
ApprovalHandlerA plain or async function of (ctx, request) that answers an approval: AllowApproval, DenyApproval, or KeepCallbackOpen() to keep it open.
ApprovalRequestA tool call that waits for an approval.
ApprovalToolThe tool that an approval is for.
AllowApprovalAllow this call once, or matching calls for the rest of the session.
DenyApprovalDeny this call.
KeepCallbackOpenWhat a callback handler returns to keep the callback open, for someone to answer later.
SignInRequestOne source that a sign-in asks the user to connect.
SignInAnswerThe answer to an OpenSignIn: the IDs of the sources the user connected, and of those they refused to connect.
AnsweredThe callback was open when read, and it was answered.
AlreadySettledAn answer named a callback that is no longer open: it was answered, or it closed.
NotFoundAn answer named a callback that the session never opened, and that none of its subagents has open.
ApprovalHandler

ApprovalHandler

class ApprovalHandler(Protocol):
    def __call__(
        ctx: ApprovalCallbackContext,
        request: ApprovalRequest,
        /,
    ) -> AllowApproval | DenyApproval | KeepCallbackOpen | Awaitable[AllowApproval | DenyApproval | KeepCallbackOpen]

A plain or async function of (ctx, request) that answers an approval: AllowApproval, DenyApproval, or KeepCallbackOpen() to keep it open. Any other return, None included, raises SdkError. Its parameters may have any names.

  • It runs in your program, and only while agent.run(), session.run() or turn.result() waits. Between those calls, an approval stays open. iter_events() never runs it.
  • A plain function runs on the event loop: do not block in it. An async def function can await.
  • If it raises, or returns something else, the waiting call raises that error and the approval stays open. The turn keeps running.
  • It also answers the approvals of the session's subagents, with ctx.session set to the subagent's session.
  • Decide on request.tool.id, the tool's stable ID, never on the name the model called.
  • AllowApproval(scope="session") also allows the same call with the same input for the rest of the session.
ApprovalRequest

ApprovalRequest

class ApprovalRequest(
    *,
    tool: ApprovalTool,
    hook: str | None = None,
    title: str,
    input: JsonValue = None,
)

A tool call that waits for an approval. The IDs are in the handler's ctx.

A hook's approval cannot be allowed for the session. Answer it with AllowApproval(), once.

ParameterTypeDefaultDescription
toolApprovalToolrequiredThe tool the call is for.
hookstr | NoneNoneThe name of the pre_tool_call hook that asked for the approval, or None when the agent's permission policy asked.
titlestrrequiredThe question to show a person, such as "Allow bash?".
inputJsonValueNoneThe arguments of the call: the model's, or the ones a pre_tool_call hook set.
ApprovalTool

ApprovalTool

class ApprovalTool(
    *,
    id: str,
    name: str,
    kind: Literal['builtin', 'client', 'mcp', 'connector', 'plugin'] | UnknownValue,
    description: str | None = None,
    input_schema: dict[str, JsonValue] | None = None,
    output_schema: dict[str, JsonValue] | None = None,
    definition: PythonTool[..., Any] | ClientTool[..., Any] | ToolDefinition | None = None,
    annotations: ToolAnnotations = ToolAnnotations(),
)

The tool that an approval is for.

Decide on id, never on name: name changes with how the model called the tool. A hint the tool does not declare in annotations takes MCP's default, which assumes the worst.

ParameterTypeDefaultDescription
idstrrequiredThe tool's stable ID, the one permission rules use, such as "file_system.bash", "client.open_case", "billing.charge" or "github.create_issue".
namestrrequiredThe name the model called, which changes with how it called the tool: deploy_service directly, tools.client.deploy_service from run_typescript.
kindLiteral['builtin', 'client', 'mcp', 'connector', 'plugin'] | UnknownValuerequiredWhere the tool comes from: "builtin" for the agent's built-in tools, "client" for the agent's @agents.tool and client tools, "mcp" for an MCP server's, "connector" for a Mistral connector's, "plugin" for any other, or an UnknownValue for a kind added after your SDK version.
descriptionstr | NoneNoneThe tool's description, as the model reads it; None for builtins.
input_schemadict[str, JsonValue] | NoneNoneThe JSON Schema of the tool's input, as the model reads it; None for builtins.
output_schemadict[str, JsonValue] | NoneNoneThe JSON Schema of the tool's output; None for builtins and for a tool that declares none.
definitionPythonTool[..., Any] | ClientTool[..., Any] | ToolDefinition | NoneNoneThe tool's definition. For a tool of the agent or of one of its subagents, the tool object the agent was given, such as the one @agents.tool returned; None on a session handle that does not hold the agent's tools. For an MCP server's or a connector's tool, an agents.ToolDefinition of the whole tool as the server listed it, such as its title, annotations, output_schema and meta, with the server's name as source, with or without the agent. None for the other tools. Every definition has display_title, the tool's name to show people.
annotationsToolAnnotationsToolAnnotations()The hints the tool declares, such as read_only_hint: those of annotations= on @agents.tool, agents.PythonTool and agents.client_tool, and those an MCP server or a connector declares for its tool. The approval carries them, so every approver reads the same ones, with or without the agent. A hint the tool does not declare takes MCP's default, which assumes the worst: a policy that allows only read-only tools allows a tool only if it declares read_only_hint=True.
AllowApproval

AllowApproval

class AllowApproval(*, scope: Literal['once', 'session'] = 'once')

Allow this call once, or matching calls for the rest of the session.

ParameterTypeDefaultDescription
scopeLiteral['once', 'session']'once'"once" for this call. "session" also allows the same call with the same input for the rest of the session.
AttributeTypeDescription
decisionLiteral['allow']Always "allow".
DenyApproval

DenyApproval

class DenyApproval()

Deny this call.

AttributeTypeDescription
decisionLiteral['deny']Always "deny".
KeepCallbackOpen

KeepCallbackOpen

class KeepCallbackOpen()

What a callback handler returns to keep the callback open, for someone to answer later.

Return agents.KeepCallbackOpen() from on_approval, on_client_tool or a client tool's answer: nothing is sent, the callback stays open, and the waiting call returns Blocked on it, unless it was given after= a Blocked that already lists the callback: that wait returns only on a later change. Answer it later with turn.answer, an approval with AllowApproval or DenyApproval and a client tool call with the tool's result, or answer it from another client.

It is a handler's return only, never an answer: turn.answer refuses it. None is not this either: an approval handler that returns None raises agents.SdkError, and a client tool that returns None sends the null result.

Sign-ins

Sign-ins

SignInRequest

SignInRequest

class SignInRequest(
    *,
    source: Literal['connector', 'mcp'] | UnknownValue,
    id: str,
    name: str,
    mode: Literal['connect', 'reconnect'] | UnknownValue = 'connect',
)

One source that a sign-in asks the user to connect.

ParameterTypeDefaultDescription
sourceLiteral['connector', 'mcp'] | UnknownValuerequired"connector" for a Mistral connector, "mcp" for an MCP server, or an UnknownValue for a source added after your SDK version.
idstrrequiredThe source's ID, the one SignInAnswer takes: a connector's is the name the agent gives it, as in agents.connector("linear").
namestrrequiredThe name to show the user: its display name when Mistral has one, else the ID.
modeLiteral['connect', 'reconnect'] | UnknownValue'connect'"connect" for a first sign-in, "reconnect" when the source was connected and its access has to be renewed, or an UnknownValue for a mode added after your SDK version.
SignInAnswer

SignInAnswer

class SignInAnswer(*, connected: Iterable[str] = (), denied: Iterable[str] = ())

The answer to an OpenSignIn: the IDs of the sources the user connected, and of those they refused to connect.

Name each source the sign-in asks for in one list: an answer that leaves one out raises SdkError and sends nothing, and the sign-in stays open. The agent checks each source said to be connected, and is told when one is still not connected.

ParameterTypeDefaultDescription
connectedIterable[str]()The IDs of the sources the user connected.
deniedIterable[str]()The IDs of the sources the user refused to connect.
Answer results

Answer results

Answered

Answered

class Answered(
    *,
    session_id: str,
    callback_id: str,
    answer: AllowApproval | DenyApproval | None = None,
)

The callback was open when read, and it was answered.

ParameterTypeDefaultDescription
session_idstrrequiredThe ID of the session.
callback_idstrrequiredThe ID of the callback.
answerAllowApproval | DenyApproval | NoneNoneThe approval answer that counted. It is another client's when that client answered first, so compare it with yours to tell. It is None for a client tool call or a sign-in, or when the answer that counted could not be read in time or is of a kind added after your SDK version; read the turn to see the outcome.
AlreadySettled

AlreadySettled

class AlreadySettled(*, session_id: str, callback_id: str)

An answer named a callback that is no longer open: it was answered, or it closed. Nothing changed.

Another client may have answered first, the callback's turn ended, or the session ended.

ParameterTypeDefaultDescription
session_idstrrequiredThe ID of the session.
callback_idstrrequiredThe ID of the callback.
NotFound

NotFound

class NotFound(*, session_id: str, callback_id: str)

An answer named a callback that the session never opened, and that none of its subagents has open. Nothing changed.

Once a subagent's callback is no longer open, its root says NotFound. The subagent's own session says AlreadySettled.

ParameterTypeDefaultDescription
session_idstrrequiredThe ID of the session.
callback_idstrrequiredThe ID of the callback.