# `ExAthena.Compactor.Stage`
[🔗](https://github.com/udin-io/ex_athena/blob/v0.7.1/lib/ex_athena/compactor/stage.ex#L1)

Behaviour for a single compaction stage.

A stage is one cheap-to-expensive transformation in the
`ExAthena.Compactor.Pipeline`. The pipeline runs stages in order;
each stage may shrink the conversation a little (returning the new
estimate) or skip (returning `:skip`). Earlier stages run before
later ones, so cheap deterministic transformations get to reduce
the budget before the LLM-summary stage pays for inference.

## Contract

Each stage receives the current `Loop.State` and a token estimate. It
returns:

  * `{:ok, new_state, new_estimate}` — applied a reduction.
  * `:skip` — nothing to do this pass.
  * `{:error, reason}` — surfaces to the kernel as
    `:error_compaction_failed`. The pipeline aborts.

Stages should be **idempotent** when re-run on a state they already
processed (the reactive-recovery path may run the pipeline a second
time with `force: true` after a context-window error).

## Optional `force?/2` callback

The pipeline calls `force?(state, estimate)` to ask whether a stage
should run *unconditionally* on the recovery path (where the goal
is "shrink as much as possible"). The default implementation returns
`true`.

# `result`

```elixir
@type result() ::
  {:ok, ExAthena.Loop.State.t(), ExAthena.Compactor.estimate()}
  | :skip
  | {:error, term()}
```

# `compact_stage`

```elixir
@callback compact_stage(ExAthena.Loop.State.t(), ExAthena.Compactor.estimate()) ::
  result()
```

# `name`

```elixir
@callback name() :: atom()
```

Stage's display name (atom). Used in telemetry.

# `default_pipeline`

```elixir
@spec default_pipeline() :: [module()]
```

Default builtin pipeline ordering. Stages are placed cheapest-first
so the LLM-summary stage only fires when the deterministic stages
couldn't get the conversation under target.

---

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