> Discover all available pages from the documentation index: https://mastra.ai/llms.txt

# Tool approvals and permissions

The AgentController provides a permission system that controls which tools require user approval before execution. You can configure policies at the category level or per-tool, and grant session-wide exceptions for trusted tools. This gives agents with access to destructive or sensitive tools — file writes, command execution, API calls — a human-in-the-loop checkpoint before those tools run.

## Permission policies

Three policies control tool behavior:

- `allow`: The tool runs without prompting
- `ask`: The tool pauses and emits a `tool_approval_required` event; the user must approve or decline
- `deny`: The tool is blocked from execution

### Setting policies

Set policies per-category or per-tool. Per-tool policies take precedence over category policies:

```typescript
// Category-level: all execute tools require approval
await agentController.session.permissions.setForCategory({ category: 'execute', policy: 'ask' })

// Tool-level: this specific tool is always blocked
await agentController.session.permissions.setForTool({ toolName: 'dangerous_tool', policy: 'deny' })
```

### Tool categories

The `toolCategoryResolver` maps tool names to categories. Pass it to the AgentController constructor:

```typescript
const agentController = new AgentController({
  id: 'my-agent',
  toolCategoryResolver: toolName => {
    if (toolName.includes('write') || toolName.includes('delete')) return 'edit'
    if (toolName.includes('execute')) return 'execute'
    return 'read'
  },
})
```

Built-in categories are `read`, `edit`, `execute`, `mcp`, and `other`.

## Responding to approval requests

When a tool's policy is `ask`, the AgentController emits a `tool_approval_required` event. Your UI should display a prompt and call `session.respondToToolApproval()`:

```typescript
agentController.subscribe(event => {
  if (event.type === 'tool_approval_required') {
    // Show approval UI...
    agentController.session.respondToToolApproval({ decision: 'approve' })
  }
})
```

The `decision` field accepts `'approve'`, `'decline'`, or `'always_allow_category'`. When `always_allow_category` is used, the tool's category is granted for the rest of the session. Future tools in the same category are auto-approved.

```typescript
agentController.session.respondToToolApproval({ decision: 'always_allow_category' })
```

## Session grants

The AgentController owns permission _policy_ (which categories require approval); the [`Session`](https://mastra.ai/docs/agent-controller/session) owns the _grants_ a user makes during a conversation. Grant a category or tool for the rest of the session so it runs without further prompting:

```typescript
// Grant all edit tools for this session
agentController.session.grantCategory('edit')

// Grant a specific tool
agentController.session.grantTool('mastra_workspace_execute_command')

// Check current grants
const grants = agentController.session.getGrants()
// { categories: ['edit'], tools: ['mastra_workspace_execute_command'] }
```

## Tool suspensions

Interactive built-in tools (`ask_user`, `submit_plan`) use the native tool-suspension primitive instead of the approval flow. They emit a `tool_suspended` event with `toolCallId`, `toolName`, and `suspendPayload`. Resume with `respondToToolSuspension()`:

```typescript
agentController.subscribe(event => {
  if (event.type === 'tool_suspended' && event.toolName === 'ask_user') {
    const { question } = event.suspendPayload as { question: string }
    // Show question to user, then resume:
    agentController.respondToToolSuspension({
      toolCallId: event.toolCallId,
      resumeData: 'User response here',
    })
  }
})
```

### Plan approval

The `submit_plan` tool suspends via the same mechanism. Resume with an `action` field:

```typescript
// Approve the plan
agentController.respondToToolSuspension({
  toolCallId: event.toolCallId,
  resumeData: { action: 'approved' },
})

// Reject with feedback
agentController.respondToToolSuspension({
  toolCallId: event.toolCallId,
  resumeData: { action: 'rejected', feedback: 'Needs more detail' },
})
```

## Built-in tools

The AgentController provides these built-in tools to agents in every mode:

| Tool            | Description                                                         |
| --------------- | ------------------------------------------------------------------- |
| `ask_user`      | Ask the user a question (free text, single-select, or multi-select) |
| `submit_plan`   | Submit a plan for user review and approval                          |
| `task_write`    | Create or replace a structured task list                            |
| `task_update`   | Update one tracked task by ID                                       |
| `task_complete` | Mark one tracked task completed                                     |
| `task_check`    | Check task list completion status                                   |
| `subagent`      | Spawn a focused subagent (requires `subagents` config)              |

Disable specific built-in tools with `disableBuiltinTools`:

```typescript
const agentController = new AgentController({
  id: 'no-plans',
  disableBuiltinTools: ['submit_plan'],
})
```

## Related

- [AgentController overview](https://mastra.ai/docs/agent-controller/overview)
- [Session](https://mastra.ai/docs/agent-controller/session)
- [Subagents](https://mastra.ai/docs/agent-controller/subagents)
- [Agent approval](https://mastra.ai/docs/agents/agent-approval)
- [API reference](https://mastra.ai/reference/agent-controller/agent-controller-class)