Core Concepts

On this page

Plugin

A plugin is any concrete subclass of BasePlugin that:

  1. Declares a class-level METADATA: PluginMetadata attribute.
  2. Implements the three abstract methods initialize(), execute(), cleanup().
  3. Optionally overrides validate() and health_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():

TypePLUGIN_TYPEexecute() returnsNotes
RetrievalPlugin"retrieval"List[Dict[str, Any]]Output contract expects each dict to have content, score, source. Adds a retrieve(query, top_k, context) convenience wrapper.
ToolPlugin"tool"Dict[str, Any]No additional overrides; the primary target for LLM function-calling.
ActionPlugin"action"Dict[str, Any] with success/messageAdds a can_execute() pre-flight hook that safe_execute() checks before running; raises PluginPermissionError if it returns False. REQUIRES_CONFIRMATION = True by default.
ProcessingPlugin"processing"List[Any]Input contract expects {"items": [...], ...}. Adds a process(items, context, **kwargs) convenience wrapper.

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 by is_compatible_with()).
  • AI discoverability: capabilities, tags, use_cases, cost_tier feed the keyword scorer in PluginRegistry.search() and AIPluginSelector.select().
  • LLM tool-calling: input_schema (a list of SchemaProperty) 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:

  1. PermissionGuard — a capability-based allow/deny list per plugin name, optionally scoped to a session or user. Nothing is granted by default; PluginManager.startup() calls security.grant_all_required(plugin) for every auto-discovered plugin only when safe_mode=False.
  2. InputSanitizer — validates incoming data against a plugin's input_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.
  3. ExecutionSandbox — wraps a coroutine in asyncio.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.

Next: put it into practice

Related pages