Permission policies
PermissionPolicy interface, StaticPermissionPolicy rule lists, and custom inline evaluators.
Permission policies
A PermissionPolicy evaluates each PendingAction — a concrete tool call with principal, toolset, tool name, and serialized arguments — before the runner invokes the handler.
import type { PermissionPolicy, PendingAction, Decision } from "@maniac-ai/agents";
interface PermissionPolicy {
evaluate(action: PendingAction): Decision | Promise<Decision>;
evaluateMany?(actions: PendingAction[]): Decision[] | Promise<Decision[]>;
}The optional evaluateMany hook lets HTTP- or database-backed policies answer batched sub-action fan-outs in a single round trip. The default evaluateMany in @maniac-ai/agents/permissions loops over evaluate.
StaticPermissionPolicy
Rule-list evaluator for most production setups. Import from @maniac-ai/agents or @maniac-ai/agents/permissions.
import { StaticPermissionPolicy } from "@maniac-ai/agents";
const policy = new StaticPermissionPolicy(
[
{
id: "allow-readonly",
principal: "*",
scope: { toolset: "files", tool: "read_file", arg_constraints: [] },
effect: "allow"
},
{
id: "ask-destructive",
principal: "*",
scope: { toolset: "files", tool: "delete_file", arg_constraints: [] },
effect: "require_approval"
},
{
id: "deny-shell",
principal: "*",
scope: { toolset: "shell", arg_constraints: [] },
effect: "deny"
}
],
{ allowed: false, requires_approval: false } // default when no rule matches
);Rule effects
| Effect | Result |
|---|---|
allow | { allowed: true, requires_approval: false } |
deny | { allowed: false, requires_approval: false } |
require_approval | { allowed: false, requires_approval: true } |
allow_unless_matches | Allow unless arg constraints match |
deny_unless_matches | Deny unless arg constraints match |
Rules are evaluated in order; the first match wins.
Matching dimensions
Each rule matches on:
principal— caller identity (often"*"or a user/session id)scope.toolset— toolset id (e.g."chat","computer_use","mcp:github")scope.tool— individual tool name (optional wildcard)arg_constraints— path grammar over serialized arguments
Arg constraint paths
resolvePath supports JSON-path-style selectors:
// Match any element in calls array
{ path: "calls[*].destination", operator: "equals", value: "prod" }
// Match header keys
{ path: 'headers["X-Env"]', operator: "equals", value: "production" }See tests/permission-scope.test.ts in the package for exhaustive path-grammar examples.
Shipped convenience policies
| Class | Behavior |
|---|---|
AllowAllPolicy | Always { allowed: true, requires_approval: false } |
DenyAllPolicy | Always { allowed: false, requires_approval: false } |
Custom inline policies
For dynamic logic, implement PermissionPolicy directly:
const approvalPolicy: PermissionPolicy = {
evaluate(action) {
if (action.call.tool === "lookup") {
return { allowed: true, requires_approval: false };
}
return {
allowed: false,
requires_approval: true,
reason: `${action.call.tool} requires approval`
};
}
};Wiring on Agent vs Maniac
// App-wide default
const app = new Maniac({ policy: appPolicy, agents: [...] });
// Per-agent override
app.agent({
id: "support",
instructions: "...",
model,
policy: stricterPolicy
});Per-agent policy replaces the app default for that agent's runs (including nested sub-agent sessions that inherit the parent's policy unless overridden).
Evaluation order with guardrails
On the tool path the runner evaluates in this order:
tool_middleware.beforeInvoketool_guardrails.checkCall(guardrails)policy.evaluate(this page)- Tool handler
tool_guardrails.checkResulttool_middleware.afterInvoke
A guardrail require_approval decision routes through the same HITL pause path as a policy requires_approval decision.