Files
hermes-agent/fast-paths
ARIA 9ea72886dd feat(fast-paths): Universeller Fast-Path-Layer -- Steuerbefehle ohne LLM-Roundtrip
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.
2026-07-20 16:17:29 +00:00
..

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

  1. Neue Datei patterns/<skill>.py anlegen.
  2. Handler-Funktion(en) schreiben: def handler(match, event, gateway) -> str | None.
    • event ist ein MessageEvent (siehe gateway/platforms/base.py im Hermes-Sourcecode) — event.text, event.source.chat_id, event.source.platform, etc.
    • gateway ist der GatewayRunner — 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).
  3. Am Ende der Datei: PATTERNS = [pattern("name", r"^regex$", handler), ...] mit dem Helper aus _base.py.
  4. 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 matcht pause auch 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, siehe difflib-basiertes Matching dort — bricht bei Mehrdeutigkeit bewusst ab und gibt an das LLM ab statt zu raten).