Jido.Plugin behaviour
(Jido v2.0.0-rc.4)
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 signal_routes(_ctx) 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: []).signal_routes- List of signal 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.
Called during checkpoint to determine how this plugin's state should be persisted.
Called during restore to rehydrate externalized plugin state.
Returns the plugin specification with optional per-agent configuration.
Returns the signal routes for this plugin.
Returns bus subscriptions for this plugin.
Caller view transform for 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, transform, or override which action runs for a signal.
Hooks execute in plugin declaration order. The first {:override, ...}
short-circuits; the first {:error, ...} aborts. Plugins with non-empty
signal_patterns only receive signals matching those patterns; plugins
with empty patterns act as global middleware.
Parameters
signal- The incomingJido.Signalstruct (may be modified by earlier plugins)context- Map with:agent,:agent_module,:plugin,:plugin_spec,:plugin_instance,:config
Returns
{:ok, nil}or{:ok, :continue}- Continue to normal routing{:ok, {:continue, %Signal{}}}- Rewrite the signal and continue routing{:ok, {:override, action_spec}}- Bypass router, use this action instead{:ok, {:override, action_spec, %Signal{}}}- Bypass router with rewritten signal{: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 on_checkpoint(plugin_state :: term(), context :: map()) :: {:externalize, key :: atom(), pointer :: term()} | :keep | :drop
Called during checkpoint to determine how this plugin's state should be persisted.
Plugins can declare one of three strategies for their state slice:
:keep— Include in checkpoint state as-is (default):drop— Exclude from checkpoint (transient/ephemeral state){:externalize, key, pointer}— Strip from checkpoint state and store a pointer separately. The pointer is a lightweight reference (e.g.,%{id, rev}) that can be used to rehydrate the full state on restore.
Parameters
plugin_state- The plugin's current state slice (may be nil)context- Map with checkpoint context (e.g.,:config)
Returns
:keep— Include plugin state in checkpoint (default):drop— Exclude from checkpoint{:externalize, key, pointer}— Store pointer underkeyin checkpoint
Example
def on_checkpoint(%Thread{} = thread, _ctx) do
{:externalize, :thread, %{id: thread.id, rev: thread.rev}}
end
def on_checkpoint(nil, _ctx), do: :keep
Called during restore to rehydrate externalized plugin state.
When a plugin's on_checkpoint/2 returns {:externalize, key, pointer},
the pointer is stored in the checkpoint. During restore, on_restore/2
is called with that pointer to allow the plugin to reconstruct its state.
For plugins that require IO to restore (e.g., loading a thread from storage),
returning {:ok, nil} signals that the state will be rehydrated by the
persistence layer (e.g., Jido.Persist).
Parameters
pointer- The pointer stored during checkpoint (fromon_checkpoint/2)context- Map with restore context (e.g.,:config)
Returns
{:ok, restored_state}— The restored plugin state{:ok, nil}— State will be rehydrated externally (e.g., by Persist){:error, reason}— Restore failed
@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 routes for this plugin.
The signal routes determine 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()
Caller view transform for the agent returned from AgentServer.call/3.
Called after signal processing on the synchronous call path only.
Does not affect cast/2, handle_info, or internal server state — only
the agent struct returned to the caller. Transforms chain through all
plugins in declaration order.
Parameters
action- The resolved action module that was executed, or the signal type string when no single module can be determinedresult- The agent struct to transformcontext- Map with:agent,:agent_module,:plugin,:plugin_spec,:plugin_instance,: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