Skip to main content

Compactor

Struct Compactor 

Source
pub struct Compactor {
    pub threshold_chars: usize,
    pub threshold_prompt_tokens: Option<u32>,
    pub keep_recent_n: usize,
}
Expand description

Configuration for LLM-driven transcript compaction.

Fields§

§threshold_chars: usize

Character-count threshold above which compaction is triggered. Defaults to usize::MAX (disabled).

This estimate is reliable for English content (~4 chars/token) but underestimates token density in CJK languages. When actual prompt_tokens from the API are available, threshold_prompt_tokens is checked first and takes priority.

§threshold_prompt_tokens: Option<u32>

Token-count threshold: if the last turn’s prompt_tokens from the API response meets or exceeds this value, compaction is triggered.

When Some and the last prompt_tokens is non-zero, this check takes priority over threshold_chars. This eliminates the 4-char/token assumption that fails for CJK content. Set to None to rely solely on the char-based heuristic.

§keep_recent_n: usize

Number of most-recent messages to keep verbatim during compaction.

Implementations§

Source§

impl Compactor

Source

pub fn new(threshold_chars: usize) -> Self

Create a new compactor with the given threshold and default keep_recent_n (8).

Source

pub fn threshold_prompt_tokens(self, n: u32) -> Self

Set the token-count threshold for compaction.

When the last turn’s prompt_tokens (from the API response) meets or exceeds this value, compaction is triggered instead of relying on the character-count estimate. Prefer this for non-English workloads.

Source

pub fn keep_recent_n(self, n: usize) -> Self

Set the number of recent messages to preserve verbatim.

Source

pub fn estimate_bytes(transcript: &[Message]) -> usize

Estimate the prompt byte count of a transcript.

This is a rough proxy for token count (≈4 bytes/token for English; over-counts CJK ~3× because CJK chars are 3 UTF-8 bytes each). The agent uses this to decide whether compaction is needed before the next LLM call. It is intentionally byte-based and kept in bytes — the char/byte threshold calibration depends on it. Includes tool_calls arguments and reasoning_content which were previously omitted, causing the threshold to be systematically underestimated.

Source

pub fn should_compact( &self, estimate_bytes: usize, last_prompt_tokens: u32, ) -> bool

Decide whether compaction should run, preferring the token-based threshold (accurate, CJK-safe) when actual API prompt_tokens are available, falling back to the byte estimate.

last_prompt_tokens of 0 means “no API reading yet” (first step, or a provider that never reports usage) — the byte estimate is used.

Source

pub fn safe_split_point(transcript: &[Message], keep_n: usize) -> usize

Compute the safe split point for compaction: the index at which to divide “older messages to summarize” from “recent messages to keep”.

The retained (“kept”) segment must start with a User (or System) message, because OpenAI/Anthropic require that the first non-System message after the compaction summary is a User message. Two cases can violate this invariant:

  1. The split point lands on a Tool result message — backing up preserves tool-call / tool-result pairs.
  2. The split point lands on an Assistant message that carries tool_calls — backing up avoids starting the kept segment with an Assistant-with-tool-calls, which would produce an invalid [System(summary), Assistant(tool_calls), Tool, …] sequence.
Source

pub fn would_compact(&self, transcript: &[Message]) -> bool

Returns true iff [apply_to_transcript] would actually compact this transcript (return Ok(Some(..))): long enough AND the older slice has enough real conversation. Pure — no provider call, no mutation.

Proactive-compaction callers should consult this before dispatching PreCompact so that PreCompact and PostCompact stay balanced (both fire when compaction runs, neither fires when it is rejected). Without this guard the Goal 345 degenerate-slice rejection made PreCompact fire without a matching PostCompact. (Goal 345)

Source

pub async fn apply_to_transcript( &self, provider: &dyn ChatProvider, transcript: &mut Vec<Message>, step: usize, ) -> Result<Option<(usize, usize)>>

Source

pub async fn compact( &self, provider: &dyn ChatProvider, transcript: &[Message], step: usize, ) -> Result<Message>

Compact the transcript: summarize older messages into a single system message, keeping the last keep_recent_n messages verbatim.

step is the current turn number and is embedded in the compaction header for debuggability.

Returns the summary Message that should replace the older portion. The caller is responsible for splicing it into the transcript.

If the summarisation call returns a context-window-exceeded error, the oldest message groups are dropped (via truncate_head_for_retry) and the call is retried up to MAX_PTL_RETRIES times.

Trait Implementations§

Source§

impl Clone for Compactor

Source§

fn clone(&self) -> Self

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for Compactor

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for Compactor

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> DynClone for T
where T: Clone,

Source§

fn __clone_box(&self, _: Private) -> *mut ()

Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T> FromRef<T> for T
where T: Clone,

Source§

fn from_ref(input: &T) -> T

Converts to this type from a reference to the input type.
Source§

impl<A, B, T> HttpServerConnExec<A, B> for T
where B: Body,

Source§

impl<T> Instrument for T

Source§

fn instrument(self, span: Span) -> Instrumented<Self> ⓘ

Instruments this type with the provided Span, returning an Instrumented wrapper. Read more
Source§

fn in_current_span(self) -> Instrumented<Self> ⓘ

Instruments this type with the current Span, returning an Instrumented wrapper. Read more
Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> IntoMaybeUndefined<T> for T

Source§

fn into_maybe_undefined(self) -> MaybeUndefined<T>

Converts this value into a three-state builder argument.
Source§

impl<T> IntoOption<T> for T

Source§

fn into_option(self) -> Option<T>

Converts this value into an optional builder argument.
Source§

impl<T> PolicyExt for T
where T: ?Sized,

Source§

fn and<P, B, E>(self, other: P) -> And<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow only if self and other return Action::Follow. Read more
Source§

fn or<P, B, E>(self, other: P) -> Or<T, P>
where T: Sized + Policy<B, E>, P: Policy<B, E>,

Create a new Policy that returns Action::Follow if either self or other returns Action::Follow. Read more
Source§

impl<T> Same for T

Source§

type Output = T

Should always be Self
Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = !

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, !>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
Source§

impl<T> WithSubscriber for T

Source§

fn with_subscriber<S>(self, subscriber: S) -> WithDispatch<Self> ⓘ
where S: Into<Dispatch>,

Attaches the provided Subscriber to this type, returning a WithDispatch wrapper. Read more
Source§

fn with_current_subscriber(self) -> WithDispatch<Self> ⓘ

Attaches the current default Subscriber to this type, returning a WithDispatch wrapper. Read more