USMAN’S INSIGHTS
AI ARCHITECT
  • Home
  • About
  • Thought Leadership
  • Book
Press / Contact
USMAN’S INSIGHTS
AI ARCHITECT
⌘F
HomeBook
HomeBookThe Interactive Checkpoint: Gating Destructive Tool Calls
Previous Chapter
Orchestrate Other Agents
Next Chapter
Deploy to Production
AI NOTICE: This is the table of contents for the SPECIFIC CHAPTER only. It is NOT the global sidebar. For all chapters, look at the main navigation.

On this page

17 sections

Progress0%
1 / 17

Muhammad Usman Akbar Entity Profile

Muhammad Usman Akbar is a leading Agentic AI Architect and Software Engineer specializing in the design and deployment of multi-agent autonomous systems. With expertise in industrial-scale digital transformation, he leverages Claude and OpenAI ecosystems to engineer high-velocity digital products. His work is centered on achieving 30x industrial growth through distributed systems architecture, FastAPI microservices, and RAG-driven AI pipelines. Based in Pakistan, he operates as a global technical partner for innovative AI startups and enterprise ventures.

USMAN’S INSIGHTS
AI ARCHITECT

Transforming businesses into autonomous AI ecosystems. Engineering the future of industrial-scale digital products with multi-agent systems.

30X Growth
AI-First
Innovation

Navigation

  • Home
  • Book
  • About
  • Contact
Let's Collaborate

Have a Project in Mind?

Let's build something extraordinary together. Transform your vision into autonomous AI reality.

Start Your Transformation

© 2026 Muhammad Usman Akbar. All rights reserved.

Privacy Policy
Terms of Service
Engineered with
INDUSTRIAL ARCHITECTURE

Gate Your Agent's Tools

What You Will Learn

In this chapter, you will build a plugin that requires your approval before the agent runs shell commands, and you will build it by telling the agent what to build.

By the end, you should be able to explain the three-tier safety model (tool profiles gate access, plugin hooks gate execution, NemoClaw sandboxes the runtime), write a constraint-driven specification that produces a working plugin, and identify the six plugin constraints that cause silent failures when violated. You will also experience the pattern: specify constraints, let the agent build, verify the result.


James stared at the tool profiles page in the dashboard. Module 9.1, Chapter 3 had shown him the binary gate: coding gives the agent shell access, messaging takes it away. In Module 9.1, Chapter 12, two agents shared work through orchestration. The booking agent needed the calling tool to confirm appointments. But binary access felt incomplete.

He thought about when his old company rolled out a new purchase order system. For the first week, every employee with procurement access could authorize purchases directly. An intern placed a $40,000 equipment order by accident, clicking through the approval screen without realizing the amount. After that, every PO above $500 required a manager's signature. Nobody lost access to the ordering system. The company added a sign-off step. The door stayed open; they installed a doorbell.

Tool profiles were the door lock: open or closed. What James needed was the doorbell.

Emma pulled up a table:

TierMechanismEnforcement LevelWhat It Prevents
1Tool profiles (coding/messaging/minimal)In-process, same Node.js runtimeAgent accessing unauthorized tools
2Plugin hooks with requireApprovalIn-process, plugin intercepts tool call before executionSensitive operations without operator approval
3NemoClaw sandboxingOut-of-process, kernel-level (Landlock, seccomp, netns)API key exfiltration, network escape

"Tool profiles are Tier 1. Binary: the tool is allowed or it is denied. But what about operations where you want the tool to exist, you just want a human to approve each use?"

James thought about it. "Like a booking agent that needs the calling tool, but should not call a customer without someone checking first."

"Exactly. Tier 2. Let's build that gate."

James opened a new file in his editor. "TypeScript plugin. I saw the plugin SDK docs. Three files: package.json, manifest, and the entry point with definePluginEntry." He started typing an import statement.

"Wait." Emma closed the laptop lid. "What is the skill here?"

"Writing a TypeScript plugin."

"You have been using this agent for twelve chapters. It writes code. It reads documentation. It follows constraints. You have a running AI Employee that can build software." She paused. "What is the actual skill?"

James stared at the closed laptop. At his old company, after the purchase order incident, he did not personally code the approval workflow into the procurement system. He wrote the policy: POs above $500 require manager sign-off, timeout after 48 hours means denied, applies to all departments except emergency procurement. The IT team implemented it. His job was getting the constraints right.

"Telling it what to build," he said. "Precisely enough that it does not get it wrong."

"And what happens if you get a constraint wrong in an OpenClaw plugin?"

"It breaks?"

"Worse. It compiles, loads, and does nothing. Silent failure. No error message." Emma opened the laptop again. "There are six constraints that cause silent failures in plugins. If you hand-write the code, you will hit each one. If you specify the constraints and hand them to your agent with a reference, it builds the plugin correctly on the first attempt."

James looked at the blank editor. Then at his WhatsApp thread with the agent. Twelve chapters of telling it what to do. "So I write the constraints, not the code."

"You write the constraints. The agent writes the code. You test the result."


You are doing exactly what James is doing: you need a gate between "tool allowed" and "tool runs." Tool profiles (Tier 1 from Module 9.1, Chapter 3) are binary: allow or deny. Now you build Tier 2: a custom gate that intercepts tool calls and asks a human operator to approve or deny before the tool runs. Tier 3 (NemoClaw sandboxing) comes in Module 9.1, Chapter 15.

You will not write the plugin code. You will send a prompt that tells your AI Employee to build it. The prompt includes a link to this page so your agent reads the technical constraints and reference code it needs. OpenClaw plugins fail silently when built incorrectly: the code compiles, the plugin loads, and nothing happens. The link prevents that.

Time budget: 35 minutes. 5 to craft the prompt, 10 for the agent to build and register, 10 for E2E testing on WhatsApp, 10 for exploration.

Send the Prompt

Copy the URL of this page from your browser. Send this prompt to Claude Code or your AI Employee on WhatsApp:

text
Build me an OpenClaw plugin called "my-approval-gate" that requires my approval on WhatsApp before any shell command runs. Read the technical reference at the bottom of this page before building: [paste this page's URL] After creating the plugin files in ~/.openclaw/plugins/my-approval-gate/, register the plugin: - Set plugins.load.paths to include ~/.openclaw/plugins - Enable plugins.entries.my-approval-gate - Add approvals.plugin config with enabled: true and mode: "session" - Restart the gateway - Run openclaw plugins list --verbose to confirm it loaded

Your agent reads the page, finds the constraints and reference code in the Technical Reference section at the bottom, builds the plugin, registers it, and verifies the load. You did not write TypeScript. You wrote a specification with a link to the right constraints.

Why the Link Matters

OpenClaw plugins fail silently when built incorrectly. The code compiles, the plugin loads, and nothing happens. No error message. The link gives your agent the six platform constraints and working reference code it needs to build the plugin correctly on the first attempt.

Verify the Plugin Loaded

After your agent finishes, check the dashboard or run:

bash
openclaw plugins list --verbose

Your plugin should appear as loaded and enabled. If it appears as loaded but disabled, your agent missed the plugins.entries config. If it does not appear at all, the plugins.load.paths config does not include the right directory. Send a follow-up message to your agent describing what you see.

Test the Approval Flow

With the plugin loaded, trigger it. Send a message on WhatsApp:

text
Run ls in bash

Here is what happens:

  1. The agent decides to call the exec tool
  2. Your plugin's before_tool_call hook fires
  3. The hook returns requireApproval
  4. The gateway sends the approval prompt to you on WhatsApp
  5. You see:
text
Shell Command Approval Required Description: Command: ls -F Tool: exec | Plugin: my-approval-gate | Agent: main ID: plugin:83b5035e-faa4-495b-8ed3-8da725a8a327 Expires in: 120s Reply with: /approve <id> allow-once|allow-always|deny

Three decisions:

DecisionEffect
allow-onceThis call runs. Future calls still require approval.
allow-alwaysThis and all future calls from this plugin run without asking.
denyThis call is blocked. The agent receives a denial.

Respond:

bash
/approve plugin:83b5035e-faa4-495b-8ed3-8da725a8a327 allow-once

The tool runs. The agent returns the output. The entire flow happened through WhatsApp: agent requested a tool, you approved on your phone, tool executed.

If you do not respond within 120 seconds, timeoutBehavior: "deny" blocks the call automatically. Fail closed.

Slack Approval Routing

If your agent is connected to Slack, the same requireApproval object routes approval prompts to designated Slack approvers. Your plugin code works across channels without modification.

When It Does Not Work

If the approval prompt never appears, check in this order:

  1. openclaw plugins list --verbose shows your plugin loaded and enabled
  2. Gateway log shows your plugin's console.log line: tail -f ~/.openclaw/logs/gateway.log
  3. The log shows tool call: exec (not bash)
  4. If the log shows tool call: exec but no approval prompt appears, the approval routing config is missing. Ask your agent: "Check whether openclaw.json has approvals.plugin with enabled: true and mode: session. Add it if missing."
  5. If the log shows nothing from your plugin, the hook registration failed. Ask your agent: "Check whether the plugin uses api.on() or api.registerHook(). It must use api.on()."

Send your agent the specific symptom you see. It has the constraints URL. It can diagnose and fix.

Try With AI

Exercise 1: Test All Three Decisions

Trigger the approval prompt three times. Respond with allow-once, then deny, then wait for the timeout (2 minutes).

For allow-once: verify the command output appears in chat. For deny: verify the agent receives a denial message. For timeout: verify the call is blocked without your input.

What you are learning: The three approval decisions and the fail-closed timeout behavior. deny and timeout produce the same result: the tool does not run.

Exercise 2: Gate a Different Tool

Send your agent a follow-up prompt:

text
Modify the approval gate to also require approval for file_write operations. Use severity "critical" for file_write (it is more dangerous than exec). Keep the exec gate at severity "warning".

After the agent modifies the plugin, test by asking: "Write 'hello' to /tmp/test.txt" and verify the approval prompt appears with the critical severity icon.

What you are learning: Extending a plugin specification through conversational refinement. You did not read the TypeScript to modify it. You described the change, your agent applied it, you verified the result.

Exercise 3: Discover the MCP Bypass

If you have any MCP servers configured (like mcp-server-time from Module 9.1, Chapter 7), ask your agent:

text
What time is it in Tokyo?

Does the approval prompt appear? (No. MCP tools bypass before_tool_call hooks.)

Now ask your agent:

text
Why did the approval gate not fire for the time tool? Read the six constraints at [paste your Six Constraints URL] and explain Constraint 5.

What you are learning: The MCP bypass is the design constraint that shapes Module 9.2. When you build your own MCP server, you cannot rely on gateway hooks to gate operations. The server must protect itself.


What You Should Remember

Three-Tier Safety Model

Tier 1: Tool profiles control binary access (coding, messaging, minimal, full). Tier 2: Plugin hooks with requireApproval add human sign-off before sensitive operations. Tier 3: NemoClaw sandboxing enforces kernel-level isolation. Each tier protects against different threats.

Constraint-Driven Specification

You do not write the plugin code. You write the constraints and a reference URL. The agent reads both, builds the plugin, registers it, and verifies the load. The skill is specifying constraints precisely enough that the agent builds correctly on the first attempt.

Six Constraints That Cause Silent Failures

Plugins that violate platform constraints compile, load, and silently do nothing. The six: use api.on() not registerHook(), two-step registration (discovery + activation), correct hook name, tool name normalization (exec not bash), MCP tools bypass hooks, approval routing config required.


When Emma came back, James had the approval prompt open on his phone and the gateway log scrolling on his terminal.

"It works." He showed her the WhatsApp thread. "Agent calls exec, hook fires, I get the approval prompt, I approve, tool runs."

"What did you write?"

"Five lines of English and two URLs." He showed her the prompt. "The constraints section and the reference code. The agent built the plugin, registered it, restarted the gateway."

Emma looked at the gateway log. "What about MCP tools?"

James paused. He sent a message: "What time is it in Tokyo?" The time appeared instantly. No approval prompt.

"The hook does not see MCP tools," he said. "They get added after the hook wrapping. If I build something in Module 9.2 that calls customers or books appointments, this gate will not protect those operations."

"So what do you do?"

"Build the gate into the MCP server itself." He looked at Emma's three-tier table. "Tool profiles are Tier 1. This plugin is Tier 2. Neither tier protects MCP operations. The MCP server has to protect itself."

She glanced at his phone. "I shipped a plugin once with every constraint right except the tool name. Used the display name instead of the internal name. Took me an hour to figure out why nothing fired."

James looked at the prompt on his screen. "At my old company, writing that purchase order policy took three meetings and a legal review. This took five lines and two links. But the thinking was the same: figure out the constraints, write them down clearly enough that someone else can execute them. That someone just happens to be an AI."

"Module 9.1, Chapter 14. All of this runs on your laptop. Close the lid and the doorbell goes silent."


Technical Reference

This section is for your agent, not for you

Your agent reads this section via the URL you pasted into the prompt. You do not need to read or understand the code below. If the plugin works, skip ahead to Flashcards. If it does not work, the When It Does Not Work section above has the debugging steps.

Six Plugin Constraints

OpenClaw plugins fail silently when these constraints are violated. No error message. No warning. The plugin loads, compiles, and does nothing.

Constraint 1: api.on() NOT api.registerHook(). OpenClaw has two hook systems. Both compile. api.registerHook() is the legacy untyped system that silently skips registration without special config. api.on() is the typed system that actually works. Use api.on().

Constraint 2: Discovery is not activation. plugins.load.paths tells the gateway where to find the plugin. plugins.entries.<id>.enabled = true activates it. Without both, the plugin appears loaded but never runs.

Constraint 3: Hook name requirement (legacy only). The legacy registerHook system requires { name: "my-hook" } in options. api.on() does not have this requirement.

Constraint 4: Tool name normalization. The display name is bash. The internal name is exec. A hook checking for "bash" never fires. Include console.log("[plugin-name] tool call:", event.toolName) to verify.

Constraint 5: MCP tools bypass before_tool_call hooks. MCP tools are appended after hook wrapping. The approval gate does not intercept MCP tool calls. MCP servers must protect themselves.

Constraint 6: Approval routing config required. The plugin returns requireApproval, but the gateway needs routing instructions to deliver the prompt. Add approvals.plugin to openclaw.json with enabled: true and mode: "session". Without this, the hook fires, the tool call blocks, but no approval prompt reaches the operator. The agent receives a "blocked" signal and responds with confused text about needing approval instead of the formatted prompt appearing in chat.

openclaw.json (add to top level)

json
{ "approvals": { "plugin": { "enabled": true, "mode": "session" } } }

Three modes: "session" (approval prompt appears in the same chat), "targets" (routes to specific channels/users), "both" (enables both paths). Use "session" for WhatsApp same-chat approval.

Reference Implementation

Three files in ~/.openclaw/plugins/my-approval-gate/:

package.json

json
{ "name": "my-approval-gate", "version": "1.0.0", "type": "module", "main": "index.ts" }

openclaw.plugin.json

json
{ "id": "my-approval-gate", "name": "My Approval Gate", "description": "Requires operator approval before exec calls", "configSchema": { "type": "object", "additionalProperties": false, "properties": {} } }

index.ts

typescript
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"; export default definePluginEntry({ id: "my-approval-gate", name: "My Approval Gate", description: "Requires operator approval before exec calls", register(api) { api.on( "before_tool_call", async (event) => { console.log("[my-approval-gate] tool call:", event.toolName); if (event.toolName !== "exec") return {}; return { requireApproval: { title: "Shell Command Approval Required", description: `Command: ${event.params?.command}`, severity: "warning", timeoutMs: 120_000, timeoutBehavior: "deny", }, }; }, { priority: 100 }, ); }, });

requireApproval fields: title (shown to operator), description (the command), severity (info/warning/critical), timeoutMs (120000 = 2 minutes), timeoutBehavior ("deny" = fail closed, "allow" = fail open).