# 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 .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/.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`: ```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).