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
endBuilding 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.
Summary
Callbacks
Compact the message list.
RPC call timeout in milliseconds when this compactor is invoked remotely.
Functions
Build an on_compact function for the given model.
Default RPC timeout used when a compactor module omits compact_timeout/0.
Types
@type opts() :: [ ratio: float(), keep_recent: pos_integer(), sidecar_node: atom() | nil, compactor: String.t() | nil ]
Options accepted by build/2.
:ratio— fraction ofmodel.context_windowthat triggers compaction (default0.8):keep_recent— number of recent messages to keep verbatim (default10):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_nodeis set.
Callbacks
@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}Messagecontaining the summary textkept— recent messages retained verbatim after the summary
@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.
Functions
@spec build(Planck.AI.Model.t(), opts()) :: ([Planck.Agent.Message.t()] -> :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"
)
@spec default_compact_timeout() :: pos_integer()
Default RPC timeout used when a compactor module omits compact_timeout/0.