# `Planck.Agent.Compactor`
[🔗](https://github.com/alexdesousa/planck/blob/v0.1.0/lib/planck/agent/compactor.ex#L1)

Behaviour and default implementation for context compaction in `Planck.Agent`.

## Behaviour

Use `use Planck.Agent.Compactor` to implement a custom compaction strategy.
The only required callback is `compact/2`; `compact_timeout/0` has a default
implementation of 120000 ms.

    defmodule MySidecar.Compactors.Builder do
      use Planck.Agent.Compactor

      @impl true
      def compact(_model, messages) do
        summary = Message.new({:custom, :summary}, [{:text, summarise(messages)}])
        kept    = Enum.take(messages, -5)
        {:compact, summary, kept}
      end

      # Optional — override to declare a custom RPC timeout.
      @impl true
      def compact_timeout, do: 60_000
    end

## Building an on_compact function

`build/2` is the single entry point for both the local LLM-based compactor and
remote sidecar compactors. When `sidecar_node:` and `compactor:` options are
absent it runs locally; when both are present it calls the remote module via
`:rpc.call/5` and falls back to the local compactor if the RPC fails.

    # Local (default):
    on_compact = Planck.Agent.Compactor.build(model)

    # Remote sidecar:
    on_compact = Planck.Agent.Compactor.build(model,
      sidecar_node: :planck_sidecar@localhost,
      compactor:    "MySidecar.Compactors.Builder"
      # The string is converted to the atom :"Elixir.MySidecar.Compactors.Builder"
      # before the RPC call. Always use the bare Elixir module name (no "Elixir." prefix).
    )

Both return a `fn messages ->` closure of arity 1, as expected by `Planck.Agent`.

## Compaction strategy (local default)

When triggered, the oldest messages (everything except the last `keep_recent`
messages) are summarised into a single `{:custom, :summary}` message. On LLM
failure the local compactor falls back to the original message list unchanged
(`:skip`). On remote failure the local compactor is used as the fallback.

# `opts`

```elixir
@type opts() :: [
  ratio: float(),
  keep_recent: pos_integer(),
  sidecar_node: atom() | nil,
  compactor: String.t() | nil
]
```

Options accepted by `build/2`.

- `:ratio` — fraction of `model.context_window` that triggers compaction
  (default `0.8`)
- `:keep_recent` — number of recent messages to keep verbatim (default `10`)
- `:sidecar_node` — node name of a connected sidecar (enables remote compaction)
- `:compactor` — fully-qualified module name string in the sidecar,
  e.g. `"MySidecar.Compactors.Builder"`. Required when `:sidecar_node` is set.

# `compact`

```elixir
@callback compact(model :: Planck.AI.Model.t(), messages :: [Planck.Agent.Message.t()]) ::
  {:compact, summary :: Planck.Agent.Message.t(),
   kept :: [Planck.Agent.Message.t()]}
  | :skip
```

Compact the message list.

Return `{:compact, summary_msg, kept}` to replace older messages with a summary,
or `:skip` to leave the list unchanged.

- `summary_msg` — a `{:custom, :summary}` `Message` containing the summary text
- `kept` — recent messages retained verbatim after the summary

# `compact_timeout`

```elixir
@callback compact_timeout() :: pos_integer()
```

RPC call timeout in milliseconds when this compactor is invoked remotely.

Defaults to 120000 ms. Override to declare a custom
expected latency for the compactor — the module knows its own logic better
than any caller default.

# `build`

```elixir
@spec build(Planck.AI.Model.t(), opts()) :: ([Planck.Agent.Message.t()] -&gt;
                                         :skip
                                         | {:compact, Planck.Agent.Message.t(),
                                            [Planck.Agent.Message.t()]})
```

Build an `on_compact` function for the given model.

Returns a `fn messages ->` closure of arity 1. When remote options are provided
(`sidecar_node:` and `compactor:`), the closure calls the remote module via RPC
and falls back to the local LLM-based compactor if the call fails.

## Examples

    # Local:
    on_compact = Planck.Agent.Compactor.build(model, ratio: 0.75)

    # Remote sidecar with local fallback:
    on_compact = Planck.Agent.Compactor.build(model,
      sidecar_node: :planck_sidecar@localhost,
      compactor: "MySidecar.Compactors.Builder"
    )

# `default_compact_timeout`

```elixir
@spec default_compact_timeout() :: pos_integer()
```

Default RPC timeout used when a compactor module omits `compact_timeout/0`.

---

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