requestApproval
Problem
Some tool calls are too high-stakes to let an AI agent execute without a human reviewing them first — deleting data, sending emails, making payments, publishing content. Building a human-in-the-loop pause into an agent loop manually is messy and hard to reason about.
Solution
requestApproval is a clean async gate you add before any tool executes. You provide a handler function. The handler decides — synchronously or asynchronously — whether to approve the call. If it returns false, the tool is blocked. If it returns true, execution continues.
Feature & Use-Case
Use requestApproval when:
- Certain tools should require human sign-off before running (payment, deletion, messaging)
- You are building a supervised agent where a UI or webhook confirms each tool call
- You want a conditional approval gate — some tools always approved, others need review based on input values
- You are building for a compliance-sensitive environment and need audit trail hooks
Import
import { requestApproval } from "llm-layer-engine";Function Signature
async function requestApproval(
config: ApprovalConfig,
ctx: ApprovalContext
): Promise<boolean>Types
type ApprovalContext = {
toolName: string;
userId?: string;
input?: unknown;
metadata?: Record<string, unknown>;
};
interface ApprovalConfig {
handler?: ApprovalHandler;
}
type ApprovalHandler = (ctx: ApprovalContext) => boolean | Promise<boolean>;Parameters
| Parameter | Type | Required | Description |
|---|---|---|---|
config.handler | ApprovalHandler | ❌ | Your approval logic. If omitted, all calls are auto-approved |
ctx.toolName | string | ✅ | Name of the tool awaiting approval |
ctx.userId | string | ❌ | User who triggered the agent |
ctx.input | unknown | ❌ | The tool’s input data — for the approver to inspect |
ctx.metadata | object | ❌ | Extra context (e.g. session ID, request ID) |
Example — Block High-Risk Tools
import { requestApproval } from "llm-layer-engine";
const approvalConfig = {
handler: async ({ toolName, input }) => {
const requiresApproval = ["send_email", "delete_record", "charge_card"];
if (requiresApproval.includes(toolName)) {
// In production: send to approval queue, webhook, or UI
// Here: auto-deny for example
console.log(`Approval required for ${toolName}`, input);
return false;
}
return true; // Safe tools auto-approved
},
};
const approved = await requestApproval(approvalConfig, {
toolName: "send_email",
userId: "user_789",
input: { to: "client@example.com", subject: "Invoice" },
});
if (!approved) {
console.log("Tool call blocked — awaiting human approval");
}Example — Webhook-Based Approval (Async)
import { requestApproval } from "llm-layer-engine";
import axios from "axios";
const approvalConfig = {
handler: async ({ toolName, input, userId }) => {
// Send approval request to your backend
const { data } = await axios.post("https://your-app.com/approvals", {
toolName,
input,
userId,
});
return data.approved === true;
},
};Your handler can be fully async — await a webhook, a database query, or a user response from your UI.
Example — No Handler (Auto-Approve All)
const approved = await requestApproval({}, {
toolName: "get_weather",
});
console.log(approved); // → trueNo handler means everything is auto-approved — safe default for low-risk tools.
Conclusion
requestApproval is your human-in-the-loop gate. With no handler it is invisible — all calls pass through. Add a handler the moment any tool needs review before execution. It works well alongside checkPermission: permissions handle role-based blocking, approval handles explicit human confirmation for sensitive actions.