Ein Plugin installieren, ohne das Gateway neu zu starten: Hermes verdrahtet es live

Du installierst ein Plugin auf deinem Bot in einer Telegram-Gruppe. Es bringt einen neuen Slash-Befehl mit. Du tippst ihn voller Erwartung in die Gruppe — und der Bot tut, als hätte er nichts gehört. Die Doku sagt: Gateway neu starten. Was bedeutet, dass eine Streaming-Antwort abgeschnitten wird, Hintergrundjobs sterben und jede Session von vorn beginnt. Das ist ein hoher Preis für einen einzigen Slash-Befehl.
Also warten die meisten auf den nächsten Neustart. Das hat sich gerade geändert (#119266, behebt #87770): Installiere ein Plugin, während das Gateway läuft, und seine Platform-Handler werden auf der Stelle verdrahtet.
Warum ein Neustart früher Pflicht war
Das ganze Problem saß in der Frage, wann Plugins entdeckt werden:
discover_plugins()macht einen Short-Circuit, sobald die Discovery stattgefunden hat, es sei denn, du übergibst explizitforce=True.- Keiner der Installationspfade mitten im Lauf übergab es. Schlimmer:
tools/mcp_tool_config._portable_mcp_serversrief es ohne Force auf — einmcp.reloadnach einer Installation mitten im Lauf lud also das alte Server-Set neu. - Das
plugins.manage installder TUI kehrte zurück, sobald der Install-Helper fertig war, ganz ohne Rescan. - Platform-Adapter (Telegram, Slack, …) bauten ihre Handler-Registry beim Boot auf und schauten nie wieder hin.
Unterm Strich: Die Dateien des Plugins lagen auf der Platte, die Config hatte es aktiviert, und der laufende Prozess wusste nichts davon.
Eine Aktivierungszusammenfassung: jetzt live vs. nächste Session
Jeder Ladepfad mitten im Lauf ist jetzt ein echter erzwungener Rescan, und er gibt aus diesem Rescan heraus einen Callback aus (PluginManager.on_plugin_loaded), der eine Aktivierungszusammenfassung pro neu geladenem Plugin trägt. Sie sieht so aus:
{"name": "late-mcp", "key": "late-mcp",
"activated_now": {"gateway_commands": ["late"], "callbacks": ["telegram"]},
"deferred": {"tools": ["late_tool"], "prompt": ["late.section"], "mcp_servers": ["worker"]}}
In einfachen Worten:
| Kategorie | Wann sie greift | Was sie umfasst |
|---|---|---|
| Gateway-Slash-Befehle | Sofort | die Befehlsnamen, die das Plugin registriert hat |
| Gateway-Transform-Hooks | Sofort | transform_llm_output, transform_tool_result, transform_terminal_output, pre_gateway_dispatch, gateway_platform_event, pre_command |
| Andere Hooks | Sofort | die übrigen Hook-Namen |
| Platform-Callbacks | Sofort | Plattformen mit einer register_platform_handler-Factory plus Slack-Action-IDs |
| Tools | Nächste Session | Tool-Namen |
| System-Prompt-Abschnitte | Nächste Session | Abschnitts-IDs |
| Portable MCP-Server | Ein mcp.reload oder die nächste Session |
die Namen genau so, wie sie in mcp.json stehen |
Die nützlichste der drei „Sofort“-Zeilen sind die Platform-Callbacks: Die Button-Callbacks und eingehenden Nachrichten-Transforms eines Plugins funktionieren jetzt ohne Neustart.
Warum die Verdrahtung weder dupliziert noch gegen Core-Handler verliert
„Funktioniert sofort“ klingt einfach; zwei Fallen machen es weniger einfach.
Falle eins: doppelte Registrierung. Ein Force-Reload gibt neue Funktionsobjekte zurück, sodass die Funktionsidentität nicht verrät, ob etwas bereits verdrahtet war. Die Lösung ist, jede Verdrahtung auf (plugin, factory qualname) zu schlüsseln — ein Plugin, eine Factory, eine Verdrahtung. Deshalb heißt die Methode idempotentes Neu-Verdrahten (rewire_plugin_handlers()): Sie liest die Factory-Registry neu und führt nur die Factories aus, die noch nicht auf dem live nativen Client verdrahtet sind.
Falle zwei: Reihenfolge. Auf Telegram werden neu hinzugefügte Handler vor die Catch-alls des Cores gehoben. Das ist wichtig, weil PTB pro Gruppe nur den ersten Treffer ausliefert, und der Core registriert ein Catch-all filters.COMMAND — ein dahinter sitzender Late-Handler würde nie feuern. Slack macht das Äquivalent, indem register_slack_action_handler pro AsyncApp dedupliziert wird.
Der Gateway-Runner abonniert beim Boot pro bedientem Profile und verdrahtet beim Event jeden Live-Adapter im Event Loop neu. All das passiert innerhalb des Gateway-Prozesses, nicht in welchem Prozess auch immer die Installation ausgeführt hat.
Jede Installationsoberfläche sagt dir die Wahrheit
CLI, Desktop, TUI oder die REST-Installation des Dashboards — alle melden genau, was jetzt live ist und was wartet:
hermes plugins install late-mcp --enable
Der Befehl stößt zuerst das laufende Gateway an (ein reload-plugins-Control-Socket-Verb, der es bittet, das angeforderte Home per Force neu zu scannen), und gibt dann diese Aufteilung live-vs-verzögert aus. Das plugins.manage install/toggle/update von Desktop und TUI liefert activation plus gateway_reloaded; restart_required ist nur dann true, wenn kein Gateway geantwortet hat.
In diesem Design steckt eine bewusste redaktionelle Haltung: lieber nichts tun als etwas vorzutäuschen. Deshalb gilt:
- Tools und System-Prompt-Abschnitte eines spät geladenen Plugins gelten nicht für die laufende Session. Dieselbe Regel wie bei
/skills install— Prompt und Tool-Schema einer laufenden Session bleiben cache-stabil, und eine Änderung mittendrin würde das brechen. - Portable MCP-Server warten auf
mcp.reload. - Deaktivieren entfernt die Verdrahtung nicht. Deaktiviere ein Plugin mitten im Lauf, und seine bereits verdrahteten Handler bleiben, bis das Gateway neu startet. Jede Oberfläche sagt das, statt dich glauben zu lassen, es sei sofort sauber.
Wie das in der Praxis aussieht
Ein typischer Ablauf:
# 1. installieren und aktivieren (stößt das laufende Gateway zu einem Rescan an)
hermes plugins install late-mcp --enable
# 2. die Ausgabe sagt, dass gateway_commands: ["late"] jetzt live ist
# also funktioniert /late schon in deiner Telegram-Gruppe — kein Neustart
# 3. wenn das Plugin außerdem einen MCP-Server mitbringt, bring den auch hoch
# (über mcp.reload, oder starte einfach eine neue Session)
Ein Vorbehalt, den man verinnerlichen sollte: Das Gateway muss das Home bedienen, in dem das Plugin lebt. Das Event verbreitet sich pro Profile, und ein Gateway, das dieses Profile nicht bedient, verdrahtet auch nicht dafür. Bei Multi-Profile- und Multi-Gateway-Deployments stolpert man darüber leichter als auf einer einzelnen Maschine — Hintergrund in ein Gateway pro Host.
Grenzen und Vorbehalte
- Dieses Release deckt nur Handler ab. Tools, System-Prompt-Abschnitte und portable MCP-Server bleiben aus dem Sofort-Set heraus, aus den oben genannten Gründen des Prompt-Caches und der Verbindungslebensdauer.
- Ein Un-Wire gibt es nicht. Die Verdrahtung ist einseitig: Eine Installation wirkt sofort; ein Disable räumt erst nach einem Neustart wirklich auf. Upgrades verhalten sich genauso.
- Das Plugin-Vertrauen ist nicht gelockert worden. Das ist ein Runtime-Verdrahtungsmechanismus, keine Aufweichung der Prüfung; woher Plugins kommen und wie sie zugelassen werden, steht im Vertrauensmodell des Plugin-Katalogs und im Desktop-Plugin-SDK.
- Terminal-Backend-Plugins sind eine eigene Spur — siehe Terminal-Backend-Plugins.
Zum Schluss
„Nach der Installation einfach neu starten“ ist auf einem Spielzeug-Setup überlebbar, aber auf einem Gateway, das echte Arbeit leistet, ist es ein hartes Gate — es schiebt jedes Upgrade auf 3 Uhr nachts. Die Hälfte dieses Gates ist jetzt weg: Was sofort wirken soll (Platform-Handler, Slash-Befehle, Transform-Hooks), wirkt, und was eine Runde warten muss (Tools, Prompt-Abschnitte, MCP-Verbindungen), sagt es laut — einschließlich der Tatsache, dass Deaktivieren die Verdrahtung nicht entfernt. Die andere Hälfte wird sich so schnell nicht bewegen, weil sie direkt in die Prompt-Cache-Invariante läuft.