Token-aware message history compaction for agent sessions.
When an agent's conversation history exceeds a token budget, older messages are summarized into a single system message while preserving recent messages verbatim.
Usage
config = %{max_tokens: 8_000, preserve_recent: 4}
case ContextCompactor.compact(messages, config) do
%{compacted: true, messages: compacted_messages} ->
# Use compacted_messages for next LLM call
%{compacted: false} ->
# History is within budget, no changes needed
endToken estimation uses a simple byte_size/4 heuristic (no tokenizer dependency). Summaries are generated purely from message metadata -- no LLM call required.
Summary
Functions
Build a structured summary from a list of messages.
Compact messages if over token budget.
Estimate token count for a string or message list.
Format a summary struct into a human-readable continuation message.
Check if compaction is needed for the given messages.
Types
@type compaction_result() :: %{ messages: [message()], compacted: boolean(), original_tokens: non_neg_integer(), compacted_tokens: non_neg_integer(), summary: String.t() | nil }
@type config() :: %{ max_tokens: pos_integer(), preserve_recent: pos_integer(), summary_max_tokens: pos_integer() }
@type summary() :: %{ message_count: non_neg_integer(), role_counts: %{required(atom()) => non_neg_integer()}, recent_user_requests: [String.t()], key_topics: [String.t()], file_paths: [String.t()], pending_work: [String.t()] }
Functions
Build a structured summary from a list of messages.
Extracts metadata (role counts, file paths, recent requests, pending work) without requiring an LLM call.
@spec compact([message()], config()) :: compaction_result()
Compact messages if over token budget.
Splits system messages from conversation, preserves the last N non-system messages verbatim, and summarizes the rest into a continuation system message.
Returns a compaction_result map. If no compaction was needed, compacted
is false and messages are returned unchanged.
@spec estimate_tokens(String.t()) :: non_neg_integer()
@spec estimate_tokens([message()]) :: non_neg_integer()
Estimate token count for a string or message list.
Uses a byte_size/4 heuristic. Not exact, but sufficient for budget decisions.
Format a summary struct into a human-readable continuation message.
Check if compaction is needed for the given messages.