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.

Python · 24 lines
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] METADATA is required BasePlugin.__init__ raises TypeError immediately if self.__class__ has no METADATA attribute. This check happens before anything else in the constructor.

2. Register and start a manager

Python · 14 lines
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:

Python · 2 lines
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:

Python · 3 lines
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:

Python
result = await manager.auto_execute("search the web for RAG papers")

5. Expose plugins as LLM tools

Python · 2 lines
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

Related pages