DEV Community

RouterBase for RouterBase

Posted on

Build a Human Approval Gate for Agent Tool Calls in JavaScript

An agent should not treat every available tool as permission to execute it.

A production-friendly tool layer needs at least three outcomes:

  • allow: safe to execute automatically
  • review: pause and ask a person
  • deny: reject the call

Here is a small, dependency-free gate you can run with Node.js.

Prerequisites

  • Node.js 18 or later
  • No external packages

Save the following as approval-gate.mjs:

const policy = {
  searchDocs: { risk: "low", required: ["query"] },
  updateCustomer: { risk: "medium", required: ["customerId", "patch"] },
  issueRefund: { risk: "high", required: ["paymentId", "amount"] },
};

function decideToolCall(call) {
  const rule = policy[call.name];
  if (!rule) return { decision: "deny", reason: "Unknown tool" };

  const missing = rule.required.filter(
    (key) => call.arguments[key] == null,
  );
  if (missing.length) {
    return {
      decision: "deny",
      reason: `Missing: ${missing.join(", ")}`,
    };
  }

  if (rule.risk === "low") return { decision: "allow" };

  return {
    decision: "review",
    reason: `${rule.risk}-risk action requires human approval`,
    approvalKey: call.id,
  };
}

const calls = [
  {
    id: "call_1",
    name: "searchDocs",
    arguments: { query: "refund policy" },
  },
  {
    id: "call_2",
    name: "issueRefund",
    arguments: { paymentId: "p_123", amount: 49 },
  },
  {
    id: "call_3",
    name: "deleteWorkspace",
    arguments: { workspaceId: "w_1" },
  },
];

console.table(
  calls.map((call) => ({ tool: call.name, ...decideToolCall(call) })),
);
Enter fullscreen mode Exit fullscreen mode

Run it:

node approval-gate.mjs
Enter fullscreen mode Exit fullscreen mode

Expected decisions:

```plain text
searchDocs allow
issueRefund review
deleteWorkspace deny




## Why this pattern helps

### 1. Unknown tools fail closed

If a model invents a tool name or reaches a tool that was not registered for this workflow, the gate returns `deny`. It never assumes that an unknown action is harmless.

### 2. Arguments are checked before execution

The example only checks required fields. In production, validate types, ranges, identifiers, and object shapes with a schema library before any side effect.

### 3. Risk changes the execution path

Low-risk reads can continue. Medium- and high-risk actions return `review` with a stable `approvalKey`. A UI or queue can use that identifier to collect a human decision.

## What to add before production

This example is intentionally small. A real approval service should also include:

- An immutable audit log
- An approval expiry time
- The exact tool arguments shown to the reviewer
- Idempotency protection
- The reviewer identity and decision reason
- A fresh authorization check when execution resumes
- Tests for policy changes and previously observed failures

Do not execute the original call just because an approval record exists. Verify that the approval matches the same tool, arguments, user, and current policy version.

OpenAIโ€™s Presence announcement describes the same broader production problem: companies need to define allowed actions, approval points, evaluations, escalation rules, and controlled changes after launch.

If your agent also needs one OpenAI-compatible API for models across providers, RouterBase is available at [https://routerbase.com](https://routerbase.com).
Enter fullscreen mode Exit fullscreen mode

Top comments (0)