Quickstart
On this page
This walks through the smallest possible end-to-end example: define a plugin, register it with a
PluginManager, and execute it. Every line below is consistent with the actual signatures in the
source (see API Overview for links to each class).
1. Define a plugin
Every plugin subclasses one of the four plugin types and must set a class-level
METADATA attribute (a PluginMetadata) plus implement
initialize(), execute(), and cleanup() — execute() is already declared abstract by the type
base class you inherit from, but initialize()/cleanup() are declared on BasePlugin itself and
must be provided by your concrete class.
from zerda.plugins import ToolPlugin, PluginMetadata, SchemaProperty, ExecutionContext
class EchoPlugin(ToolPlugin):
METADATA = PluginMetadata(
name="echo",
version="1.0.0",
author="you",
description="Echoes back whatever text it is given.",
capabilities=["echo", "debug"],
tags=["utility"],
use_cases=["Test that the plugin pipeline is wired correctly"],
input_schema=[
SchemaProperty(name="text", type="string", description="Text to echo back"),
],
)
async def initialize(self) -> bool:
return True
async def execute(self, input_data: dict, context: ExecutionContext) -> dict:
return {"echo": input_data["text"]}
async def cleanup(self) -> None:
pass
[!WARNING]
METADATAis requiredBasePlugin.__init__raisesTypeErrorimmediately ifself.__class__has noMETADATAattribute. This check happens before anything else in the constructor.
2. Register and start a manager
import asyncio
from zerda.plugins import PluginManager
async def main():
manager = PluginManager() # no plugin_dirs → nothing auto-discovered
await manager.register(EchoPlugin())
await manager.startup() # initializes every registered plugin
result = await manager.execute("echo", {"text": "hello"})
print(result) # {'echo': 'hello'}
await manager.shutdown()
asyncio.run(main())
PluginManager.register() runs a compatibility check against system_version, a dependency check
against plugin.metadata.dependencies, and (in safe_mode) a permission check, before registering the
instance and calling initialize() on it. See
PluginManager.register for the full contract.
3. Auto-discover plugins from disk
Instead of manually instantiating and registering each plugin, point the manager at one or more
directories of .py files. PluginLoader scans each directory (non-recursively, ignoring files
starting with _), imports every file, and registers every concrete BasePlugin subclass that
defines METADATA:
manager = PluginManager(plugin_dirs=["./my_plugins"])
await manager.startup() # calls loader.discover_all() internally
4. Let an LLM pick the plugin
PluginManager ships an AIPluginSelector that scores
plugins against a natural-language query using keyword overlap on name, description, tags,
capabilities, and use_cases — with an optional embedding function for semantic re-ranking:
plugin = manager.select_one("I need to search the web for something")
if plugin:
result = await manager.execute(plugin.name, {"query": "..."})
Or skip the two-step process entirely with auto_execute, which selects and executes in one call:
result = await manager.auto_execute("search the web for RAG papers")
5. Expose plugins as LLM tools
openai_tools = manager.tool_descriptors() # OpenAI function-calling format
anthropic_tools = manager.anthropic_tools() # Anthropic tool-use format
Both are built from PluginMetadata.to_tool_descriptor() /
PluginMetadata.to_anthropic_tool() — see metadata for the exact schema they
produce.
Next steps
- Core Concepts — understand the object model before building real plugins.
- Plugin Development tutorial — a deeper walkthrough with all four plugin types.
- Security — permissions, input sanitization, and sandboxing you get for free.