Chapter 19 · Approvals, Memory, and Subagents
Chapter goals:
- Master three configurations of
toolApproval: per-tool mapping, function deciding by input, global strategy function- Understand the approval state machine (
not-applicable/approved/denied/user-approval) and the two-phase manual approval call流程- Render approve/reject buttons in
useChatwithaddToolApprovalResponse; understand the security hardening ofexperimental_toolApprovalSecret- Compare three memory方案: Provider-Defined Tools, Memory Providers, custom tools
- Learn the subagent模式: context卸载, streaming progress,
toModelOutputsummary, and注意事项
19.1 Tool Approvals
By default, tools with an execute function execute自动 when called by the model. Using toolApproval on ToolLoopAgent lets youreview, approve, or deny selected tools before they execute.
toolApproval is suitable for tools that可能修改数据, spend money, execute code, send messages, or access私有数据.
📌
toolApprovalonly作用于 tools executed by the AI SDK; tools executed by the provider run on the provider side and do not走 AI SDK approval.
Four Approval States
Each approval rule returns one of the following states (string or object with a type field):
'not-applicable': Execute the tool normally, without approval metadata (default).'approved': Record an automatic approval, then execute the tool.'denied': Record an automatic denial and return the post-denial tool output.'user-approval':发出审批请求并等待显式响应.
For automatic approve/deny, you can附带 a reason using the object form:
toolApproval: {
deleteFile: {
type: 'denied',
reason: 'Deleting files is disabled in this workspace',
},
}Approval functions can also return undefined, which is equivalent to 'not-applicable'.
Require Approval for a Single Tool
For simple per-tool strategies, use a per-tool映射:
import { ToolLoopAgent, tool, createGateway } from 'ai';
import { z } from 'zod';
const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY ?? '' });
const agent = new ToolLoopAgent({
model: gateway('openai/gpt-5'),
tools: {
runCommand: tool({
inputSchema: z.object({ command: z.string() }),
execute: async ({ command }) => runCommand(command),
}),
},
toolApproval: {
runCommand: 'user-approval',
},
});Function-Based Approval
For more nuanced decisions, pass a function that receives the tool call and returns a state:
const agent = new ToolLoopAgent({
model: gateway('openai/gpt-5'),
tools: {
sendEmail: tool({
inputSchema: z.object({ to: z.string(), body: z.string() }),
execute: async ({ to, body }) => sendEmail(to, body),
}),
},
toolApproval: async ({ toolCall }) => {
if (toolCall.toolName === 'sendEmail') {
// Auto-approve emails to internal domains
if (toolCall.args.to.includes('@internal.com')) {
return { type: 'approved', reason: 'Internal domain' };
}
// Require approval for external emails
return 'user-approval';
}
return 'not-applicable';
},
});Global Strategy Function
For a global policy, pass a function that receives all tool calls in the current step:
const agent = new ToolLoopAgent({
model: gateway('openai/gpt-5'),
tools: { /* ... */ },
toolApproval: async ({ toolCalls }) => {
// Deny all write operations
const writeTools = toolCalls.filter(tc => tc.toolName.includes('write'));
if (writeTools.length > 0) {
return writeTools.map(() => ({
type: 'denied',
reason: 'Write operations are not allowed',
}));
}
return 'not-applicable';
},
});Manual Two-Phase Approval
For user-approval, the agent pauses and waits. The client用 addToolApprovalResponse to respond:
const { addToolApprovalResponse } = useChat();
// On approve:
addToolApprovalResponse({
toolCallId: 'tc_123',
result: { type: 'approved' },
});
// On deny:
addToolApprovalResponse({
toolCallId: 'tc_123',
result: { type: 'denied', reason: 'User denied' },
});19.2 Memory
Three approaches to记忆 in AI SDK agents:
1. Provider-Defined Tools (Built-in Memory)
Some providers have built-in memory — the provider manages conversation历史 automatically. Use these providers and pass a sessionId or threadId:
const result = await generateText({
model: gateway('openai/gpt-5'),
messages: [{ role: 'user', content: 'Hello' }],
// Provider-specific memory settings
providerOptions: {
openai: {
threadId: 'thread-123',
},
},
});2. Memory Providers
Use a dedicated memory provider (e.g., AI SDK's built-in memory abstraction) to manageconversation history across calls. The provider stores和retrieves messages, appending new ones to the history:
import { memory } from 'ai/memory';
const result = await generateText({
model: gateway('openai/gpt-5'),
messages: await memory.getMessages('user-123'),
// Save messages after generation
onFinish: async ({ messages }) => {
await memory.saveMessages('user-123', messages);
},
});3. Custom Tools
For fine-grained控制, implement memory as a custom tool. The tool reads/writes to a database or file system; the agentdecides when to call it:
const memoryTool = tool({
description: 'Save or retrieve conversation memory',
inputSchema: z.object({
action: z.enum(['save', 'load']),
key: z.string(),
value: z.string().optional(),
}),
execute: async ({ action, key, value }) => {
if (action === 'save' && value) {
await db.set(key, value);
return 'Saved';
}
if (action === 'load') {
return await db.get(key) ?? 'No memory found';
}
},
});19.3 Subagents
Subagentslet you delegate子任务 to a separate agent instance with its own context, tools, and instructions. This is useful for:
- Context卸载: Keep the parent agent's context clean by delegating complex subtasks.
- Specialization: Give the subagent specific tools and instructions for its domain.
- Parallelism: Run multiple subagents concurrently.
Basic Subagent Pattern
import { ToolLoopAgent, subagent, createGateway } from 'ai';
const gateway = createGateway({ apiKey: process.env.AI_GATEWAY_API_KEY ?? '' });
// Subagent: specialized in summarization
const summarizer = new ToolLoopAgent({
model: gateway('openai/gpt-5-mini'),
instructions: 'Summarize the given text in 3 bullet points.',
});
// Parent agent: delegates to subagent
const agent = new ToolLoopAgent({
model: gateway('openai/gpt-5'),
tools: {
summarize: subagent({
agent: summarizer,
description: 'Summarize text using a specialized agent',
}),
},
});Streaming Progress
Subagents支持流式进度 — the parent can receive incremental updates from the subagent:
const result = await agent.stream({
prompt: 'Summarize this long document',
onStepStart: async ({ stepNumber }) => {
if (stepNumber > 1) {
// Subagent step — stream progress
console.log(`Subagent step ${stepNumber} started`);
}
},
});toModelOutput Summary
When a subagent completes, you can用 toModelOutput to convert its output into a concise summary for the parent:
import { toModelOutput } from 'ai';
const subagentResult = await summarizer.generate({
prompt: 'Original long text...',
});
const summary = toModelOutput(subagentResult);###注意事项
- Context isolation: Subagents have their own context; they do not share the parent's conversation history unless explicitly passed.
- Token cost: Each subagent call是独立的 LLM request; monitor总 token usage.
- Timeout: Set appropriate timeouts to prevent无限 loops.
- Sequential vs parallel: Run subagents sequentially by default; use Promise.all for parallel execution.
19.4 Chapter Summary
- Tool approvals: three configurations (per-tool map, function-by-input, global strategy); four states (
not-applicable/approved/denied/user-approval); manual two-phase approval viaaddToolApprovalResponse; - Memory: three approaches (provider-built-in, memory provider, custom tools); choose based on granularity needs;
- Subagents: delegate子任务 for context卸载, specialization, and parallelism; watch for context isolation, token cost, and timeout.
🧪 随堂测验
点击你认为正确的选项。答错时会展示正确答案与原因解析。
1. What are the four approval states in AI SDK tool approval?
2. How do you respond to a user-approval request in useChat?
3. Which memory approach gives you the most fine-grained control?
4. What is the primary benefit of subagents?
🛠️ Hands-On Exercises
- Implement a tool approval system: require user approval for all
deletetools, auto-approvereadtools, and denywritetools with a reason. - Build a memory-enabled chat: use a custom tool to save/load messages to a JSON file; verify the agent remembers previous conversations across calls.
- Create a subagent pattern: parent agent delegates research to a subagent with a search tool; verify the parent's context remains clean after subagent完成.