Skip to main content
Plugins add tools, hooks, and guardrails to agents — drop a Python file in ~/.praisonai/plugins/ and load it in one line.
from praisonaiagents import Agent, discover_and_load_plugins

discover_and_load_plugins()

agent = Agent(
    name="Assistant",
    instructions="Help users with weather queries.",
    tools=["get_weather"],
)
agent.start("What's the weather in Paris?")
The user asks a question; plugins add tools and hooks before the agent answers.

Quick Start

1

Simple Usage

Create ~/.praisonai/plugins/my_tools.py:
"""
Plugin Name: My Tools
Description: Custom tools for my agent
Version: 1.0.0
"""

from praisonaiagents import tool

@tool
def get_weather(city: str) -> str:
    """Get weather for a city."""
    return f"Sunny in {city}, 22°C"
Load and run:
from praisonaiagents import Agent, discover_and_load_plugins

discover_and_load_plugins()

agent = Agent(
    name="Assistant",
    instructions="Help users",
    tools=["get_weather"],
)
agent.start("What's the weather in Paris?")
2

With Configuration

Point the environment (or config file) at plugins and Agent construction wires them for you — no explicit plugins.enable() call needed:
export PRAISONAI_PLUGINS=true
from praisonaiagents import Agent

agent = Agent(name="Assistant", instructions="Help users")
agent.start("Hello!")   # entry-point plugins are wired in before this runs
Prefer to turn plugins on in code? Call plugins.enable(...) before creating the agent:
from praisonaiagents import Agent, plugins

plugins.enable(["logging", "metrics"])

agent = Agent(name="Assistant", instructions="Help users")
agent.start("Hello!")
Place plugins in ~/.praisonai/plugins/ (user-wide) or ./.praisonai/plugins/ (project-specific).
plugins.enable() auto-discovers filesystem and entry-point plugins only when PRAISONAI_ALLOW_PLUGIN_DISCOVERY=true (or 1/yes). Without this env var, plugins.enable() still bridges plugins you register manually via PluginManager.register(...), but no directory or entry-point scan runs.

How It Works

plugins.enable() auto-calls wire_into_hook_registry(), which registers each enabled plugin’s lifecycle methods on the default hook registry the agent consults at runtime — and Agent init calls this for you when the env var or config file requests it (see Auto-Enable from Env or Config). The hooks fire in this order around each agent run:
Plugin typeWhat it adds
ToolFunctions registered via @tool
HookLifecycle callbacks (before_tool, before_tool_definitions, after_llm, …)
GuardrailOutput validation on after_agent

Choosing How to Load Plugins

Pick the loading method that fits your setup.

Plugin Locations

LocationScope
~/.praisonai/plugins/User-wide
./.praisonai/plugins/Project-specific

Hook Plugin Example

Create ~/.praisonai/plugins/my_logger.py:
"""
Plugin Name: My Logger
Description: Logs tool calls
Version: 1.0.0
Hooks: before_tool, after_tool
"""

from praisonaiagents.hooks import add_hook

@add_hook("before_tool")
def log_before(data):
    print(f"Calling: {data.tool_name}")

@add_hook("after_tool")
def log_after(data):
    print(f"Done: {data.tool_name}")
Create ~/.praisonai/plugins/tool_sandbox.py to filter advertised tools:
"""
Plugin Name: Tool Sandbox
Description: Filters tool definitions sent to the LLM
Version: 1.0.0
Hooks: before_tool_definitions
"""

from praisonaiagents.hooks import add_hook

BLOCKED = {"delete_file", "shell_exec"}

@add_hook("before_tool_definitions")
def sandbox_tools(data):
    data.tool_definitions[:] = [
        t for t in data.tool_definitions
        if t["function"]["name"] not in BLOCKED
    ]
from praisonaiagents import Agent, discover_and_load_plugins

discover_and_load_plugins()
agent = Agent(name="Assistant", instructions="Help users")
agent.start("Search for Python tutorials")

Lifecycle-Method Plugins

Subclass Plugin and override a lifecycle method to transform prompts, messages, or responses. Call plugins.enable() — or set PRAISONAI_PLUGINS=true / [plugins] enabled = true and let Agent construction wire it — to activate the method at runtime. Create ~/.praisonai/plugins/pii_redactor.py:
"""
Plugin Name: PII Redactor
Description: Scrubs SSNs from LLM responses
Version: 1.0.0
"""
import re
from praisonaiagents import Plugin, PluginInfo, PluginHook

class PIIRedactor(Plugin):
    @property
    def info(self):
        return PluginInfo(
            name="pii_redactor",
            version="1.0.0",
            description="Scrubs SSNs from LLM responses",
            hooks=[PluginHook.AFTER_LLM],
        )

    def after_llm(self, response: str, usage: dict) -> str:
        return re.sub(r"\b\d{3}-\d{2}-\d{4}\b", "[SSN]", response)

def create_plugin():
    return PIIRedactor()
Load and enable:
from praisonaiagents import Agent, plugins

plugins.enable()   # bridges lifecycle methods into the runtime hook registry

agent = Agent(name="Assistant", instructions="Help users.")
agent.start("My SSN is 123-45-6789")   # response is redacted before the user sees it

Session & Error Lifecycle Plugins

Override session_start, session_end, on_error, on_config, or on_auth to react to session boundaries, errors, config, and credential resolution.

Choosing a lifecycle event

Observe sessions

session_start and session_end observe when a session opens and closes.
from praisonaiagents import Agent, plugins

plugins.enable()

agent = Agent(name="Assistant", instructions="Help users.")
agent.start("Hello!")
Create ~/.praisonai/plugins/session_logger.py:
"""
Plugin Name: Session Logger
Description: Records when sessions start and end
Version: 1.0.0
"""
from praisonaiagents.plugins import Plugin, PluginInfo, PluginHook

class SessionLogger(Plugin):
    @property
    def info(self):
        return PluginInfo(
            name="session_logger",
            hooks=[PluginHook.SESSION_START, PluginHook.SESSION_END],
        )

    def session_start(self, context):
        print(f"session started: {context.get('session_name')}")

    def session_end(self, context):
        print(f"session ended: {context.get('reason')} after {context.get('total_turns')} turns")

def create_plugin():
    return SessionLogger()

Observe errors

on_error observes errors during a run — use it to log without changing behavior.
from praisonaiagents import Agent, plugins

plugins.enable()

agent = Agent(name="Assistant", instructions="Help users.")
agent.start("Do something that might fail")
Create ~/.praisonai/plugins/error_reporter.py:
"""
Plugin Name: Error Reporter
Description: Logs errors during agent runs
Version: 1.0.0
"""
from praisonaiagents.plugins import Plugin, PluginInfo, PluginHook

class ErrorReporter(Plugin):
    @property
    def info(self):
        return PluginInfo(
            name="error_reporter",
            hooks=[PluginHook.ON_ERROR],
        )

    def on_error(self, error_type, error_message, context):
        print(f"{error_type}: {error_message}")
        print(context.get("stack_trace"))

def create_plugin():
    return ErrorReporter()

Rewrite config

on_config returns a dict to rewrite runtime configuration in place.
from praisonaiagents import Agent, plugins

plugins.enable()

agent = Agent(name="Assistant", instructions="Help users.")
agent.start("Hello!")
Create ~/.praisonai/plugins/config_defaults.py:
"""
Plugin Name: Config Defaults
Description: Injects a default temperature into runtime config
Version: 1.0.0
"""
from praisonaiagents.plugins import Plugin, PluginInfo, PluginHook

class ConfigDefaults(Plugin):
    @property
    def info(self):
        return PluginInfo(
            name="config_defaults",
            hooks=[PluginHook.ON_CONFIG],
        )

    def on_config(self, config):
        config.setdefault("temperature", 0.2)
        return config

def create_plugin():
    return ConfigDefaults()

Inject credentials

on_auth returns a dict of credentials — the bridge writes them back even when credentials starts as None, so first-time injection works.
from praisonaiagents import Agent, plugins

plugins.enable()

agent = Agent(name="Assistant", instructions="Help users.")
agent.start("Call the authenticated API")
Create ~/.praisonai/plugins/token_injector.py:
"""
Plugin Name: Token Injector
Description: Supplies credentials on first-time auth
Version: 1.0.0
"""
import os
from praisonaiagents.plugins import Plugin, PluginInfo, PluginHook

class TokenInjector(Plugin):
    @property
    def info(self):
        return PluginInfo(
            name="token_injector",
            hooks=[PluginHook.ON_AUTH],
        )

    def on_auth(self, auth_type, credentials):
        return {"token": os.environ["MY_API_TOKEN"]}

def create_plugin():
    return TokenInjector()

Auto-Enable from Env or Config

Agent construction auto-enables plugins when the environment or config file requests it — no explicit plugins.enable() call needed. Set the env var, then any Agent(...) wires plugins before it runs:
export PRAISONAI_PLUGINS=true
from praisonaiagents import Agent

agent = Agent(name="Assistant", instructions="Help users")
agent.start("Hello!")   # plugins are already active
Or turn them on in .praisonai/config.toml:
[plugins]
enabled = true          # or false, or ["logging", "metrics"]
Under the hood, Agent init calls plugins.maybe_enable_from_config(), which reads the env var and config file, then runs enable(get_enabled_plugins()) exactly once per process. Precedence: explicit plugins.enable(...) > PRAISONAI_PLUGINS env var > [plugins] in config.toml > disabled.
maybe_enable_from_config() is idempotent and runs at most once per process, so instantiating multiple agents is safe — plugins are wired a single time.
Auto-enable does not imply discovery. Filesystem and entry-point scanning still require PRAISONAI_ALLOW_PLUGIN_DISCOVERY=true (or 1/yes). Without it, only plugins registered manually via PluginManager.register(...) are bridged.
Verify auto-enable at runtime — constructing an Agent triggers the wiring:
from praisonaiagents import Agent, plugins

Agent(name="probe", instructions="noop")   # triggers maybe_enable_from_config
print(plugins.is_enabled(), plugins.list_plugins())

How the Bridge Works

plugins.enable() auto-calls wire_into_hook_registry() — no manual step. Only lifecycle methods a plugin actually overrides (or declares in PluginInfo.hooks) are bridged, so a plugin with one guardrail never fires on every event. Return a new value and the bridge writes it back onto the payload in place. Errors in a lifecycle method are non-fatal, and plugins.disable([...]) calls unwire_from_hook_registry(name) so the plugin truly stops firing.
Plugin methodHook eventIn-place mutation
before_agent(prompt, ctx)BEFORE_AGENTReturn a str → rewrites data.prompt
after_agent(response, ctx)AFTER_AGENTReturn a str → rewrites data.response
before_llm(messages, params)BEFORE_LLMReturn (new_messages, ...) → replaces data.messages
after_llm(response, usage)AFTER_LLMReturn a str → rewrites data.response
before_tool(name, args)BEFORE_TOOLReturn a dict → replaces data.tool_input
after_tool(name, result)AFTER_TOOLObservational (no mutation)
before_tool_definitions(defs)BEFORE_TOOL_DEFINITIONSReturn a list → replaces data.tool_definitions
before_message(msg)MESSAGE_RECEIVEDReturn {"content": "..."} → rewrites data.content
after_message(msg)MESSAGE_SENDINGReturn {"content": "..."} → rewrites data.content
on_permission_ask(target, reason)ON_PERMISSION_ASKTrue/False/None → allow/deny/allow
on_config(config)ON_CONFIGReturn a dict → rewrites data.config
on_auth(auth_type, credentials)ON_AUTHReturn a dict → rewrites data.credentials (works when starting as None)
session_start(context)SESSION_STARTObservational (session source, session_name, session_id)
session_end(context)SESSION_ENDObservational (reason, total_turns, total_tokens, session_id)
on_error(error_type, error_message, context)ON_ERRORObservational (stack_trace, nested context, session_id)
on_config and on_auth write their returned dict back onto the payload even when the target attribute starts as None — so a plugin can inject credentials the first time they’re requested, not only edit an existing dict.

Ship a Plugin as a pip Package

Register your plugin class in the praisonai.plugins entry-point group in pyproject.toml:
[project.entry-points."praisonai.plugins"]
my_plugin = "my_package.plugins:MyPlugin"
Agent construction (or plugins.enable()) auto-discovers and bridges it — no user code changes needed.

CLI Commands

praisonai plugins init my_plugin
praisonai plugins list
praisonai plugins scan --verbose

Configuration Options

FieldRequiredDescription
Plugin NameYesDisplay name in plugin header
DescriptionYesWhat the plugin does
VersionYesSemantic version
HooksNoHook events this plugin registers

Best Practices

One file per concern — weather tools, logging, or guardrails, not all three.
Load before creating the agent so tools and hooks register globally.
Pass tools=["get_weather"] — the string must match the @tool function name.
Enable logging and metrics without writing plugin files.
Tools and guardrails work without plugins.enable(). But lifecycle-method plugins (subclasses of Plugin that override before_llm, after_llm, and so on) only fire after they are wired into the runtime hook registry. Call plugins.enable() explicitly, or set PRAISONAI_PLUGINS=true / [plugins] enabled = true and Agent construction wires them automatically.

Hooks

Hook events and the HookRegistry API

Toolsets

Create and register custom agent tools

Config File

Turn plugins on from [plugins] in config.toml

Tool Discovery

How Agent resolves tool names at runtime