Contract reference¶
PluginMetadata¶
Returned by register(). Fields the registry reads:
| Field | Type | Notes |
|---|---|---|
name / version / description / author |
str |
required |
dependencies / capabilities / data_sources / databases |
list[str] |
optional metadata |
registered_at |
datetime |
defaults to now (UTC) |
api_version |
str |
defaults to minder.dev/v1 (stable — distinct from the manifest's minder.dev/v1alpha1; the two surfaces version independently, don't reuse one for the other) |
Lifecycle (Plugin protocol)¶
register · initialize · health_check · collect_data · analyze ·
shutdown — all async. Plus the optional apply_config(config). Duck-typed on
register; inheriting Plugin / PluginBase is optional.
Backend config (self.config)¶
The registry constructs each module plugin with a dict of backend handles,
plugin_class(config). PluginBase stores it as self.config. Read connection
details from it instead of hard-wiring hosts, and don't read POSTGRES_* or other
platform credentials from the environment.
| Key | Present | Shape |
|---|---|---|
redis |
always | {host, port, password, db} |
influxdb |
always | {enabled, host, port, token, org, bucket} |
database |
only when the operator has configured the plugin database role | {host, port, user: "minder_plugins", password, database, schema: "plugin_data"} |
There is no postgres or qdrant key; config["postgres"] raises KeyError.
database is a least-privilege Postgres handle, not the platform's own
credentials:
- The
minder_pluginsrole can only use its ownplugin_dataschema and has no access to the platform's tables. - Its
search_pathis pinned toplugin_data, so an unqualifiedCREATE TABLElands there, owned by the plugin.schemais passed too, so you can schema-qualify your own DDL. - The key may be missing. If
DB_PLUGIN_PASSWORDisn't set, nodatabasekey is injected at all (the registry fails closed and never falls back to the owner credentials). Useself.config.get("database")and degrade gracefully, for example by disabling the feature or reporting it fromhealth_check().
db = self.config.get("database")
if db is None:
self.db_enabled = False # no plugin database role on this instance
else:
dsn = (f"postgresql://{db['user']}:{db['password']}"
f"@{db['host']}:{db['port']}/{db['database']}")
The redis and influxdb handles still carry the platform's shared credentials;
see the plugin trust model.
Config: JSON Schema + UI Schema¶
The simple CONFIG_SCHEMA list is compiled to standard JSON Schema — so
config scales to any shape (nested objects, arrays, enums, conditionals) while
simple plugins stay one-liners:
from minder_plugin_sdk import resolve_config_schema, validate_config
schema, ui = resolve_config_schema(plugin) # (JSON Schema, UI hints)
validate_config(plugin, {"level": 3}) # raises ConfigError if invalid
Advanced plugins can declare a full CONFIG_JSONSCHEMA + UI_SCHEMA directly.
Capabilities¶
A plugin declares (or the SDK infers) what it does — an open vocabulary; unknown capabilities are ignored:
from minder_plugin_sdk import capabilities
capabilities(plugin) # {"config", "data-source", "ai-tools", ...}
data-source · ai-tools · actions · webhook-ingest · scheduler ·
connection · ui-panel. Per-capability protocols (DataSource, Scheduler,
WebhookHandler, ConnectionProvider, UIPanelProvider) let you type-check the
one you implement.
Requirements¶
Validated against KNOWN_SERVICES / KNOWN_BUNDLES. The platform can refuse to
enable a plugin whose hard services are missing, and offer to enable its bundles.
Design¶
Why this shape scales to thousands of plugin types without plugins shipping their own UI code — see RFC 0001.