Back to skills
SKILL.md
2169 Context Overflow Handling 7397deb4
ASecurityImplement surgical token limit handling with: 1. **Proactive estimation** before LLM calls 2. **Reactive error catching** from both Anthropic and OpenAI-style providers 3. **Auto-remediation** (memory evacuation, message pruning) with retry
- 9 stars
- 0 votes
- 0 copies
- 0 views
- Added October 11, 2026
Works with
Security analysis
100/100npx -y skills add tools-only/X-Skills --skill 2169-context-overflow-handling_7397deb4 --agent claude-codeAre you the author of 2169 Context Overflow Handling 7397deb4?
Add the live security badge to your README. It updates with every re-scan.
[](https://www.skillsdirectory.com/skills/tools-only-2169-context-overflow-handling-7397deb4)# Token Limit Handling: Proactive + Reactive with Remediation
## Overview
Implement surgical token limit handling with:
1. **Proactive estimation** before LLM calls
2. **Reactive error catching** from both Anthropic and OpenAI-style providers
3. **Auto-remediation** (memory evacuation, message pruning) with retry
## User Requirements
- In-memory token tracking (no persistence)
- Include tool estimation (~100 tokens/tool)
- 5% overhead buffer
- Take remedial action on overflow (not just error)
- Surgical chokepoints, NOT defense-in-depth
---
## Implementation
### 1. Custom Exception (`clients/llm_provider.py:~70`)
```python
class ContextOverflowError(Exception):
"""Raised when request exceeds model context window."""
def __init__(self, estimated_tokens: int, context_window: int, provider: str):
self.estimated_tokens = estimated_tokens
self.context_window = context_window
self.provider = provider
super().__init__(f"Context overflow: ~{estimated_tokens} tokens vs {context_window} limit")
```
### 2. Token Estimation Method (`cns/services/orchestrator.py`)
Add method to orchestrator (not llm_provider - keeps estimation with the context owner):
```python
def _estimate_request_tokens(
self,
messages: List[Dict],
tools: List[Dict],
last_turn_input_tokens: Optional[int] = None
) -> int:
"""Estimate tokens for upcoming request."""
if last_turn_input_tokens is not None:
# Use actual count from last turn as baseline
base_tokens = last_turn_input_tokens
else:
# Fallback: 4 chars/token (conservative)
total_chars = sum(len(str(m.get('content', ''))) for m in messages)
base_tokens = total_chars // 4
tool_tokens = len(tools) * 100 if tools else 0
return int((base_tokens + tool_tokens) * 1.05) # 5% buffer
```
### 3. In-Memory State Tracking (`cns/services/orchestrator.py:~78`)
Add to `__init__`:
```python
self._last_turn_usage: Dict[str, int] = {} # {continuum_id: input_tokens}
self._pending_context_trim: Dict[str, int] = {} # {continuum_id: trim_index} - one-shot from async LLM
```
Capture after CompleteEvent (~line 408):
```python
if hasattr(raw_response, 'usage') and raw_response.usage:
self._last_turn_usage[str(continuum.id)] = raw_response.usage.input_tokens
```
### 4. Proactive Check + Remediation Loop (`cns/services/orchestrator.py:~315`)
**Location**: Wrap the `stream_events()` call (lines 318-409) in a retry loop.
```python
from clients.llm_provider import ContextOverflowError
max_overflow_retries = 3
overflow_attempt = 0
messages_for_llm = complete_messages # Mutable for pruning
# Check for one-shot adjustment from previous async LLM judgment
one_shot_trim = self._pending_context_trim.pop(str(continuum.id), None)
if one_shot_trim:
logger.info(f"Applying one-shot trim from async LLM judgment: {one_shot_trim} messages")
messages_for_llm = messages_for_llm[:1] + messages_for_llm[one_shot_trim + 1:]
while overflow_attempt <= max_overflow_retries:
# === PROACTIVE CHECK ===
last_input = self._last_turn_usage.get(str(continuum.id))
estimated = self._estimate_request_tokens(messages_for_llm, available_tools, last_input)
available_for_input = config.api.context_window_tokens - config.api.max_tokens
if estimated > available_for_input:
raise ContextOverflowError(estimated, config.api.context_window_tokens, 'proactive')
try:
# Existing stream_events() loop here
for event in self.llm_provider.stream_events(...):
...
break # Success
except ContextOverflowError as e:
overflow_attempt += 1
logger.warning(f"Context overflow attempt {overflow_attempt}/{max_overflow_retries}")
if overflow_attempt > max_overflow_retries:
raise RuntimeError("Request exceeds context window after remediation attempts")
# Remediation 1: Force memory evacuation (preserves conversation)
if overflow_attempt == 1 and self.memory_evacuator:
logger.info("Remediation 1: Aggressive memory evacuation")
# Aggressively evacuate memories to shrink system prompt...
continue
# Remediation 2: Embedding-based topic drift (fast, no LLM)
if overflow_attempt == 2:
logger.info("Remediation 2: Embedding-based topic drift pruning")
messages_for_llm = self._prune_by_topic_drift(messages_for_llm)
# Fire async LLM judgment for next request (one-shot)
self._schedule_async_context_judgment(continuum.id, complete_messages)
continue
# Remediation 3: Pure oldest-first fallback (maximum speed)
if overflow_attempt == 3:
logger.info("Remediation 3: Oldest-first fallback")
prune_count = config.context.overflow_fallback_prune_count
messages_for_llm = messages_for_llm[:1] + messages_for_llm[prune_count + 1:]
continue
```
### 4a. Smart Topic-Drift Chunking (`cns/services/orchestrator.py`)
New method for intelligent message pruning based on semantic similarity:
```python
def _prune_by_topic_drift(self, messages: List[Dict]) -> List[Dict]:
"""
Find topic drift boundary using sliding window embedding similarity.
Drop messages before the boundary to reduce context.
Algorithm:
1. Generate embeddings for sliding windows of messages
2. Compare adjacent windows via cosine similarity
3. Find largest similarity drop (= topic drift boundary)
4. If drop below threshold: cut at boundary
5. Fallback: oldest-first pruning with configurable count
"""
from utils.embeddings import get_embedding # Existing infrastructure
# Config values
window_size = config.context.topic_drift_window_size # e.g., 3
drift_threshold = config.context.topic_drift_threshold # e.g., 0.6
fallback_prune_count = config.context.overflow_fallback_prune_count # e.g., 5
# Need enough messages to analyze
if len(messages) < window_size * 2 + 1: # +1 for system message
# Too few messages, use oldest-first fallback
return messages[:1] + messages[fallback_prune_count + 1:]
# Generate window embeddings (exclude system message at [0])
content_messages = messages[1:]
windows = []
for i in range(len(content_messages) - window_size + 1):
window_text = " ".join(
str(m.get('content', '')) for m in content_messages[i:i+window_size]
)
windows.append((i, get_embedding(window_text)))
# Find candidate cut points (similarity drops)
candidate_cuts = []
for i in range(len(windows) - 1, 0, -1):
similarity = cosine_similarity(windows[i][1], windows[i-1][1])
drop = 1.0 - similarity
if drop > (1.0 - drift_threshold):
candidate_cuts.append({
'index': windows[i][0],
'similarity': similarity,
'drop': drop
})
# ═══════════════════════════════════════════════════════════════════════
# EXTENSION POINT: LLM Judgment (future)
# ═══════════════════════════════════════════════════════════════════════
# When ready to add LLM judgment, implement _llm_select_cut_point() that:
# - Takes candidate_cuts and messages
# - Asks LLM which boundary is the best semantic cut point
# - Returns the selected index or None to use embedding-based selection
#
# Example future implementation:
# best_cut_idx = self._llm_select_cut_point(candidate_cuts, content_messages)
# if best_cut_idx is None:
# best_cut_idx = self._select_by_largest_drop(candidate_cuts)
# ═══════════════════════════════════════════════════════════════════════
# Current implementation: select largest drop
best_cut_idx = None
if candidate_cuts:
best_cut = max(candidate_cuts, key=lambda c: c['drop'])
best_cut_idx = best_cut['index']
if best_cut_idx is not None:
# Found topic boundary - keep system msg + messages from boundary onward
logger.info(f"Topic drift detected at message {best_cut_idx}, dropping {best_cut_idx} messages")
return messages[:1] + content_messages[best_cut_idx:]
else:
# No clear boundary - use oldest-first fallback
logger.info(f"No topic drift found, using oldest-first fallback ({fallback_prune_count} messages)")
return messages[:1] + messages[fallback_prune_count + 1:]
```
### 4b. Async LLM Judgment Scheduler (`cns/services/orchestrator.py`)
Fire-and-forget async task that runs LLM judgment in background:
```python
def _schedule_async_context_judgment(self, continuum_id: UUID, messages: List[Dict]) -> None:
"""
Schedule async LLM judgment to determine optimal cut point.
Result stored in _pending_context_trim for one-shot application on next request.
"""
import asyncio
async def _run_judgment():
try:
# ═══════════════════════════════════════════════════════════════════════
# EXTENSION POINT: LLM Judgment Implementation (future)
# ═══════════════════════════════════════════════════════════════════════
# When ready to implement:
# 1. Extract candidate cut points from messages (reuse embedding logic)
# 2. Call LLM with candidates + recent context
# 3. LLM returns optimal cut index
# 4. Store in _pending_context_trim
#
# For now: no-op placeholder
# optimal_cut = await self._llm_judge_cut_point(messages)
# if optimal_cut:
# self._pending_context_trim[str(continuum_id)] = optimal_cut
# ═══════════════════════════════════════════════════════════════════════
pass
except Exception as e:
logger.warning(f"Async context judgment failed (non-critical): {e}")
# Fire and forget - don't block current request
asyncio.create_task(_run_judgment())
```
### 5. Reactive Catch: Anthropic (`clients/llm_provider.py:1687`)
Add before the 401 check in `_handle_anthropic_error()`:
```python
# Check for context length exceeded (400 with specific patterns)
if status_code == 400:
error_lower = message.lower()
if "prompt is too long" in error_lower or "context" in error_lower:
self.logger.error("Anthropic context length exceeded")
yield ErrorEvent(error="Request too large for model context window")
raise ContextOverflowError(0, config.api.context_window_tokens, 'anthropic')
```
### 6. Reactive Catch: Generic Providers (`utils/generic_openai_client.py:612`)
Modify existing handler to raise `ContextOverflowError`:
```python
if "context_length" in str(error_code) or "reduce the length" in error_message.lower():
logger.error("Generic OpenAI client context length exceeded")
from clients.llm_provider import ContextOverflowError
raise ContextOverflowError(0, 200000, 'generic')
```
---
## Files to Modify
| File | Changes |
|------|---------|
| `clients/llm_provider.py` | Add `ContextOverflowError` class (~line 70), add 400 check in `_handle_anthropic_error()` (~line 1687) |
| `cns/services/orchestrator.py` | Add `_estimate_request_tokens()`, `_prune_by_topic_drift()`, `_last_turn_usage` tracking, wrap `stream_events()` in retry loop |
| `utils/generic_openai_client.py` | Modify existing context length handler to raise `ContextOverflowError` (~line 612) |
| `config/config.py` | Add `context` section with topic drift config values |
## Config Additions (`config/config.py`)
Add new context management section:
```python
class ContextConfig(BaseModel):
"""Context window management configuration."""
topic_drift_window_size: int = 3 # Messages per sliding window
topic_drift_threshold: float = 0.6 # Similarity below this = topic change
overflow_fallback_prune_count: int = 5 # Messages to prune if no drift found
class Config(BaseModel):
# ... existing fields ...
context: ContextConfig = ContextConfig()
```
---
## Why These Solutions Are Correct
### 1. Proactive Check at Orchestrator Level
- **Root cause**: Requests can exceed context window before we even call the API
- **Causal chain**: [conversation + memories + tools] -> [token count > limit] -> [API error]
- **Solution mechanics**: Estimate before calling, fail fast, allow remediation
- **Not a symptom fix**: We prevent the error rather than just catching it
- **Production**: Uses actual previous token counts for accuracy; falls back to conservative estimate
### 2. Reactive Catch with Unified Exception
- **Root cause**: Estimation may be inaccurate; API is source of truth
- **Causal chain**: [underestimated tokens] -> [API call] -> [400 error] -> [need retry opportunity]
- **Solution mechanics**: Catch specific patterns, normalize to `ContextOverflowError`, enable retry
- **Not a symptom fix**: Provides structured type for retry logic
### 3. Three-Tier Remediation with Async Intelligence
- **Root cause**: Context overflow is recoverable by reducing request size
- **Causal chain**: [too many tokens] -> [error] -> [reduce payload] -> [retry] -> [success]
- **Solution mechanics** (tiered for speed):
1. Memory evacuation (preserves conversation, shrinks system prompt)
2. Embedding-based topic drift (fast, no LLM) + fire async LLM judgment
3. Pure oldest-first fallback (maximum speed)
4. Next request: apply one-shot adjustment from async LLM judgment
- **Not a symptom fix**: Intelligently reduces payload while maximizing context quality
- **Production**:
- Max 3 retries prevent infinite loops
- Fast path unblocks user immediately
- Async LLM judgment improves next request without blocking current one
- Configurable thresholds for tuning
- Multiple fallback layers ensure request always succeeds
### 4. Topic-Drift Detection Algorithm
- **Root cause**: Arbitrary message pruning loses valuable context unnecessarily
- **Causal chain**: [overflow] -> [analyze similarity] -> [find topic boundary] -> [cut at natural point]
- **Solution mechanics**: Sliding window embeddings find semantic discontinuities in conversation
- **Not a symptom fix**: Preserves coherent recent context, drops semantically-distant older content
- **Production**: Compute cost is fractional (edge case only); fallback prevents failure if no boundary found
**Engineering Assertion**: These solutions eliminate root causes, not symptoms, and possess the robustness required for production deployment.
Attribution
Comments
Loading comments…
Newsletter
Get the weekly list of safe new skills, and the ones we pulled.