# Harness Class **Added in:** `@mastra/core@1.1.0` The `Harness` class orchestrates multiple agent modes, shared state, memory, and storage. It provides a control layer that a TUI or other UI can drive to manage threads, switch models and modes, send messages, handle tool approvals, and track events. ## Usage example ```typescript import { Harness } from '@mastra/core/harness'; import { LibSQLStore } from '@mastra/libsql'; import { z } from 'zod'; const harness = new Harness({ id: 'my-coding-agent', storage: new LibSQLStore({ url: 'file:./data.db' }), stateSchema: z.object({ currentModelId: z.string().optional(), }), modes: [ { id: 'plan', name: 'Plan', default: true, agent: planAgent }, { id: 'build', name: 'Build', agent: buildAgent }, ], }); harness.subscribe((event) => { if (event.type === 'message_update') { renderMessage(event.message); } }); await harness.init(); await harness.selectOrCreateThread(); await harness.sendMessage({ content: 'Hello!' }); ``` ## Constructor parameters **id:** (`string`): Unique identifier for this harness instance. **resourceId?:** (`string`): Resource ID for grouping threads (e.g., project identifier). Threads are scoped to this resource ID. Defaults to \`id\`. **storage?:** (`MastraCompositeStore`): Storage backend for persistence (threads, messages, state). **stateSchema?:** (`z.ZodObject`): Zod schema defining the shape of harness state. Used for validation and extracting defaults. **initialState?:** (`Partial>`): Initial state values. Must conform to the schema if provided. **memory?:** (`MastraMemory`): Memory configuration shared across all modes. Propagated to mode agents that don't have their own memory. **modes:** (`HarnessMode[]`): Available agent modes. At least one mode is required. Each mode defines an agent and optional defaults. **tools?:** (`ToolsInput | ((ctx) => ToolsInput)`): Tools available to all agents across all modes. It can be a static tools object or a dynamic function that receives the request context. **workspace?:** (`Workspace | WorkspaceConfig | ((ctx) => Workspace)`): Workspace configuration. Accepts a pre-constructed Workspace, a WorkspaceConfig for the harness to construct internally, or a dynamic factory function. **subagents?:** (`HarnessSubagent[]`): Subagent definitions. When provided, the harness creates a built-in \`subagent\` tool that parent agents can call to spawn focused subagents. **resolveModel?:** (`(modelId: string) => MastraLanguageModel`): Converts a model ID string (e.g., \`"anthropic/claude-sonnet-4"\`) to a language model instance. Used by subagents and observational memory model resolution. **omConfig?:** (`HarnessOMConfig`): Default configuration for observational memory (observer/reflector model IDs and thresholds). **heartbeatHandlers?:** (`HeartbeatHandler[]`): Periodic background tasks started during \`init()\`. Use for gateway sync, cache refresh, and similar tasks. **idGenerator?:** (`() => string`): Custom ID generator for threads, messages, and other entities. (Default: `timestamp + random string`) **modelAuthChecker?:** (`ModelAuthChecker`): Custom auth checker for model providers. Return \`true\`/\`false\` to override the default environment variable check, or \`undefined\` to fall back to defaults. **modelUseCountProvider?:** (`ModelUseCountProvider`): Provides per-model use counts for sorting and display in \`listAvailableModels()\`. **toolCategoryResolver?:** (`(toolName: string) => ToolCategory | null`): Maps tool names to permission categories (\`'read'\`, \`'edit'\`, \`'execute'\`, \`'mcp'\`, \`'other'\`). Used by the permission system to resolve category-level policies. **threadLock?:** (`{ acquire, release }`): Thread locking callbacks to prevent concurrent access from multiple processes. \`acquire\` should throw if the lock is held. ### HarnessMode Each entry in the `modes` array configures a single agent mode. **id:** (`string`): Unique identifier for this mode (e.g., \`"plan"\`, \`"build"\`). **name?:** (`string`): Human-readable name for display. **default?:** (`boolean`): Whether this is the default mode when the harness starts. (Default: `false`) **defaultModelId?:** (`string`): Default model ID for this mode (e.g., \`"anthropic/claude-sonnet-4-20250514"\`). Used when no per-mode model has been explicitly selected. **color?:** (`string`): Hex color for the mode indicator (e.g., \`"#7c3aed"\`). **agent:** (`Agent | ((state) => Agent)`): The agent for this mode. It can be a static Agent instance or a function that receives harness state and returns an Agent. ### HarnessSubagent Each entry in the `subagents` array defines a subagent the harness can spawn. **id:** (`string`): Unique identifier for this subagent type (e.g., \`"explore"\`, \`"execute"\`). **name:** (`string`): Human-readable name shown in tool output. **description:** (`string`): Description of what this subagent does. Used in the auto-generated tool description. **instructions:** (`string`): System prompt for this subagent. **tools?:** (`ToolsInput`): Tools this subagent has direct access to. **allowedHarnessTools?:** (`string[]`): Tool IDs from the harness's shared \`tools\` config. Merged with \`tools\` above to let subagents use a subset of harness tools. **defaultModelId?:** (`string`): Default model ID for this subagent type. ## Properties **id:** (`string`): Harness identifier, set at construction. ## Methods ### Lifecycle #### `init()` Initialize the harness. Loads storage, initializes the workspace, propagates memory and workspace to mode agents, and starts heartbeat handlers. Call this before using the harness. ```typescript await harness.init(); ``` #### `selectOrCreateThread()` Select the most recent thread for the current resource, or create one if none exist. Loads thread metadata and acquires a thread lock. ```typescript const thread = await harness.selectOrCreateThread(); ``` #### `destroy()` Stop all heartbeat handlers and clean up resources. ```typescript await harness.destroy(); ``` ### State #### `getState()` Return a read-only snapshot of the current harness state. ```typescript const state = harness.getState(); ``` #### `setState(updates)` Update the harness state. Validates against `stateSchema` if provided, and emits a `state_changed` event with the new state and changed keys. ```typescript await harness.setState({ currentModelId: 'anthropic/claude-sonnet-4-20250514' }); ``` ### Modes #### `listModes()` Return all configured `HarnessMode` instances. ```typescript const modes = harness.listModes(); ``` #### `getCurrentModeId()` Return the ID of the currently active mode. ```typescript const modeId = harness.getCurrentModeId(); ``` #### `getCurrentMode()` Return the `HarnessMode` object for the current mode. ```typescript const mode = harness.getCurrentMode(); ``` #### `switchMode({ modeId })` Switch to a different mode. Aborts any in-progress generation, saves the current model to the outgoing mode, loads the incoming mode's model, and emits `mode_changed` and `model_changed` events. ```typescript await harness.switchMode({ modeId: 'build' }); ``` ### Models #### `getCurrentModelId()` Return the ID of the currently selected model from state. ```typescript const modelId = harness.getCurrentModelId(); ``` #### `getModelName()` Return a short display name from the current model ID. For example, `"claude-sonnet-4"` from `"anthropic/claude-sonnet-4"`. ```typescript const name = harness.getModelName(); ``` #### `getFullModelId()` Return the complete model ID string. ```typescript const fullId = harness.getFullModelId(); ``` #### `hasModelSelected()` Check if a model ID is currently selected. ```typescript if (harness.hasModelSelected()) { // Ready to send messages } ``` #### `switchModel({ modelId, scope?, modeId? })` Switch the active model. When `scope` is `'thread'`, the model ID is persisted to thread metadata so it's restored when switching back. Emits a `model_changed` event. ```typescript // Set for current session only await harness.switchModel({ modelId: 'anthropic/claude-sonnet-4-20250514' }); // Persist to the current thread await harness.switchModel({ modelId: 'anthropic/claude-sonnet-4-20250514', scope: 'thread' }); ``` #### `getCurrentModelAuthStatus()` Check if the current model's provider has authentication configured. Uses `modelAuthChecker` if provided, falling back to environment variable checks from the provider registry. ```typescript const status = await harness.getCurrentModelAuthStatus(); // { hasAuth: true, apiKeyEnvVar: 'ANTHROPIC_API_KEY' } ``` #### `listAvailableModels()` Retrieve all available models from the provider registry, including their authentication status and use counts. ```typescript const models = await harness.listAvailableModels(); // [{ id, provider, modelName, hasApiKey, apiKeyEnvVar, useCount }] ``` ### Threads #### `getCurrentThreadId()` Return the ID of the currently active thread. ```typescript const threadId = harness.getCurrentThreadId(); ``` #### `createThread({ title? })` Create a new thread. Initializes thread metadata, saves it to storage, acquires a thread lock, and emits a `thread_created` event. ```typescript const thread = await harness.createThread({ title: 'New conversation' }); ``` #### `switchThread({ threadId })` Switch to a different thread. Aborts any in-progress operations, acquires a lock on the new thread, releases the lock on the previous thread, loads the thread's metadata, and emits a `thread_changed` event. ```typescript await harness.switchThread({ threadId: 'thread-abc123' }); ``` #### `listThreads(options?)` List threads from storage. By default, only threads for the current resource are returned. ```typescript // List threads for current resource const threads = await harness.listThreads(); // List all threads across resources const allThreads = await harness.listThreads({ allResources: true }); ``` #### `renameThread({ title })` Update the title of the current thread. ```typescript await harness.renameThread({ title: 'Updated title' }); ``` #### `getResourceId()` Return the current resource ID. ```typescript const resourceId = harness.getResourceId(); ``` #### `setResourceId({ resourceId })` Set the resource ID and clear the current thread. ```typescript harness.setResourceId({ resourceId: 'project-xyz' }); ``` #### `getSession()` Return current session information including thread ID, mode ID, and the list of threads. ```typescript const session = await harness.getSession(); // { currentThreadId, currentModeId, threads } ``` ### Messages #### `sendMessage({ content, images? })` Send a message to the current agent. Creates a thread if none exists, builds a `RequestContext` and toolsets, and streams the agent's response. Handles tool calls, approvals, and errors automatically. ```typescript await harness.sendMessage({ content: 'Explain the authentication flow' }); ``` #### `listMessages(options?)` Retrieve messages for the current thread. ```typescript const messages = await harness.listMessages(); // Limit to the last 50 messages const recent = await harness.listMessages({ limit: 50 }); ``` #### `listMessagesForThread({ threadId, limit? })` Retrieve messages for a specific thread. ```typescript const messages = await harness.listMessagesForThread({ threadId: 'thread-abc123' }); ``` #### `getFirstUserMessageForThread({ threadId })` Retrieve the first user message for a given thread. ```typescript const firstMsg = await harness.getFirstUserMessageForThread({ threadId: 'thread-abc123' }); ``` ### Flow control #### `abort()` Abort any in-progress generation. ```typescript harness.abort(); ``` #### `steer({ content })` Steer the agent mid-stream by injecting an instruction into the current generation. ```typescript harness.steer({ content: 'Focus on security implications' }); ``` #### `followUp({ content })` Queue a follow-up message to be sent after the current generation completes. If no operation is running, sends the message immediately. ```typescript harness.followUp({ content: 'Now apply those changes' }); ``` ### Tool approvals #### `respondToToolApproval({ decision })` Respond to a pending tool approval request. Called when a `tool_approval_required` event is received. ```typescript harness.respondToToolApproval({ decision: 'approve' }); harness.respondToToolApproval({ decision: 'decline' }); ``` ### Questions and plans #### `respondToQuestion({ questionId, answer })` Respond to a pending question from the `ask_user` built-in tool. ```typescript harness.respondToQuestion({ questionId: 'q-123', answer: 'Yes, proceed with the refactor' }); ``` #### `respondToPlanApproval({ planId, response })` Respond to a pending plan approval from the `submit_plan` built-in tool. The `response` object contains `action` (`'approved'` or `'rejected'`) and an optional `feedback` string. ```typescript harness.respondToPlanApproval({ planId: 'plan-123', response: { action: 'approved' } }); harness.respondToPlanApproval({ planId: 'plan-123', response: { action: 'rejected', feedback: 'Needs more detail' } }); ``` ### Permissions #### `grantSessionCategory({ category })` Grant a tool category for the current session. Tools in this category are auto-approved without prompting. ```typescript harness.grantSessionCategory({ category: 'edit' }); ``` #### `grantSessionTool({ toolName })` Grant a specific tool for the current session. ```typescript harness.grantSessionTool({ toolName: 'mastra_workspace_execute_command' }); ``` #### `getSessionGrants()` Return currently granted session categories and tools. ```typescript const grants = harness.getSessionGrants(); // { categories: Set, tools: Set } ``` #### `setPermissionForCategory({ category, policy })` Set the permission policy for a tool category. ```typescript harness.setPermissionForCategory({ category: 'execute', policy: 'ask' }); ``` #### `setPermissionForTool({ toolName, policy })` Set the permission policy for a specific tool. Per-tool policies take precedence over category policies. ```typescript harness.setPermissionForTool({ toolName: 'dangerous_tool', policy: 'deny' }); ``` #### `getPermissionRules()` Return the current permission rules. ```typescript const rules = harness.getPermissionRules(); // { categories: { execute: 'ask' }, tools: { dangerous_tool: 'deny' } } ``` #### `getToolCategory({ toolName })` Resolve a tool's category using the configured `toolCategoryResolver`. ```typescript const category = harness.getToolCategory({ toolName: 'mastra_workspace_write_file' }); // 'edit' ``` ### Observational memory #### `loadOMProgress()` Load observational memory records for the current thread and emit an `om_status` event with reconstructed progress. ```typescript await harness.loadOMProgress(); ``` #### `getObserverModelId()` Return the observer model ID from state or the default from `omConfig`. ```typescript const modelId = harness.getObserverModelId(); ``` #### `getReflectorModelId()` Return the reflector model ID from state or the default from `omConfig`. ```typescript const modelId = harness.getReflectorModelId(); ``` #### `switchObserverModel({ modelId })` Switch the observer model. Persists the setting to thread metadata and emits an `om_model_changed` event. ```typescript await harness.switchObserverModel({ modelId: 'anthropic/claude-haiku-3.5' }); ``` #### `switchReflectorModel({ modelId })` Switch the reflector model. Persists the setting to thread metadata and emits an `om_model_changed` event. ```typescript await harness.switchReflectorModel({ modelId: 'anthropic/claude-haiku-3.5' }); ``` #### `getObservationThreshold()` Return the observation threshold in tokens from state or the default from `omConfig`. ```typescript const threshold = harness.getObservationThreshold(); ``` #### `getReflectionThreshold()` Return the reflection threshold in tokens from state or the default from `omConfig`. ```typescript const threshold = harness.getReflectionThreshold(); ``` ### Subagents #### `getSubagentModelId({ agentType? })` Retrieve the subagent model ID. Prioritizes per-type settings over the global setting. ```typescript const modelId = harness.getSubagentModelId({ agentType: 'explore' }); ``` #### `setSubagentModelId({ modelId, agentType? })` Set the subagent model ID. Pass an `agentType` to set a per-type override, or omit it to set the global default. Persists to thread settings and emits a `subagent_model_changed` event. ```typescript // Set global subagent model await harness.setSubagentModelId({ modelId: 'anthropic/claude-sonnet-4-20250514' }); // Set per-type model await harness.setSubagentModelId({ modelId: 'anthropic/claude-haiku-3.5', agentType: 'explore' }); ``` ### Events #### `subscribe(listener)` Register an event listener. Returns an unsubscribe function. ```typescript const unsubscribe = harness.subscribe((event) => { switch (event.type) { case 'message_update': renderMessage(event.message); break; case 'tool_approval_required': showApprovalPrompt(event.toolName); break; case 'error': console.error(event.error); break; } }); // Later: unsubscribe(); ``` ## Events The harness emits events through registered listeners. The following table lists the available event types: | Event type | Description | | -------------------------- | ------------------------------------------------------------------- | | `mode_changed` | The active mode changed. | | `model_changed` | The active model changed. | | `thread_changed` | The active thread changed. | | `thread_created` | A new thread was created. | | `state_changed` | Harness state was updated. | | `agent_start` | The agent started processing. | | `agent_end` | The agent finished processing. | | `message_start` | A new message started streaming. | | `message_update` | A message was updated with new content. | | `message_end` | A message finished streaming. | | `tool_start` | A tool call started. | | `tool_approval_required` | A tool call requires user approval. | | `tool_update` | A tool call was updated with progress. | | `tool_end` | A tool call finished. | | `tool_input_start` | Tool input started streaming. | | `tool_input_delta` | Tool input received a streaming delta. | | `tool_input_end` | Tool input finished streaming. | | `usage_update` | Token usage was updated. | | `error` | An error occurred. | | `info` | An informational message was emitted. | | `follow_up_queued` | A follow-up message was queued. | | `workspace_status_changed` | The workspace status changed. | | `workspace_ready` | The workspace finished initializing. | | `workspace_error` | The workspace encountered an error. | | `om_status` | Observational memory status update. | | `om_observation_start` | An observation started. | | `om_observation_end` | An observation completed. | | `om_reflection_start` | A reflection started. | | `om_reflection_end` | A reflection completed. | | `ask_question` | The agent asked a question via the `ask_user` tool. | | `plan_approval_required` | The agent submitted a plan for approval via the `submit_plan` tool. | | `plan_approved` | A plan was approved. | | `subagent_start` | A subagent started processing. | | `subagent_text_delta` | A subagent emitted a text delta. | | `subagent_tool_start` | A subagent started a tool call. | | `subagent_tool_end` | A subagent finished a tool call. | | `subagent_end` | A subagent finished processing. | | `subagent_model_changed` | A subagent's model changed. | | `task_updated` | A task list was updated. | ## Built-in tools The harness provides built-in tools to agents in every mode: | Tool | Description | | ------------- | ------------------------------------------------------------------------------------------------ | | `ask_user` | Ask the user a question and wait for their response. | | `submit_plan` | Submit a plan for user review and approval. | | `task_write` | Create or update a structured task list for tracking progress. | | `task_check` | Check the completion status of the current task list. | | `subagent` | Spawn a focused subagent with constrained tools (only available when `subagents` is configured). |