PtcRunner.SubAgent.Compaction.Trim (PtcRunner v0.11.0)

Copy Markdown View Source

Deterministic pressure-triggered trimming strategy.

Keeps:

  1. The first user message (when keep_initial_user: true) — the first message with role :user in the input list, defined as the head, not a re-derivation from turns.
  2. The last keep_recent_turns × 2 messages.

Drops everything in between when triggered. Pure function — no LLM calls, no state mutations.

Triggers

  • trigger[:turns] — fires when ctx.turn > N.
  • trigger[:tokens] — fires when estimated total message tokens ≥ N.

Both may be set; either firing triggers compaction (OR, not AND). When not triggered, returns the input unchanged.

Edge cases

  • Fewer messages than keep_recent_turns × 2 + 1 → input unchanged, triggered: false.
  • First message not :user → skip initial-user retention; kept_initial_user?: false.
  • Recent slice begins with :assistant → drop one more from the front so it starts with :user.
  • Single retained message exceeds token budget → keep it whole, set over_budget?: true.

Token estimation is a pressure heuristic, not adapter-accurate.

Summary

Functions

Run the trim strategy.

Types

message()

@type message() :: %{role: :user | :assistant, content: String.t()}

stats()

@type stats() :: %{
  enabled: boolean(),
  triggered: boolean(),
  strategy: String.t(),
  reason: :turn_pressure | :token_pressure | nil,
  messages_before: non_neg_integer(),
  messages_after: non_neg_integer(),
  estimated_tokens_before: non_neg_integer(),
  estimated_tokens_after: non_neg_integer(),
  kept_initial_user?: boolean(),
  kept_recent_turns: non_neg_integer(),
  over_budget?: boolean()
}

Functions

run(messages, ctx, opts)

Run the trim strategy.

Always returns {messages, stats}. Use stats.triggered (boolean) to distinguish triggered from not-triggered runs — the stats map always has the same shape, so callers can rely on every field being present.