mirror of
https://github.com/headroomlabs-ai/headroom.git
synced 2026-08-27 14:17:10 -04:00
fix(ccr): skip compact summaries for proactive expansion (#2242)
## Description Fixes #2186. Claude Code `/compact` continuation summaries are already session context. When Headroom tracks those summaries for CCR proactive expansion, later fresh sessions can receive stale compacted history again inside `<headroom_proactive_expansion>` blocks, increasing token usage and busting cache stability. This PR keeps CCR storage/retrieval intact but excludes probable Claude Code compact-summary payloads from the proactive-expansion tracker. ## Type of Change - [x] Bug fix (non-breaking change that fixes an issue) - [ ] New feature (non-breaking change that adds functionality) - [ ] Breaking change (fix or feature that would cause existing functionality to change) - [ ] Documentation update - [ ] Performance improvement - [ ] Code refactoring (no functional changes) ## Changes Made - Added a narrow Claude Code compact-summary detector to the CCR context tracker. - Skipped tracking compact summaries when feeding Anthropic CCR metadata into proactive expansion. - Added an original-content preview to CCR metadata so the Anthropic feed point can classify compact summaries even when compressed text loses the distinctive header. - Added regression coverage proving compact summaries are not tracked and ordinary summaries are still eligible. ## Testing - [x] Unit tests pass - [x] Linting passes - [x] Formatting check passes - [x] New tests added for new functionality - [ ] Manual testing performed ### Test Output ```text $ pytest tests/test_ccr_context_tracker.py -q 37 passed $ uvx ruff==0.15.17 check headroom/ccr/context_tracker.py headroom/cache/compression_store.py headroom/proxy/handlers/anthropic.py tests/test_ccr_context_tracker.py --output-format concise All checks passed! $ uvx ruff==0.15.17 format --check headroom/ccr/context_tracker.py headroom/cache/compression_store.py headroom/proxy/handlers/anthropic.py tests/test_ccr_context_tracker.py 4 files already formatted ``` ## Real Behavior Proof - Environment: local checkout on macOS, Python test environment used by the repository. - Exact command / steps: ran the focused CCR context tracker suite after adding compact-summary detection and tracker-feed filtering. - Observed result: compact-summary payloads are not tracked for proactive expansion, ordinary summary-like tool output is still eligible, and the existing tracker behavior remains covered by the full focused suite. - Not tested: live Claude Code `/compact` session through a running proxy. ## Review Readiness - [x] I have performed a self-review - [x] This PR is ready for human review - [x] I have added tests that prove the fix is effective - [ ] I have updated the CHANGELOG.md if applicable
This commit is contained in:
parent
fcf455a7eb
commit
3f241e472b
4 changed files with 83 additions and 0 deletions
1
headroom/cache/compression_store.py
vendored
1
headroom/cache/compression_store.py
vendored
|
|
@ -480,6 +480,7 @@ class CompressionStore:
|
|||
"compressed_item_count": entry.compressed_item_count,
|
||||
"query_context": entry.query_context,
|
||||
"compressed_content": entry.compressed_content,
|
||||
"original_content_preview": entry.original_content[:2000],
|
||||
"created_at": entry.created_at,
|
||||
"ttl": entry.ttl,
|
||||
}
|
||||
|
|
|
|||
|
|
@ -31,6 +31,38 @@ from ..cache.compression_store import get_compression_store
|
|||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def looks_like_claude_code_compact_summary(*texts: str | None) -> bool:
|
||||
"""Return true for Claude Code `/compact` continuation summaries.
|
||||
|
||||
Claude Code can carry a previous session forward by injecting a compact
|
||||
conversation summary into a fresh session. Those summaries are already
|
||||
context; tracking them for CCR proactive expansion makes Headroom re-add
|
||||
stale session state to later turns. Keep this detector deliberately narrow
|
||||
so ordinary tool output that happens to mention "summary" remains eligible.
|
||||
"""
|
||||
combined = " ".join(text.strip() for text in texts if text and text.strip())
|
||||
if not combined:
|
||||
return False
|
||||
|
||||
normalized = " ".join(combined.lower().split())
|
||||
has_summary = "summary" in normalized or "summarized" in normalized
|
||||
|
||||
if "this session is being continued from a previous conversation" in normalized and has_summary:
|
||||
return True
|
||||
|
||||
if "conversation is summarized below" in normalized and (
|
||||
"ran out of context" in normalized or "previous conversation" in normalized
|
||||
):
|
||||
return True
|
||||
|
||||
return (
|
||||
"/compact" in normalized
|
||||
and "claude" in normalized
|
||||
and has_summary
|
||||
and ("conversation" in normalized or "session" in normalized)
|
||||
)
|
||||
|
||||
|
||||
@dataclass
|
||||
class CompressedContext:
|
||||
"""Represents a piece of compressed context from the conversation.
|
||||
|
|
@ -157,6 +189,13 @@ class ContextTracker:
|
|||
if not self.config.enabled:
|
||||
return
|
||||
|
||||
if looks_like_claude_code_compact_summary(query_context, sample_content):
|
||||
logger.debug(
|
||||
"CCR Tracker: skipped Claude Code compact summary %s for proactive expansion",
|
||||
hash_key,
|
||||
)
|
||||
return
|
||||
|
||||
context = CompressedContext(
|
||||
hash_key=hash_key,
|
||||
turn_number=turn_number,
|
||||
|
|
|
|||
|
|
@ -24,6 +24,7 @@ if TYPE_CHECKING:
|
|||
import httpx
|
||||
|
||||
from headroom.agent_savings import proxy_pipeline_kwargs
|
||||
from headroom.ccr.context_tracker import looks_like_claude_code_compact_summary
|
||||
from headroom.copilot_auth import build_copilot_upstream_url
|
||||
from headroom.pipeline import PipelineStage, summarize_routing_markers
|
||||
from headroom.proxy.auth_mode import classify_auth_mode, classify_client
|
||||
|
|
@ -1934,6 +1935,16 @@ class AnthropicHandlerMixin:
|
|||
store = get_compression_store()
|
||||
entry = store.get_metadata(hash_key)
|
||||
if entry:
|
||||
if looks_like_claude_code_compact_summary(
|
||||
entry.get("query_context"),
|
||||
entry.get("compressed_content"),
|
||||
entry.get("original_content_preview"),
|
||||
):
|
||||
logger.info(
|
||||
f"[{request_id}] CCR: skipping proactive "
|
||||
f"tracking for Claude Code compact summary {hash_key}"
|
||||
)
|
||||
continue
|
||||
self.ccr_context_tracker.track_compression(
|
||||
hash_key=hash_key,
|
||||
turn_number=self._turn_counter,
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@ from headroom.ccr.context_tracker import (
|
|||
ContextTrackerConfig,
|
||||
ExpansionRecommendation,
|
||||
get_context_tracker,
|
||||
looks_like_claude_code_compact_summary,
|
||||
reset_context_tracker,
|
||||
)
|
||||
|
||||
|
|
@ -58,6 +59,37 @@ class TestContextTrackerBasics:
|
|||
stats = tracker.get_stats()
|
||||
assert stats["tracked_contexts"] == 1
|
||||
|
||||
def test_claude_code_compact_summary_not_tracked(self):
|
||||
"""Claude Code `/compact` summaries are not proactively re-expanded."""
|
||||
tracker = ContextTracker()
|
||||
|
||||
tracker.track_compression(
|
||||
hash_key="compact_summary",
|
||||
turn_number=1,
|
||||
tool_name="Claude Code",
|
||||
original_count=1,
|
||||
compressed_count=1,
|
||||
query_context="Claude Code /compact summary",
|
||||
sample_content=(
|
||||
"This session is being continued from a previous conversation "
|
||||
"that ran out of context. The conversation is summarized below: "
|
||||
"We changed the auth middleware and still need to run tests."
|
||||
),
|
||||
workspace_key="ws-test",
|
||||
)
|
||||
|
||||
assert tracker.get_tracked_hashes() == []
|
||||
|
||||
def test_claude_code_compact_summary_detector_is_narrow(self):
|
||||
"""The compact-summary detector does not reject ordinary summaries."""
|
||||
assert looks_like_claude_code_compact_summary(
|
||||
"This session is being continued from a previous conversation. "
|
||||
"The conversation is summarized below."
|
||||
)
|
||||
assert not looks_like_claude_code_compact_summary(
|
||||
"search results summary for auth middleware files"
|
||||
)
|
||||
|
||||
def test_track_multiple_compressions(self):
|
||||
"""Track multiple compression events."""
|
||||
tracker = ContextTracker()
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue