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

  1. Compile-time: Plugin is declared in agent's plugins: option
  2. Agent.new/1: mount/2 is called to initialize plugin state (pure)
  3. AgentServer.init/1: child_spec/1 processes are started and monitored
  4. Signal processing: handle_signal/2 runs before routing, can override or abort
  5. After cmd/2 (call path): transform_result/3 wraps 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
end

Using Plugins

defmodule MyAgent do
  use Jido.Agent,
    name: "my_agent",
    plugins: [
      MyApp.ChatPlugin,
      {MyApp.DatabasePlugin, %{pool_size: 5}}
    ]
end

Configuration 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

child_spec(config)

@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 processes
  • Supervisor.child_spec() - Single child
  • [Supervisor.child_spec()] - Multiple children

Example

def child_spec(config) do
  %{
    id: {__MODULE__, :worker},
    start: {MyWorker, :start_link, [config]}
  }
end

handle_signal(signal, context)

@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 incoming Jido.Signal struct
  • context - 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

mount(agent, config)

@callback mount(agent :: term(), config :: map()) :: {:ok, map() | nil} | {:error, term()}

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

plugin_spec(config)

@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.

router(config)

@callback router(config :: map()) :: term()

Returns the signal router for this plugin.

The router determines how signals are routed to handlers.

subscriptions(config, context)

@callback subscriptions(config :: map(), context :: map()) :: [
  {module(), keyword() | map()}
]

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 plugin
  • context - 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

transform_result(action, result, context)

@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 executed
  • result - The agent struct to transform
  • context - 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