{"id":51324745,"url":"https://github.com/webdevtodayjason/hermes-embodiment","last_synced_at":"2026-07-01T17:04:00.401Z","repository":{"id":361392589,"uuid":"1254298934","full_name":"webdevtodayjason/hermes-embodiment","owner":"webdevtodayjason","description":"Give your Hermes agent a body — an animated face, live RGB presence, and mood, all driven by the agent's real state. Minnie is the flagship example.","archived":false,"fork":false,"pushed_at":"2026-05-30T12:01:42.000Z","size":1787,"stargazers_count":1,"open_issues_count":0,"forks_count":0,"subscribers_count":0,"default_branch":"main","last_synced_at":"2026-05-30T13:17:51.495Z","etag":null,"topics":["ai-agent","ai-companion","animated-face","desk-companion","elevenlabs","embodiment","expressive-ai","hermes","hermes-agent","neopixel","pironman","pironman5","python","raspberry-pi","raspberry-pi-5","robot-face","text-to-speech","voice-assistant","ws2812"],"latest_commit_sha":null,"homepage":null,"language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/webdevtodayjason.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null,"zenodo":null,"notice":null,"maintainers":null,"copyright":null,"agents":null,"dco":null,"cla":null}},"created_at":"2026-05-30T11:49:19.000Z","updated_at":"2026-05-30T12:48:24.000Z","dependencies_parsed_at":null,"dependency_job_id":null,"html_url":"https://github.com/webdevtodayjason/hermes-embodiment","commit_stats":null,"previous_names":["webdevtodayjason/hermes-embodiment"],"tags_count":null,"template":false,"template_full_name":null,"purl":"pkg:github/webdevtodayjason/hermes-embodiment","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webdevtodayjason%2Fhermes-embodiment","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webdevtodayjason%2Fhermes-embodiment/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webdevtodayjason%2Fhermes-embodiment/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webdevtodayjason%2Fhermes-embodiment/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/webdevtodayjason","download_url":"https://codeload.github.com/webdevtodayjason/hermes-embodiment/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/webdevtodayjason%2Fhermes-embodiment/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":35015061,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-26T15:22:16.424Z","status":"online","status_checked_at":"2026-07-01T02:00:05.325Z","response_time":130,"last_error":null,"robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":true,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["ai-agent","ai-companion","animated-face","desk-companion","elevenlabs","embodiment","expressive-ai","hermes","hermes-agent","neopixel","pironman","pironman5","python","raspberry-pi","raspberry-pi-5","robot-face","text-to-speech","voice-assistant","ws2812"],"created_at":"2026-07-01T17:03:55.983Z","updated_at":"2026-07-01T17:04:00.396Z","avatar_url":"https://github.com/webdevtodayjason.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"# hermes-embodiment\n\n**Give your Hermes agent a body — an animated face, a living RGB presence, and mood, all driven by the agent's real state.**\n\n[![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](LICENSE) [![Built on Hermes Agent](https://img.shields.io/badge/Built%20on-Hermes%20Agent-7C3AED)](https://github.com/webdevtodayjason) [![Platform: Raspberry Pi 5](https://img.shields.io/badge/Platform-Raspberry%20Pi%205-C51A4A?logo=raspberrypi\u0026logoColor=white)](https://www.raspberrypi.com/) [![Python 3.9+](https://img.shields.io/badge/Python-3.9%2B-3776AB?logo=python\u0026logoColor=white)](https://www.python.org/) [![PRs Welcome](https://img.shields.io/badge/PRs-Welcome-brightgreen.svg)](https://github.com/webdevtodayjason/hermes-embodiment/pulls)\n\n![Minnie — her animated cat-eye faces cycling through moods on the Pironman kiosk](img/minnie-faces-animated-optimized.gif)\n\n---\n\n## What it is\n\n`hermes-embodiment` is the **embodiment layer** for [Hermes Agent](#credits). It takes the agent's *real* per-turn state — thinking, calling a tool, speaking — and renders it as a physical presence:\n\n- an **animated face** on a screen (a browser, or a kiosk display),\n- **case RGB LEDs** that change color with the agent's state,\n- a **voice** that speaks the agent's replies,\n- and a **status wordmark** that shows what the agent is actually doing right now (\"searching the web…\").\n\nNone of this is faked or scripted. The plugin registers Hermes lifecycle hooks (`pre_llm_call`, `pre_tool_call`, `post_llm_call`, …) and fans each transition out over Server-Sent Events to the face and to any attached hardware. When the agent starts thinking, the eyes look up and the LEDs go amber. When it calls `web_search`, the wordmark reads \"searching the web…\". When it answers, it speaks and the aura pulses green.\n\n**Minnie** is the flagship example persona — a feminine, intelligent, cyberpunk face with cat-eye glasses (think Evy, the librarian). She's just *config*: the plugin code is fully generic and persona-agnostic, so you can point it at any persona, voice, and theme. Minnie ships as `examples/minnie/config.yaml` so you have a complete, opinionated showcase to copy from.\n\nIt runs anywhere Hermes runs (the face is a self-contained web app in a browser, audio on the default sink), and it fully lights up on a SunFounder **Pironman 5 (Pro MAX)** Raspberry Pi — RGB case LEDs over SPI plus a DSI touchscreen kiosk.\n\n## Features\n\n- **Animated cat-eye SVG face** — 16 expressions (idle, listening, thinking, speaking, alert, sleeping + 10 emotional moods), randomized blinking and eye-darts, spring-damper head physics, themed particle aura, and a state-agnostic mouth. Vanilla HTML/CSS/SVG/JS + Canvas — no framework, no build step.\n- **Live status wordmark** — the persona's nameplate doubles as an activity line. While the agent works, it shows the *friendly* name of the tool in flight (\"searching the web…\", \"running code…\", \"working with files…\"), then falls back to the persona name when idle.\n- **Case LEDs synced to state** — 18× WS2812 RGB driven directly over SPI (`spidev0.0`), color-and-style per state. Auto-detected and fully optional: present on a Pironman, inert everywhere else — it never raises into the agent.\n- **Two-way streaming voice** — speaks the agent's replies through ElevenLabs (or whatever TTS Hermes is configured with), **streaming** so she starts talking within a few hundred ms, with the **mouth driven by her real audio** (per-chunk RMS over the SSE stream). Markdown is stripped so she never reads syntax aloud, and a new reply **preempts** the previous one so two responses never double up.\n- **Push-to-talk + barge-in** — hold the on-screen mic (or a floating button) to talk; audio is transcribed by a **local Whisper** and injected back to the agent. Pressing the mic again **interrupts her mid-sentence** so you can jump in.\n- **Touch control panel** — a translucent tap-to-open overlay for brightness, volume, **mic gain**, and push-to-talk that lets her face show through behind it, plus a guarded **power-off** flow (confirm modal → graceful shutdown).\n- **Live mic tuning** — a real-time input-level **VU meter** (green → amber → red, with a clip latch) plus a **capture-gain slider** on the touch panel, fed by per-block RMS/peak over the same SSE stream. Dial the mic in *visually* — no SSH, no guesswork — so push-to-talk and STT get a clean signal.\n- **Touch reactions** — poke her face (nose, forehead, cheeks, glasses, eyes) and she reacts — a quick expression change plus an LED flourish.\n- **Mood layer** — her reply's sentiment is inferred into one of **9 moods**; the face *and* the case LEDs settle on that mood at rest, so her body reflects how she feels.\n- **Memory** — an optional Hermes memory provider gives her real cross-session recall. The Minnie showcase uses **`holographic`** (local SQLite + FTS5 + HRR — no cloud, no server), so she grows with you over time.\n- **Config-driven personas** — persona, wake word, voice, audio device, face theme, and LED palette all live in `config.yaml`. Every key has a built-in default, so a bare box still runs as \"face-in-a-browser + TTS\".\n- **Fully offline kiosk** — no CDN, no fonts to fetch, no network at render time. Ships as static files served by the plugin itself.\n- **~60fps** — hardware-accelerated Canvas + SVG, tuned for a small kiosk display.\n\n## Install\n\n**Primary (recommended):** install straight from GitHub through Hermes' plugin manager:\n\n```bash\nhermes plugins install webdevtodayjason/hermes-embodiment --enable\n```\n\nThat fetches the plugin, prompts for any required env (`ELEVENLABS_API_KEY`), and enables it. Restart your gateway and open the face at \u003chttp://127.0.0.1:8830/\u003e.\n\n**Dev / local:** clone the repo and run the installer to symlink or copy it into your Hermes plugins dir:\n\n```bash\ngit clone https://github.com/webdevtodayjason/hermes-embodiment.git\ncd hermes-embodiment\n./install.sh            # copy into ~/.hermes/plugins/embody/ + seed generic config\n./install.sh --minnie   # ...seed the Minnie showcase config instead\n./install.sh --link     # ...symlink instead of copy (live edits)\n```\n\n`install.sh` honors `$HERMES_HOME` (default `~/.hermes`), never clobbers an existing active `config.yaml`, and prints the next steps (`hermes plugins enable embody`, then restart the gateway).\n\n## Configure\n\nAll behavior is read from `config.yaml`, with a built-in default for every key. Start from `config.yaml.example` (generic) or `examples/minnie/config.yaml` (the showcase). The shape:\n\n```yaml\npersona:\n  name: \"Assistant\"            # face nameplate + page title\n  wake_word: \"hey assistant\"   # reserved for a future STT wave\n\nvoice:\n  enabled: true\n  provider: \"\"                 # \"\" =\u003e inherit Hermes' TTS provider (e.g. elevenlabs)\n  voice_id: \"\"                 # \"\" =\u003e inherit Hermes' configured voice\n  speak_on: \"post_llm_call\"\n\naudio:\n  backend: \"auto\"              # auto | pipewire | alsa | hermes-default | off\n  device: \"\"                   # \"\" =\u003e system default sink; or pin a PipeWire/ALSA device\n\nface:\n  enabled: true\n  host: \"127.0.0.1\"\n  port: 8830\n  theme: \"default\"             # the face-ui applies data-theme=\u003ctheme\u003e (e.g. \"minnie\")\n  kiosk:\n    enabled: false             # true =\u003e launch a dedicated kiosk browser at the face URL\n\nleds:\n  backend: \"auto\"              # auto (active iff a Pironman is detected) | pironman | off\n  brightness: 60               # 0-100 global default; per-state may override\n  states:                      # state -\u003e { color: \u003c6-hex, no #\u003e, style: solid|breathing|flow }\n    idle:     { color: \"1E3A5F\", style: \"breathing\" }\n    thinking: { color: \"FFB000\", style: \"flow\" }\n    working:  { color: \"8000FF\", style: \"solid\" }\n    speaking: { color: \"00C853\", style: \"flow\" }\n```\n\nSee **[`examples/minnie/config.yaml`](examples/minnie/config.yaml)** for a complete annotated instance (HDMI audio pinned by sink name, Pironman LEDs, kiosk on).\n\n`ELEVENLABS_API_KEY` (declared in `plugin.yaml`) drives the voice. If it's unset, the face and LEDs still run and speech falls back to Hermes' own configured TTS.\n\n## Hardware\n\n**Runs anywhere.** With no special hardware, you get the animated face in any browser plus audio on the default sink. The LED backend auto-detects as absent and no-ops; nothing to configure.\n\n**Full embodiment on the Pironman 5 Pro MAX.** On a SunFounder Pironman 5 (Pro MAX) Raspberry Pi, the plugin lights up completely:\n\n- **18× WS2812 case LEDs** driven directly over `spidev0.0` (instant, no CLI shell-out, no service restart),\n- a **4.3\" DSI touchscreen** running the face as a fullscreen kiosk,\n- HDMI / 3.5mm audio out for the voice.\n\nThe LED backend is active only when `/dev/spidev0.0` is writable (your user is in the `spi` group) and the `neopixel_spi` / `board` Blinka driver imports. Otherwise it stays inert.\n\n\u003e **⚠️ Pironman LED handoff — important.** Two processes can't both own the SPI bus. To let `embody` drive the case LEDs, pironman5 must release them: remove `\"ws2812\"` from the **Pro MAX variant's `PERIPHERALS`** list so pironman5's own `WS2812Addon` never opens `/dev/spidev0.0`. (OLED, fans, and the dashboard are unaffected.)\n\u003e\n\u003e **A `pironman5` package upgrade restores pironman's own RGB control** by reinstating that list — so after any pironman5 upgrade, **re-apply the one-line `\"ws2812\"` removal** or you'll get two processes fighting over the bus.\n\n## 🛠️ Build Your Own Minnie\n\nWant the full physical Minnie — animated face on a touchscreen, RGB case synced to state, and a voice? She runs on a **Raspberry Pi 5** in a **SunFounder Pironman 5 Pro Max** case. The short BOM:\n\n| Part | Notes |\n|---|---|\n| Raspberry Pi 5 (8GB / 16GB) | the brain |\n| **SunFounder Pironman 5 Pro Max** (w/ touchscreen) | her body — WS2812 RGB, OLED, 4.3\" DSI screen, speakers, camera, dual-NVMe ([buy](https://a.co/d/09DaQDy9)) |\n| NVMe M.2 SSD | OS / boot drive |\n| 27W USB-C PD power supply | headroom for NVMe + accessories |\n| microSD card | initial OS flash |\n| USB mic · M5StickC Plus · ElevenLabs key | *optional* (voice / companion) |\n\n**→ Full step-by-step hardware guide: [`docs/BUILD.md`](docs/BUILD.md)** — assembly, OS, pironman5 software, kiosk autostart, the LED handoff, and the audio jumper.\n\n## Architecture\n\n```\nHermes agent hooks                 embody                         surfaces\n──────────────────                 ──────                         ────────\non_session_start  ─┐\npre_llm_call       │   set_state(state, status)\npre_tool_call      ├──────────────►  core.state  ──SSE /events──► face web app (browser/kiosk)\npost_tool_call     │   (daemon-thread HTTP server, :8830)         │\npost_llm_call      │                    │                         └─► /config  (persona, theme)\non_session_end    ─┘                    │\n                                        └──► backends.*  ──────────► WS2812 LEDs (SPI), null, …\npost_llm_call ──► core.voice ──► backends.audio ──────────────────► TTS + audio out (HDMI/sink)\n```\n\n- **State server** (`core/state.py`) — a daemon-thread HTTP server on `:8830`, deliberately isolated from the gateway's asyncio loop. Serves the face app, an SSE `/events` stream, `/state.json`, and `/config`.\n- **Hooks** (`__init__.py`) — map agent lifecycle to states: `pre_llm_call → thinking`, `pre_tool_call → working` (with a friendly tool label), `post_llm_call → speaking` then `idle`, etc.\n- **Backends** (`backends/`) — a tiny module-interface (`is_available` / `setup` / `on_state`) with auto-detection. `leds_pironman` drives WS2812 over SPI; `null` is the universal no-op fallback; `audio` is transport (it has `play()`, not `on_state`). Backends are best-effort — a flaky write can never crash a hook.\n- **Face web app** (`face/`) — a self-contained, offline HTML/CSS/SVG/JS + Canvas kiosk. Connects to `/events` for live state and reads persona/theme from `/config`. Supports `?state=\u003cname\u003e` for offline preview.\n\n## The Minnie example\n\nMinnie is the showcase persona: a warm, intelligent, English-librarian character behind a neon cyberpunk cat-eye visor. Everything that makes her *Minnie* — the name, the British voice, the `minnie` face theme, the HDMI audio pin, the Pironman LEDs, the kiosk — is config in **[`examples/minnie/config.yaml`](examples/minnie/config.yaml)**. Copy it to your active `config.yaml` (or run `./install.sh --minnie`) to bring her up, then swap the values to make the persona your own.\n\n## Preview the face\n\nYou can render the face offline without Hermes — handy for theming or screenshots:\n\n```bash\ncd face\npython3 -m http.server 8907\n# open http://127.0.0.1:8907/?state=idle   (also: thinking, speaking, listening, …)\n```\n\n## Credits\n\n- Built on **[Hermes Agent](https://github.com/webdevtodayjason)** — the agent runtime whose lifecycle hooks and TTS this plugin embodies.\n- The **Minnie** face design was iterated with **Gemini Antigravity**.\n- By **Jason Brashear** / **Titanium Computing**.\n\n## License\n\n[MIT](LICENSE) © 2026 Jason Brashear (Titanium Computing).\n\n---\n\n\u003cdiv align=\"center\"\u003e\n\n**Built by [Jason Brashear](https://jasonbrashear.com) · Titanium Computing**\n\n🌐 [jasonbrashear.com](https://jasonbrashear.com)  ·  ✍️ [Substack](https://jasonbrashear.substack.com/)\n\n\u003c/div\u003e\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebdevtodayjason%2Fhermes-embodiment","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fwebdevtodayjason%2Fhermes-embodiment","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fwebdevtodayjason%2Fhermes-embodiment/lists"}