Auto-compression blocked by hardcoded 64K context floor
Cluster 14690 · agent. Data as of
3 open items. Fix merged in #49998. Check it covers your case.
The short answer
- Known?
- Yes. 18 related items grouped here, 2 issues already closed.
- Fix event
- A fix was merged to main in PR #49998. Check it covers your case. No release data is tracked.
- Tracker
- The maintainers' tracker marks this
partially resolved, deciding PR #52053. That is separate from the fix event. - Still open
- 1 issue and 2 PRs.
Next step: read #49998, then check whether your report is one of the 1 still open.
Read #49998Pick up a Lab taskA cluster of bug reports and fixes addressing cases where auto-compression never triggers because the minimum context length constant (64,000) equals or exceeds the actual model context window, particularly affecting local/Ollama models with smaller real contexts. Fixes involve making the floor configurable, clamping thresholds correctly, and honoring model-specific context overrides.
Open issues1
- Issue#60103[Bug]: Compression unreachable on local models whose real context is silently clamped by Ollama — dead zone causes permanent context-shift and multi-minute latency
Open PRs2
Fixes on record
-
#49998 fix(compression): auto-compression triggers at minimum context length (#14690)
files:
agent/context_compressor.py,tests/agent/test_context_compressor.pyShow the diff
-
agent/context_compressor.py@@ -656,9 +656,8 @@ def update_model( self.provider = provider self.api_mode = api_mode self.context_length = context_length - self.threshold_tokens = max( - int(context_length * self.threshold_percent), - MINIMUM_CONTEXT_LENGTH, + self.threshold_tokens = [redacted:secret-assignment] + context_length, self.threshold_percent ) # Recalculate token budgets for the new context length so the # compressor stays calibrated after a model switch (e.g. 200K → 32K). @@ -690,6 +689,40 @@ def update_model( self.awaiting_real_usage_after_compression = False self._ineffective_compression_count = 0 + # When the MINIMUM_CONTEXT_LENGTH floor meets/exceeds a small context + # window, compacting at the percentage (50% → 32K of a 64K window) wastes + # half the usable context. Trigger near the top of the window instead so a + # minimum-context model uses most of its budget before compacting — same + # rationale as the gpt-5.5/Codex 85% autoraise. + _MIN_CTX_TRIGGER_RATIO = 0.85 + + @staticmethod + def _compute_threshold_tokens(context_length: int, threshold_percent: float) -> int: + """Compute the compaction trigger threshold in tokens. + + The base value is ``context_length * threshold_percent``, floored at + ``MINIMUM_CONTEXT_LENGTH`` so large-context models don't compress + prematurely at 50%. BUT that floor degenerates at small windows: for a + model whose ``context_length`` is at/below the minimum (e.g. a 64K + local model), ``max(0.5*64000, 64000) == 64000`` makes the threshold + equal the ENTIRE window — auto-compression can never fire because the + provider rejects the request before usage reaches 100% (#14690). + + When the floor would meet or exceed the context window, trigger at + ``_MIN_CTX_TRIGGER_RATIO`` (85%) of the window — high enough that a + small model uses most of its context before compacting, but below + 100% so compaction fires before the provider rejects the request. + """ + pct_value = int(context_length * threshold_percent) + floored = max(pct_value, MINIMUM_CONTEXT_LENGTH) + # If flooring pushed the threshold to/over the window it can never be + # reached. Trigger at 85% of the window so a minimum-context model + # rides most of its budget before compacting instead of wasting half. + if context_length > 0 and floored >= context_length: + return max(1, min(int(context_length * ContextCompressor._MIN_CTX_TRIGGER_RATIO), + context_length - 1)) + return floored + def __init__( self, model: str, @@ -730,10 +763,11 @@ def __init__( # Floor: never compress below MINIMUM_CONTEXT_LENGTH tokens even if # the percentage would suggest a lower value. This prevents premature # compression on large-context models at 50% while keeping the % sane - # for models right at the minimum. - self.threshold_tokens = max( - int(self.context_length * threshold_percent), - MINIMUM_CONTEXT_LENGTH, + # for models right at the minimum. _compute_threshold_tokens also + # guards the degenerate case where the floor would equal/exceed the + # window (small models), so auto-compression can still fire (#14690). + self.threshold_tokens = [redacted:secret-assignment] + self.context_length, threshold_percent ) self.compression_count = 0 -
tests/agent/test_context_compressor.py@@ -204,6 +204,44 @@ def test_fallback_summary_does_not_triplicate_latest_user_ask(self): f"#49307), found {count}x:\n{summary}" ) + def test_threshold_below_window_at_minimum_ctx(self): + """Regression for #14690: at context_length == MINIMUM_CONTEXT_LENGTH + the floored threshold used to equal the whole window, so + auto-compression could never fire. It now triggers at 85% of the + window — high enough not to waste the small budget, below 100% so it + actually fires.""" + from agent.context_compressor import MINIMUM_CONTEXT_LENGTH + t = ContextCompressor._compute_threshold_tokens(MINIMUM_CONTEXT_LENGTH, 0.50) + assert t < MINIMUM_CONTEXT_LENGTH + assert t == 54400 # 85% of 64000 + + def test_threshold_below_window_for_small_ctx(self): + # 32K model: the 64000 floor exceeds the window — trigger at 85%. + t = ContextCompressor._compute_threshold_tokens(32000, 0.50) + assert t == 27200 # 85% of 32000 + assert t < 32000 + + def test_threshold_floored_for_large_ctx(self): + from agent.context_compressor import MINIMUM_CONTEXT_LENGTH + # 200K model at 50% = 100000 (above floor) — unchanged. + assert ContextCompressor._compute_threshold_tokens(200000, 0.50) == 100000 + # 100K model at 50% = 50000 (below floor) — floored to MINIMUM. + assert ContextCompressor._compute_threshold_tokens(100000, 0.50) == MINIMUM_CONTEXT_LENGTH + + def test_minimum_ctx_model_can_actually_compress(self): + """End-to-end: a model at exactly the minimum context length must have + should_compress() fire below its window (at the 85% trigger), not only + at 100%.""" + with patch("agent.context_compressor.get_model_context_length", return_value=64000): + c = ContextCompressor(model="small-64k", quiet_mode=True) + c.context_length = 64000 + c.threshold_tokens = [redacted:secret-assignment] c.threshold_percent) + assert c.threshold_tokens == 54400 + assert c.threshold_tokens < 64000 + # At 85%+ usage compaction fires; below it, it doesn't (no premature compact). + assert c.should_compress(55000) is True + assert c.should_compress(40000) is False + def test_compression_increments_count(self, compressor): msgs = self._make_messages(10) # Default config (abort_on_summary_failure=False) — fallback path
-
-
#52053 fix(compression): make minimum context floor configurable (#31600)
files:
agent/agent_init.py,agent/context_compressor.py,agent/conversation_compression.py,agent/model_metadata.py,gateway/run.py,tests/gateway/test_agent_cache.py,tests/run_agent/test_compression_feasibility.py,tests/run_agent/test_compression_minimum_floor.py
11 PRs closed without merging
From the close comments: merged via another change 4 · superseded by another PR 2 · won't fix / by design 2 · duplicate 1 · stale or abandoned 1 · other 1
-
#8962 fix(agent): honor model.context_length override below 64K floor · won't fix / by design →
#53569 -
#11097 fix(run_agent): honor model.context_length override for sub-64K models · won't fix / by design →
#53569 - #14696 fix(compression): three bugs causing auto-compression to never trigger · stale or abandoned
-
#14878 fix: keep compression reachable at 64k context · merged via another change →
#49998 -
#15431 fix(agent): clamp compression threshold below context_length to ensure compression can trigger (#14690) · merged via another change →
#49998
Show 6 more
-
#15496 fix(compressor): prevent MINIMUM_CONTEXT_LENGTH floor from blocking compression at small contexts · duplicate →
#15431,#14696,#14690 -
#24076 fix(context): avoid 100 percent threshold at minimum context length · merged via another change →
#49998 -
#31686 fix(compression): make minimum context floor configurable (#31600) · superseded by another PR →
#52053,#31600 -
#33825 fix(agent): compress after repeated tool-call truncation · superseded by another PR →
#31600 -
#35888 fix(gateway): clear bloated compression binding on compression-exhaustion auto-reset · merged via another change →
#45613 -
#54071 fix(compression): make minimum context floor configurable (#31600) · other →
#31600
Closed issues · 2
How this cluster was grouped
-
Link evidence (
partly structural links): some members are connected by structural evidence (shared changed files, or a closing/superseded reference); the rest are joined by an automated relevance judgement over their text. - Members outside that structural core may belong to a different underlying problem.
-
Pairwise link density
0.10: the share of item pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36.1.00means every pair is linked.
Pick up a Lab task
Check whether a fix covers the open issues here, with your own agent. Bring back evidence for maintainers.
Open the Lab