# Network Approval Agent networks can require the same [human-in-the-loop](https://mastra.ai/docs/workflows/human-in-the-loop) oversight used in individual agents and workflows. When a tool, subagent, or workflow within a network requires approval or suspends execution, the network pauses and emits events that allow your application to collect user input before resuming. ## Storage Network approval uses snapshots to capture execution state. Ensure you've enabled a storage provider in your Mastra instance. If storage isn't enabled you'll see an error relating to snapshot not found. ```typescript import { Mastra } from "@mastra/core/mastra"; import { LibSQLStore } from "@mastra/libsql"; export const mastra = new Mastra({ storage: new LibSQLStore({ id: "mastra-storage", url: ":memory:" }) }); ``` ## Approving network tool calls When a tool within a network has `requireApproval: true`, the network stream emits an `agent-execution-approval` chunk and pauses. To allow the tool to execute, call `approveNetworkToolCall` with the `runId`. ```typescript const stream = await routingAgent.network("Process this query", { memory: { thread: "user-123", resource: "my-app" } }); let runId: string; for await (const chunk of stream) { runId = stream.runId; // if the requirApproval is in a tool inside a subAgent or the subAgent has requireToolApproval set to true if (chunk.type === "agent-execution-approval") { console.log("Tool requires approval:", chunk.payload); } // if the requirApproval is in a tool directly in the network agent if (chunk.type === "tool-execution-approval") { console.log("Tool requires approval:", chunk.payload); } } // Approve and resume execution const approvedStream = await routingAgent.approveNetworkToolCall({ runId, memory: { thread: "user-123", resource: "my-app" } }); for await (const chunk of approvedStream) { if (chunk.type === "network-execution-event-step-finish") { console.log(chunk.payload.result); } } ``` ## Declining network tool calls To decline a pending tool call and prevent execution, call `declineNetworkToolCall`. The network continues without executing the tool. ```typescript const declinedStream = await routingAgent.declineNetworkToolCall({ runId, memory: { thread: "user-123", resource: "my-app" } }); for await (const chunk of declinedStream) { if (chunk.type === "network-execution-event-step-finish") { console.log(chunk.payload.result); } } ``` ## Resuming suspended networks When a primitive in the network calls `suspend()`, the stream emits an `agent-execution-suspended`/`tool-execution-suspended`/`workflow-execution-suspended` chunk with a `suspendPayload` containing context from the primitive. Use `resumeNetwork` to provide the data requested by the primitive and continue execution. ```typescript import { createTool } from "@mastra/core/tools"; import { z } from "zod"; const confirmationTool = createTool({ id: "confirmation-tool", description: "Requests user confirmation before proceeding", inputSchema: z.object({ action: z.string() }), outputSchema: z.object({ confirmed: z.boolean(), action: z.string() }), suspendSchema: z.object({ message: z.string(), action: z.string() }), resumeSchema: z.object({ confirmed: z.boolean() }), execute: async (inputData, context) => { const { resumeData, suspend } = context?.agent ?? {}; if (!resumeData?.confirmed) { return suspend?.({ message: `Please confirm: ${inputData.action}`, action: inputData.action }); } return { confirmed: true, action: inputData.action }; } }); ``` Handle the suspension and resume with user-provided data: ```typescript const stream = await routingAgent.network("Delete the old records", { memory: { thread: "user-123", resource: "my-app" } }); for await (const chunk of stream) { if (chunk.type === "workflow-execution-suspended") { console.log(chunk.payload.suspendPayload); // { message: "Please confirm: delete old records", action: "delete old records" } } } // Resume with user confirmation const resumedStream = await routingAgent.resumeNetwork( { confirmed: true }, { runId: stream.runId, memory: { thread: "user-123", resource: "my-app" } } ); for await (const chunk of resumedStream) { if (chunk.type === "network-execution-event-step-finish") { console.log(chunk.payload.result); } } ``` ## Automatic primitive resumption When using primitives that call `suspend()`, you can enable automatic resumption so the network resumes suspended primitives based on the user's next message. This creates a conversational flow where users provide the required information naturally. ### Enabling auto-resume Set `autoResumeSuspendedTools` to `true` in the agent's `defaultNetworkOptions` or when calling `network()`: ```typescript import { Agent } from "@mastra/core/agent"; import { Memory } from "@mastra/memory"; // Option 1: In agent configuration const routingAgent = new Agent({ id: "routing-agent", name: "Routing Agent", instructions: "You coordinate tasks across multiple agents", model: "openai/gpt-4o-mini", tools: { confirmationTool }, memory: new Memory(), defaultNetworkOptions: { autoResumeSuspendedTools: true, }, }); // Option 2: Per-request const stream = await routingAgent.network("Process this request", { autoResumeSuspendedTools: true, memory: { thread: "user-123", resource: "my-app" } }); ``` ### How it works When `autoResumeSuspendedTools` is enabled: 1. A primitive suspends execution by calling `suspend()` with a payload 2. The suspension is persisted to memory along with the conversation 3. When the user sends their next message on the same thread, the network: - Detects the suspended primitive from message history - Extracts `resumeData` from the user's message based on the tool's `resumeSchema` - Automatically resumes the primitive with the extracted data ### Example ```typescript const stream = await routingAgent.network("Delete the old records", { autoResumeSuspendedTools: true, memory: { thread: "user-123", resource: "my-app" } }); for await (const chunk of stream) { if (chunk.type === "workflow-execution-suspended") { console.log(chunk.payload.suspendPayload); // { message: "Please confirm: delete old records", action: "delete old records" } } } // User provides confirmation in their next message const resumedStream = await routingAgent.network("Yes, confirmed", { autoResumeSuspendedTools: true, memory: { thread: "user-123", resource: "my-app" } }); for await (const chunk of resumedStream) { if (chunk.type === "network-execution-event-step-finish") { console.log(chunk.payload.result); } } ``` **Conversation flow:** ```text User: "Delete the old records" Agent: "Please confirm: delete old records" User: "Yes, confirmed" Agent: "Records deleted successfully" ``` ### Requirements For automatic tool resumption to work: - **Memory configured**: The agent needs memory to track suspended tools across messages - **Same thread**: The follow-up message must use the same memory thread and resource identifiers - **`resumeSchema` defined**: The tool (either directly in the network agent or in a subAgent) / workflow (step that gets suspended) must define a `resumeSchema` so the agent knows what data to extract from the user's message ### Manual vs automatic resumption | Approach | Use case | | -------------------------------------- | ------------------------------------------------------------------------ | | Manual (`resumeNetwork()`) | Programmatic control, webhooks, button clicks, external triggers | | Automatic (`autoResumeSuspendedTools`) | Conversational flows where users provide resume data in natural language | 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. ## Related - [Agent Networks](https://mastra.ai/docs/agents/networks) - [Agent Approval](https://mastra.ai/docs/agents/agent-approval) - [Human-in-the-Loop](https://mastra.ai/docs/workflows/human-in-the-loop) - [Agent Memory](https://mastra.ai/docs/agents/agent-memory)