# `Jido.Plugin`
[🔗](https://github.com/agentjido/jido/blob/v2.3.0/lib/jido/plugin.ex#L1)

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. **Prepare signal**: `prepare_signal/2` can verify/rewrite the effective signal and contribute runtime context
6. **Prepare action**: `prepare_action/3` can authorize the resolved action using runtime context
7. **Before signal emit dispatch**: `prepare_emit/2` can rewrite outbound emitted signals or dispatch
8. **After cmd/3 (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.*"],
        signal_routes: [
          {"chat.send", MyApp.Actions.SendMessage},
          {"chat.history", MyApp.Actions.ListHistory}
        ]

      @impl Jido.Plugin
      def mount(agent, config) do
        # Custom initialization beyond schema defaults
        {:ok, %{initialized_at: DateTime.utc_now()}}
      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: []).
- `signal_routes` - List of signal route tuples like `{"post", ActionModule}` (default: []).
- `subscriptions` - List of sensor subscription tuples like `{SensorModule, config}` or `{tag, SensorModule, config}` (default: []).
- `schedules` - List of schedule tuples like `{"*/5 * * * *", ActionModule}` (default: []).

For static routes and subscriptions, prefer the compile-time `signal_routes:` and `subscriptions:` options in `use Jido.Plugin`.
Use the `signal_routes/1` and `subscriptions/2` callbacks only for dynamic generation based on runtime config.

# `sensor_subscription`

```elixir
@type sensor_subscription() ::
  {module(), keyword() | map()} | {term(), module(), keyword() | map()}
```

# `child_spec`

```elixir
@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`

```elixir
@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 receive all inbound signals for this phase.

This callback remains broad for backwards compatibility and route override.
Prefer `prepare_signal/2` for identity, encryption, canonicalization, and
runtime context extraction.

## Parameters

- `signal` - The incoming `Jido.Signal` struct (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

# `mount`

```elixir
@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

# `on_checkpoint`

```elixir
@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 under `key` in 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

# `on_restore`

```elixir
@callback on_restore(pointer :: term(), context :: map()) ::
  {:ok, term()} | {:error, term()}
```

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 (from `on_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

# `plugin_spec`

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

# `prepare_action`

```elixir
@callback prepare_action(signal :: term(), action_arg :: term(), context :: map()) ::
  {:ok, map()} | {:error, term()}
```

Post-routing hook called after routing and before action execution.

Plugins can authorize the resolved action using the prepared signal and
accumulated runtime context. This hook cannot rewrite the signal or action;
it can only contribute additional runtime context or fail closed.

## Parameters

- `signal` - The prepared `Jido.Signal` struct after `prepare_signal/2`
- `action_arg` - The resolved action argument that will be passed to the
  agent command phase
- `context` - Map with `:agent`, `:agent_module`, `:plugin`, `:plugin_spec`,
  `:plugin_instance`, `:config`, `:runtime_context`

## Returns

- `{:ok, runtime_context_delta}` - Continue with additional runtime context.
- `{:error, reason}` - Abort signal processing with error.

# `prepare_emit`

```elixir
@callback prepare_emit(signal :: term(), context :: map()) ::
  {:ok, term()} | {:ok, term(), term()} | {:error, term()}
```

Pre-emit hook called before an emitted signal is dispatched.

This hook is intended for outbound signal signing, trace enrichment, and other
signal-level transformations that must happen after an action returns an emit
directive but before runtime dispatch. The context includes `:input_signal`,
`:runtime_context`, `:directive`, `:dispatch`, plugin metadata, agent
metadata, `:jido_instance`, and `:partition`.

## Returns

- `{:ok, signal}` - Continue with the prepared signal and existing dispatch.
- `{:ok, signal, dispatch}` - Continue with the prepared signal and rewritten
  dispatch.
- `{:error, reason}` - Abort the emit through the configured error policy.

# `prepare_signal`

```elixir
@callback prepare_signal(signal :: term(), context :: map()) ::
  {:ok, term(), map()} | {:error, term()}
```

Pre-routing hook called after `handle_signal/2` rewrites and before routing.

Plugins can verify, decrypt, canonicalize, or rewrite the final effective
signal and contribute runtime context. Returned context is merged into the
context given to routed actions and later plugin phases. This is the preferred
inbound hook for identity and encrypted communication extensions because it
cannot override routing and has an explicit runtime context contract. Plugins
may not provide reserved runtime keys such as `:state`, `:signal`, `:agent`,
`:agent_server_pid`, `:input_signal`, `:directive`, or `:dispatch`;
duplicate context keys fail closed.

## Parameters

- `signal` - The effective `Jido.Signal` struct after `handle_signal/2`
  hooks have run.
- `context` - Map with `:agent`, `:agent_module`, `:plugin`, `:plugin_spec`,
  `:plugin_instance`, `:config`, `:runtime_context`

## Returns

- `{:ok, signal, runtime_context_delta}` - Continue with possibly rewritten signal
  and runtime context.
- `{:error, reason}` - Abort signal processing with error.

# `signal_routes`

```elixir
@callback signal_routes(config :: map()) :: term()
```

Returns the signal routes for this plugin.

The signal routes determine how signals are routed to handlers.
Prefer compile-time `signal_routes:` in `use Jido.Plugin` for static routes,
and implement this callback only for dynamic route generation.

# `subscriptions`

```elixir
@callback subscriptions(config :: map(), context :: map()) :: [sensor_subscription()]
```

Returns a list of sensors to be started for this plugin.

Called during `AgentServer.init/1` to start and monitor
plugin-specific sensors. These sensors are managed and monitored by the
agent runtime and can emit signals back to it.

## Parameters

- `config` - Per-agent configuration for this plugin
- `context` - A map containing:
  - `:agent_id` - The unique identifier of the agent
  - `:agent_ref` - A reference (PID or via-tuple) to the AgentServer
  - `:agent_module` - The module of the agent
  - `:plugin_spec` - The specification of the current plugin
  - `:jido_instance` - The Jido instance name

## Returns

List of `{sensor_module, sensor_opts}` tuples or `{tag, sensor_module, sensor_opts}`
tuples. Each sensor will be started under a `Jido.Sensor.Runtime`. Use the
tagged form when a plugin starts multiple instances of the same sensor module.

## Example

    def subscriptions(_config, context) do
      [
        {MyApp.Sensors.MarketData, %{symbol: "AAPL", interval: 1000}},
        {Jido.Sensors.Heartbeat, %{interval: 5000, agent_ref: context.agent_ref}}
      ]
    end

# `transform_result`

```elixir
@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`, routing, authorization, dispatch, or
internal server state — only the agent struct returned to the caller.
Transforms chain through all plugins in declaration order, so this callback is
display/return shaping and is not a security hook.

## Parameters

- `action` - The resolved action module that was executed, or the signal
  type string when no single module can be determined
- `result` - The agent struct to transform
- `context` - Map with `:agent`, `:agent_module`, `:plugin`, `:plugin_spec`,
  `:plugin_instance`, `:config`, `:runtime_context`

## 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

