Plugin-System
Verantwortung
Plugins (plugins) sind das Erweiterungsskelett von Hermes: Provider-Profile, Plattform-Adapter (adapter), Context-Engine, Cron-Provider (provider), Memory-Backends, Browser, Bild-/Video-Generierung – alles wird über den Plugin-Mechanismus angebunden. plugins/ stellt PluginContext (Registrierungseinstieg) + Hilfsfunktionen; jedes Plugin-Unterverzeichnis ist domänen-lokal gekapselt. Es arbeitet mit ToolRegistry/PlatformRegistry zusammen: Das Plugin «deklariert und registriert» nur, die Registry (registry) ist zuständig für «Lookup und Dispatch (dispatch)».
Designmotiv
Warum «Verzeichnis-Konvention + Selbst-Registrierung beim Modul-Import» statt einer expliziten plugins.yaml? Kern ist, die Reibung beim Hinzufügen neuer Fähigkeiten zu senken: Ein Unterverzeichnis in $HERMES_HOME/plugins/model-providers/<name>/ werfen, beim Fund wird __init__.py automatisch importiert, der Modul-Code ruft register_provider() und schließt die Registrierung ab — keine Konfigurationsdatei anfassen. Der Preis: Reihenfolge und Überschreibung werden über Registry-Strategien abgesichert (Namespace, explizites override-Opt-in); die Gegenleistung ist Hot-Swap und eine Drittanbieter-Plugin-Anbindung ohne Reibung.
Schlüsseldateien
plugins/__init__.py—PluginContext, Registrierungseingänge (register_platform,register_providerusw.)plugin_utils.py— plugin-geteilte Hilfsfunktionenmodel-providers/— LLM-Provider-Profile (siehe Providers)platforms/— Telegram-/Discord-/Slack-Adapter (siehe Plattform-Adapter)context_engine/— Kontext-Engine-Erweiterungencron_providers/— Cron-Scheduler-Backendsmemory/— Memory-Backendsbrowser//web/— Browser- und Web-Fähigkeitenimage_gen//video_gen/— multimodale Generierungobservability//security-guidance/— Observability und Sicherheit
Datenfluss
- Beim Start löst
init_agent(agent/agent_init.py:276) die Plugin-Discovery aus. - Jedes Plugin-Unterverzeichnis wird importiert; der modulweite Code ruft die Registrierungsmethoden von
PluginContextauf:- model-provider →
register_provider:53 - platform →
platform_registry.register:231 - tool →
registry.register:365(über Namespace-Strategie)
- model-provider →
- Die Registrys (Singletons) werden zur Abfrage-Wahrheit; Hauptschleife und Gateway (gateway) lesen nur aus ihnen und hängen nicht direkt an konkreten Plugins.
- Plugins sind hot-swapable: Entfernen läuft über
unregister; die Registry behandelt Priorität und Überschreibung.
Schlüsselcode
Verzeichnis-Scan zur Plugin-Discovery
_discover_providers ist der Discovery-Einstieg der model-providers. Es scannt zwei Verzeichnisse (das repo-eigene + das Benutzerverzeichnis $HERMES_HOME) und ruft für jedes Unterverzeichnis _import_plugin_dir auf:
def _discover_providers() -> None:
"""1. Bundled at <repo>/plugins/model-providers/<name>/
2. User at $HERMES_HOME/plugins/model-providers/<name>/
Later steps win on name collision."""
global _discovered
if _discovered: return
_discovered = True
if _BUNDLED_PLUGINS_DIR.is_dir():
for child in sorted(_BUNDLED_PLUGINS_DIR.iterdir()):
if child.is_dir() and not child.name.startswith(("_", ".")):
_import_plugin_dir(child, "bundled")
user_dir = _user_plugins_dir()
if user_dir is not None:
for child in sorted(user_dir.iterdir()):
if child.is_dir() and not child.name.startswith(("_", ".")):
_import_plugin_dir(child, "user")«Benutzerverzeichnis überschreibt eingebaut» kommt aus dieser Reihenfolge: Beide Schritte rufen register_provider auf, der zweite überschreibt den ersten. Verzeichnisse mit _/.-Präfix werden ignoriert — das lässt einen Fluchtweg für __pycache__ und Konsorten.
Selbst-Registrierung: Modul-Import als Registrierung
_import_plugin_dir lädt das Verzeichnis via importlib.util als Modul; beim Ablauf des Modul-Codes schiebt register_provider(profile) das Profil in die globale Registry:
module_name = (f"plugins.model_providers.{safe_name}" if source == "bundled"
else f"_hermes_user_provider_{safe_name}")
spec = importlib.util.spec_from_file_location(
module_name, init_file, submodule_search_locations=[str(plugin_dir)])
module = importlib.util.module_from_spec(spec)
sys.modules[module_name] = module
spec.loader.exec_module(module)Bundled und User laufen über getrennte Modul-Namespaces, damit sich gleichnamige Profile aus zwei HERMES_HOME-Quellen in sys.modules nicht gegenseitig aliasen.
Registry: stillschweigendes Überschreiben eingebauter Tools verhindern
registry.register lehnt gleichnamige Überschreibungen über Toolset-Grenzen hinweg standardmäßig ab. Zwischen MCPs ist Überschreiben erlaubt (Refresh-Szenario legitim); ein Plugin, das ein eingebautes Tool ersetzen will, muss explizit override=True setzen und vorher in der Config allow_tool_override: true freigeschaltet haben — sonst fliegt direkt eine PermissionError statt stiller Ersetzung. Die Owner-Erkennung basiert auf handler.__globals__["__name__"], das beim Definieren gebunden wird; ein Plugin kann eine Überschreibung nicht über eine verschachtelte Lambda als «aus dem Modul eingebaut» waschen. Siehe registry.register:365-436.
Grenzen und Fehler
- Plugin-Import fehlschlägt:
_import_plugin_dirschluckt die Ausnahme, schreibt nur ein Warning und entfernt das Modul aussys.modules, um einen halbgeladenen Zustand zu vermeiden. Ein kaputtes Plugin reißt nicht den ganzen Agenten mit, aber der entsprechende Provider/Plattform ist nicht erreichbar — nur im Log sichtbar. - Überschreiben eines eingebauten Tools ohne explizites Opt-in:
registry.registerwirft einePermissionErrorstatt still zu ersetzen; der Operator muss in derconfig.yamlplugins.entries.<plugin_id>.allow_tool_override: truesetzen, damit es freigegeben wird. - Context-Engine nicht tief kopierbar: Die von mehreren Agenten geteilte Context-Engine wird beim Start eines Child-Agenten per
copy.deepcopyvom Budget (budget)-Zustand isoliert; hält ein Plugin Objekte, die sich nicht tief kopieren lassen (Locks, DB-Verbindungen), schlägt das Deep-Copy fehl und fällt auf den eingebauten Compressor zurück.
Zusammenfassung
Plugin = «Unterverzeichnis + Registrierung». Alle querschnittlichen Fähigkeiten (Provider, Plattform, Werkzeug (tool), Memory, Cron, Multimodal) laufen über dasselbe Registrierungsskelett, wodurch die Kernschleife stabil bleibt. Eine neue Fähigkeit = neues Plugin-Unterverzeichnis plus Registrierung, ohne Eingriff in den Agent-Stamm.