Skip to content

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 useChat with addToolApprovalResponse; understand the security hardening of experimental_toolApprovalSecret
  • Compare three memory方案: Provider-Defined Tools, Memory Providers, custom tools
  • Learn the subagent模式: context卸载, streaming progress, toModelOutput summary, 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私有数据.

📌 toolApproval only作用于 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:

ts
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映射:

ts
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:

ts
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:

ts
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:

ts
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:

ts
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:

ts
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:

ts
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 ​

ts
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:

ts
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:

ts
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 via addToolApprovalResponse;
  • 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 ​

  1. Implement a tool approval system: require user approval for all delete tools, auto-approve read tools, and deny write tools with a reason.
  2. 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.
  3. Create a subagent pattern: parent agent delegates research to a subagent with a search tool; verify the parent's context remains clean after subagent完成.

Next: Chapter 20 · Capstone 1: RAG Knowledge-Base Q&A