Core Compaction
Purpose
Manages context window limits by pruning old tool outputs from conversation history while preserving recent context. Uses a backward-scanning algorithm to identify and replace old tool outputs with placeholders.
When Pruning Occurs
Pruning is triggered proactively at the start of each agent request, before the agent iteration loop begins:
Location: core/agents/main.py:367-372
# In RequestOrchestrator._run_impl()
session_messages = self.state_manager.session.conversation.messages
_, tokens_reclaimed = prune_old_tool_outputs(session_messages, self.model)
message_history = list(session_messages)
This ensures context is compacted before sending to the provider, not reactively after overflow.
Pruning Algorithm
The prune_old_tool_outputs() function at lines 160-238 implements a 4-phase algorithm:
Phase 1: Backward Scanning (lines 187-204)
Iterates through messages newest to oldest:
- Identifies tool return parts using
is_tool_return_part() - Estimates token count for each using
estimate_part_tokens() - Builds list of
(message_index, part_index, part, token_count)tuples
Phase 2: Protection Boundary (lines 209-221)
Determines which outputs to protect:
- Accumulates tokens from newest to oldest
- When accumulated tokens exceed
protect_tokensthreshold, marks pruning boundary - All outputs within protection window remain untouched
Phase 3: Threshold Check (lines 223-229)
Validates pruning is worthwhile:
- Calculates total tokens reclaimable from old outputs
- Only proceeds if savings exceed
minimum_threshold - Prevents unnecessary work for minimal gains
Phase 4: Content Replacement (lines 231-236)
Performs in-place mutation:
- Replaces old tool output content with placeholder
- Uses
prune_part_content()for safe mutation - Returns total tokens reclaimed
Key Functions
prune_old_tool_outputs(messages, model)
Main entry point. Returns (pruned_count, tokens_reclaimed).
get_prune_thresholds()
Returns (protect_tokens, minimum_threshold).
is_tool_return_part(part)
Returns True if part has part_kind == "tool-return".
count_user_turns(messages)
Counts user messages. Pruning requires >= 2 user turns (PRUNE_MIN_USER_TURNS).
estimate_part_tokens(part)
Estimates tokens in a message part using 4-char heuristic.
prune_part_content(part)
Replaces part content with placeholder. Returns tokens reclaimed or 0 if immutable.
Safety Guards
| Guard | Location | Purpose |
|---|---|---|
| Min user turns | line 183-185 | Requires 2+ turns before pruning |
| Immutability handling | line 151-156 | Gracefully handles frozen parts |
| Already-pruned check | line 138-139 | Skips parts with placeholder |
| Empty messages | line 179-180 | Early return for empty list |
Pruning Thresholds
| Mode | protect_tokens | minimum_threshold | Behavior |
|---|---|---|---|
| Standard | 40,000 | 20,000 | Keep ~40k recent tokens |
Thresholds are defined at lines 14-18 and are not user-configurable.
Placeholder
Pruned content is replaced with:
[Old tool result content cleared]
Defined as PRUNE_PLACEHOLDER at line 21.
What Gets Pruned
Targets:
- Tool return parts (
part_kind == "tool-return") - Content older than protection window
Never Pruned:
- User messages
- Recent tool outputs (within protection window)
- System prompts
- Tool call requests (only returns are pruned)
Integration Points
| Component | File | Integration |
|---|---|---|
| Trigger point | core/agents/main.py |
Called at request start |
| Token counting | utils/messaging |
estimate_tokens() |
| Message types | pydantic_ai |
ToolReturnPart, ModelRequest |
| State | core/state.py |
session.conversation.messages storage |
Constants
PRUNE_PROTECT_TOKENS = 40_000
PRUNE_MINIMUM_THRESHOLD = 20_000
PRUNE_MIN_USER_TURNS = 2
PRUNE_PLACEHOLDER = "[Old tool result content cleared]"
Seams (M)
Modification Points:
- Adjust thresholds in constants (lines 14-18)
- Customize placeholder format (line 21)
- Add selective preservation rules in phase 2
- Implement content-aware pruning (e.g., preserve error messages)