@mastra/libsql 0.0.0-structured-output-issue-20260302184919 → 0.0.0-structured-output-errors-20260409185629
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +344 -3
- package/LICENSE.md +15 -0
- package/dist/docs/SKILL.md +15 -19
- package/dist/docs/assets/SOURCE_MAP.json +1 -1
- package/dist/docs/references/docs-agents-agent-approval.md +136 -185
- package/dist/docs/references/docs-agents-networks.md +90 -207
- package/dist/docs/references/docs-memory-memory-processors.md +15 -15
- package/dist/docs/references/docs-memory-message-history.md +10 -8
- package/dist/docs/references/docs-memory-overview.md +219 -24
- package/dist/docs/references/docs-memory-semantic-recall.md +54 -29
- package/dist/docs/references/docs-memory-storage.md +14 -16
- package/dist/docs/references/docs-memory-working-memory.md +22 -22
- package/dist/docs/references/docs-rag-retrieval.md +16 -16
- package/dist/docs/references/docs-workflows-snapshots.md +1 -1
- package/dist/docs/references/guides-agent-frameworks-ai-sdk.md +3 -3
- package/dist/docs/references/reference-core-getMemory.md +4 -5
- package/dist/docs/references/reference-core-listMemory.md +3 -4
- package/dist/docs/references/reference-core-mastra-class.md +18 -18
- package/dist/docs/references/reference-memory-memory-class.md +16 -18
- package/dist/docs/references/reference-storage-composite.md +19 -11
- package/dist/docs/references/reference-storage-dynamodb.md +16 -16
- package/dist/docs/references/reference-storage-libsql.md +3 -3
- package/dist/docs/references/reference-vectors-libsql.md +47 -47
- package/dist/index.cjs +512 -82
- package/dist/index.cjs.map +1 -1
- package/dist/index.js +513 -83
- package/dist/index.js.map +1 -1
- package/dist/storage/db/index.d.ts +12 -0
- package/dist/storage/db/index.d.ts.map +1 -1
- package/dist/storage/domains/datasets/index.d.ts.map +1 -1
- package/dist/storage/domains/experiments/index.d.ts +3 -1
- package/dist/storage/domains/experiments/index.d.ts.map +1 -1
- package/dist/storage/domains/memory/index.d.ts +5 -2
- package/dist/storage/domains/memory/index.d.ts.map +1 -1
- package/dist/storage/domains/observability/index.d.ts.map +1 -1
- package/package.json +8 -8
- package/dist/docs/references/docs-agents-agent-memory.md +0 -209
- package/dist/docs/references/docs-agents-network-approval.md +0 -275
- package/dist/docs/references/docs-observability-overview.md +0 -70
- package/dist/docs/references/docs-observability-tracing-exporters-default.md +0 -209
|
@@ -1,82 +1,100 @@
|
|
|
1
|
-
# Agent
|
|
1
|
+
# Agent approval
|
|
2
2
|
|
|
3
|
-
Agents sometimes require the same [human-in-the-loop](https://mastra.ai/docs/workflows/human-in-the-loop) oversight used in workflows when calling tools that handle sensitive operations, like deleting resources or
|
|
3
|
+
Agents sometimes require the same [human-in-the-loop](https://mastra.ai/docs/workflows/human-in-the-loop) oversight used in workflows when calling tools that handle sensitive operations, like deleting resources or running long processes. With agent approval you can suspend a tool call before it executes so a human can approve or decline it, or let tools suspend themselves to request additional context from the user.
|
|
4
4
|
|
|
5
|
-
##
|
|
5
|
+
## When to use agent approval
|
|
6
6
|
|
|
7
|
-
|
|
7
|
+
- **Destructive or irreversible actions** such as deleting records, sending emails, or processing payments.
|
|
8
|
+
- **Cost-heavy operations** like calling expensive third-party APIs where you want to verify arguments first.
|
|
9
|
+
- **Conditional confirmation** where a tool starts executing and then discovers it needs the user to confirm or supply extra data before finishing.
|
|
8
10
|
|
|
9
|
-
|
|
11
|
+
## Quickstart
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Mark a tool with `requireApproval: true`, then check for the `tool-call-approval` chunk in the stream to approve or decline:
|
|
12
14
|
|
|
13
15
|
```typescript
|
|
14
|
-
import {
|
|
15
|
-
import {
|
|
16
|
+
import { Agent } from '@mastra/core/agent'
|
|
17
|
+
import { createTool } from '@mastra/core/tools'
|
|
18
|
+
import { z } from 'zod'
|
|
16
19
|
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
}),
|
|
20
|
+
const deleteTool = createTool({
|
|
21
|
+
id: 'delete-record',
|
|
22
|
+
description: 'Delete a record by ID',
|
|
23
|
+
inputSchema: z.object({ id: z.string() }),
|
|
24
|
+
outputSchema: z.object({ deleted: z.boolean() }),
|
|
25
|
+
requireApproval: true,
|
|
26
|
+
execute: async ({ id }) => {
|
|
27
|
+
await db.delete(id)
|
|
28
|
+
return { deleted: true }
|
|
29
|
+
},
|
|
22
30
|
})
|
|
23
|
-
```
|
|
24
31
|
|
|
25
|
-
|
|
32
|
+
const agent = new Agent({
|
|
33
|
+
id: 'my-agent',
|
|
34
|
+
name: 'My Agent',
|
|
35
|
+
model: 'openai/gpt-5-mini',
|
|
36
|
+
tools: { deleteTool },
|
|
37
|
+
})
|
|
26
38
|
|
|
27
|
-
|
|
39
|
+
const stream = await agent.stream('Delete record abc-123')
|
|
28
40
|
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
41
|
+
for await (const chunk of stream.fullStream) {
|
|
42
|
+
if (chunk.type === 'tool-call-approval') {
|
|
43
|
+
const approved = await agent.approveToolCall({ runId: stream.runId })
|
|
44
|
+
for await (const c of approved.textStream) process.stdout.write(c)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
33
47
|
```
|
|
34
48
|
|
|
35
|
-
|
|
49
|
+
> **Note:** Agent approval uses snapshots to capture request state. Configure a [storage provider](https://mastra.ai/docs/memory/storage) on your Mastra instance or you'll see a "snapshot not found" error.
|
|
36
50
|
|
|
37
|
-
|
|
51
|
+
## How approval works
|
|
38
52
|
|
|
39
|
-
|
|
40
|
-
const handleApproval = async () => {
|
|
41
|
-
const approvedStream = await agent.approveToolCall({ runId: stream.runId })
|
|
53
|
+
Mastra offers two distinct mechanisms for pausing tool calls: **pre-execution approval** and **runtime suspension**.
|
|
42
54
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
process.stdout.write('\n')
|
|
47
|
-
}
|
|
48
|
-
```
|
|
55
|
+
### Pre-execution approval
|
|
56
|
+
|
|
57
|
+
Pre-execution approval pauses a tool call _before_ its `execute` function runs. The LLM still decides which tool to call and provides arguments, but `execute` doesn't run until you explicitly approve.
|
|
49
58
|
|
|
50
|
-
|
|
59
|
+
Two flags control this, combined with OR logic. If _either_ is `true`, the call pauses:
|
|
51
60
|
|
|
52
|
-
|
|
61
|
+
| Flag | Where to set it | Scope |
|
|
62
|
+
| --------------------------- | --------------------------------- | ------------------------------------------- |
|
|
63
|
+
| `requireToolApproval: true` | `stream()` / `generate()` options | Pauses **every** tool call for that request |
|
|
64
|
+
| `requireApproval: true` | `createTool()` definition | Pauses calls to **that specific tool** |
|
|
65
|
+
|
|
66
|
+
The stream emits a `tool-call-approval` chunk containing the `toolCallId`, `toolName`, and `args`. Call `approveToolCall()` or `declineToolCall()` with the stream's `runId` to continue:
|
|
53
67
|
|
|
54
68
|
```typescript
|
|
55
|
-
const
|
|
56
|
-
|
|
69
|
+
const stream = await agent.stream("What's the weather in London?", {
|
|
70
|
+
requireToolApproval: true,
|
|
71
|
+
})
|
|
57
72
|
|
|
58
|
-
|
|
59
|
-
|
|
73
|
+
for await (const chunk of stream.fullStream) {
|
|
74
|
+
if (chunk.type === 'tool-call-approval') {
|
|
75
|
+
console.log('Tool:', chunk.payload.toolName)
|
|
76
|
+
console.log('Args:', chunk.payload.args)
|
|
77
|
+
|
|
78
|
+
// Approve
|
|
79
|
+
const approved = await agent.approveToolCall({ runId: stream.runId })
|
|
80
|
+
for await (const c of approved.textStream) process.stdout.write(c)
|
|
81
|
+
|
|
82
|
+
// Or decline
|
|
83
|
+
const declined = await agent.declineToolCall({ runId: stream.runId })
|
|
84
|
+
for await (const c of declined.textStream) process.stdout.write(c)
|
|
60
85
|
}
|
|
61
|
-
process.stdout.write('\n')
|
|
62
86
|
}
|
|
63
87
|
```
|
|
64
88
|
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
Tool approval also works with the `generate()` method for non-streaming use cases. When using `generate()` with `requireToolApproval: true`, the method returns immediately when a tool requires approval instead of executing it.
|
|
68
|
-
|
|
69
|
-
### How it works
|
|
89
|
+
### Runtime suspension with `suspend()`
|
|
70
90
|
|
|
71
|
-
|
|
91
|
+
A tool can also pause _during_ its `execute` function by calling `suspend()`. This is useful when the tool starts running and then discovers it needs additional user input or confirmation before it can finish.
|
|
72
92
|
|
|
73
|
-
|
|
74
|
-
- `suspendPayload` - contains tool call details (`toolCallId`, `toolName`, `args`)
|
|
75
|
-
- `runId` - needed to approve or decline the tool call
|
|
93
|
+
The stream emits a `tool-call-suspended` chunk with a custom payload defined by the tool's `suspendSchema`. You resume by calling `resumeStream()` with data matching the tool's `resumeSchema`.
|
|
76
94
|
|
|
77
|
-
|
|
95
|
+
## Tool approval with `generate()`
|
|
78
96
|
|
|
79
|
-
|
|
97
|
+
Tool approval also works with `generate()` for non-streaming use cases. When a tool requires approval, `generate()` returns immediately with `finishReason: 'suspended'`, a `suspendPayload` containing the tool call details (`toolCallId`, `toolName`, `args`), and a `runId`:
|
|
80
98
|
|
|
81
99
|
```typescript
|
|
82
100
|
const output = await agent.generate('Find user John', {
|
|
@@ -85,35 +103,23 @@ const output = await agent.generate('Find user John', {
|
|
|
85
103
|
|
|
86
104
|
if (output.finishReason === 'suspended') {
|
|
87
105
|
console.log('Tool requires approval:', output.suspendPayload.toolName)
|
|
88
|
-
console.log('Arguments:', output.suspendPayload.args)
|
|
89
106
|
|
|
90
|
-
// Approve
|
|
107
|
+
// Approve
|
|
91
108
|
const result = await agent.approveToolCallGenerate({
|
|
92
109
|
runId: output.runId,
|
|
93
110
|
toolCallId: output.suspendPayload.toolCallId,
|
|
94
111
|
})
|
|
95
|
-
|
|
96
112
|
console.log('Final result:', result.text)
|
|
97
|
-
}
|
|
98
|
-
```
|
|
99
|
-
|
|
100
|
-
### Declining tool calls
|
|
101
113
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
```typescript
|
|
105
|
-
if (output.finishReason === 'suspended') {
|
|
114
|
+
// Or decline
|
|
106
115
|
const result = await agent.declineToolCallGenerate({
|
|
107
116
|
runId: output.runId,
|
|
108
117
|
toolCallId: output.suspendPayload.toolCallId,
|
|
109
118
|
})
|
|
110
|
-
|
|
111
|
-
// Agent will respond acknowledging the declined tool
|
|
112
|
-
console.log(result.text)
|
|
113
119
|
}
|
|
114
120
|
```
|
|
115
121
|
|
|
116
|
-
### Stream vs
|
|
122
|
+
### Stream vs generate comparison
|
|
117
123
|
|
|
118
124
|
| Aspect | `stream()` | `generate()` |
|
|
119
125
|
| ------------------ | ---------------------------- | ------------------------------------------------ |
|
|
@@ -123,13 +129,15 @@ if (output.finishReason === 'suspended') {
|
|
|
123
129
|
| Decline method | `declineToolCall({ runId })` | `declineToolCallGenerate({ runId, toolCallId })` |
|
|
124
130
|
| Result | Stream to iterate | Full output object |
|
|
125
131
|
|
|
132
|
+
> **Note:** `toolCallId` is optional on all four methods. Pass it when multiple tool calls may be pending at the same time (common in supervisor agents). When omitted, the agent resumes the most recent suspended tool call.
|
|
133
|
+
|
|
126
134
|
## Tool-level approval
|
|
127
135
|
|
|
128
|
-
|
|
136
|
+
Instead of pausing every tool call at the agent level, you can mark individual tools as requiring approval. This gives you granular control: only specific tools pause, while others execute immediately.
|
|
129
137
|
|
|
130
|
-
###
|
|
138
|
+
### Approval using `requireApproval`
|
|
131
139
|
|
|
132
|
-
|
|
140
|
+
Set `requireApproval: true` on a tool definition. The tool pauses before execution regardless of whether `requireToolApproval` is set on the agent:
|
|
133
141
|
|
|
134
142
|
```typescript
|
|
135
143
|
export const testTool = createTool({
|
|
@@ -154,30 +162,30 @@ export const testTool = createTool({
|
|
|
154
162
|
})
|
|
155
163
|
```
|
|
156
164
|
|
|
157
|
-
When `requireApproval` is true
|
|
165
|
+
When `requireApproval` is `true`, the stream emits `tool-call-approval` chunks the same way agent-level approval does. Use `approveToolCall()` or `declineToolCall()` to continue:
|
|
158
166
|
|
|
159
167
|
```typescript
|
|
160
168
|
const stream = await agent.stream("What's the weather in London?")
|
|
161
169
|
|
|
162
170
|
for await (const chunk of stream.fullStream) {
|
|
163
171
|
if (chunk.type === 'tool-call-approval') {
|
|
164
|
-
console.log('Approval required.
|
|
172
|
+
console.log('Approval required for:', chunk.payload.toolName)
|
|
165
173
|
}
|
|
166
174
|
}
|
|
167
175
|
|
|
168
|
-
const
|
|
169
|
-
const
|
|
176
|
+
const handleApproval = async () => {
|
|
177
|
+
const approvedStream = await agent.approveToolCall({ runId: stream.runId })
|
|
170
178
|
|
|
171
|
-
for await (const chunk of
|
|
179
|
+
for await (const chunk of approvedStream.textStream) {
|
|
172
180
|
process.stdout.write(chunk)
|
|
173
181
|
}
|
|
174
182
|
process.stdout.write('\n')
|
|
175
183
|
}
|
|
176
184
|
```
|
|
177
185
|
|
|
178
|
-
###
|
|
186
|
+
### Approval using `suspend()`
|
|
179
187
|
|
|
180
|
-
With this approach, neither the agent nor the tool uses `requireApproval`. Instead, the tool
|
|
188
|
+
With this approach, neither the agent nor the tool uses `requireApproval`. Instead, the tool's `execute` function calls `suspend()` to pause at a specific point and return context or confirmation prompts to the user. This is useful when approval depends on runtime conditions rather than being unconditional.
|
|
181
189
|
|
|
182
190
|
```typescript
|
|
183
191
|
export const testToolB = createTool({
|
|
@@ -210,7 +218,7 @@ export const testToolB = createTool({
|
|
|
210
218
|
})
|
|
211
219
|
```
|
|
212
220
|
|
|
213
|
-
With this approach the stream
|
|
221
|
+
With this approach the stream includes a `tool-call-suspended` chunk, and the `suspendPayload` contains the `reason` defined by the tool's `suspendSchema`. Call `resumeStream` with the `resumeSchema` data and `runId` to continue:
|
|
214
222
|
|
|
215
223
|
```typescript
|
|
216
224
|
const stream = await agent.stream("What's the weather in London?")
|
|
@@ -233,94 +241,64 @@ const handleResume = async () => {
|
|
|
233
241
|
|
|
234
242
|
## Automatic tool resumption
|
|
235
243
|
|
|
236
|
-
When using tools that call `suspend()`, you can enable automatic resumption so the agent resumes suspended tools based on the user's next message.
|
|
237
|
-
|
|
238
|
-
### Enabling auto-resume
|
|
239
|
-
|
|
240
|
-
Set `autoResumeSuspendedTools` to `true` in the agent's default options or when calling `stream()`:
|
|
244
|
+
When using tools that call `suspend()`, you can enable automatic resumption so the agent resumes suspended tools based on the user's next message. Set `autoResumeSuspendedTools` to `true` in the agent's default options or per-request:
|
|
241
245
|
|
|
242
246
|
```typescript
|
|
243
247
|
import { Agent } from '@mastra/core/agent'
|
|
244
248
|
import { Memory } from '@mastra/memory'
|
|
245
249
|
|
|
246
|
-
// Option 1: In agent configuration
|
|
247
250
|
const agent = new Agent({
|
|
248
251
|
id: 'my-agent',
|
|
249
252
|
name: 'My Agent',
|
|
250
253
|
instructions: 'You are a helpful assistant',
|
|
251
|
-
model: 'openai/gpt-
|
|
254
|
+
model: 'openai/gpt-5-mini',
|
|
252
255
|
tools: { weatherTool },
|
|
253
256
|
memory: new Memory(),
|
|
254
257
|
defaultOptions: {
|
|
255
258
|
autoResumeSuspendedTools: true,
|
|
256
259
|
},
|
|
257
260
|
})
|
|
258
|
-
|
|
259
|
-
// Option 2: Per-request
|
|
260
|
-
const stream = await agent.stream("What's the weather?", {
|
|
261
|
-
autoResumeSuspendedTools: true,
|
|
262
|
-
})
|
|
263
261
|
```
|
|
264
262
|
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
When `autoResumeSuspendedTools` is enabled:
|
|
268
|
-
|
|
269
|
-
1. A tool suspends execution by calling `suspend()` with a payload (e.g., requesting more information)
|
|
270
|
-
|
|
271
|
-
2. The suspension is persisted to memory along with the conversation
|
|
272
|
-
|
|
273
|
-
3. When the user sends their next message on the same thread, the agent:
|
|
274
|
-
|
|
275
|
-
- Detects the suspended tool from message history
|
|
276
|
-
- Extracts `resumeData` from the user's message based on the tool's `resumeSchema`
|
|
277
|
-
- Automatically resumes the tool with the extracted data
|
|
278
|
-
|
|
279
|
-
### Example
|
|
263
|
+
When enabled, the agent detects suspended tools from message history on the next user message, extracts `resumeData` based on the tool's `resumeSchema`, and automatically resumes the tool. The following example shows a complete conversational flow:
|
|
280
264
|
|
|
281
265
|
```typescript
|
|
282
266
|
import { createTool } from '@mastra/core/tools'
|
|
283
267
|
import { z } from 'zod'
|
|
284
268
|
|
|
285
|
-
|
|
286
|
-
id: 'weather-
|
|
287
|
-
description: 'Fetches weather
|
|
269
|
+
const weatherTool = createTool({
|
|
270
|
+
id: 'weather-tool',
|
|
271
|
+
description: 'Fetches weather for a city',
|
|
272
|
+
inputSchema: z.object({
|
|
273
|
+
city: z.string(),
|
|
274
|
+
}),
|
|
275
|
+
outputSchema: z.object({
|
|
276
|
+
weather: z.string(),
|
|
277
|
+
}),
|
|
288
278
|
suspendSchema: z.object({
|
|
289
279
|
message: z.string(),
|
|
290
280
|
}),
|
|
291
281
|
resumeSchema: z.object({
|
|
292
282
|
city: z.string(),
|
|
293
283
|
}),
|
|
294
|
-
execute: async (
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
})
|
|
284
|
+
execute: async (inputData, context) => {
|
|
285
|
+
const { resumeData, suspend } = context?.agent ?? {}
|
|
286
|
+
|
|
287
|
+
// If no city provided, ask the user
|
|
288
|
+
if (!inputData.city && !resumeData?.city) {
|
|
289
|
+
return suspend?.({ message: 'What city do you want to know the weather for?' })
|
|
301
290
|
}
|
|
302
291
|
|
|
303
|
-
|
|
304
|
-
const { city } = context.agent.resumeData
|
|
292
|
+
const city = resumeData?.city ?? inputData.city
|
|
305
293
|
const response = await fetch(`https://wttr.in/${city}?format=3`)
|
|
306
294
|
const weather = await response.text()
|
|
307
295
|
|
|
308
|
-
return { city
|
|
309
|
-
},
|
|
310
|
-
})
|
|
311
|
-
|
|
312
|
-
const agent = new Agent({
|
|
313
|
-
id: 'my-agent',
|
|
314
|
-
name: 'My Agent',
|
|
315
|
-
instructions: 'You are a helpful assistant',
|
|
316
|
-
model: 'openai/gpt-4o-mini',
|
|
317
|
-
tools: { weatherTool },
|
|
318
|
-
memory: new Memory(),
|
|
319
|
-
defaultOptions: {
|
|
320
|
-
autoResumeSuspendedTools: true,
|
|
296
|
+
return { weather: `${city}: ${weather}` }
|
|
321
297
|
},
|
|
322
298
|
})
|
|
299
|
+
```
|
|
323
300
|
|
|
301
|
+
```typescript
|
|
324
302
|
const stream = await agent.stream("What's the weather like?")
|
|
325
303
|
|
|
326
304
|
for await (const chunk of stream.fullStream) {
|
|
@@ -329,18 +307,13 @@ for await (const chunk of stream.fullStream) {
|
|
|
329
307
|
}
|
|
330
308
|
}
|
|
331
309
|
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
process.stdout.write(chunk)
|
|
337
|
-
}
|
|
338
|
-
process.stdout.write('\n')
|
|
310
|
+
// User sends follow-up on the same thread
|
|
311
|
+
const resumedStream = await agent.stream('San Francisco')
|
|
312
|
+
for await (const chunk of resumedStream.textStream) {
|
|
313
|
+
process.stdout.write(chunk)
|
|
339
314
|
}
|
|
340
315
|
```
|
|
341
316
|
|
|
342
|
-
**Conversation flow:**
|
|
343
|
-
|
|
344
317
|
```text
|
|
345
318
|
User: "What's the weather like?"
|
|
346
319
|
Agent: "What city do you want to know the weather for?"
|
|
@@ -349,7 +322,7 @@ User: "San Francisco"
|
|
|
349
322
|
Agent: "The weather in San Francisco is: San Francisco: ☀️ +72°F"
|
|
350
323
|
```
|
|
351
324
|
|
|
352
|
-
The second message automatically resumes the suspended tool
|
|
325
|
+
The second message automatically resumes the suspended tool. The agent extracts `{ city: "San Francisco" }` from the user's message and passes it as `resumeData`.
|
|
353
326
|
|
|
354
327
|
### Requirements
|
|
355
328
|
|
|
@@ -368,21 +341,21 @@ For automatic tool resumption to work:
|
|
|
368
341
|
|
|
369
342
|
Both approaches work with the same tool definitions. Automatic resumption triggers only when suspended tools exist in the message history and the user sends a new message on the same thread.
|
|
370
343
|
|
|
371
|
-
## Tool approval: Supervisor
|
|
372
|
-
|
|
373
|
-
The [supervisor pattern](https://mastra.ai/docs/agents/networks) lets a supervisor agent coordinate multiple subagents using `.stream()` or `.generate()`. The supervisor delegates tasks to subagents, which may use tools that require approval. When this happens, tool approvals properly propagate through the delegation chain -- the approval request surfaces at the supervisor level where you can handle it, regardless of which subagent triggered it.
|
|
344
|
+
## Tool approval: Supervisor agents
|
|
374
345
|
|
|
375
|
-
|
|
346
|
+
A [supervisor agent](https://mastra.ai/docs/agents/supervisor-agents) coordinates multiple subagents using `.stream()` or `.generate()`. When a subagent calls a tool that requires approval, the request propagates up through the delegation chain and surfaces at the supervisor level:
|
|
376
347
|
|
|
377
|
-
1. The supervisor
|
|
348
|
+
1. The supervisor delegates a task to a subagent.
|
|
378
349
|
2. The subagent calls a tool that has `requireApproval: true` or uses `suspend()`.
|
|
379
|
-
3. The approval request bubbles up
|
|
380
|
-
4. You
|
|
381
|
-
5. The decision propagates back down to the subagent
|
|
350
|
+
3. The approval request bubbles up to the supervisor.
|
|
351
|
+
4. You approve or decline at the supervisor level.
|
|
352
|
+
5. The decision propagates back down to the subagent.
|
|
382
353
|
|
|
383
|
-
|
|
354
|
+
Tool approvals also propagate through multiple levels of delegation. If a supervisor delegates to subagent A, which delegates to subagent B that has a tool with `requireApproval: true`, the approval request still surfaces at the top-level supervisor.
|
|
384
355
|
|
|
385
|
-
|
|
356
|
+
### Approve and decline in supervisor agents
|
|
357
|
+
|
|
358
|
+
The example below creates a subagent with a tool requiring approval. When the tool triggers an approval request, it surfaces in the supervisor's stream as a `tool-call-approval` chunk:
|
|
386
359
|
|
|
387
360
|
```typescript
|
|
388
361
|
import { Agent } from '@mastra/core/agent'
|
|
@@ -390,7 +363,6 @@ import { createTool } from '@mastra/core/tools'
|
|
|
390
363
|
import { Memory } from '@mastra/memory'
|
|
391
364
|
import { z } from 'zod'
|
|
392
365
|
|
|
393
|
-
// subagent with approval-required tool
|
|
394
366
|
const findUserTool = createTool({
|
|
395
367
|
id: 'find-user',
|
|
396
368
|
description: 'Finds user by ID in the database',
|
|
@@ -404,7 +376,7 @@ const findUserTool = createTool({
|
|
|
404
376
|
email: z.string(),
|
|
405
377
|
}),
|
|
406
378
|
}),
|
|
407
|
-
requireApproval: true,
|
|
379
|
+
requireApproval: true,
|
|
408
380
|
execute: async input => {
|
|
409
381
|
const user = await database.findUser(input.userId)
|
|
410
382
|
return { user }
|
|
@@ -415,7 +387,7 @@ const dataAgent = new Agent({
|
|
|
415
387
|
id: 'data-agent',
|
|
416
388
|
name: 'Data Agent',
|
|
417
389
|
description: 'Handles database queries and user data retrieval',
|
|
418
|
-
model: 'openai/gpt-
|
|
390
|
+
model: 'openai/gpt-5-mini',
|
|
419
391
|
tools: { findUserTool },
|
|
420
392
|
})
|
|
421
393
|
|
|
@@ -424,12 +396,11 @@ const supervisorAgent = new Agent({
|
|
|
424
396
|
name: 'Supervisor Agent',
|
|
425
397
|
instructions: `You coordinate data retrieval tasks.
|
|
426
398
|
Delegate to data-agent for user lookups.`,
|
|
427
|
-
model: 'openai/gpt-5.
|
|
399
|
+
model: 'openai/gpt-5.4',
|
|
428
400
|
agents: { dataAgent },
|
|
429
401
|
memory: new Memory(),
|
|
430
402
|
})
|
|
431
403
|
|
|
432
|
-
// When supervisor delegates to dataAgent and tool requires approval
|
|
433
404
|
const stream = await supervisorAgent.stream('Find user with ID 12345')
|
|
434
405
|
|
|
435
406
|
for await (const chunk of stream.fullStream) {
|
|
@@ -443,40 +414,22 @@ for await (const chunk of stream.fullStream) {
|
|
|
443
414
|
toolCallId: chunk.payload.toolCallId,
|
|
444
415
|
})
|
|
445
416
|
|
|
446
|
-
// Process resumed stream
|
|
447
417
|
for await (const resumeChunk of resumeStream.textStream) {
|
|
448
418
|
process.stdout.write(resumeChunk)
|
|
449
419
|
}
|
|
450
|
-
}
|
|
451
|
-
}
|
|
452
|
-
```
|
|
453
|
-
|
|
454
|
-
### Declining tool calls in supervisor pattern
|
|
455
|
-
|
|
456
|
-
You can also decline tool calls at the supervisor level by calling `declineToolCall`. The supervisor will respond acknowledging the declined tool without executing it:
|
|
457
|
-
|
|
458
|
-
```typescript
|
|
459
|
-
for await (const chunk of stream.fullStream) {
|
|
460
|
-
if (chunk.type === 'tool-call-approval') {
|
|
461
|
-
console.log('Declining tool call:', chunk.payload.toolName)
|
|
462
420
|
|
|
463
|
-
//
|
|
421
|
+
// To decline instead, use:
|
|
464
422
|
const declineStream = await supervisorAgent.declineToolCall({
|
|
465
423
|
runId: stream.runId,
|
|
466
424
|
toolCallId: chunk.payload.toolCallId,
|
|
467
425
|
})
|
|
468
|
-
|
|
469
|
-
// The supervisor will respond acknowledging the declined tool
|
|
470
|
-
for await (const declineChunk of declineStream.textStream) {
|
|
471
|
-
process.stdout.write(declineChunk)
|
|
472
|
-
}
|
|
473
426
|
}
|
|
474
427
|
}
|
|
475
428
|
```
|
|
476
429
|
|
|
477
|
-
###
|
|
430
|
+
### Use `suspend()` in supervisor agents
|
|
478
431
|
|
|
479
|
-
Tools can also use [`suspend()`](#
|
|
432
|
+
Tools can also use [`suspend()`](#approval-using-suspend) to pause execution and return context to the user. This approach works through the supervisor delegation chain the same way `requireApproval` does: the suspension surfaces at the supervisor level:
|
|
480
433
|
|
|
481
434
|
```typescript
|
|
482
435
|
const conditionalTool = createTool({
|
|
@@ -504,8 +457,10 @@ const conditionalTool = createTool({
|
|
|
504
457
|
return await performOperation(input.operation)
|
|
505
458
|
},
|
|
506
459
|
})
|
|
460
|
+
```
|
|
507
461
|
|
|
508
|
-
|
|
462
|
+
```typescript
|
|
463
|
+
// When using this tool through a subagent in supervisor agents
|
|
509
464
|
for await (const chunk of stream.fullStream) {
|
|
510
465
|
if (chunk.type === 'tool-call-suspended') {
|
|
511
466
|
console.log('Tool suspended:', chunk.payload.suspendPayload.message)
|
|
@@ -523,9 +478,9 @@ for await (const chunk of stream.fullStream) {
|
|
|
523
478
|
}
|
|
524
479
|
```
|
|
525
480
|
|
|
526
|
-
###
|
|
481
|
+
### Supervisor approval with `generate()`
|
|
527
482
|
|
|
528
|
-
Tool approval propagation also works with `generate()` in supervisor
|
|
483
|
+
Tool approval propagation also works with `generate()` in supervisor agents:
|
|
529
484
|
|
|
530
485
|
```typescript
|
|
531
486
|
const output = await supervisorAgent.generate('Find user with ID 12345', {
|
|
@@ -545,14 +500,10 @@ if (output.finishReason === 'suspended') {
|
|
|
545
500
|
}
|
|
546
501
|
```
|
|
547
502
|
|
|
548
|
-
### Multi-level delegation
|
|
549
|
-
|
|
550
|
-
Tool approvals propagate through multiple levels of delegation. For example, if a supervisor delegates to subagent A, which in turn delegates to subagent B that has a tool with `requireApproval: true`, the approval request still surfaces at the top-level supervisor. You handle the approval or decline there, and the result flows back down through the entire delegation chain to the tool that requested it.
|
|
551
|
-
|
|
552
503
|
## Related
|
|
553
504
|
|
|
554
|
-
- [
|
|
555
|
-
- [Agent
|
|
556
|
-
- [
|
|
557
|
-
- [
|
|
558
|
-
- [Request
|
|
505
|
+
- [Tools](https://mastra.ai/docs/agents/using-tools)
|
|
506
|
+
- [Agent overview](https://mastra.ai/docs/agents/overview)
|
|
507
|
+
- [MCP overview](https://mastra.ai/docs/mcp/overview)
|
|
508
|
+
- [Memory](https://mastra.ai/docs/memory/overview)
|
|
509
|
+
- [Request context](https://mastra.ai/docs/server/request-context)
|