# `SuperPi.Compaction`
[🔗](https://github.com/oleksandr/super-pi-beam/blob/v0.1.0/lib/super_pi/compaction.ex#L1)

4-Mode Context Compaction Engine (`Decision 08`, `REQ-PBT-06`).

Supports all 4 compaction modes:
1. `:manual` — User or RPC explicitly triggers `/compact` with optional custom instructions.
2. `:threshold` — Synchronous turn-boundary compaction when `current_tokens + reserve_tokens >= context_window`
   (`reserve_tokens: 16_384`, `keep_recent_tokens: 20_000`).
3. `:background` — Asynchronous Oban worker (`SuperPi.Workers.CompactionWorker`) triggered when
   `current_tokens >= background_tokens`; summarizes older turns concurrently and appends a
   non-destructive `pi.compaction` entry (`head_entry_id`) without blocking the active stream.
4. `:overflow` — Auto-recovery compaction when an LLM provider returns a context-window overflow error;
   compacts with a tighter `keep_recent_tokens` budget and signals immediate retry.

## Cut-Point Tool-Pair Invariant (`REQ-PBT-06`)
`find_safe_cut_point/2` walks backwards from the end of `visible_entries` until `keep_recent_tokens`
is accumulated, and then advances/adjusts the cut index so that **no `tool_call` assistant entry is ever
separated from its corresponding `role: "tool"` result entries**. Every `tool` result in the kept tail
is guaranteed to have its originating `assistant` `tool_call` also in the kept tail.

# `check_policy`

Evaluates whether `:threshold` or `:background` compaction should run based on current token usage.
Returns `:none`, `{:threshold, meta}`, or `{:background, meta}`.

# `compact!`

```elixir
@spec compact!(integer(), atom(), keyword()) ::
  {:ok, :noop} | {:ok, map()} | {:enqueued, Oban.Job.t()}
```

Executes compaction in one of the 4 modes (`:manual`, `:threshold`, `:background`, `:overflow`).
- For `:background` with `enqueue: true`, schedules an asynchronous `Oban` job on the `:compaction` queue.
- Otherwise computes the safe cut point (`find_safe_cut_point/2`), summarizes the prefix, and appends a
  durable `pi.compaction` entry with `head_entry_id: first_kept_id`.

# `estimate_entry_tokens`

Estimates token count for an entry (`~4 chars per token` heuristic).

# `estimate_total_tokens`

# `find_safe_cut_point`

Computes `{compacted_prefix, kept_tail, head_entry_id}` for `visible_entries` given `keep_recent_tokens`.

Guarantees the **Tool-Pair Cut-Point Invariant (`REQ-PBT-06`)**:
- `kept_tail` never starts on a `role == "tool"` message entry.
- Every `role == "tool"` entry in `kept_tail` has its preceding `role == "assistant"` tool-call entry
  included in `kept_tail` as well.

---

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