Motivation: selbst mit allen bisherigen Proxy-Fixes braucht jede Nachricht mindestens einen vollen LLM-Turn (mehrere Sekunden). Fuer "pause" oder "naechstes Lied" unnoetig langsam -- Alexa & Co. loesen sowas ohne generatives Modell per Intent-Klassifikation in Millisekunden. Neues Hermes-Plugin fast-paths/, haengt am pre_gateway_dispatch-Hook (feuert bei JEDER eingehenden Nachricht auf JEDER Plattform, noch VOR Auth/Pairing und Agent-Dispatch -- gefunden im echten Hermes-Sourcecode, hermes_cli/plugins.py + gateway/run.py). Matcht der normalisierte Text gegen eine Regex, wird der Handler direkt ausgefuehrt und das LLM komplett uebersprungen. Ein File pro Skill unter patterns/ (analog zu ARIAs eigenen fast_patterns), erster Skill: patterns/spotify.py (pause/next/previous/ volume/current, plus "spiel Playlist X auf Geraet Y ab" per Fuzzy-Match ohne LLM fuer den Freitext-Namen). Sicherheit: pre_gateway_dispatch feuert VOR Hermes' eigener Autorisierung -- der Loader prueft deshalb explizit gateway._is_user_authorized() vor jedem Pattern-Treffer, sonst koennte ein nicht autorisierter Absender per Fast-Path an Hermes vorbei Aktionen ausloesen. Handler koennen sich zudem bewusst per Rueckgabe None zurueckziehen (z.B. mehrdeutiger Playlist-Name) und faellen dann normal ans LLM zurueck statt zu raten. Lokal verifiziert (Testharness auf aria-wohnung, Hermes' eigenen _load_directory_module-Lademechanismus nachgebaut, gemockter SpotifyClient): Matching, Sicherheits-Check bei nicht autorisiertem Sender, Fuzzy-Match fuer Playlist+Geraet, und mehrere Regression-Faelle gegen False-Positives bei laengeren/anderen Saetzen -- alle Faelle bestehen. docker-compose.yml: neuer verschachtelter Read-Only-Mount ./fast-paths -> /opt/data/plugins/fast-paths (User-Plugin-Pfad unter $HERMES_HOME). Muss zusaetzlich einmalig in config.yaml unter plugins.enabled aktiviert werden (Hermes-Plugins sind Opt-in) -- siehe README.md und fast-paths/README.md.
fast-paths — Steuerbefehle ohne LLM-Roundtrip
Warum
Jede Nachricht an Hermes geht normalerweise durch's volle LLM: Text → System-Prompt bauen → Claude aufrufen (mehrere Sekunden, siehe Root-Cause-Historie im Haupt-README) → Tool-Call parsen → Tool ausfuehren → Antwort formulieren. Fuer einfache Steuerbefehle ("pause", "naechstes Lied", "lauter") ist das unnoetig langsam — Alexa & Co. machen sowas mit Intent-Klassifikation in Millisekunden, ganz ohne generatives Modell.
Dieses Plugin baut das fuer Hermes nach: ein Regex-Layer, der VOR dem LLM sitzt. Matcht der Text, wird die Aktion direkt ausgefuehrt und Claude nie gefragt. Matcht nichts (oder ist sich ein Handler nicht sicher), laeuft alles normal weiter — als waere das Plugin gar nicht da.
Architektur
fast-paths/
plugin.yaml <- Hermes-Plugin-Manifest (kind: standalone)
__init__.py <- Loader: sammelt patterns/*.py ein, ein Hook-Callback
patterns/
__init__.py
_base.py <- FastPattern-Dataclass, pattern()-Helper, send_reply()
spotify.py <- EIN Skill = EINE Datei
<dein-neuer-skill>.py
Ein Skill = eine Datei unter patterns/. Kein zentrales Registrieren
noetig — __init__.py importiert automatisch jede *.py-Datei in
patterns/ (ausser Dateien mit fuehrendem _, das ist Infrastruktur) und
sammelt deren PATTERNS-Liste ein.
Neuen Fast-Path hinzufuegen
- Neue Datei
patterns/<skill>.pyanlegen. - Handler-Funktion(en) schreiben:
def handler(match, event, gateway) -> str | None.eventist einMessageEvent(siehegateway/platforms/base.pyim Hermes-Sourcecode) —event.text,event.source.chat_id,event.source.platform, etc.gatewayist derGatewayRunner— u.a.gateway._adapter_for_source(source)um den Platform-Adapter zu bekommen,gateway._is_user_authorized(source)fuer Auth-Checks (macht der Loader schon automatisch, siehe unten).
- Am Ende der Datei:
PATTERNS = [pattern("name", r"^regex$", handler), ...]mit dem Helper aus_base.py. - Fertig — kein Eintrag irgendwo sonst noetig. Container neu starten
(
docker-compose up -d --build hermes-agent), Loader findet die Datei automatisch beim naechsten Plugin-Discovery.
Rueckgabewerte des Handlers (wichtig!)
| Rueckgabe | Bedeutung |
|---|---|
"Text" |
Wird 1:1 an den User geschickt, LLM wird uebersprungen. |
"" (leer) |
Aktion ausgefuehrt, aber bewusst stumm. LLM trotzdem uebersprungen. |
None |
Handler ist sich nicht sicher genug (z.B. mehrdeutiger Name) — faellt zurueck auf die normale LLM-Verarbeitung, GENAU wie "kein Match". Nie raten, im Zweifel None. |
| (Exception) | Wird geloggt, faellt ebenfalls automatisch aufs LLM zurueck. |
Regex-Konventionen
- Immer mit
^und$anchorn — sonst matchtpauseauch mitten in "pause die Musik und erzaehl mir was" und reisst den Rest des Satzes weg. - Mehrere Formulierungen/Synonyme + Fuellwoerter ueber Alternativen und
optionale Gruppen abdecken (
(?:mal|bitte)?), nicht nur eine Formulierung. - Text wird vor dem Matchen normalisiert (lowercase, Satzzeichen am Ende weg, Mehrfach-Leerzeichen zusammengefasst) — Regexe koennen daher durchgehend lowercase geschrieben werden.
- Lieber ein Pattern zu eng als zu weit — ein Fast-Path, der faelschlich matcht, kapert eine Nachricht die eigentlich ans LLM sollte. Ein Pattern, das gar nicht matcht, kostet nur die eingesparte Geschwindigkeit (die Nachricht geht stattdessen ganz normal ans LLM, kein Schaden).
Sicherheit
pre_gateway_dispatch (der Hook, an dem dieses Plugin haengt) feuert VOR
Hermes' eigener Autorisierungspruefung. Der Loader in __init__.py prueft
deshalb bei JEDEM Pattern-Treffer explizit gateway._is_user_authorized(...)
BEVOR der Handler ueberhaupt aufgerufen wird — ein nicht autorisierter
Absender (z.B. auf einer oeffentlich erreichbaren Plattform wie Telegram)
kann also nie per Fast-Path an Hermes vorbei etwas ausloesen. Diese Pruefung
lebt zentral im Loader, nicht in den einzelnen Skill-Dateien — neue Skills
muessen sich darum nicht kuemmern.
Bekannter Trade-off
pre_gateway_dispatch-Callbacks laufen synchron (Hermes' PluginManager
awaited sie nicht, siehe hermes_cli/plugins.py). Handler duerfen also
ganz normal blockierende Netzwerk-Calls machen (z.B. httpx.request wie
SpotifyClient), das blockiert den Event-Loop aber kurz (typischerweise
100-500ms pro API-Call). Fuer eine Text-Antwort an den User (das ist
async — adapter.send()) gibt's send_reply() in _base.py: schedult
einen Fire-and-forget asyncio.Task auf dem laufenden Event-Loop. Beides
zusammen ist immer noch um Groessenordnungen schneller als der
LLM-Roundtrip (mehrere Sekunden bis >20s, siehe Haupt-README).
Aktivieren
Plugins sind bei Hermes standardmaessig Opt-in. Einmalig in
hermes-data/agent-home/config.yaml:
plugins:
enabled:
- fast-paths
Danach docker-compose up -d --build hermes-agent (Volume-Mount siehe
docker-compose.yml, Kommentar beim hermes-agent-Service).
Bisherige Skills
patterns/spotify.py— pause/weiter/next/previous/lauter/leiser/ Lautstaerke setzen/aktueller Titel, plus "spiel Playlist X [auf Geraet Y] ab" per Fuzzy-Match (kein LLM fuer den Freitext-Namen noetig, siehedifflib-basiertes Matching dort — bricht bei Mehrdeutigkeit bewusst ab und gibt an das LLM ab statt zu raten).