Approval Settings
Put passive approval policy on a tool for Studio to interpret.
Use tool approval settings when a side-effect tool should usually require review in Studio. The policy stays next to the tool definition, so users can move between Studio and their own frontend/backend without changing the tool contract.
Core only stores this metadata. Studio interprets it by installing a per-run request hook.
Protect a Tool
import { AgentBuilder, createTool } from "@anvia/core";
import { Studio } from "@anvia/studio";
import { z } from "zod";
const issueRefund = createTool({
name: "issue_refund",
description: "Issue a customer refund.",
input: z.object({
orderId: z.string(),
amount: z.number().positive(),
reason: z.string(),
}),
output: z.object({
refundId: z.string(),
status: z.enum(["issued"]),
}),
approval: {
when: ({ args }) => args.amount > 100,
reason: ({ args }) => `Review refund of $${args.amount} for ${args.orderId}.`,
rejectMessage: "Refund was not approved.",
},
async execute({ orderId }) {
return {
refundId: `rf_${orderId.toLowerCase()}`,
status: "issued" as const,
};
},
});
const agent = new AgentBuilder("support", model)
.tool(issueRefund)
.defaultMaxTurns(3)
.build();
new Studio([agent]).start();When the model calls issue_refund, Studio evaluates approval.when(...). If it returns true, Studio creates a pending approval. If the reviewer approves, Studio runs the tool. If the reviewer rejects, Studio skips the tool and returns the rejection message to the model.
Approval Context
type ToolApprovalContext<TArgs> = {
toolName: string;
args: TArgs;
rawArgs: string;
toolCallId?: string;
internalCallId: string;
run: {
agentId: string;
runId: string;
sessionId?: string;
metadata?: JsonObject;
};
};args is parsed with the tool input schema. Use it for conditional policy.
approval: {
when: ({ args, run }) =>
args.amount > 100 || run.metadata?.environment === "production",
}Dynamic Reasons
Use reason to show reviewers what they are deciding.
approval: {
when: ({ args }) => args.amount > 100,
reason: ({ args }) =>
`Approve ${args.reason} refund of $${args.amount} for ${args.orderId}.`,
rejectMessage: ({ args }) =>
`Refund ${args.orderId} was not approved.`,
}reason and rejectMessage may be strings or async functions.
When to Use Hooks Instead
Use hook approval requests when approval depends on request-local state, app permissions, external policy services, or policy that is not tied to one tool definition.
Agent hooks run before normal tool execution. If a hook returns tool.skip(...) or tool.cancel(...), the tool does not run and no tool-level approval policy is evaluated. If it returns tool.requestApproval(...), the active approval handler decides that hook-created approval request.
