Jido.Plugin behaviour
(Jido v2.0.0-rc.3)
View Source
A Plugin is a composable capability that can be attached to an agent.
Plugins encapsulate:
- A set of actions the agent can perform
- State schema for plugin-specific data (nested under
state_key) - Configuration schema for per-agent customization
- Signal routing rules
- Optional lifecycle hooks and child processes
Lifecycle
- Compile-time: Plugin is declared in agent's
plugins:option - Agent.new/1:
mount/2is called to initialize plugin state (pure) - AgentServer.init/1:
child_spec/1processes are started and monitored - Signal processing:
handle_signal/2runs before routing, can override or abort - After cmd/2 (call path):
transform_result/3wraps call results
Example Plugin
defmodule MyApp.ChatPlugin do
use Jido.Plugin,
name: "chat",
state_key: :chat,
actions: [MyApp.Actions.SendMessage, MyApp.Actions.ListHistory],
schema: Zoi.object(%{
messages: Zoi.list(Zoi.any()) |> Zoi.default([]),
model: Zoi.string() |> Zoi.default("gpt-4")
}),
signal_patterns: ["chat.*"]
@impl Jido.Plugin
def mount(agent, config) do
# Custom initialization beyond schema defaults
{:ok, %{initialized_at: DateTime.utc_now()}}
end
@impl Jido.Plugin
def router(config) do
[
{"chat.send", MyApp.Actions.SendMessage},
{"chat.history", MyApp.Actions.ListHistory}
]
end
endUsing Plugins
defmodule MyAgent do
use Jido.Agent,
name: "my_agent",
plugins: [
MyApp.ChatPlugin,
{MyApp.DatabasePlugin, %{pool_size: 5}}
]
endConfiguration Options
name- Required. The plugin name (letters, numbers, underscores).state_key- Required. Atom key for plugin state in agent.actions- Required. List of action modules.description- Optional description.category- Optional category.vsn- Optional version string.schema- Optional Zoi schema for plugin state.config_schema- Optional Zoi schema for per-agent config.signal_patterns- List of signal pattern strings (default: []).tags- List of tag strings (default: []).capabilities- List of atoms describing what the plugin provides (default: []).requires- List of requirements like{:config, :token},{:app, :req},{:plugin, :http}(default: []).routes- List of route tuples like{"post", ActionModule}(default: []).schedules- List of schedule tuples like{"*/5 * * * *", ActionModule}(default: []).
Summary
Callbacks
Returns child specification(s) for supervised processes.
Pre-routing hook called before signal routing in AgentServer.
Called when the plugin is mounted to an agent during new/1.
Returns the plugin specification with optional per-agent configuration.
Returns the signal router for this plugin.
Returns bus subscriptions for this plugin.
Transform the agent returned from AgentServer.call/3.
Callbacks
@callback child_spec(config :: map()) :: nil | Supervisor.child_spec() | [Supervisor.child_spec()]
Returns child specification(s) for supervised processes.
Called during AgentServer.init/1. Returned processes are
started and monitored. If any crash, AgentServer receives exit signals.
Parameters
config- Per-agent configuration for this plugin
Returns
nil- No child processesSupervisor.child_spec()- Single child[Supervisor.child_spec()]- Multiple children
Example
def child_spec(config) do
%{
id: {__MODULE__, :worker},
start: {MyWorker, :start_link, [config]}
}
end
@callback handle_signal(signal :: term(), context :: map()) :: {:ok, term()} | {:ok, {:override, term()}} | {:error, term()}
Pre-routing hook called before signal routing in AgentServer.
Can inspect, log, or override which action runs for a signal.
Parameters
signal- The incomingJido.Signalstructcontext- Map with:agent,:agent_module,:plugin,:plugin_spec,:config
Returns
{:ok, nil}or{:ok, :continue}- Continue to normal routing{:ok, {:override, action_spec}}- Bypass router, use this action instead{:error, reason}- Abort signal processing with error
Example
def handle_signal(signal, _context) do
if signal.type == "admin.override" do
{:ok, {:override, MyApp.AdminAction}}
else
{:ok, :continue}
end
end
Called when the plugin is mounted to an agent during new/1.
Use this to initialize plugin-specific state beyond schema defaults. This is a pure function - no side effects allowed.
Parameters
agent- The agent struct (with state from previously mounted plugins)config- Per-agent configuration for this plugin
Returns
{:ok, plugin_state}- Map to merge into plugin's state slice{:ok, nil}- No additional state (schema defaults only){:error, reason}- Raises during agent creation
Example
def mount(_agent, config) do
{:ok, %{initialized_at: DateTime.utc_now(), api_key: config[:api_key]}}
end
@callback plugin_spec(config :: map()) :: Jido.Plugin.Spec.t()
Returns the plugin specification with optional per-agent configuration.
This is the primary interface for getting plugin metadata and configuration.
Returns the signal router for this plugin.
The router determines how signals are routed to handlers.
Returns bus subscriptions for this plugin.
Called during AgentServer.init/1 to determine which bus adapters
to subscribe to and with what options.
Parameters
config- Per-agent configuration for this plugincontext- Map with:agent_id,:agent_module
Returns
List of {adapter_module, opts} tuples. Each adapter's subscribe/2
will be called with the AgentServer pid.
Example
def subscriptions(_config, context) do
[
{Jido.Bus.Adapters.Local, topic: "events.*"},
{Jido.Bus.Adapters.PubSub, pubsub: MyApp.PubSub, topic: context.agent_id}
]
end
@callback transform_result( action :: module() | String.t(), result :: term(), context :: map() ) :: term()
Transform the agent returned from AgentServer.call/3.
Called after signal processing on the synchronous call path only.
Does not affect cast/2 or internal state - only the returned agent.
Parameters
action- The signal type or action module that was executedresult- The agent struct to transformcontext- Map with:agent,:agent_module,:plugin,:plugin_spec,:config
Returns
The transformed agent struct (or original if no transformation needed).
Example
def transform_result(_action, agent, _context) do
# Add metadata to returned agent
new_state = Map.put(agent.state, :last_call_at, DateTime.utc_now())
%{agent | state: new_state}
end