~/.praisonai/plugins/ and load it in one line.
Quick Start
1
Simple Usage
Create Load and run:
~/.praisonai/plugins/my_tools.py:2
With Configuration
Point the environment (or config file) at plugins and Agent construction wires them for you — no explicit Prefer to turn plugins on in code? Call
plugins.enable() call needed:plugins.enable(...) before creating the agent:Place plugins in
~/.praisonai/plugins/ (user-wide) or ./.praisonai/plugins/ (project-specific).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:
Choosing How to Load Plugins
Pick the loading method that fits your setup.Plugin Locations
Hook Plugin Example
Create~/.praisonai/plugins/my_logger.py:
~/.praisonai/plugins/tool_sandbox.py to filter advertised tools:
Lifecycle-Method Plugins
SubclassPlugin 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:
Session & Error Lifecycle Plugins
Overridesession_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.
~/.praisonai/plugins/session_logger.py:
Observe errors
on_error observes errors during a run — use it to log without changing behavior.
~/.praisonai/plugins/error_reporter.py:
Rewrite config
on_config returns a dict to rewrite runtime configuration in place.
~/.praisonai/plugins/config_defaults.py:
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.
~/.praisonai/plugins/token_injector.py:
Auto-Enable from Env or Config
Agent construction auto-enables plugins when the environment or config file requests it — no explicitplugins.enable() call needed.
Set the env var, then any Agent(...) wires plugins before it runs:
.praisonai/config.toml:
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.yaml / 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.Configure Plugins from .praisonai/config.yaml
Turn plugins on and hand each one its own options from a single YAML file — no code.
~/.praisonai/config.yaml:
app.py:
Agent construction auto-loads the config, enables plugins, and hands
pii_guardrail its {redact: [email, phone]} dict via on_config. Nothing else to wire up.Where the config file is found
The first file found wins — TOML and YAML have equal standing; whichever appears first in the search stops the walk.
The four shapes of enabled
The JSON config schema advertises
boolean and array for enabled; the loader also accepts a bare string at runtime for TOML users who prefer enabled = "pii_guardrail".Per-plugin option blocks
Reserved keys (enabled, auto_discover, directories) configure the plugin system; any other key whose value is a mapping is treated as a per-plugin option map delivered to that plugin’s on_config hook.
An explicit top-level
enabled: false wins over any per-plugin block. Omit enabled at the top level and per-plugin blocks decide.Reading options inside a plugin — on_config
apply_plugin_options() invokes plugin.on_config(options) for every enabled plugin that has a configured option map — errors in a single plugin are logged and do not abort delivery to the others.
Create ~/.praisonai/plugins/pii_guardrail.py:
on_config fires once per enable — the reserved enabled flag (if present in the plugin block) is preserved so plugins can read it too.Deliver options from code — options_by_name
Skip the file and hand each plugin its options directly. plugins.enable() takes an options_by_name map that it delivers to each plugin’s on_config hook:
.praisonai/config.yaml calls this for you — maybe_enable_from_config() runs enable(get_enabled_plugins(), options_by_name=get_plugin_options()) under the hood. Call get_plugin_options() yourself to read the per-plugin maps the loader parsed.Choose a plugin config surface
Precedence
Explicitplugins.enable(...) in code > PRAISONAI_PLUGINS env var > [plugins] in config.yaml / config.toml > disabled by default.
Edge cases
- PyYAML missing — YAML configs silently return empty (debug log only); install
pyyamlto enable. - Unknown reserved key — the wrapper resolver’s typo-suggestion validator only checks against
{enabled, auto_discover, directories}; per-plugin blocks (dicts) are always accepted. - Removed plugin block on reload — the manager stores options with
replace=Trueby default, so a block dropped from a later config no longer delivers stale options. - Plugin
on_configraises — logged as a warning; delivery to other plugins is not aborted.
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.
The five
before_* methods (before_agent, before_llm, before_tool, before_tool_definitions, before_message) also accept a deny/block decision instead of a rewrite — see Blocking Plugins.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.
Blocking Plugins (Guardrails & Policies)
Guardrail and policy plugins can now stop an action before it happens, not just rewrite it — refuse a dangerous tool call, drop a spam message, or decline an LLM request.Quick Start
1
Block a tool call
Return
PluginDecision.block(reason) from before_tool to skip a forbidden tool.2
Refuse an LLM request
Return
PluginDecision.deny(reason) from before_llm and the agent returns "[LLM request blocked by hook: <reason>]" without calling the model.3
Drop an inbound message
Return
PluginDecision.deny(reason) from before_message to drop a message before the agent sees it.Three Ways to Block
The same block, written three ways — pick the one that reads cleanest in your code.PluginDecision.deny(reason) and PluginDecision.block(reason) both stop the action (is_denied() is True for each); allow(reason=None) is an explicit no-op. GuardrailBlocked(reason: str = "Blocked by guardrail plugin") is caught by the bridge and converted to a block.
Where Blocks Fire
Fivebefore_* methods can block; after_* methods are observational and cannot.
How a Block Flows
Which Style Should I Use?
Ship a Plugin as a pip Package
Register your plugin class in thepraisonai.plugins entry-point group in pyproject.toml:
plugins.enable()) auto-discovers and bridges it — no user code changes needed.
CLI Commands
Configuration Options
Best Practices
Keep plugins single-purpose
Keep plugins single-purpose
One file per concern — weather tools, logging, or guardrails, not all three.
Call discover_and_load_plugins() once
Call discover_and_load_plugins() once
Load before creating the agent so tools and hooks register globally.
Reference tools by name
Reference tools by name
Pass
tools=["get_weather"] — the string must match the @tool function name.Use plugins.enable for built-ins
Use plugins.enable for built-ins
Enable
logging and metrics without writing plugin files.Call plugins.enable() to activate lifecycle methods
Call plugins.enable() to activate lifecycle methods
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.Reach for PluginDecision before HookResult
Reach for PluginDecision before HookResult
PluginDecision.deny(reason) reads as one line and only needs the plugin import. Use HookResult.deny(reason) only when you already import it for another reason, and raise GuardrailBlocked(reason) only when you’re inside a validator that already raises on failure. Prefer block over deny when the action is categorically forbidden; use deny when it’s contextually refused (this input, this user, this time).Related
Hooks
Hook events and the HookRegistry API
Toolsets
Create and register custom agent tools
Config File
Turn plugins on from
[plugins] in config.tomlTool Discovery
How Agent resolves tool names at runtime
Guardrails
Validate agent output — automatic retry on failure

