# `ADK.Plugin`
[🔗](https://github.com/zeroasterisk/adk-elixir/blob/main/lib/adk/plugin.ex#L1)

Global plugin behaviour for intercepting the Runner pipeline.

Unlike callbacks (per-invocation, per-agent), plugins are **global** — registered
at the application level and applied to every `Runner.run/5` call. Plugins can
inspect/transform the context before execution, and inspect/transform results after.

## Behaviour

Implement any subset of callbacks:

- `init/1` — initialize plugin state from config, return `{:ok, state}`
- `before_run/2` — called before Runner executes, receives `{context, plugin_state}`,
  returns `{:cont, context, state}` or `{:halt, result, state}`
- `after_run/3` — called after Runner executes, receives `{result, context, plugin_state}`,
  returns `{result, state}`

### Per-model and per-tool hooks (stateless)

These hooks are called inline during LLM agent execution. They do not carry
plugin state (use ETS or GenServer in `init/1` for statefulness across calls):

- `before_model/2` — called before each LLM call; can modify the request or skip
  the call entirely by returning a canned response
- `after_model/2` — called after each LLM call; can transform the response
- `before_tool/3` — called before each tool execution; can modify args or skip
  the tool call by returning a canned result
- `after_tool/3` — called after each tool execution; can transform the result
- `on_event/2` — called for each event emitted during execution; observe-only

## Example

    defmodule MyPlugin do
      @behaviour ADK.Plugin

      @impl true
      def init(config), do: {:ok, config}

      @impl true
      def before_run(context, state) do
        {:cont, context, state}
      end

      @impl true
      def after_run(result, _context, state) do
        {result, state}
      end

      @impl true
      def before_model(context, request) do
        # Inject extra context into every model request
        {:ok, Map.put(request, :extra, "injected")}
      end

      @impl true
      def after_model(_context, response) do
        response
      end

      @impl true
      def before_tool(_context, tool_name, args) do
        IO.puts("Calling tool: #{tool_name}")
        {:ok, args}
      end

      @impl true
      def after_tool(_context, _tool_name, result) do
        result
      end

      @impl true
      def on_event(_context, event) do
        IO.inspect(event, label: "event")
        :ok
      end
    end

    # Register globally
    ADK.Plugin.Registry.start_link([])
    ADK.Plugin.register({MyPlugin, my_config: true})

# `state`

```elixir
@type state() :: term()
```

# `after_model`
*optional* 

```elixir
@callback after_model(ADK.Context.t(), {:ok, map()} | {:error, term()}) ::
  {:ok, map()} | {:error, term()}
```

Called after each LLM model call.

Receives the raw LLM result and may return a transformed result.

# `after_run`
*optional* 

```elixir
@callback after_run([ADK.Event.t()], ADK.Context.t(), state()) ::
  {[ADK.Event.t()], state()}
```

Called after Runner.run executes the agent.

Receives the result (list of events), the context, and plugin state.
Return `{result, new_state}`.

# `after_tool`
*optional* 

```elixir
@callback after_tool(ADK.Context.t(), tool_name :: String.t(), ADK.Tool.result()) ::
  ADK.Tool.result()
```

Called after each tool execution.

Receives the tool result and may return a transformed result.

# `before_model`
*optional* 

```elixir
@callback before_model(ADK.Context.t(), request :: map()) ::
  {:ok, map()} | {:skip, {:ok, map()} | {:error, term()}}
```

Called before each LLM model call.

Return `{:ok, request}` to continue (possibly with a modified request), or
`{:skip, response}` to skip the model call entirely and use the given response.

The `response` in `{:skip, response}` should be `{:ok, map()}` or `{:error, term()}`.

# `before_run`
*optional* 

```elixir
@callback before_run(ADK.Context.t(), state()) ::
  {:cont, ADK.Context.t(), state()} | {:halt, term(), state()}
```

Called before Runner.run executes the agent.

Return `{:cont, context, new_state}` to continue or `{:halt, result, new_state}` to short-circuit.

# `before_tool`
*optional* 

```elixir
@callback before_tool(ADK.Context.t(), tool_name :: String.t(), args :: map()) ::
  {:ok, map()} | {:skip, ADK.Tool.result()}
```

Called before each tool execution.

Return `{:ok, args}` to continue (possibly with modified args), or
`{:skip, result}` to skip the tool and return the given result directly.

# `init`
*optional* 

```elixir
@callback init(config :: term()) :: {:ok, state()}
```

Initialize plugin state from config. Return `{:ok, state}`.

# `on_event`
*optional* 

```elixir
@callback on_event(ADK.Context.t(), ADK.Event.t()) :: :ok
```

Called for each event emitted during execution (observe-only).

Always return `:ok`. Use this for logging, telemetry, or side effects.

# `on_model_error`
*optional* 

```elixir
@callback on_model_error(
  ADK.Context.t(),
  {:error, term()}
) :: {:ok, map()} | {:error, term()}
```

Called when an LLM model call fails.

Return `{:ok, response}` to recover and use the fake response, or
`{:error, new_error}` to continue the error chain.

# `on_tool_error`
*optional* 

```elixir
@callback on_tool_error(ADK.Context.t(), tool_name :: String.t(), {:error, term()}) ::
  ADK.Tool.result()
```

Called when a tool execution fails.

Return `{:ok, response}` to recover and use the fake response, or
`{:error, new_error}` to continue the error chain.

# `list`

```elixir
@spec list() :: [{module(), state()}]
```

List all registered plugins as `[{module, state}]`.

# `register`

```elixir
@spec register(module() | {module(), term()}) :: :ok
```

Register a plugin globally. Accepts `module` or `{module, config}`.

# `run_after`

```elixir
@spec run_after([{module(), state()}], [ADK.Event.t()], ADK.Context.t()) ::
  {[ADK.Event.t()], [{module(), state()}]}
```

Run after_run hooks for a list of `{module, state}` tuples.

Returns `{result, updated_plugins}`.

# `run_after_model`

```elixir
@spec run_after_model(
  [{module(), state()}],
  ADK.Context.t(),
  {:ok, map()} | {:error, term()}
) ::
  {:ok, map()} | {:error, term()}
```

Run after_model hooks for all registered plugins, threading the result through each.

Plugins that don't implement `after_model/2` are skipped.

# `run_after_tool`

```elixir
@spec run_after_tool(
  [{module(), state()}],
  ADK.Context.t(),
  String.t(),
  ADK.Tool.result()
) ::
  ADK.Tool.result()
```

Run after_tool hooks for all registered plugins, threading the result through each.

Plugins that don't implement `after_tool/3` are skipped.

# `run_before`

```elixir
@spec run_before([{module(), state()}], ADK.Context.t()) ::
  {:cont, ADK.Context.t(), [{module(), state()}]}
  | {:halt, term(), [{module(), state()}]}
```

Run before_run hooks for a list of `{module, state}` tuples.

Returns `{:cont, context, updated_plugins}` or `{:halt, result, updated_plugins}`.

# `run_before_model`

```elixir
@spec run_before_model([{module(), state()}], ADK.Context.t(), map()) ::
  {:ok, map()} | {:skip, {:ok, map()} | {:error, term()}}
```

Run before_model hooks for all registered plugins.

Returns `{:ok, final_request}` if all plugins continue, or
`{:skip, response}` if any plugin short-circuits the model call.

Plugins that don't implement `before_model/2` are skipped.

# `run_before_tool`

```elixir
@spec run_before_tool([{module(), state()}], ADK.Context.t(), String.t(), map()) ::
  {:ok, map()} | {:skip, ADK.Tool.result()}
```

Run before_tool hooks for all registered plugins.

Returns `{:ok, final_args}` if all plugins continue, or
`{:skip, result}` if any plugin short-circuits the tool call.

Plugins that don't implement `before_tool/3` are skipped.

# `run_on_event`

```elixir
@spec run_on_event([{module(), state()}], ADK.Context.t(), ADK.Event.t()) :: :ok
```

Run on_event hooks for all registered plugins.

All plugins that implement `on_event/2` are called. Errors are ignored.
Always returns `:ok`.

Plugins that don't implement `on_event/2` are skipped.

# `run_on_model_error`

```elixir
@spec run_on_model_error([{module(), state()}], ADK.Context.t(), {:error, term()}) ::
  {:ok, map()} | {:error, term()}
```

Run on_model_error hooks for all registered plugins, threading the error through each.

If any plugin recovers the error and returns `{:ok, response}`, subsequent plugins
are skipped for the error chain and the error is considered handled.

# `run_on_tool_error`

```elixir
@spec run_on_tool_error(
  [{module(), state()}],
  ADK.Context.t(),
  String.t(),
  {:error, term()}
) ::
  ADK.Tool.result()
```

Run on_tool_error hooks for all registered plugins, threading the error through each.

---

*Consult [api-reference.md](api-reference.md) for complete listing*
