Skip to content

Latest commit

 

History

History
291 lines (225 loc) · 8.64 KB

File metadata and controls

291 lines (225 loc) · 8.64 KB

Plugin Development Guide

Plugins extend OpenClaw without modifying its core. They are loaded automatically when you place their directory in plugins/ and unloaded when you remove it — no restart required.


Plugin Types

Type plugin_type value Description
Agent plugin agent Adds one or more new specialist agents
Tool plugin tool Adds new tools that all agents can use
Provider plugin provider Adds an alternative AI provider
Extension plugin extension Combination of agents + tools + config

Plugin Directory Structure

plugins/
└── my_plugin/                ← directory name = Python package name
    ├── __init__.py           ← defines the plugin class
    ├── plugin.yaml           ← manifest (REQUIRED)
    └── agent.py              ← optional: agent implementation
    └── tools.py              ← optional: tool implementations

Step 1 — Create the Manifest (plugin.yaml)

# plugins/my_plugin/plugin.yaml

name: my_plugin                 # Unique identifier (used internally)
version: 1.0.0
description: My example plugin that adds a weather agent
author: Your Name
plugin_type: agent              # agent | tool | provider | extension
entry_point: MyPlugin           # Class name in __init__.py

# Optional fields
dependencies: []                # pip install requirements
capabilities:
  - weather
  - forecast
min_openclaw_version: "0.1.0"
homepage: ""
license: MIT

Step 2 — Create the Plugin Class (__init__.py)

# plugins/my_plugin/__init__.py
from openclaw.plugins.plugin_base import BasePlugin, PluginManifest
from openclaw.core.registry import AgentRegistry
from openclaw.core.tool_base import ToolRegistry

from my_plugin.agent import WeatherAgent   # relative import won't work here
# Use: from my_plugin import agent as weather_agent_module


class MyPlugin(BasePlugin):
    """Example plugin that adds a WeatherAgent."""

    def on_load(
        self,
        agent_registry: AgentRegistry,
        tool_registry: ToolRegistry,
    ) -> None:
        """Called when the plugin is loaded. Initialise resources here."""
        print(f"MyPlugin v{self.manifest.version} loading...")
        # Note: don't call agent_registry.register() here.
        # Return agents via get_agents() and let PluginManager register them.

    def on_unload(self) -> None:
        """Called before the plugin is unloaded. Clean up resources here."""
        print("MyPlugin unloading...")

    def get_agents(self) -> list:
        """Return agent instances this plugin contributes."""
        from my_plugin.agent import WeatherAgent
        return [WeatherAgent()]

    def get_tools(self) -> list:
        """Return ToolDefinition instances this plugin contributes."""
        return []  # This plugin has no standalone tools

Step 3 — Implement the Agent (agent.py)

# plugins/my_plugin/agent.py
from openclaw.agents._agent_mixin import AgentMixin
from openclaw.core.agent_base import BaseAgent
from openclaw.core.session import Session
from openclaw.core.tool_base import ToolDefinition, ToolRegistry
from my_plugin.tools import weather_tools  # your custom tools module


class WeatherAgent(AgentMixin, BaseAgent):
    name = "weather"
    description = "Fetches weather forecasts and climate data."
    capabilities = ["weather", "forecast", "climate", "temperature"]

    def can_handle(self, task: str) -> float:
        keywords = {"weather", "forecast", "temperature", "rain", "sunny", "climate"}
        hits = sum(1 for kw in keywords if kw in task.lower())
        return min(0.95, 0.2 + hits * 0.2)

    def get_tools(self) -> list[ToolDefinition]:
        tr = ToolRegistry()
        tr.register_module(weather_tools)
        return tr.all()

    def _build_system_prompt(self) -> str:
        return (
            "You are a weather specialist. Provide accurate, concise weather "
            "information. Always include temperature units and timezone."
        )

    async def run(self, task: str, session: Session) -> str:
        return await self._run(task, session)

Step 4 — Implement Tools (tools.py)

# plugins/my_plugin/tools.py
import httpx
from openclaw.core.tool_base import tool


@tool(description="Get current weather for a city. Returns temperature, conditions, humidity.")
def get_current_weather(city: str) -> str:
    """Fetch current weather for a city."""
    # Example using a public weather API
    try:
        resp = httpx.get(
            f"https://wttr.in/{city}?format=3",
            timeout=10
        )
        return resp.text.strip()
    except Exception as exc:
        return f"Error fetching weather: {exc}"


@tool(description="Get a 3-day weather forecast for a city.")
def get_weather_forecast(city: str, days: int = 3) -> str:
    """Fetch a multi-day weather forecast."""
    try:
        resp = httpx.get(
            f"https://wttr.in/{city}?format=j1",
            timeout=10
        )
        data = resp.json()
        # Parse and format the forecast
        forecasts = []
        for day in data.get("weather", [])[:days]:
            forecasts.append(
                f"Date: {day['date']}, Max: {day['maxtempC']}°C, "
                f"Min: {day['mintempC']}°C"
            )
        return "\n".join(forecasts) or "No forecast data available."
    except Exception as exc:
        return f"Error fetching forecast: {exc}"

Step 5 — Install and Test

# The plugin directory already exists in plugins/ — it auto-loads!
# Verify:
openclaw plugins list

# Test:
openclaw run "What is the weather in London?"
openclaw chat --agent weather

If you add the plugin while OpenClaw is running (and hot_reload is enabled), it will be detected and loaded automatically within ~1 second.


Tool-only Plugins

For a plugin that just adds tools (no new agent), use plugin_type: tool:

# plugins/my_tools/__init__.py
from openclaw.plugins.plugin_base import BasePlugin
from my_tools import calculator_tools

class MyToolsPlugin(BasePlugin):
    def on_load(self, agent_registry, tool_registry):
        # Tools in get_tools() are auto-registered by PluginManager
        pass

    def on_unload(self):
        pass

    def get_tools(self):
        from openclaw.core.tool_base import ToolRegistry
        tr = ToolRegistry()
        tr.register_module(calculator_tools)
        return tr.all()
# plugins/my_tools/plugin.yaml
name: my_tools
version: 1.0.0
description: Calculator tools for mathematical operations
author: Me
plugin_type: tool
entry_point: MyToolsPlugin

Plugin Lifecycle

On startup:
  PluginManager.load_all()
    └── For each plugins/<dir> with plugin.yaml:
          load_plugin(dir)

While running (hot-reload):
  New dir appears in plugins/  →  load_plugin(new_dir)
  Dir removed from plugins/    →  unload_plugin(dir_name)

On shutdown:
  App.stop() → PluginWatcher.stop()
  (loaded plugins remain until process exits)

Plugin Manifest Reference

Field Required Type Description
name Yes string Unique identifier
version Yes string Semantic version (e.g. 1.2.3)
description Yes string Short description
author Yes string Author name or email
plugin_type Yes enum agent, tool, provider, extension
entry_point Yes string Class name in __init__.py
dependencies No list[str] pip install requirements
capabilities No list[str] Routing capability tags
min_openclaw_version No string Minimum OpenClaw version
homepage No string Plugin homepage URL
license No string License identifier

Common Mistakes

Mistake Fix
Calling agent_registry.register() inside on_load() Return agents from get_agents() instead
Using relative imports in __init__.py Use from my_plugin.submodule import X (absolute package imports)
entry_point class name doesn't match Check spelling; it's case-sensitive
Missing plugin.yaml The watcher won't recognise the directory
Tool function raises exceptions Catch exceptions and return error strings
Plugin holds external connections without cleanup Implement on_unload() to close them

Sharing Plugins

A plugin is a standard Python package. You can:

  • Share as a zip/tarball and unzip into plugins/
  • Publish to PyPI (but then install into plugins/ as a directory)
  • Include in git as a submodule under plugins/

The only requirement: it must be a directory (not a .py file) containing plugin.yaml.