Intelligent LRU context manager for LLM agents โ scores chunks by relevance, recency, and access frequency. Keeps hot context, evicts cold. Drop-in for Strands, LangChain, and OpenAI.
Project description
๐ฅ hot-context
LRU-based intelligent context manager for LLM agents. Keeps hot context, evicts cold.
Most conversation managers use a naive sliding window โ drop the oldest messages when context fills up. hot-context treats your context window like an intelligent cache: every chunk is scored by relevance, recency, and access frequency, and only the highest-value context survives.
Key Features
- Relevance-aware trimming โ trims tool results to only the lines the LLM actually used, instead of evicting entire entries
- Adaptive token calibration โ learns the real chars-per-token ratio from actual LLM feedback, self-corrects over time
- Entity echo feedback loop โ uses the LLM's own response as a proxy for attention weights, zero extra API calls
- Turn-aware eviction โ never evicts during a turn (LLM needs full context to reason), only trims between turns
- Framework-agnostic core with drop-in adapters for Strands, LangChain, and OpenAI
How It Works
Turn N:
User query โ Tools return data โ LLM sees FULL context โ Answers
โ
Entity echo: what did the LLM use?
Trimmer: keep only relevant lines from tool results
Calibrator: learn real token ratio from Bedrock
โ
Turn N+1:
Context has trimmed tool results (5k instead of 80k) + all assistant answers preserved
Scoring formula (no LLM needed):
score = ฮฑยทsimilarity(chunk, query) + ฮฒยทrecency(chunk) + ฮณยทaccess_frequency(chunk) - ฮดยทsize_penalty(chunk)
- Similarity: Jaccard overlap on extracted entities (BM25-inspired, zero API calls)
- Recency: Exponential decay by turn distance
- Access frequency: Time-decayed count of how often the LLM actually used this chunk
- Size penalty: Large tool results are penalized proportionally to budget
Topic shift detection: When the user changes topics, similarity weight increases and frequency weight decreases โ preventing stale popular chunks from dominating.
Results
Tested against Strands SlidingWindowConversationManager on a real multi-turn agent with MCP tools (SQL queries, knowledge base lookups):
| Metric | SlidingWindow | hot-context |
|---|---|---|
| Q3 (heavy query) | CRASHED โ context overflow | โ Answered (2831 chars) |
| Peak context Q2 | Unbounded (168k+) | 105k (trimmed) |
| Peak context Q3 | Overflow (>200k) | 113k (controlled) |
| Queries completed | 2.5 of 3 | 3 of 3 |
Peak context went DOWN from Q1โQ2 (128kโ105k) โ the trimmer removed irrelevant lines from Q1's tool result.
Quick Start
Installation
# Core โ no dependencies
pip install hot-context
# With AWS Strands adapter
pip install hot-context[strands]
# With LangChain adapter
pip install hot-context[langchain]
# With embedding-based similarity (recommended for production)
pip install hot-context[embeddings]
# Everything
pip install hot-context[all]
Strands (drop-in replacement)
from strands import Agent
from strands.models.bedrock import BedrockModel
from hot_context.adapters.strands import StrandsHotContextManager
from hot_context import ScoringWeights
agent = Agent(
model=BedrockModel(model_id="us.anthropic.claude-sonnet-4-5-20250929-v1:0"),
conversation_manager=StrandsHotContextManager(
token_budget=100_000,
weights=ScoringWeights.tool_agent(),
),
)
LangChain (drop-in replacement)
from langchain_core.runnables import RunnableWithMessageHistory
from hot_context.adapters.langchain import HotContextChatHistory
from hot_context import ScoringWeights
history = HotContextChatHistory(
token_budget=50_000,
weights=ScoringWeights.conversational(),
)
chain_with_history = RunnableWithMessageHistory(
runnable=your_chain,
get_session_history=lambda session_id: history,
)
Framework-agnostic core
from hot_context import HotContextManager, ScoringWeights
manager = HotContextManager(
token_budget=100_000,
weights=ScoringWeights.tool_agent(),
pin_last_n=2,
)
# Before each query
manager.on_query(user_input)
# After LLM responds
manager.on_response(str(result))
Adaptive calibration (Strands)
# Token estimator self-calibrates from actual Bedrock token counts.
# After each LLM call, feed back the reported input_tokens:
cm = agent.conversation_manager
cm.calibrate(agent.messages, actual_input_tokens=87450)
# Future estimates are now more accurate โ no manual tuning needed.
Weight Presets
Use a built-in preset or define your own custom weights:
ScoringWeights.tool_agent() # high similarity โ for agents with many tool calls
ScoringWeights.conversational() # high recency + frequency โ for multi-turn chat
ScoringWeights.research() # high similarity + frequency โ for RAG/research agents
# Custom weights โ all four components must sum to 1.0
ScoringWeights(
similarity=0.5, # how much query-chunk relevance matters
recency=0.2, # how much to favor recent turns
access_freq=0.2, # how much to favor chunks the LLM has used before
size_penalty=0.1, # how much to penalize large chunks
)
Token Budget
token_budget is the maximum tokens reserved for conversation history. It should be sized as:
token_budget = model_context_limit - expected_query_tokens - system_prompt_tokens - tool_definition_tokens
For example, with Claude's 200k context window:
HotContextManager(
token_budget=100_000, # leaves headroom for prompts, tools, and the response
)
Setting it too high risks context overflow; too low and hot-context will aggressively evict useful history.
pin_last_n
pin_last_n protects the most recent N turns from eviction, ensuring the LLM always has immediate conversational context regardless of score:
HotContextManager(
pin_last_n=2, # last 2 turns are never evicted (default)
)
Increase this if your agent relies heavily on recent tool results that shouldn't be trimmed mid-conversation.
Custom Entity Patterns
import re
from hot_context import HotContextManager
manager = HotContextManager(
extra_patterns=[
("order_id", re.compile(r"\b\d{3}-\d{7}-\d{7}\b")),
("asin", re.compile(r"\bB[A-Z0-9]{9}\b")),
]
)
Architecture
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ HotContextManager โ
โ โ
โ TokenEstimator โโโ EntityExtractor โโโ ContextScorer โ
โ โ โ โ
โ calibrate() score + rank โ
โ (from LLM) โ โ
โ โ โ
โ Trimmer โโโ FeedbackLoop โโโ LLM Response โโโ Evictor โ
โ (line-level (entity echo) (entry-level) โ
โ relevance) โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Adapters: Strands | LangChain | OpenAI
Why Not Just Summarize?
| Approach | Token cost | Latency | Quality |
|---|---|---|---|
| Sliding window | Free | None | Poor โ drops relevant old context |
| LLM summarization | High | +500ms/turn | Good โ but expensive at scale |
| hot-context | Free | ~1ms/turn | Good โ keeps what matters, trims what doesn't |
Components
| File | Purpose |
|---|---|
manager.py |
Core orchestrator: on_query() โ score + evict, on_response() โ feedback |
scorer.py |
Scoring formula with topic shift detection and pluggable similarity |
token_estimator.py |
Adaptive chars-per-token calibration from real LLM feedback |
trimmer.py |
Line-level relevance trimming of tool results |
chunker.py |
Smart chunking respecting tool-use/tool-result pairing |
entity_extractor.py |
Regex-based entity extraction (dates, IDs, numbers, custom patterns) |
feedback.py |
Entity echo feedback loop โ updates access logs from LLM response |
types.py |
ContextEntry, AccessRecord, ScoringWeights with presets |
adapters/strands.py |
Drop-in ConversationManager for Strands agents |
adapters/langchain.py |
BaseChatMessageHistory adapter for LangChain |
adapters/openai.py |
Message list manager for OpenAI Chat API |
Roadmap
P2 โ Benchmarks & Paper
- Benchmark harness โ synthetic multi-turn conversations with known answers. Measure token savings + answer quality (ROUGE/BERTScore on trimmed vs full context)
- Standard benchmarks โ LongBench, SCROLLS, QuALITY, NarrativeQA for long-context evaluation
- Ablation studies โ trimmer-only vs scorer-only vs full pipeline vs baselines (MemGPT, LLMLingua, attention sink)
- Token savings curves โ plot peak context vs conversation length across strategies
Contributing
Issues and PRs welcome at github.com/Ankit28P/hot-context.
License
Apache 2.0 โ see LICENSE.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file hot_context-0.1.2.tar.gz.
File metadata
- Download URL: hot_context-0.1.2.tar.gz
- Upload date:
- Size: 33.4 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
8e3fe16e7f98cdc8fc3f2f83bfbe0a6bd32c0ed655dbe9b9fd77ee1b58840996
|
|
| MD5 |
f59ea69357620dcfbd1198e3244a4100
|
|
| BLAKE2b-256 |
30279cda083b4c3071181ab1f1076aa6be525a1d48cbecaad13e3d23fb3c6dc3
|
Provenance
The following attestation bundles were made for hot_context-0.1.2.tar.gz:
Publisher:
publish.yml on Ankit28P/hot-context
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hot_context-0.1.2.tar.gz -
Subject digest:
8e3fe16e7f98cdc8fc3f2f83bfbe0a6bd32c0ed655dbe9b9fd77ee1b58840996 - Sigstore transparency entry: 1339248503
- Sigstore integration time:
-
Permalink:
Ankit28P/hot-context@108411cddafb8544e86e10a26ddee4a5ce9b04f3 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/Ankit28P
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@108411cddafb8544e86e10a26ddee4a5ce9b04f3 -
Trigger Event:
push
-
Statement type:
File details
Details for the file hot_context-0.1.2-py3-none-any.whl.
File metadata
- Download URL: hot_context-0.1.2-py3-none-any.whl
- Upload date:
- Size: 27.4 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
737c1bf42af1cf3efb49ba8be010b6d0ac372fa8027444880c808d298e2bba38
|
|
| MD5 |
3c212c5407c5e11751b94765481ebb42
|
|
| BLAKE2b-256 |
0bc80963781087b7990c587d3b08426a6013e7ae2b52db6b3ba7634d7c232ed2
|
Provenance
The following attestation bundles were made for hot_context-0.1.2-py3-none-any.whl:
Publisher:
publish.yml on Ankit28P/hot-context
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
hot_context-0.1.2-py3-none-any.whl -
Subject digest:
737c1bf42af1cf3efb49ba8be010b6d0ac372fa8027444880c808d298e2bba38 - Sigstore transparency entry: 1339248565
- Sigstore integration time:
-
Permalink:
Ankit28P/hot-context@108411cddafb8544e86e10a26ddee4a5ce9b04f3 -
Branch / Tag:
refs/tags/v0.1.2 - Owner: https://github.com/Ankit28P
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
publish.yml@108411cddafb8544e86e10a26ddee4a5ce9b04f3 -
Trigger Event:
push
-
Statement type: