@gizmo-ai/hitl-plugin
v0.2.1
Published
Human-in-the-Loop approval plugin for Gizmo runtime
Readme
@gizmo-ai/hitl-plugin
Human-in-the-Loop approval plugin for Gizmo. Blocks tool calls pending human approval via HTTP API.
Install
bun add @gizmo-ai/hitl-pluginQuick Start
import { createRuntime } from "@gizmo-ai/runtime";
import { agentPlugin } from "@gizmo-ai/agent-plugin";
import { createServer, createServerPlugin } from "@gizmo-ai/server";
import { createHITLPlugin } from "@gizmo-ai/hitl-plugin";
// 1. Create HITL plugin
const hitl = createHITLPlugin({
requiresApproval: ["bash", "write", "edit"],
defaultTimeoutMs: 60000,
});
// 2. Create runtime with HITL plugin FIRST
const agent = agentPlugin({ model, tools });
const serverPlugin = createServerPlugin();
const runtime = createRuntime({
plugins: [hitl.plugin, agent, serverPlugin.plugin],
});
// 3. Connect HITL to runtime
hitl.connect(runtime);
// 4. Create server and wire HITL routes
const server = createServer({ runtime, plugin: serverPlugin });
server.app.route("/approvals", hitl.createRoutes());
server.listen(3000);Configuration
interface HITLPluginConfig {
// Which tools require approval
requiresApproval: "all" | string[] | ((toolName: string, args: Record<string, unknown>) => boolean);
// Timeout for approval requests (0 = no timeout)
defaultTimeoutMs?: number; // Default: 60000
// History ring buffer size
historySize?: number; // Default: 100
}HTTP API
| Method | Path | Description |
|--------|------|-------------|
| GET | /approvals | List pending approvals |
| GET | /approvals/:id | Get approval details |
| POST | /approvals/:id | Approve or reject |
| GET | /approvals/history | Recent decisions |
Approve/Reject
# Approve
curl -X POST http://localhost:3000/approvals/<id> \
-H "Content-Type: application/json" \
-d '{"decision": "approve", "reason": "Looks safe"}'
# Reject
curl -X POST http://localhost:3000/approvals/<id> \
-H "Content-Type: application/json" \
-d '{"decision": "reject", "reason": "Dangerous command"}'Programmatic API
// Get pending approvals
const pending = hitl.getPending();
// Approve programmatically
hitl.approve(approvalId, "Auto-approved by policy");
// Reject programmatically
hitl.reject(approvalId, "Auto-rejected by policy");Custom Approval Logic
const hitl = createHITLPlugin({
requiresApproval: (toolName, args) => {
// Only require approval for destructive bash commands
if (toolName === "bash") {
const cmd = args.command as string;
return cmd.includes("rm") || cmd.includes("delete");
}
return false;
},
});Actions
| Action | Description |
|--------|-------------|
| HITL_APPROVAL_REQUESTED | Tool blocked, awaiting decision |
| HITL_APPROVAL_GRANTED | Human approved, tool replayed |
| HITL_APPROVAL_REJECTED | Human rejected, tool fails |
| HITL_APPROVAL_EXPIRED | Timeout reached, tool fails |
State
interface HITLState {
pending: Record<string, PendingApproval>; // By approval ID
history: PendingApproval[]; // Ring buffer
config: { defaultTimeoutMs: number; historySize: number };
}
interface PendingApproval {
id: string;
executionId: string;
toolCall: { id: string; name: string; args: Record<string, unknown> };
status: "pending" | "approved" | "rejected" | "expired";
requestedAt: number;
expiresAt?: number;
decidedAt?: number;
reason?: string;
}Security
- Approval IDs are UUIDs (
crypto.randomUUID()) - State validated before approval (must be pending + have blocked action)
- Expired approvals cannot be approved (410 Gone)
- Re-approving returns 409 Conflict
License
MIT
