Context Compression#
gptme provides a pluggable context compression system that allows conversations to be compacted when they grow too large. This enables long-running sessions while keeping context windows manageable.
Overview#
The context compression system has two main components:
Automatic Compaction - Triggered when conversations exceed token limits or contain massive tool results
Plugin Interface - Allows third-party packages to provide custom compression strategies
Built-in Compression Strategy#
By default, gptme uses a 3-phase compression algorithm:
Reasoning Stripping - Remove reasoning tags from older messages (age-based)
Tool Result Truncation - Truncate largest tool results first
Extractive Compression - Summarize long assistant messages
This approach intelligently prioritizes the largest messages for removal to achieve target reduction with minimal information loss.
Using the Default Compressor#
When you enable the autocompact tool, automatic compression is triggered via a post-turn hook:
gptme --tool autocompact
You can also manually compact a conversation:
/compact # Rule-based trim (default)
/compact trim # Same as above
/compact summarize # LLM-powered summarization
Two strategies are available:
trim (default) — rule-based: strips old reasoning blocks, truncates massive tool results, and compresses long assistant messages. Fast and deterministic; no LLM call. If savings would be low, gptme will suggest
/compact summarizeinstead.summarize — LLM-powered: asks the model to produce a
RESUME.mdcapturing key decisions, open tasks, and relevant file paths, then starts a fresh context from that summary. More thorough but requires a model call and restarts context.
Deprecated since version ``/compact: auto`` and /compact resume are deprecated aliases for trim and
summarize respectively. They still work but emit a deprecation warning.
Custom Compression Providers (Plugin Interface)#
The plugin interface allows third-party packages to ship custom compression strategies as pip packages. This is useful for:
Domain-specific compaction strategies (e.g., code-aware, markdown-aware)
Experimental compression algorithms
Integration with external summarization services
Specialized handling for specific tool outputs
Implementing a Custom Provider#
Create a class that implements the ContextProvider abstract base class:
from gptme.tools.autocompact.context_provider import (
ContextProvider, CompressionConfig, CompactionResult,
)
from gptme.message import Message
class MyContextProvider(ContextProvider):
"""Custom context compression provider."""
@property
def name(self) -> str:
"""Return a unique identifier for this provider."""
return "my-compressor"
def should_compress(
self, messages: list[Message], config: CompressionConfig
) -> bool:
"""Decide whether compression should be applied."""
# Your logic here
return len(messages) > 50
def compress(
self, messages: list[Message], config: CompressionConfig
) -> CompactionResult:
"""Apply compression and return a CompactionResult."""
# Your compression logic here — return a CompactionResult, not a generator
source_digest = self._compute_digest(messages)
compacted = messages[-50:] # keep last 50 messages
return CompactionResult(
messages=compacted,
source_digest=source_digest,
)
Provider Interface#
ContextProvider ABC#
All custom providers must inherit from gptme.tools.autocompact.context_provider.ContextProvider and implement:
name property#
Returns a unique identifier for this provider (e.g., "my-compressor").
@property
def name(self) -> str:
return "my-compressor"
should_compress() method#
Decides whether compression should be applied based on message list and configuration.
def should_compress(
self, messages: list[Message], config: CompressionConfig
) -> bool:
"""
Args:
messages: List of messages in the conversation
config: Compression configuration (see CompressionConfig below)
Returns:
True if compression should be applied, False otherwise
"""
This allows intelligent decisions: some providers might always compress when close to the limit, others only when detecting redundancy exceeds a threshold.
compress() method#
Applies compression and returns a CompactionResult.
def compress(
self, messages: list[Message], config: CompressionConfig
) -> CompactionResult:
"""
Args:
messages: List of messages to compress
config: Compression configuration
Returns:
CompactionResult: Contains the compacted message list and
metadata (source_digest) for cache invalidation.
Note:
- Preserve message structure (role, timestamp, tool_calls, etc.)
- Do NOT return a generator — the caller accesses .messages directly
- May optionally set covered_through for partial-context tracking
"""
CompressionConfig Dataclass#
Configuration passed to compression methods:
@dataclass
class CompressionConfig:
limit: int | None = None
# Target token limit. None disables automatic should_compress()
# checks (DefaultContextProvider.should_compress returns False).
# Custom providers may handle None differently.
max_tool_result_tokens: int = 2000
# Maximum tokens allowed in a tool result before removal
reasoning_strip_age_threshold: int = 5
# Strip reasoning from messages older than N positions
logdir: Path | None = None
# Path to save removed outputs for recovery
extra_config: dict[str, Any] | None = None
# Provider-specific configuration
Registering Your Provider#
Entry Points (Recommended)#
Add your provider to your package’s pyproject.toml:
[project.entry-points."gptme.context_providers"]
my-compressor = "my_package.providers:MyContextProvider"
When gptme starts, it will automatically discover and load your provider via entry-points.
Programmatic Registration#
You can also register providers at runtime:
from gptme.tools.autocompact.context_provider import register_provider
from my_package.providers import MyContextProvider
register_provider("my-compressor", MyContextProvider)
Using a Custom Provider#
Once registered, use your provider by name:
from gptme.tools.autocompact.context_provider import get_context_provider
provider = get_context_provider("my-compressor")
config = CompressionConfig(limit=4000)
# Check if compression is needed
if provider.should_compress(messages, config):
result = provider.compress(messages, config)
compacted = result.messages # CompactionResult.messages is a list[Message]
Discovering Available Providers#
List all registered providers:
from gptme.tools.autocompact.context_provider import list_providers
providers = list_providers()
# Returns: ['default', 'my-compressor', ...]
Design Considerations#
Message Integrity#
Compression implementations must preserve:
Message Role: Keep assistant/user/system roles intact
Message Order: Maintain original chronological order
Timestamps: Don’t modify message timestamps
Tool References: Preserve tool_use_id and tool_result associations
This ensures the compacted conversation remains valid for continuation.
Recovery Strategy#
For significant compression, consider adding recovery references:
# When removing or summarizing messages, optionally record:
# - Byte ranges in the original conversation.jsonl
# - A `sourceDigest` hash for verification
# - Coverage metadata (how many events/turns were covered)
This allows recovery of the full context if needed later.
Performance#
The compress method must return a CompactionResult
(not a generator or a bare list). When building the result, process messages
lazily to avoid allocating unnecessary copies:
def compress(self, messages, config) -> CompactionResult:
# Build compacted list efficiently with a comprehension or generator expression
compacted = [self._maybe_trim(m) for m in messages]
return CompactionResult(
messages=compacted,
source_digest=self._compute_digest(messages),
)
Examples#
Code-Aware Compression#
Example provider that’s more aggressive with code comments:
from gptme.tools.autocompact.context_provider import (
ContextProvider, CompressionConfig, CompactionResult,
)
class CodeAwareProvider(ContextProvider):
@property
def name(self) -> str:
return "code-aware"
def should_compress(self, messages, config) -> bool:
# Only compress when close to limit
tokens = self.estimate_tokens(messages)
limit = config.limit or 4000
return tokens > int(0.9 * limit)
def compress(self, messages, config) -> CompactionResult:
compacted = [
self._compress_code_message(msg, config)
if msg.role == "assistant" and "```" in msg.content
else msg
for msg in messages
]
return CompactionResult(
messages=compacted,
source_digest=self._compute_digest(messages),
)
Statistical Compression#
Provider that uses redundancy detection:
class StatisticalProvider(ContextProvider):
@property
def name(self) -> str:
return "statistical"
def should_compress(self, messages, config) -> bool:
# Analyze redundancy before deciding
redundancy = self._compute_redundancy_score(messages)
return redundancy > 0.3 # 30% redundant content
def compress(self, messages, config) -> CompactionResult:
# Remove duplicate patterns, extract key information
compacted = list(self._extract_unique_content(messages))
return CompactionResult(
messages=compacted,
source_digest=self._compute_digest(messages),
)
API Reference#
- class gptme.tools.autocompact.context_provider.ContextProvider#
Abstract base class for context compression providers.
Implementations must provide a
nameproperty, ashould_compresspredicate, and acompressmethod returning aCompactionResult.- abstract compress(messages: list[Message], config: CompressionConfig) CompactionResult#
Compress messages and return a
CompactionResult.The result carries the projected message stream alongside coverage metadata (source digest, covered-through index, limitations).
- abstract should_compress(messages: list[Message], config: CompressionConfig) bool#
Return True if these messages should be compressed.
- class gptme.tools.autocompact.context_provider.DefaultContextProvider#
Default provider — delegates to
gptme.tools.autocompact.engine.auto_compact_log.- compress(messages: list[Message], config: CompressionConfig) CompactionResult#
Compress messages and return a
CompactionResult.The result carries the projected message stream alongside coverage metadata (source digest, covered-through index, limitations).
- should_compress(messages: list[Message], config: CompressionConfig) bool#
Return True if these messages should be compressed.
- class gptme.tools.autocompact.context_provider.CompactionResult#
Result of a context compression operation.
Carries the projected message stream alongside coverage metadata so callers can validate or invalidate a summary against the log it was derived from. Inspired by apache/maka’s
HistoryCompactCheckpoint(idea #1143).- __init__(messages: list[Message], source_digest: str, covered_through: int = -1, limitations: list[str] = <factory>) None#
- class gptme.tools.autocompact.context_provider.CompressionConfig#
Configuration for context compression.
- gptme.tools.autocompact.context_provider.get_context_provider(name: str = 'default') ContextProvider#
Return an instance of the named provider.
Raises
ValueErrorfor unknown names.