Human-in-the-loop for OpenClaw
Human approval for OpenClaw tool calls, on a phone.
Register a before_tool_call hook in an OpenClaw plugin. The hook calls decisions.ask() for the tools you gate, the question lands on a person's phone, and the hook returns { block: true } unless they approve. Install @pushary/server, connect the phone once with a keyless one-tap link, and every allowed or blocked call is written to a durable ledger.
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry"
import { createPusharyServer } from "@pushary/server"
const pushary = createPusharyServer({ apiKey: process.env.PUSHARY_API_KEY! })
const GATED = new Set(["deploy_service", "delete_record", "send_email"])
export default definePluginEntry({
id: "pushary-approvals",
name: "Pushary approvals",
register(api) {
api.on("before_tool_call", async (event) => {
if (!GATED.has(event.toolName)) return
const d = await pushary.decisions.ask({
question: `Allow ${event.toolName}?`,
externalId: "user_123",
type: "confirm",
})
if (!d.approved) {
return { block: true, blockReason: "A human declined this action." }
}
})
},
})Why you need it
OpenClaw pauses the agent. Pushary reaches the human.
OpenClaw's hooks can stop a tool call, and the hook has no channel to a person who is away from the gateway. decisions.ask() supplies the person: it delivers the question, waits for a fail-closed answer, and keeps the record. Gate only the tools that matter, like deploys, deletes, and outbound messages, so routine calls run untouched.
How it works
From ask to resume
Connect the phone once, then ask a human from the tool or step that guards the action. The waiting, the delivery, and the record are handled for you.
- 01
Install and set your key
Run npm i @pushary/server in your plugin workspace and set PUSHARY_API_KEY.
- 02
Connect the phone
Call enroll(externalId) once to connect the approver's phone with a keyless one-tap link.
- 03
Register the hook
In your plugin entry, register a before_tool_call hook that calls decisions.ask() for the tool names you gate.
- 04
Block on anything but a yes
Return { block: true } with a blockReason when the decision is not an explicit approval, so a decline or a timeout stops the call.
A decision is a yes or no confirm, a multiple-choice select, or a free-text input. Every one is stored with its outcome and kept in the audit log.
For your users
Your agent asks your users, not just you
Ship the agent to real users and let each of them approve their own decisions from their phone, under your brand.
Your users answer on their own phones
The externalId is your own id for the end-user. Connect their phone once with a keyless one-tap link, and every approval lands with the right person. No account, no app, no key on their side.
Durable, fail-closed waits
The agent stays open on a durable wait backed by a decisions ledger. If nobody answers in the window you set, the decision comes back denied, so a timeout or a restart never turns into an unapproved action.
Phone-first delivery
Pushary reaches the person on the device they carry: native app push first, then PWA push notifications, then Slack if you route it there. You do not build a delivery pipeline.
Signed webhooks and an audit trail
Every decision is written to a durable ledger you can query and export, and resolved decisions post a signed webhook to your callback URL. You have the record of who approved what and when.
FAQ
Questions, answered
Human-in-the-loop for other frameworks
Let your OpenClaw agent ask a human.
Connect a user with one tap, ask a human with one call, and act on a fail-closed decision. The durable store and the audit log come with it.
Partner plan, $99 a month. Your users never see a Pushary login or bill.