🎼 Leitmotif
Hear what your agent is doing.
Leitmotif turns every Hermes tool call into a short musical motif, generated live by a tiny pure-Python synthesizer. You don't have to watch the terminal to know what's happening:
- Reading sounds like high plucked strings, writing like a warm pad swell, and shell commands like marimba.
- A loop is audible. The same call always plays the same motif, so when the agent retries
npm testfor the fifth time, the motif repeats and climbs, with a tritone warning from the third time on. - Errors clash. Consecutive failures sink lower, and a recovery resolves upward.
- Waiting on you sounds like a heartbeat. It keeps pulsing until you answer the approval prompt, then confirms with a bright third (approved) or a low thump (denied). If you're away for more than 5 minutes, the heartbeat gives way to a distinct still waiting cue and a soft reminder every minute, so a pending approval never sounds like an idle agent.
- Turns resolve. A finished turn ends on a V → I cadence; a failed or interrupted one doesn't resolve.
Long-running agents are meant to be left alone, and Leitmotif is how you keep half an ear on one. It's also a non-visual status channel for blind and low-vision developers. No screen reader has to announce every tool call, because the music carries it.
A real session, exported with /leitmotif export. Reads up high, a write, a failing npm test loop (orange staircase) over sinking errors (red), the fix, three approval heartbeats (white), and the closing cadence. Listen to it.
Commands
/leitmotif status
/leitmotif tour play every cue once, with what it means
/leitmotif legend which sound means what
/leitmotif on | off unmute / mute (the session keeps recording)
/leitmotif mode <full|ambient|alerts>
/leitmotif theme <studio|arcade|zen>
/leitmotif volume <0-100>
/leitmotif export [name] save the session as WAV + MIDI + piano-roll SVG
- full: every tool call
- ambient: every tool call, at half volume
- alerts: silent until something needs you: an approval, an error streak, a loop, a subagent failing, or a turn ending
What you're hearing
| event | sound |
|---|---|
| shell commands | marimba, mid register |
| reading / searching | high plucked strings |
| writing / patching files | warm low pad swell |
| web / browser | FM bell |
| subagents | glass voice rises when one starts, settles when it ends |
| tool error | dissonant minor-second buzz, lower with each consecutive error |
| identical call repeated | same motif, a semitone higher each time; tritone warning from the 3rd |
| waiting for your approval | heartbeat until you answer (up to 5 min) |
| still waiting (heartbeat cap reached) | one low beat under a rising, unresolved question; then a soft reminder every minute until you answer |
| approved / denied | bright major third / low falling thump |
| turn finished | V → I cadence |
| turn interrupted / failed | falling fourth / minor iv → i |
Each session gets its own key (derived from the session id), so two agents running side by side sound different.
Install
hermes plugins install JustSteez/hermes-leitmotif
hermes plugins enable leitmotif
Then start a session and run /leitmotif tour.
Configuration
| key | default | |
|---|---|---|
| enabled | true | play sounds (recording continues when muted) |
| mode | full | full, ambient, or alerts |
| theme | studio | studio, arcade (chiptune), or zen (glass and pads) |
| volume | 60 | 0–100 |
| loop_threshold | 2 | identical calls per task before the motif starts climbing |
| heartbeat_max_seconds | 300 | how long the approval heartbeat runs before the still waiting cue |
| approval_reminder_seconds | 60 | after that, how often the soft reminder plays until you answer (0 = off) |
| max_events_per_sec | 6 | ambient sounds above this rate are skipped; alerts always play |
| player | auto | afplay (macOS), pw-play / paplay / aplay (Linux), winsound (Windows), or none |
| finalize_notice | true | offer /leitmotif export when a session with 30+ notes closes |
How it works
- Hooks only, observer only. It listens on
pre_tool_call,post_tool_call,pre_approval_request,post_approval_response,subagent_start,subagent_stop,on_session_start,on_session_endandon_session_finalize. It never blocks, modifies or approves anything. - Never stalls the agent. Hooks only push notes onto a queue. One daemon thread renders and plays them. Every hook catches its own errors.
- Pure-Python synth. Additive marimba and glass, Karplus–Strong plucked strings, a 2-operator FM bell, detuned pads and chiptune squares. There are no dependencies, no numpy and no audio libraries. Each note renders in a few milliseconds and is cached to disk, so a repeated motif costs nothing.
- Plays through the OS player. It uses
afplay,pw-play,paplay,aplayorwinsound. On a headless gateway with no audio device it stays silent and just records. - Export. Long idle gaps (the model thinking) are squeezed out, so a session exports as a listenable piece. The MIDI file uses one General MIDI channel per instrument, with marker events naming each cue.
Privacy and risk disclosures
- No network calls, no telemetry, no credentials.
- It starts the OS audio player (
afplay,pw-play,paplayoraplay) as a short-lived subprocess for each sound. It runs no other commands. - It keeps tool names and a hash of each call's arguments in memory to build motifs. Arguments themselves are never written to disk.
- It writes rendered note WAVs to its plugin data directory as a cache, and writes exports only when you run
/leitmotif export.
Tests
python3 -m unittest discover -s tests
License
MIT
README from the reviewed commit 7d22129; it updates when the author re-pins.

JustSteez