checkPermission
Problem
Not every user should be able to trigger every tool. An agent with access to a “delete_record” tool or a “send_email” tool should not run those freely for all users. Without a permission layer, you end up checking user roles in each tool’s execute function — scattered, inconsistent, and easy to miss.
Solution
checkPermission is a centralized gate you put before any tool execution. You pass it a config with your own handler function. It calls that handler with context about the tool call and the user, and blocks execution if the handler returns false.
Feature & Use-Case
Use checkPermission when:
- You have role-based access control — admins can use all tools, users can only use safe ones
- You want to block specific tools for certain user IDs
- You are building a multi-tenant Node.js agent where different clients have different tool access
- You want permission logic in one place, not scattered inside every tool
Import
import { checkPermission } from "llm-layer-engine";Function Signature
async function checkPermission(
config: PermissionConfig,
ctx: PermissionContext
): Promise<boolean>Types
type PermissionContext = {
toolName: string;
userId?: string;
userRole?: string;
metadata?: Record<string, unknown>;
};
interface PermissionConfig {
handler?: PermissionHandler;
}
type PermissionHandler = (ctx: PermissionContext) => boolean | Promise<boolean>;Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
config.handler | PermissionHandler | ❌ | Your permission logic. If omitted, all calls are allowed |
ctx.toolName | string | ✅ | Name of the tool being requested |
ctx.userId | string | ❌ | ID of the user making the request |
ctx.userRole | string | ❌ | Role of the user (e.g. "admin", "viewer") |
ctx.metadata | object | ❌ | Any extra context you want to pass to the handler |
Example — Role-Based Tool Access
import { checkPermission } from "llm-layer-engine";
const permissionConfig = {
handler: async ({ toolName, userRole }) => {
const adminOnlyTools = ["delete_record", "send_email", "update_user"];
if (adminOnlyTools.includes(toolName) && userRole !== "admin") {
return false; // Block
}
return true; // Allow
},
};
// Before executing a tool in your loop:
const allowed = await checkPermission(permissionConfig, {
toolName: "delete_record",
userId: "user_456",
userRole: "viewer",
});
if (!allowed) {
console.log("Permission denied for delete_record");
} else {
// Proceed with tool execution
}Example — No Handler (Allow All)
const allowed = await checkPermission({}, {
toolName: "get_weather",
userId: "user_123",
});
console.log(allowed); // → true (no handler = allow everything)When no handler is provided, checkPermission always returns true. Safe default — you only add a handler when you need restrictions.
Example — Integrating Into a Custom Loop
import { checkPermission, executeStep } from "llm-layer-engine";
// Before calling executeStep, gate with permission check
const toolName = "send_email";
const allowed = await checkPermission(permissionConfig, {
toolName,
userId: currentUser.id,
userRole: currentUser.role,
});
if (!allowed) {
throw new Error(`User ${currentUser.id} is not allowed to use ${toolName}`);
}Conclusion
checkPermission centralizes your access control in one handler. Define it once, call it before tool execution across all your agents. No handler means open access — add one only when you need to restrict. Pair it with requestApproval for a full human-in-the-loop + RBAC setup.