A physical traffic light for Hermes Agent β shows agent status via RGB LEDs on an ESP32-C3.
π’ Green blink β agent is processing your message
π΄ Red blink β agent needs you to approve a command
π‘ Yellow steady β agent is idle / done
Uses Hermes Shell Hooks β the light switches automatically at the framework level. Zero tool call overhead.
Forked from eternityspring/agent-light β the original Claude Code physical traffic light. This fork adapts it for Hermes Agent with a Python bridge and native shell hook integration.
You need three things:
| # | Item | Link | Price |
|---|---|---|---|
| 1 | DORHEA ESP32-C3 Mini Dev Board (5-pack) | Amazon | ~$18 |
| 2 | Adeept Mini Traffic Light LED Module (5-pack) | Amazon | ~$9 |
| 3 | ELEGOO Dupont Jumper Wires (120pcs) | Amazon | ~$7 |
Total: ~$34 (you get 5 of each β enough to build 5 traffic lights or have spares).
Connect the Adeept traffic light module to the ESP32-C3 with Dupont wires:
| Adeept Module Pin | ESP32-C3 Pin | GPIO |
|---|---|---|
| R (Red) | GPIO2 | 2 |
| Y (Yellow) | GPIO1 | 1 |
| G (Green) | GPIO0 | 0 |
| VCC | 5V (or 3.3V) | β |
| GND | GND | β |
β οΈ The Adeept module has built-in current-limiting resistors. No external resistors needed.β οΈ The module is common cathode βHIGHturns the LED on,LOWturns it off.
The traffic light supports two modes β local (light on the same machine as Hermes) and remote (light on a different machine, connected via Tailscale/LAN).
Hermes (this machine) β hook β serial β USB β ESP32-C3 β light
The light plugs directly into the machine running Hermes. Simplest setup.
Hermes (desktop) β hook β curl β Tailscale/LAN β API server (laptop) β serial β USB β light
The light plugs into a different machine (e.g. your laptop). Hermes hooks send HTTP requests over Tailscale to a small API server on the light machine.
Why remote? You want the light next to you (on your laptop) while Hermes runs on a headless server (in a closet, cloud, etc).
# 1. Install dependencies
curl -fsSL https://raw.githubusercontent.com/arduino/arduino-cli/master/install.sh | BINDIR=~/.local/bin sh
~/.local/bin/arduino-cli core update-index
~/.local/bin/arduino-cli core install esp32:esp32
pip3 install pyserial
sudo usermod -a -G dialout $USER
# 2. Clone and set up
git clone https://github.com/BruceBest/agent-light.git ~/agent-light
cd ~/agent-light
bash scripts/setup.sh
# 3. Restart Hermes gateway (from a separate terminal)
hermes gateway restart# 1. Install Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
# 2. Clone and install dependencies
git clone https://github.com/BruceBest/agent-light.git ~/agent-light
cd ~/agent-light
pip3 install pyserial
sudo usermod -a -G dialout $USER
# 3. Flash firmware (plug in ESP32-C3 via USB)
bash scripts/setup.sh # flashes firmware + tests LEDs
# 4. Start the API server
python3 scripts/traffic_light_server.py --port 9090
# Note your Tailscale IP:
tailscale ip -4
# β e.g. 100.64.0.2# 1. Install Tailscale
curl -fsSL https://tailscale.com/install.sh | sh
sudo tailscale up
# 2. Clone and set up in remote mode
git clone https://github.com/BruceBest/agent-light.git ~/agent-light
cd ~/agent-light
bash scripts/setup.sh --remote --light-host 100.64.0.2 --light-port 9090
# 3. Restart Hermes gateway (from a separate terminal)
hermes gateway restartYou send a message
β Hermes fires pre_llm_call
β hook script curls API server / sends to serial
β π’ green blinks
Agent wants to run sudo/rm/etc
β Hermes fires pre_approval_request
β hook script curls API server / sends to serial
β π΄ red blinks
Agent finishes responding
β Hermes fires post_llm_call
β hook script curls API server / sends to serial
β π‘ yellow steady
| Hermes Event | Command | Light | Meaning |
|---|---|---|---|
pre_llm_call |
working |
π’ green blink | Agent processing |
pre_approval_request |
waiting |
π΄ red blink | Needs user approval |
post_approval_response |
working |
π’ green blink | Approval granted, resuming |
post_llm_call |
idle |
π‘ yellow steady | Agent idle |
Hook scripts run as background subprocesses β they never block the agent.
# Local (direct serial)
python3 scripts/traffic_light.py working # green blink
python3 scripts/traffic_light.py waiting # red blink
python3 scripts/traffic_light.py idle # yellow steady
python3 scripts/traffic_light.py test # cycle RβYβG
# Remote (via API)
curl http://100.64.0.2:9090/working
curl http://100.64.0.2:9090/waiting
curl http://100.64.0.2:9090/idle
curl http://100.64.0.2:9090/test
curl http://100.64.0.2:9090/pingfrom traffic_light import TrafficLight
tl = TrafficLight()
tl.working() # green blink
tl.waiting() # red blink
tl.idle() # yellow steady
tl.close()
# Context manager (auto-idle on exit):
with TrafficLight() as tl:
tl.working()
# ... agent work ...Run on the machine with the USB traffic light attached:
python3 scripts/traffic_light_server.py # default port 9090
python3 scripts/traffic_light_server.py --port 8888 # custom port
TRAFFIC_LIGHT_PORT=/dev/ttyACM0 python3 scripts/traffic_light_server.py # override serial port| Endpoint | Effect |
|---|---|
GET /working |
π’ green blink |
GET /waiting |
π΄ red blink |
GET /idle |
π‘ yellow steady |
GET /off |
All off |
GET /test |
Cycle RβYβG |
GET /status |
Query current state |
GET /ping |
Health check |
# Copy and edit the service file
cp scripts/traffic-light-api.service ~/.config/systemd/user/
sed -i "s|HOME_DIR|$HOME|g" ~/.config/systemd/user/traffic-light-api.service
# Enable boot-time auto-start (starts before login)
loginctl enable-linger
# Enable and start
systemctl --user daemon-reload
systemctl --user enable traffic-light-api.service
systemctl --user start traffic-light-api.serviceagent-light/
βββ firmware/
β βββ traffic_light/traffic_light.ino # Main firmware (serial command handler)
β βββ diagnostic/diagnostic.ino # Pin diagnostic tool
βββ hermes-hooks/
β βββ traffic-light-working.sh # pre_llm_call β green
β βββ traffic-light-waiting.sh # pre_approval_request β red
β βββ traffic-light-idle.sh # post_llm_call β yellow
βββ scripts/
β βββ traffic_light.py # Serial bridge (CLI + Python API + Daemon)
β βββ traffic_light_server.py # Remote API server (HTTP β serial)
β βββ traffic-light-api.service # Systemd unit for the API server
β βββ setup.sh # One-shot installer (local + remote modes)
βββ images/ # Original photos from upstream
βββ README.md
ls /dev/ttyACM* /dev/ttyUSB*
# Empty? Unplug and replug the USB-C cable.
# Still empty? Hold BOOT button, tap RESET, then release BOOT (flash mode).groups | grep dialout
# If missing:
sudo usermod -a -G dialout $USER
# Then re-login (or: sudo chmod 666 /dev/ttyACM0 for immediate fix)The GPIO pin mapping is wrong. Run the diagnostic firmware:
~/.local/bin/arduino-cli compile --fqbn esp32:esp32:XIAO_ESP32C3 firmware/diagnostic/diagnostic.ino
sg dialout -c "~/.local/bin/arduino-cli upload --fqbn esp32:esp32:XIAO_ESP32C3 --port /dev/ttyACM0 firmware/diagnostic/diagnostic.ino"
# Watch which LED lights up at each step
# Then update pin definitions in firmware/traffic_light/traffic_light.ino# Check Tailscale is connected
tailscale status
# Check the API server is running
curl http://100.64.0.2:9090/ping
# Check firewall
sudo ufw allow 9090/tcp # if ufw is activehermes hooks list # Should show 3 hooks, all β allowed
hermes gateway restart # Reload after config changesTRAFFIC_LIGHT_PORT=/dev/ttyACM0 python3 scripts/traffic_light.py test- Original project: eternityspring/agent-light by @eternityspring
- Hermes adaptation: BruceBest/agent-light (this fork)
MIT