Tool Approval & State Persistence
Add human-in-the-loop approval gates for sensitive tools and persist conversation state across callModel invocations.
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 callStateAccessor— persist conversation state betweencallModelinvocations 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:
- Model generates tool calls — the model decides which tools to invoke
- SDK partitions tool calls — each call is checked against
requireApprovaland split into two groups: those requiring approval and those that can auto-execute - Auto-execute tools run immediately — tools that don’t need approval execute in parallel as normal
- State saves with pending approvals — the conversation state updates to
status: 'awaiting_approval'with the pending tool calls stored - Control returns to the caller — check
result.requiresApproval()and inspect pending calls withresult.getPendingToolCalls() - Resume with decisions — call
callModelagain with the samestate, passingapproveToolCallsand/orrejectToolCallsarrays of tool call IDs - 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
- 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:
Status Values
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
- Tools - Tool definitions and the
tool()helper - Stop Conditions - Control when tool execution loops terminate
- Dynamic Parameters - Adjust parameters between turns
- Examples - Complete tool implementations