Compaction System

The compaction system manages context window pressure by summarizing, truncating, or dropping older conversation turns when the token count approaches the model's limit. Two strategies coexist: a legacy in-memory approach that rewrites the message array, and a modern block-based approach that creates non-destructive overlays on LoopRecords.

Concept Overview

Compaction [EXISTS]
├── CompactionBlock [EXISTS] — non-destructive overlay
│   ├── keep_first, keep_compacted, keep_recent [EXISTS]
├── CompactionScope [EXISTS] — FixedCount(n) / TokenBudget
├── CompactionStrategy [EXISTS] — legacy in-memory
├── BlockCompactionStrategy [EXISTS] — modern overlay
├── TurnMap [EXISTS] — turn indices → message ranges
├── Callbacks: before/after compaction [EXISTS]
└── Config: consolidated in CompactionConfig [EXISTS]

CompactionBlock [EXISTS]

Non-destructive compaction overlay stored on LoopRecord alongside the original messages. When present, the context loader uses this block instead of raw messages. Three sections control what gets loaded into context.

FieldTypeStatusDescription
keep_firstOption<TurnRange>[EXISTS]Turns kept verbatim from the start; only populated for the MOST RECENT loop
keep_compactedOption<CompactedSection>[EXISTS]Fully summarised section; populated for ALL loops
keep_recentOption<CompactedSection>[EXISTS]Recent turns with truncated tool outputs; only populated for the MOST RECENT loop
created_atDateTime<Utc>[EXISTS]When this block was created

Loading logic:

  • Most recent loop: loads keep_first (original messages) + keep_compacted (summaries) + keep_recent (truncated)
  • Older loops: loads only keep_compacted (full-loop summary)
  • No compaction block: loads raw messages

Supporting Types

TypeStatusDescription
TurnRange { start_turn, end_turn }[EXISTS]Inclusive range of turn indices within a loop
CompactedSection { range, messages }[EXISTS]A turn range plus the replacement messages for that range

CompactionScope [EXISTS]

Controls how many earlier loops are included in compaction and context loading.

VariantStatusDescription
FixedCount(usize)[EXISTS]Compact a fixed number of earlier loops on the active chain (default: 3)
TokenBudget[EXISTS]Walk backward, accumulating per-loop token estimates, stop when max_context_tokens would be exceeded

TokenBudget note: The scope can include loops whose raw messages EXCEED max_context_tokens. This is intentional -- the compacted summaries will fit even when originals don't, enabling richer context for LLM-based summarisation strategies.


CompactionStrategy (Legacy) [EXISTS]

In-memory compaction that rewrites the message array. Used when AgentContext.session is None.

MethodStatusDescription
compact(messages, config) -> Vec<AgentMessage>[EXISTS]Takes ownership of messages and returns a compacted version

DefaultCompaction [EXISTS]

The built-in implementation. Delegates to compact_messages() which applies 3-level reduction:

  1. Truncate tool outputs
  2. Summarize turns
  3. Drop middle

BlockCompactionStrategy (Modern) [EXISTS]

Creates non-destructive CompactionBlock overlays. Used when AgentContext.session is Some.

MethodStatusDescription
keep_first(record, turn_map, config) -> Option<TurnRange>[EXISTS]Determine turns kept verbatim from start (most recent loop only)
keep_recent(record, turn_map, config) -> Option<CompactedSection>[EXISTS]Create recent section with truncated tool outputs (most recent loop only)
keep_compacted(record, turn_map, config, is_most_recent) -> Option<CompactedSection>[EXISTS]Create summarised section; for most recent: middle only; for older: entire loop
compact(record, config, is_most_recent) -> CompactionBlock[EXISTS]Default: assembles from the three methods above

DefaultBlockCompaction [EXISTS]

Stateless implementation. All parameters come from CompactionConfig.

SectionBehavior
keep_firstReturns turn range 0..keep_first_turns
keep_recentTruncates tool outputs to tool_output_max_lines
keep_compactedPer-turn one-liner summaries bounded by max_summary_tokens; drops remaining turns when budget exhausted

Limitation: DefaultBlockCompaction.keep_compacted is basic -- it drops turns that exceed the token budget rather than producing a holistic summary. More sophisticated strategies (e.g. LLM-based) should summarise ALL turns within the budget.


TurnMap [EXISTS]

Maps turn indices to message index ranges within a message array. Built from messages by grouping on TurnId.turn_index.

MethodStatusDescription
from_messages(messages) -> TurnMap[EXISTS]Build from messages; messages without turn_id are their own group
turn_count() -> u32[EXISTS]Number of turn groups
messages_for_range(range, all_msgs) -> &[AgentMessage][EXISTS]Slice of messages belonging to a TurnRange
turn_msg_range(turn_index) -> Option<(usize, usize)>[EXISTS]Message index range for a single turn

Orchestration [EXISTS]

Cross-loop compaction coordination. The orchestrator resolves scope, then creates CompactionBlocks for the current loop and earlier loops within scope.

FunctionStatusDescription
compact_session_loops(session, current_loop_id, strategy, config, max_context_tokens)[EXISTS]Creates blocks: current loop gets all three sections; earlier loops get only keep_compacted
build_context_from_session(session, current_loop_id, config, max_context_tokens)[EXISTS]Walks the loop chain, loads from CompactionBlocks where available, raw messages otherwise
resolve_scope(session, chain, scope, max_context_tokens)[EXISTS]Resolves CompactionScope to a concrete count of earlier loops

CompactionConfig [EXISTS]

Full compaction policy -- controls both WHEN and HOW to compact.

WHEN to compact

FieldTypeDefaultStatusDescription
compact_at_pctf640.90[EXISTS]Fraction of max_context_tokens at which headroom is measured
compact_budget_threshold_pctf640.05[EXISTS]Minimum headroom fraction before compaction fires
compaction_scopeCompactionScopeFixedCount(3)[EXISTS]How many earlier loops to include

HOW to compact

FieldTypeDefaultStatusDescription
keep_first_turnsusize2[EXISTS]Turns kept verbatim from start (most recent loop)
keep_recent_turnsusize10[EXISTS]Turns kept from end (extended to turn boundary)
max_summary_tokensusize2_000[EXISTS]Token budget for summarised middle section
tool_output_max_linesusize50[EXISTS]Max lines per tool output in keep_recent section

Code Reference

ConceptFile
CompactionBlock, TurnRange, CompactedSection, TurnMapsrc/context/compaction.rs
CompactionStrategy, DefaultCompaction, BlockCompactionStrategy, DefaultBlockCompactionsrc/context/strategy.rs
CompactionConfig, CompactionScope, ContextConfigsrc/context/config.rs
compact_session_loops(), build_context_from_session(), resolve_scope()src/context/orchestration.rs
compact_messages() (legacy in-memory)src/context/compact_messages.rs
ContextTracker (token tracking)src/context/tracker.rs
in_memory_strategy and block_strategy fieldssrc/context/config.rs (on CompactionConfig)

Conceptual Notes

  • before_compaction_start / after_compaction_end callbacks [EXISTS] -- Lifecycle hooks now fire around compaction. before_compaction_start fires before compaction begins (for pre-compaction indexing/memory extraction) and after_compaction_end fires after compaction completes (for post-compaction verification). Both are blank-by-default callbacks.
  • Config consolidation [EXISTS] -- Compaction strategies (in_memory_strategy and block_strategy) are now fields on CompactionConfig, consolidating what was previously split across ContextConfig.compaction and AgentLoopConfig. The strategies no longer live on AgentLoopConfig; all compaction policy and strategy configuration is in one place.
  • LLM-based Summarisation -- DefaultBlockCompaction.keep_compacted is a basic per-turn one-liner generator. The BlockCompactionStrategy trait is designed for more sophisticated strategies that call an LLM to produce holistic digests of all turns within the max_summary_tokens budget.
  • Compaction Events [EXISTS] -- CompactionStarted and CompactionEnded events bracket compaction execution, providing estimated token counts before/after. These are consumed by SessionRecorder for observability.
  • Legacy vs Modern -- Two systems coexist: CompactionStrategy (legacy, in-memory, rewrites messages) is used when AgentContext.session is None; BlockCompactionStrategy (modern, non-destructive overlays) is used when session data is available. The legacy path is preserved for backward compatibility and simple stateless use cases.