Planck.Agent.Hooks.Compactor.Default (Planck.Agent v0.2.2)

Copy Markdown View Source

Built-in Planck.Agent.Hooks.Compactor implementation.

Used whenever state.compactor is nil — see Planck.Agent.Hooks.Compactor's own moduledoc for the dispatch rules. Not special-cased by the dispatcher in any other way: this module satisfies the same behaviour a sidecar-hosted custom compactor would, and could be named explicitly via AgentSpec.compactor too.

Strategy

The messages since the last summary checkpoint are split into a recency floor (always kept verbatim — mirrors the "at least the last message is always kept" invariant) and an older bucket. The older bucket is summarized as one plain-text block by a real, ephemeral Planck.Agent — not asked for structured per-message output. An earlier version of this asked the delegate for per-message keep/drop verdicts as JSON; that turned out to be the wrong shape for a compactor that has to work with whatever model is configured, not a specific one: producing well-formed JSON with correct message-id references is a much less reliable task for a weak/small model than writing a plain summary is, and a compactor that only works with capable models defeats its own "works everywhere" purpose. Per-message relevance judgment still has a natural home — a fast classifier-style model whose native output already is one answer per item — just not here.

The delegate agent has no team_id (invisible to list_team, which only enumerates registered team members), no session_id (nothing is persisted for it), and no tools — a pure text-in/text-out summarization call. It is Process.monitor/1-ed, not Process.link/1-ed: a crashing delegate must degrade to :skip, not crash the compacting agent (worker or orchestrator). It is always stopped before this module returns, on every exit path.

compact/3 still blocks the compacting agent's own GenServer for the duration — that's deliberate, unchanged from before (see Planck.Agent.Hooks.Compactor's moduledoc on on_compacting/on_compacted). What must not happen inside that GenServer callback is a raw receive waiting on the delegate's PubSub events: the delegate is a real Planck.Agent broadcasting :turn_start/:text_delta/:usage_delta/etc, not just :turn_end, and any of those left unmatched by a receive done directly in the compacting agent's own process would sit in its mailbox, corrupting its own handle_info/2 dispatch once this call returns. So the actual subscribe/prompt/await sequence runs inside a dedicated, async_nolink Task instead — its mailbox and PubSub subscription are simply discarded when it exits, regardless of what's left in it, the same isolation Tools.call_agent's own await loop already relies on for this exact reason. compact/3 blocks on awaiting that task, not on a receive in its own process.