# Modes Modes define the different behaviors an AgentController can run. Each mode layers its own instructions and tool overrides on top of a shared backing agent, so the same agent can act as a planner in one mode and an executor in another. The AgentController keeps exactly one mode active at a time, carries the thread and state across switches, and handles the transition between them. Every AgentController needs at least one mode — the `modes` array is required, and the AgentController throws at construction if it's empty. A single-purpose AgentController still defines one mode; multiple modes are how you give a session more than one behavior to switch between. A mode supplies three things that change how the agent behaves: - **Instructions**: layered on top of the backing agent's own instructions while the mode is active. - **Tools**: either replacing or adding to the backing agent's tools (see [Mode tool overrides](#mode-tool-overrides)). - **Model**: an optional `defaultModelId` to bootstrap model selection when the session enters the mode. Because every mode shares the same backing agent, thread, and state, switching modes changes how the agent behaves without losing conversation context. ## Quickstart Import the `AgentController` class and create a new instance with your agent and modes: ```typescript import { AgentController } from '@mastra/core/agent-controller' import { myAgent } from './agents' const agentController = new AgentController({ id: 'multi-mode', agent: myAgent, modes: [ { id: 'plan', name: 'Plan', metadata: { default: true }, instructions: 'Reason about the task before making changes.', }, { id: 'build', name: 'Build', instructions: 'Implement the approved plan.' }, ], }) await agentController.init() ``` ## Defining modes Each mode requires an `id`. When a top-level `agent` is provided, modes layer instructions and tool overrides on the shared agent. Each mode can also specify a `defaultModelId` to bootstrap model selection: ```typescript import { AgentController } from '@mastra/core/agent-controller' const agentController = new AgentController({ id: 'multi-mode', agent: myAgent, modes: [ { id: 'plan', name: 'Plan', metadata: { default: true }, defaultModelId: 'anthropic/claude-sonnet-4-6', instructions: 'Reason about the task before making changes.', }, { id: 'build', name: 'Build', defaultModelId: 'anthropic/claude-sonnet-4-6', instructions: 'Implement the approved plan.', }, ], }) ``` ### Mode tool overrides Modes support two strategies for tool configuration. Use `tools` to replace the backing agent's tools entirely, or `additionalTools` to layer extra tools on top: ```typescript // Replace — agent sees only these tools in plan mode const planMode = { id: 'plan', tools: { planTool } } // Augment — agent keeps its own tools plus these const buildMode = { id: 'build', additionalTools: { deployTool } } ``` You can't set both `tools` and `additionalTools` on the same mode. ### Restricting tool visibility `tools` and `additionalTools` control which tools are **added** to a mode's run — they don't hide the backing agent's own tools. To restrict which of those tools the model can actually see and call, set `availableTools`: ```typescript const reviewMode = { id: 'review', name: 'Review', // Only these tools are visible to the model in this mode. availableTools: ['view', 'find_files', 'search_content'], } ``` `availableTools` is a per-mode visibility allowlist that matches each tool by its final exposed name: - **`undefined`** (default): no mode-level restriction — every tool is visible. - **`[]`**: no tools are available for this mode. - A denied tool stays hidden even if it appears in the list. Per-tool and per-category `deny` rules in your permission config always take precedence. Workspace tools use the same list as every other tool — reference them by their exposed names (`view`, `write_file`, `find_files`, etc.). Visibility is enforced at LLM-call time, so the model never sees — and can't attempt to call — a tool outside the allowlist. ### Mode transitions A mode can declare a `transitionsTo` target. When the `submit_plan` built-in tool runs in that mode, the AgentController transitions to the target mode on approval: ```typescript const planMode = { id: 'plan', name: 'Plan', transitionsTo: 'build', instructions: 'Reason about the task and submit a plan.', } ``` On plan approval, the AgentController automatically switches to `build` mode. On rejection, the agent remains in `plan` mode to revise. ## Switching modes The active mode lives on the Session, so call `agentController.session.mode.switch()` to change it. The switch aborts any in-progress generation, saves the current model to the outgoing mode, and emits a `mode_changed` event. It then resolves the incoming mode's model and, when one resolves, applies it and emits a `model_changed` event: ```typescript await agentController.session.mode.switch({ modeId: 'build' }) ``` ## Querying modes The AgentController exposes the full mode catalog, while the Session tracks which mode is active. Use `agentController.listModes()` to read every configured mode, `agentController.session.mode.get()` for the active mode ID, and `agentController.session.mode.resolve()` for the active mode's full definition: ```typescript // List all configured modes const modes = agentController.listModes() // Get the current mode ID const modeId = agentController.session.mode.get() // Get the full mode object const mode = agentController.session.mode.resolve() ``` ## Related - [AgentController overview](https://mastra.ai/docs/agent-controller/overview) - [Threads and state](https://mastra.ai/docs/agent-controller/threads-and-state) - [API reference](https://mastra.ai/reference/agent-controller/agent-controller-class)