From 3242efe3a57ee29c49e160057d272902ea64027e Mon Sep 17 00:00:00 2001 From: chopratejas Date: Fri, 17 Apr 2026 13:18:08 -0700 Subject: [PATCH] perf: add compress_system_messages and min_tokens_to_compress config - compress_system_messages (default True): set False to skip system prompt compression entirely - min_tokens_to_compress (default 250): minimum word count for a message to be eligible for compression (was hardcoded at 50) Both flow through CompressConfig to ContentRouter. Default behavior unchanged. --- headroom/compress.py | 12 ++++++++++++ headroom/transforms/content_router.py | 12 +++++++++++- 2 files changed, 23 insertions(+), 1 deletion(-) diff --git a/headroom/compress.py b/headroom/compress.py index 7c977cdce..a64b11f52 100644 --- a/headroom/compress.py +++ b/headroom/compress.py @@ -101,6 +101,11 @@ class CompressConfig: Set True for document compression, RAG pipelines, or when user messages contain large tool outputs.""" + compress_system_messages: bool = True + """Compress system messages (default: True). + Set False to preserve system prompts exactly as-is. Useful for voice + agents where tool definitions and instructions must not be altered.""" + protect_recent: int = 4 """Don't compress the last N messages (they're the active conversation). Set 0 to compress everything.""" @@ -115,6 +120,11 @@ class CompressConfig: Only affects Kompress (text compression). SmartCrusher (JSON) has its own logic based on array dedup.""" + min_tokens_to_compress: int = 250 + """Minimum token count (word count) for a message to be compressed. + Messages shorter than this are left unchanged. Default 250. + Set lower for voice agents where turns are short.""" + # Model variant kompress_model: str | None = None """Kompress model ID. None = default (chopratejas/kompress-base). @@ -219,9 +229,11 @@ def compress( biases=biases, # Pass CompressConfig options through to transforms compress_user_messages=cfg.compress_user_messages, + compress_system_messages=cfg.compress_system_messages, target_ratio=cfg.target_ratio, protect_recent=cfg.protect_recent, protect_analysis_context=cfg.protect_analysis_context, + min_tokens_to_compress=cfg.min_tokens_to_compress, kompress_model=cfg.kompress_model, ) diff --git a/headroom/transforms/content_router.py b/headroom/transforms/content_router.py index a740a7f3c..010cd9366 100644 --- a/headroom/transforms/content_router.py +++ b/headroom/transforms/content_router.py @@ -1484,10 +1484,12 @@ class ContentRouter(Transform): skip_user = ( kwargs.get("compress_user_messages") is not True and self.config.skip_user_messages ) + skip_system = kwargs.get("compress_system_messages") is False protect_recent = kwargs.get("protect_recent", self.config.protect_recent_code) protect_analysis = kwargs.get( "protect_analysis_context", self.config.protect_analysis_context ) + min_tokens = kwargs.get("min_tokens_to_compress", 50) # Store runtime options on self for access by _route_and_compress_block self._runtime_target_ratio: float | None = kwargs.get("target_ratio") self._runtime_kompress_model: str | None = kwargs.get("kompress_model") @@ -1664,7 +1666,15 @@ class ContentRouter(Transform): route_counts["user_msg"] += 1 continue - if not content or len(content.split()) < 50: + # Protection 1b: Never compress system messages (when disabled) + if skip_system and role == "system": + result_slots[i] = message + transforms_applied.append("router:protected:system_message") + route_counts.setdefault("system_msg", 0) + route_counts["system_msg"] += 1 + continue + + if not content or len(content.split()) < min_tokens: # Skip small content result_slots[i] = message route_counts["small"] += 1