Tool approvals and permissions
The Harness 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 policiesDirect link to Permission policies
Three policies control tool behavior:
allow: The tool runs without promptingask: The tool pauses and emits atool_approval_requiredevent; the user must approve or declinedeny: The tool is blocked from execution
Setting policiesDirect link to Setting policies
Set policies per-category or per-tool. Per-tool policies take precedence over category policies:
// Category-level: all execute tools require approval
harness.setPermissionForCategory({ category: 'execute', policy: 'ask' })
// Tool-level: this specific tool is always blocked
harness.setPermissionForTool({ toolName: 'dangerous_tool', policy: 'deny' })
Tool categoriesDirect link to Tool categories
The toolCategoryResolver maps tool names to categories. Pass it to the Harness constructor:
const harness = new Harness({
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 requestsDirect link to Responding to approval requests
When a tool's policy is ask, the Harness emits a tool_approval_required event. Your UI should display a prompt and call session.respondToToolApproval():
harness.subscribe(event => {
if (event.type === 'tool_approval_required') {
// Show approval UI...
harness.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.
harness.session.respondToToolApproval({ decision: 'always_allow_category' })
Session grantsDirect link to Session grants
The Harness owns permission policy (which categories require approval); the 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:
// Grant all edit tools for this session
harness.session.grantCategory('edit')
// Grant a specific tool
harness.session.grantTool('mastra_workspace_execute_command')
// Check current grants
const grants = harness.session.getGrants()
// { categories: ['edit'], tools: ['mastra_workspace_execute_command'] }
Tool suspensionsDirect link to 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():
harness.subscribe(event => {
if (event.type === 'tool_suspended' && event.toolName === 'ask_user') {
const { question } = event.suspendPayload as { question: string }
// Show question to user, then resume:
harness.respondToToolSuspension({
toolCallId: event.toolCallId,
resumeData: 'User response here',
})
}
})
Plan approvalDirect link to Plan approval
The submit_plan tool suspends via the same mechanism. Resume with an action field:
// Approve the plan
harness.respondToToolSuspension({
toolCallId: event.toolCallId,
resumeData: { action: 'approved' },
})
// Reject with feedback
harness.respondToToolSuspension({
toolCallId: event.toolCallId,
resumeData: { action: 'rejected', feedback: 'Needs more detail' },
})
Built-in toolsDirect link to Built-in tools
The Harness 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:
const harness = new Harness({
id: 'no-plans',
disableBuiltinTools: ['submit_plan'],
})