The short answer
Set requireApproval: true on a tool made with createTool() to pause every call to that tool, or pass requireToolApproval: true to stream() or generate() to pause every tool call in that request. If either says so, the call pauses.1 The stream then emits a tool-call-approval chunk with toolCallId, toolName and args; you answer with approveToolCall() or declineToolCall() and the run’s runId.1
Two details matter most: requireToolApproval can be a function that decides per call, and if that function throws, Mastra defaults to requiring approval. And approvals use snapshots, so without a storage provider you get a “snapshot not found” error.1
Deedglass
A tool call meets your rules before it runs
Asks to run
DELETE FROM visits;
Deletes every row in visits
Deedglass checks your rules
- Reading dataAllowed
- Deleting a whole table (DROP, TRUNCATE)Never
- Changing or deleting rows without saying which (WHERE)This one
- Other changesWait for a person
Two switches, one rule
| Switch | Where | Scope |
|---|---|---|
requireApproval |
On the tool, in createTool() |
Every call to that tool |
requireToolApproval |
On a stream() or generate() call |
Every tool call in that request |
They combine with OR: if either is true, the call pauses.1 So you can mark dangerous tools once, at definition, and still turn on approval for everything during a sensitive request, such as one acting on production data.
Approving and declining
For stream():1
- Read chunks until a
tool-call-approvalarrives. It carriestoolCallId,toolNameandargs. - Show the reviewer the tool and the full arguments.
- Call
approveToolCall()ordeclineToolCall()with the stream’srunId. Pass thetoolCallIdtoo when several calls may be pending.
For generate(), the matching calls are approveToolCallGenerate() and declineToolCallGenerate(); generate() returns with finishReason: 'suspended' while it waits.1
Declining takes an optional reason, which the model receives in place of the tool result.1 Use it. A decline with “Refunds over 100 need a manager; tell the customer it’s escalated” gives the model a next step. A bare decline invites it to try again.
Deciding per call
requireToolApproval can be a function, sync or async, that receives toolName, args, requestContext and workspace and returns true or false.1 A sketch, following the documented parameter names (we didn’t run it):
Code Program code, for developers. The text around it says what it does.
const stream = await agent.stream(messages, {
requireToolApproval: ({ toolName, args }) =>
toolName === "refund" ? Number(args.amount) > 100 : false,
});
Three rules from the docs shape how this behaves:1
- If the function throws, approval is required. A bug in your rule doesn’t let calls through.
- A tool-level
requireApprovalfunction takes precedence over the combined result. - Durable and stored agents accept only a boolean. A function there falls back to requiring approval.
The first rule is the right default, and not every framework has it. In Cedar, for example, a policy that errors is skipped, which can let a permit win; our policy language comparison walks through that case. Mastra’s choice means a malformed argument that crashes your rule ends with a person asked, not a refund sent.
Storage is not optional
Approval works by saving a snapshot of the run while it waits. From the docs:1
- Without a storage provider, you’ll see “snapshot not found.”
- Agent-run snapshots are minimal and deleted when the run finishes.
- Suspended runs survive a restart only with a persistent storage provider;
listSuspendedRuns()finds pending runs in storage again.
In practice: an in-memory setup works on your laptop and loses every pending approval when the server restarts. If approvals can wait overnight, configure persistent storage before you ship, and decide what happens to approvals nobody answers (expire them, and tell the user).
Test the approval path
Approval code tends to be tested only on the happy path. Before shipping, run four cases against a test agent with a mock or cheap model:
- A call that shouldn’t pause (a small refund): the tool runs and no
tool-call-approvalchunk appears. - A call that should pause: the chunk appears with the arguments the model chose, and the tool hasn’t run.
- Decline with a reason: the model’s next message reflects the reason instead of retrying the same call.
- Restart while paused: with persistent storage,
listSuspendedRuns()finds the run again, and approving it after the restart completes the call.
Case 4 is the one that fails in production when storage was never configured. It’s cheaper to find on day one.
Approval or suspend
Mastra has a second pause mechanism. A tool can call suspend() inside its execute function to pause mid-run; the stream emits tool-call-suspended, and you resume with resumeStream() and data matching the tool’s resumeSchema.1
| Approval | Suspend | |
|---|---|---|
| Pauses | Before the tool runs | Inside the tool, wherever it calls suspend() |
| Answer | Approve or decline | Data matching resumeSchema |
| Auto-resume from the user’s next message | No; needs an explicit decision | Yes, with autoResumeSuspendedTools1 |
| Use for | “Should this run at all?” | “I need more information to continue” |
Keep high-impact actions on approval. Auto-resume from a chat message is convenient for “which account did you mean?”, and the wrong control for “may I send this money?”.
A checklist
- Tools that write, send or spend have
requireApproval. - Sensitive requests add
requireToolApproval, as a function where amounts vary. - Persistent storage configured; pending approvals survive restarts.
-
toolCallIdpassed when approving, for runs with several pending calls. - Declines carry a reason the model can act on.
- Unanswered approvals expire and the user is told.
- Approval decisions logged with reviewer, tool and arguments.
Approvals live in your app. For rules that also hold for other clients, Deedglass’s docs include a Mastra recipe that routes the agent’s tools through its MCP server, one of nine framework recipes of 15 lines or fewer, with no live model run behind them.2 For the same pattern elsewhere, see AI SDK toolApproval and OpenAI Agents SDK approvals; the human-in-the-loop approval entry covers the idea. To see what the agents configured on your machine can change, paste the config into the check page or run npx deedglass scan (how the grade works); to try approval rules in your browser, open the playground.
Written with AI assistance and checked against our claims register.