Skip to content

Permissions

Permission callbacks let the host application decide whether a tool call should run. Use them for file writes, shell commands, external APIs, payments, deletion, or any operation where the host owns the policy.

The callback runs after the model asks to call a registered tool and before the tool input is parsed or the tool handler executes. Return { behavior: "allow" } to run the tool, or return { behavior: "deny", message } to block it.

import { createAgent } from "agent-lattice";
const agent = createAgent({
apiKey: process.env.DEEPSEEK_API_KEY,
baseURL: "https://api.deepseek.com/anthropic",
model: "deepseek-v4-flash",
workspace: {
cwd: process.cwd(),
allowedDirectories: [process.cwd()],
},
permission: async request => {
if (request.toolName === "Bash") {
return {
behavior: "deny",
message: "Shell access is disabled for this session.",
};
}
return { behavior: "allow" };
},
});

If you do not pass a permission callback, every registered tool call is allowed. When you do pass one, returning { behavior: "allow" } approves the current tool call.

The example above is a deny-list policy: it blocks Bash, then allows everything else with the final return { behavior: "allow" }.

For an allow-list policy, invert the condition:

import type { AgentOptions } from "agent-lattice";
const readOnlyTools = new Set(["Read", "LS", "Glob", "Grep"]);
const permission: AgentOptions["permission"] = async request => {
if (readOnlyTools.has(request.toolName)) {
return { behavior: "allow" };
}
return {
behavior: "deny",
message: "Only read-only tools are allowed for this session.",
};
};

Denied tools do not throw by default. The SDK sends the model a tool_result with is_error: true and the denial message as its content.

That lets the model:

  • Explain that the operation was blocked by host policy.
  • Try another allowed tool.
  • Ask the user for a different path or additional approval.

The permission callback receives:

Field Meaning
toolName The registered tool name the model wants to call.
input The raw tool input from the model. Treat it as untrusted data.
toolUseId The id for this tool call.

You can inspect request.input before allowing a tool. Keep the policy in your application code; the SDK only calls it.

import type { AgentOptions } from "agent-lattice";
const permission: AgentOptions["permission"] = async request => {
if (request.toolName === "Bash") {
const command = String(request.input.command ?? "");
if (command.includes("rm -rf")) {
return {
behavior: "deny",
message: "Destructive shell commands are disabled for this session.",
};
}
}
return { behavior: "allow" };
};

Permissions are an approval layer, not a full sandbox. They do not rewrite model output, hide prompt content, or validate every custom tool argument for you.

For built-in file tools, combine permissions with allowedDirectories so file access stays inside the workspace you configured. For Bash and custom tools that call external systems, enforce the real safety boundary in the host environment as well: expose only the tools you need, validate inputs, use scoped credentials, and redact sensitive values before logging.