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 explizit force=True.
  • Keiner der Installationspfade mitten im Lauf übergab es. Schlimmer: tools/mcp_tool_config._portable_mcp_servers rief es ohne Force auf — ein mcp.reload nach einer Installation mitten im Lauf lud also das alte Server-Set neu.
  • Das plugins.manage install der 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.