Dein Plugin lädt nicht mehr? Die alten Import-Pfade starben am 2026-09-14

Du hast ein kleines Plugin geschrieben, es lief einen Monat, und heute hört es einfach auf. Nach hermes update und einem Gateway-Neustart bleibt der Bot still, hermes plugins list sagt, dein Plugin sei nicht geladen, und im Log steht ein einziges ImportError. Du hast keine Zeile Code angefasst.
Das ist kein wackeliges Plugin-System. Es ist ein geplantes Aufräumen: Das Kompatibilitätsfenster ist geschlossen. Hier steht, warum die Schicht existierte, was genau entfernt wurde und wie du dein Plugin zurückbekommst.
1. Die Zerlegung im September hat interne Pfade verschoben
Im September 2026 hat Hermes seine großen Module in Geschwisterdateien nach dem Muster <stem>_<topic> aufgeteilt (PR #102117). Nutzer merken davon kaum etwas. Plugins, die aus internen Pfaden importieren, merken es sofort: Die Namen, auf die sie sich verlassen haben, sind nicht mehr dort definiert, wo sie früher standen.
Das Projekt war darin immer explizit: interne Import-Pfade waren nie Teil des Plugin-Vertrags. Der dokumentierte Native plugin compatibility contract schützt Verhalten — plugin.yaml, register(ctx), Hook-Payloads, Provider-Interfaces —, nicht den Ort eines Python-Moduls.
Aber die Zerlegung hätte bestehende Plugins über Nacht kaputtgemacht, also wurde zusammen mit ihr eine temporäre Kompatibilitätsschicht ausgeliefert, mit veröffentlichtem Ablaufdatum: 2026-09-14. Sie tat mehr, als der Name vermuten lässt:
- 328
PLUGIN-COMPAT-Blöcke, an die betroffenen Module angehängt; - 1.148 verschobene öffentliche Namen, über lazy
__getattr__-Zeigertabellen erneut exponiert (lazy, um Import-Zyklen zu vermeiden); - 592 Third-Party- oder stdlib-Namen wiederhergestellt, weil das alte Modul sie nebenbei exportierte;
- 290 öffentliche Definitionen wörtlich wiederhergestellt, die zuvor als ungenutzt gelöscht worden waren;
- 41 wiederhergestellte private Helfer und 17 wiederhergestellte Imports, allein um die obigen Definitionen zu stützen;
- 3 komplett gelöschte Module als Stub, die aus ihrem Ersatz re-exportieren;
- 34 Namen, die sich gar nicht wiederherstellen ließen (durchgesickerte Loop-Variablen und Ähnliches), nur der Vollständigkeit halber aufgeführt.
Plugin-Autoren hatten einen eigenen Checker: hermes plugins compat <pfad-zu-deinem-plugin> gab jede file:line aus, alter Pfad zu neuem Pfad, und beendete sich mit 1, solange etwas übrig blieb. Zur Laufzeit löste die erste Auflösung über einen Shim eine HermesPluginCompatWarning aus (eine FutureWarning), einmal pro Name und Prozess. Nutzer, die nicht auf einen Autor warten konnten, hatten eine Notluke: plugins.allow_deprecated_imports: true.
2. Was entfernt wurde
PR #126164 hat die Schicht am 2026-09-28 gelöscht: 360 Dateien, +38 / −27.749 Zeilen. Mit ihr verschwunden:
- alle 328
PLUGIN-COMPAT-Blöcke, plus die drei Re-Export-Stub-Module; - das Tooling:
COMPAT_MANIFEST.md,compat_manifest.json,scripts/check_compat_pointers.pyund sein CI-Lint-Schritt; - jede Reporting-Oberfläche: der CLI-Banner-Hinweis,
hermes plugins compat, derhermes doctor-Abschnitt, der Post-Update-Hinweis, der Einmal-Dialog im Desktop und der Pre-Import-Skip des Loaders; - die Notluke selbst —
plugins.allow_deprecated_importsexistiert nicht mehr, und sie zu setzen bewirkt nichts.
Drei inaktive Stubs wurden bewusst behalten. Die Begründung ist praktisch: Ein Update, das bereits mit Code von vor der Entfernung lief, könnte diese Module nach dem Checkout-Tausch lazy importieren, und sie einfach zu löschen würde den Updater zum Absturz bringen. Deshalb behält hermes_cli/plugin_compat.py drei stille Funktionen (compat_report gibt ein leeres dict zurück, removal_in_effect gibt true zurück, summary_lines gibt eine leere Liste zurück), allein um diese Reihenfolge zu überleben.
3. Was mit deinem Plugin passiert
Kurzfassung: Ein Plugin, das noch einen alten Pfad importiert, kann nicht laden. Es scheitert mit einem ImportError, und hermes plugins list zeigt den Grund. Es ist nicht „deaktiviert“ — es ist nie gestartet, weshalb der Bot einfach still bleibt.
Zwei Wege, das zu beheben, nach Vorliebe sortiert:
- Verwende
ctxund die dokumentierten ABCs. Die meisten Bedürfnisse nach dem Muster „ich muss eine interne Funktion importieren“ sind schon durchPluginContext-Methoden oder eine abstrakte Provider-Basisklasse abgedeckt. So geschriebener Code übersteht ein Datei-Umsortieren. - Musst du importieren, dann importiere aus dem Modul, das den Namen heute definiert. Die vollständige Zuordnung von altem Modul zu neuem Ort existiert weiterhin — es ist das Manifest aus dem letzten Commit, der die Schicht enthielt: COMPAT_MANIFEST.md (Commit 5912ed81ed9). Passe den Import an, starte das Gateway neu und prüfe den Ladestatus.
Eine Grenze solltest du kennen: private Namen wurden nie wiederhergestellt. Wenn dein Plugin etwas wie _foo importiert oder eine interne Nahtstelle gemonkeypatcht hat, war das nie von der Schicht abgedeckt — selbst wenn du nie eine Warnung gesehen hast, kann es bereits kaputt sein. Solche Fälle müssen auf das öffentliche Äquivalent oder in das neue Modul umziehen.
4. Die genaue Zeile finden, die es zerbrochen hat
hermes plugins list # Ladestatus und Grund, pro Plugin
hermes plugins show <name> # Details zu einem Plugin (inklusive emits / listens)
hermes plugins doctor <path> # validiert das Plugin-Verzeichnis selbst
Der direkteste Test umgeht Hermes und importiert das Plugin in derselben Python-Umgebung:
python -c "import your_plugin"
Das ModuleNotFoundError: No module named 'agent.tools' (oder welcher Name auch erscheint) ist die Zeile, die du ändern musst. Starte das Gateway danach neu — Plugins laden beim Prozessstart.
5. Was diese Änderung nicht angefasst hat
Wenn dein Plugin nur dokumentierte Oberflächen verwendet, ist die Entfernung für dich irrelevant:
plugin.yamlundregister(ctx): dokumentiertePluginContext-Methoden werden nicht entfernt und nicht umbenannt; neue Parameter sind optional, haben Defaults und sind als keyword-only gedacht; bestehende Rückgabefelder werden nicht entfernt oder still umgetypt.- Hook-Payloads: neue Daten kommen nur als Keyword-Felder an, nie durch Änderung der Bedeutung oder Position eines bestehenden Feldes. Ein Legacy-Callback bekommt die Felder, die er deklariert; ein Callback mit
**kwargsbekommt die vollständige Payload — weshalb neue Plugins immer**kwargsdeklarieren sollten. - Manifeste: unbekannte
plugin.yaml-Felder werden ignoriert, sodass ein älteres Hermes ein Plugin laden kann, das Metadaten aus einem neueren Release trägt. - Provider-Interfaces: neue Methoden haben Default-Implementierungen, und neuer Callback-Kontext wird nur weitergegeben, wenn die Signaturprüfung zeigt, dass der Provider ihn akzeptiert.
- Die Deprecation-Policy hat weiterhin vier harte Bedingungen: einen Ersatz plus Migrationsanleitung, eine Warnung höchstens einmal pro Prozess, die das frühestmögliche Entfernungsrelease nennt, Unterstützung des alten Verhaltens über mindestens zwei weitere Minor-Releases und verhaltensbasierte Kompatibilitätsabdeckung für beide Pfade über dieses Fenster hinweg.
Die Kurzfassung: Der Plugin-Vertrag ist Verhalten, nicht Dateipfade. Schreib gegen die Dokumentation, und der nächste Refactor fasst dich nicht an.
Wenn du gerade erst anfängst, beginne mit unserem Plugin-Katalog-Guide; für ein Memory-Backend erzählt die Memory-Provider-Migrationsbeschreibung die andere Hälfte der September-Geschichte. Details Befehl für Befehl stehen in der hermes plugins Referenz, und zum Neu-Verdrahten von Plugins zur Laufzeit siehe den Live-Rewire-Guide.