Agent Buddy Bridge: Physical Button Approval System for AI Agents

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 approvalsession_key— current session identifier (used for approval callback)pattern_key— command pattern matching keysurface— 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
| Process | Python | Port | Role |
|---|---|---|---|
| BuddyBridge | /usr/bin/python3 (system) | 8765 | BLE Central + HTTP Server |
| Approval Relay | Hermes venv Python | 8766 | Calls resolve_gateway_approval() |
Why two Python environments?
bleakrequires system Python with pyobjc (CoreBluetooth) supportresolve_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)
| # | Capability | Status | PR |
|---|---|---|---|
| 1 | pre_approval_request hook | ✅ Merged | #16776 |
| 2 | post_approval_response hook | ✅ Merged | #16776 |
| 3 | platform_registry plugin registration | ✅ Built-in | — |
| 4 | resolve_gateway_approval() | ✅ Always available | — |
| 5 | Approval wake on session cleanup | ✅ Merged | #18171 |
| 6 | pre_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
- GitHub: harryfan1985/agent-buddy-bridge
- Hermes Agent: NousResearch/hermes-agent
- M5StickC Plus: M5Stack Official
This article is based on Hermes Agent v0.12.0 and agent-buddy-bridge v1.0.0. Reviewed: May 1, 2026.