# 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)