# `Puck.Hooks`
[🔗](https://github.com/bradleygolden/puck/blob/v0.2.25/lib/puck/hooks.ex#L1)

Behaviour for lifecycle hooks.

Hooks observe and transform data at each stage of client execution.
All callbacks are optional.

## Return Types

- `{:cont, value}` - Continue with value
- `{:halt, response}` - Short-circuit with response
- `{:error, reason}` - Abort with error

## Callbacks

- `on_call_start/3` - Before LLM call
- `on_call_end/3` - After successful call
- `on_call_error/3` - On call failure
- `on_stream_start/3`, `on_stream_chunk/3`, `on_stream_end/2`, `on_stream_done/3` - Stream lifecycle
- `on_backend_request/2`, `on_backend_response/2` - Backend lifecycle
- `on_compaction_start/3`, `on_compaction_end/2` - Compaction lifecycle

## Example

    defmodule MyApp.LoggingHooks do
      @behaviour Puck.Hooks
      require Logger

      @impl true
      def on_call_start(_client, content, _context) do
        Logger.info("Call started")
        {:cont, content}
      end

      @impl true
      def on_call_end(_client, response, _context) do
        Logger.info("Call completed")
        {:cont, response}
      end
    end

## Usage

    client = Puck.Client.new({Puck.Backends.ReqLLM, "anthropic:claude-sonnet-4-5"},
      hooks: MyApp.LoggingHooks
    )

    # Multiple hooks execute in order
    Puck.call(client, "Hello", context,
      hooks: [MyApp.LoggingHooks, MyApp.MetricsHooks]
    )

# `chunk`

```elixir
@type chunk() :: map()
```

# `client`

```elixir
@type client() :: Puck.Client.t()
```

# `config`

```elixir
@type config() :: map()
```

# `context`

```elixir
@type context() :: Puck.Context.t()
```

# `messages`

```elixir
@type messages() :: [map()]
```

# `response`

```elixir
@type response() :: Puck.Response.t()
```

# `on_backend_request`
*optional* 

```elixir
@callback on_backend_request(config(), messages()) ::
  {:cont, messages()} | {:halt, response()} | {:error, term()}
```

# `on_backend_response`
*optional* 

```elixir
@callback on_backend_response(config(), response()) ::
  {:cont, response()} | {:error, term()}
```

# `on_call_end`
*optional* 

```elixir
@callback on_call_end(client(), response(), context()) ::
  {:cont, response()} | {:error, term()}
```

# `on_call_error`
*optional* 

```elixir
@callback on_call_error(client(), error :: term(), context()) :: term()
```

# `on_call_start`
*optional* 

```elixir
@callback on_call_start(client(), content :: term(), context()) ::
  {:cont, term()} | {:halt, response()} | {:error, term()}
```

# `on_compaction_end`
*optional* 

```elixir
@callback on_compaction_end(context(), strategy :: module()) ::
  {:cont, context()} | {:error, term()}
```

# `on_compaction_start`
*optional* 

```elixir
@callback on_compaction_start(context(), strategy :: module(), config :: map()) ::
  {:cont, context()} | {:halt, context()} | {:error, term()}
```

# `on_stream_chunk`
*optional* 

```elixir
@callback on_stream_chunk(client(), chunk(), context()) :: term()
```

# `on_stream_done`
*optional* 

```elixir
@callback on_stream_done(client(), response(), context()) :: term()
```

# `on_stream_end`
*optional* 

```elixir
@callback on_stream_end(client(), context()) :: term()
```

# `on_stream_start`
*optional* 

```elixir
@callback on_stream_start(client(), content :: term(), context()) ::
  {:cont, term()} | {:error, term()}
```

# `invoke`

Invokes an observational hook callback (return value is ignored).

Used for callbacks like `on_call_error`, `on_stream_chunk`, `on_stream_end`,
and `on_stream_done`
where the return value doesn't affect the pipeline.

# `invoke`

Invokes a transforming hook callback on the given hook module(s).

Returns the transformed value, a halt response, or an error.
If a callback is not implemented, the initial value is passed through.

## Returns

- `{:cont, value}` - Continue with (possibly transformed) value
- `{:halt, response}` - Short-circuit with a response
- `{:error, reason}` - Abort with error

# `merge`

Merges client-level hooks with per-call hooks.

Per-call hooks come after client-level hooks (client hooks run first).

---

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