Skip to main content

Why Approval Gates?

Some tools — sending emails, making payments, deleting records — should not auto-execute without human review. The SDK provides two mechanisms to control this:
  • requireApproval — pause execution when the model calls sensitive tools, giving users a chance to approve or reject each call
  • StateAccessor — persist conversation state between callModel invocations so approval decisions, message history, and tool results survive across runs
Together, these enable human-in-the-loop workflows where a user reviews tool calls before they execute, even across separate request/response cycles (e.g., in a web application).

Tool-Level Approval

Add requireApproval directly on a tool definition. It accepts a boolean or a function:

Always Require Approval

Conditional Approval

Pass a function to require approval only in certain cases:
The function receives the parsed tool arguments and a TurnContext, and can return a boolean or Promise<boolean>.

Call-Level Approval

Override tool-level settings with a requireApproval callback on callModel itself:
The call-level callback takes priority over tool-level requireApproval settings when both are present.

How the Approval Flow Works

When tools with approval gates are called by the model, the SDK follows this flow:
  1. Model generates tool calls — the model decides which tools to invoke
  2. SDK partitions tool calls — each call is checked against requireApproval and split into two groups: those requiring approval and those that can auto-execute
  3. Auto-execute tools run immediately — tools that don’t need approval execute in parallel as normal
  4. State saves with pending approvals — the conversation state updates to status: 'awaiting_approval' with the pending tool calls stored
  5. Control returns to the caller — check result.requiresApproval() and inspect pending calls with result.getPendingToolCalls()
  6. Resume with decisions — call callModel again with the same state, passing approveToolCalls and/or rejectToolCalls arrays of tool call IDs
  7. Approved tools execute — the SDK runs approved tools and sends results to the model. Rejected tools send an error message to the model explaining the rejection
  8. Conversation continues — the model processes tool results and generates the next response

StateAccessor Interface

The StateAccessor interface enables any storage backend:

In-Memory Implementation

For production use, implement StateAccessor with a persistent backend like Redis, a database, or file storage to survive process restarts.

ConversationState

The state object tracks everything needed to resume a conversation:
FieldTypeDescription
idstringUnique conversation identifier
messagesOpenResponsesInputUnionFull message history
previousResponseIdstring?Previous response ID for server-side chaining
pendingToolCallsParsedToolCall[]?Tool calls awaiting human input, such as approval/rejection or HITL output
unsentToolResultsUnsentToolResult[]?Executed results not yet sent to model
partialResponsePartialResponse?Data captured during interruption
interruptedBystring?Signal from a new request that interrupted this conversation
statusConversationStatusCurrent state of the conversation
createdAtnumberCreation timestamp (Unix ms)
updatedAtnumberLast update timestamp (Unix ms)

Status Values

StatusMeaning
'in_progress'Conversation is actively processing
'awaiting_approval'Paused, waiting for tool call approval/rejection
'awaiting_hitl'Paused by a HITL tool whose onToolCalled hook returned null; resume by supplying a function_call_output for each paused call
'complete'Conversation finished normally
'interrupted'Conversation was interrupted and can be resumed

Complete Example

Here is an end-to-end example showing approval gates with state persistence:

Resumption Patterns

Resuming from Approval

When the state has status: 'awaiting_approval', pass approveToolCalls and/or rejectToolCalls to resume:

Resuming from Interruption

If a conversation was interrupted (status: 'interrupted'), calling callModel with the same state resumes automatically. The SDK clears the interruption flag and continues where it left off:

Multi-Run Conversations

Messages accumulate automatically across callModel runs that share the same StateAccessor. Each run appends its input and response to the state’s message history:

Next Steps