Agent Buddy Bridge: Physical Button Approval System for AI Agents

May 1, 2026

Agent Buddy Bridge: Physical Button Approval Syste

In the AI Agent space, approval of dangerous commands has always been a dilemma: fully autonomous execution is risky, while manual approval is tedious. The Agent Buddy Bridge project offers a hardware-level solution — using the physical button on an M5StickC Plus dev board as a "hardware kill switch" for dangerous commands.

Motivation

Once you grant an AI Agent shell execution privileges, the attack surface expands instantly. rm -rf /data, DROP TABLE users — these commands can cause catastrophic damage with a moment's inattention.

Existing approval mechanisms (Telegram's /approve, terminal interactive confirmation) share a common weakness: they depend on the same device's UI interaction. If you step away from your computer, the Agent blocks indefinitely or times out and auto-denies. A physical button provides an independent, air-gapped approval channel — press it anywhere, and the Agent unblocks immediately.

Technical Architecture

Overall Architecture

M5StickC Plus (BLE Peripheral)
    ↕ BLE NUS (Nordic UART Service)
BLECentral (bleak / macOS CoreBluetooth)
    ↓ JSON over BLE
HTTPServer (:8765)
    ↑ POST /buddy/state (Hermes → Bridge, from pre_approval_request hook)
    ↓ POST /internal/approve (Bridge → Hermes, on button press)
    ↓
Approval Relay (:8766) → resolve_gateway_approval(session_key, choice)
    ↑
    Hermes tools/approval.py

Approval Flow

Dangerous command detected (e.g. rm -rf /data)
    │
    ▼
tools/approval.py: prompt_dangerous_approval()
    │
    ├─→ 🔔 pre_approval_request hook
    │       → Plugin receives {command, session_key, surface="gateway"}
    │       → BuddyPlatformAdapter pushes to Bridge (:8765)
    │       → BLE → M5StickC screen shows approval prompt
    │
    ├─→ 📱 Telegram notification (simultaneous)
    │       "⚠️ Dangerous command: rm -rf /data
    │        Reply /approve, /always, or /cancel"
    │
    └─→ ⏸️ Agent thread blocks (threading.Event.wait)
            │
    ┌───────┴───────┐
    ▼               ▼
 M5StickC button   Telegram /approve
 (BLE notify)      (slash command)
    │               │
    ▼               ▼
 Approval Relay    gateway._handle_approve_command()
    │               │
    └───────┬───────┘
            ▼
 resolve_gateway_approval(session_key, "once")
            │
            ▼
    event.set() → Agent unblocked
            │
            ▼
    🔔 post_approval_response hook
        → Clean up M5StickC screen
        → Command executes

Dual-channel approval, first-come-first-served. Users can approve from Telegram (at desk) or via the M5StickC button (away from desk). When both arrive simultaneously, the first one wins; the other returns 0 (idempotent).

Core Technical Details

1. pre_approval_request Hook — Push Mechanism for Approval Requests

This is a key capability introduced in Hermes v0.12.0 (PR #16776). When the Agent encounters a dangerous command, approval.py fires the pre_approval_request hook with the following payload:

  • command — the command text pending approval
  • session_key — current session identifier (used for approval callback)
  • pattern_key — command pattern matching key
  • surface — trigger source ("gateway" / "cli")

This hook solves the biggest gap in the previous approach: how does the M5StickC know when Hermes is waiting for approval? Now the hook pushes proactively — no polling needed.

2. post_approval_response Hook — Cleanup Mechanism for Approval Results

After approval completes (whether via button or Telegram), the hook callback carries a choice field ("once" / "always" / "deny" / "timeout"). The Plugin can use this to clean up the M5StickC screen, record audit logs, etc.

3. platform_registry — Pluggable Platform Adapters

Hermes's built-in platform_registry allows plugins to register custom platform adapters (see the IRC/Teams plugins for reference). BuddyPlatformAdapter is registered as a Hermes "platform" through this mechanism — no core code modifications required.

4. Approval Relay — HTTP-to-Python Function Bridge

Button press events travel via BLE → HTTP Server (:8765) → Approval Relay (:8766) → Hermes. resolve_gateway_approval() is a Python function, not an HTTP endpoint, so a Relay process is needed to bridge the gap. The Relay uses Hermes's venv Python (because it needs from tools.approval import resolve_gateway_approval).

Dual-Process Design

ProcessPythonPortRole
BuddyBridge/usr/bin/python3 (system)8765BLE Central + HTTP Server
Approval RelayHermes venv Python8766Calls resolve_gateway_approval()

Why two Python environments?

  • bleak requires system Python with pyobjc (CoreBluetooth) support
  • resolve_gateway_approval() requires Hermes venv to import tools.approval

The two processes communicate via HTTP (localhost) and are independent — either one crashing doesn't affect the other.

Hermes Interface Dependencies (v0.12.0)

#CapabilityStatusPR
1pre_approval_request hook✅ Merged#16776
2post_approval_response hook✅ Merged#16776
3platform_registry plugin registration✅ Built-in
4resolve_gateway_approval()✅ Always available
5Approval wake on session cleanup✅ Merged#18171
6pre_tool_call approve directive❌ Open#11816

PR #11816 is the only unmerged item — it would allow plugin-whitelisted commands to auto-approve without triggering the approval flow. This does not affect the physical button approval chain — the hook + Relay path handles button approvals independently.

Installation and Usage

Prerequisites

  • macOS (CoreBluetooth for BLE)
  • System Python 3.9+ with pyobjc
  • Hermes Agent v0.12.0+ installed at ~/.hermes/
  • M5StickC Plus flashed with Claude Desktop Buddy firmware (BLE broadcast name: Claude-XXXX)
  • Python packages: bleak, aiohttp

Installation Steps

# 1. Clone the project
cd ~/code
git clone https://github.com/harryfan1985/agent-buddy-bridge.git
cd agent-buddy-bridge

# 2. Install dependencies
/usr/bin/pip3 install bleak aiohttp

# 3. Create plugin symlink
ln -s ~/code/agent-buddy-bridge/hermes_plugin ~/.hermes/plugins/buddy-bridge

Configure Hermes

Add to ~/.hermes/config.yaml:

gateway:
  platforms:
    buddy:
      enabled: true
      extra:
        bridge_url: "http://localhost:8765"

plugins:
  enabled:
    - buddy-bridge

Start the Services

Terminal 1 — BuddyBridge (BLE + HTTP):

cd ~/code/agent-buddy-bridge
/usr/bin/python3 -m hermes_buddy_bridge.main     --http-port 8765 --relay-url http://localhost:8766

Terminal 2 — Approval Relay:

cd ~/code/agent-buddy-bridge
~/.hermes/hermes-agent/venv/bin/python     -m hermes_buddy_bridge.approval_relay     --hermes-home ~/.hermes --port 8766

After starting both processes, restart the Hermes Gateway: hermes gateway restart

Verification

# Health checks
curl -s http://localhost:8765/health
curl -s http://localhost:8766/health

# BLE device connection status
curl -s http://localhost:8765/buddy/status
# → {"connected": true, "device_name": "Claude-0C1E"}

# Approval path test (idempotent — returns resolved=0 when nothing pending)
curl -s -X POST http://localhost:8765/internal/approve   -H "Content-Type: application/json"   -d '{"session_key":"test","choice":"once"}'
# → {"status": "ok", "via": "approval-relay", "resolved": 0}

Project Status and TODO

The core approval chain is fully operational. Remaining work is prioritized as follows:

  • P1 — Adapt BuddyPlatformAdapter to the pre_approval_request hook (currently based on legacy architecture)
  • P2 — End-to-end integration testing (trigger real dangerous command → M5StickC display → button approval → Agent unblocked)
  • P3 — Await PR #11816 merge (whitelist auto-approve for known-safe commands, UX optimization)

Project Links


This article is based on Hermes Agent v0.12.0 and agent-buddy-bridge v1.0.0. Reviewed: May 1, 2026.