Core Concepts
On this page
Plugin
A plugin is any concrete subclass of BasePlugin that:
- Declares a class-level
METADATA: PluginMetadataattribute. - Implements the three abstract methods
initialize(),execute(),cleanup(). - Optionally overrides
validate()andhealth_check().
BasePlugin.__init__ will raise TypeError at instantiation time if METADATA is missing — this is
enforced in code, not just documented convention.
The four plugin types
Every built-in plugin type in plugins/types/ sets a class-level PLUGIN_TYPE string used by the
registry's type index, and narrows the return type of execute():
plugins/types/__init__ exposes PLUGIN_TYPE_MAP, a dict mapping each of the four string keys
above to its class, used internally wherever type-based dispatch is needed.
Metadata is the contract, not an afterthought
PluginMetadata is a @dataclass that every plugin must supply. It
serves three purposes simultaneously:
- Identity & compatibility:
name,version(validated as semver via__post_init__),author,min_system_version/max_system_version(checked byis_compatible_with()). - AI discoverability:
capabilities,tags,use_cases,cost_tierfeed the keyword scorer inPluginRegistry.search()andAIPluginSelector.select(). - LLM tool-calling:
input_schema(a list ofSchemaProperty) is converted directly into OpenAI (to_tool_descriptor()) and Anthropic (to_anthropic_tool()) function-calling JSON.
Execution context
ExecutionContext is the second argument to every execute() call.
It's a plain dataclass carrying query, session_id, user_id, metadata, and optional handles to
memory, cache, and router objects your application supplies — the framework itself never
constructs these handles, it only threads the context object through.
Lifecycle states
PluginStatus (an str, Enum in metadata) has seven values: disabled, enabled, loading,
error, updating, deprecated, initializing. PluginManager._initialize_plugin() moves a plugin
through initializing → enabled or initializing → error. BasePlugin._record_failure() will also
force a plugin from enabled into error automatically once it has more than 10 failed executions
and its failure rate exceeds 50% — this is the only automatic status transition triggered by runtime
behavior rather than explicit manager calls.
Three layers of defense
The security module implements defense in depth, each layer independent of the
others:
PermissionGuard— a capability-based allow/deny list per plugin name, optionally scoped to a session or user. Nothing is granted by default;PluginManager.startup()callssecurity.grant_all_required(plugin)for every auto-discovered plugin only whensafe_mode=False.InputSanitizer— validates incoming data against a plugin'sinput_schema, coerces types, enforces a 32 KB max string length, and rejects strings matching one of five regex patterns aimed at prompt-injection, script-injection, shell-injection, and path-traversal attempts.ExecutionSandbox— wraps a coroutine inasyncio.wait_for()with a hard timeout and logs a warning if execution exceeds a configurable "slow" threshold. Note this is a time sandbox only; the module docstring itself notes that a real memory cap "needs process isolation," which this implementation does not provide.
Observability is built in, not bolted on
Every PluginManager.execute() call is recorded by PluginObservability, and every BasePlugin
instance independently tracks its own execution stats (get_stats()) and a derived health score
(get_health(), one of untested / excellent / good / degraded / critical based on success
rate). PluginManager.dashboard() merges both views into a single snapshot.