fix: add sliding window to conversation history (issue #8)

- Add _trim_conversation_history() with message count + token limits
- Configurable via CLOVER_MAX_HISTORY_MESSAGES (default: 20)
- Configurable via CLOVER_MAX_HISTORY_TOKENS (default: 8000)
- Trims oldest messages first, preserves last 6 minimum
- Prevents OOM and degraded LLM quality on long sessions
This commit is contained in:
opencode 2026-07-05 07:03:20 +00:00
parent b25fb3c1c6
commit 33258ce2ad
2 changed files with 50 additions and 0 deletions

View File

@ -34,6 +34,8 @@ class AIAgent:
self.model_manager = ModelManager()
self.conversation_history = []
self.available_tools = self._setup_tools()
self.max_history_messages = self.config.get("max_history_messages", 20)
self.max_history_tokens = self.config.get("max_history_tokens", 8000)
def _setup_tools(self):
"""Setup available tools for the AI agent"""
@ -267,6 +269,47 @@ When using tools, be methodical and explain each step. Always test your creation
except Exception as e:
return {"error": f"Tool execution failed: {str(e)}"}
def _trim_conversation_history(self):
"""
Trim conversation history to prevent unbounded memory growth.
Uses a sliding window approach keeping the most recent messages.
Preserves at least the last N messages (max_history_messages)
and stays within token budget (max_history_tokens).
"""
if len(self.conversation_history) <= self.max_history_messages:
return
# Rough token estimation: ~4 chars per token
def estimate_tokens(msg):
return len(msg.get("content", "")) // 4
# Calculate total tokens in history
total_tokens = sum(estimate_tokens(msg) for msg in self.conversation_history)
if total_tokens <= self.max_history_tokens:
# Within token budget, just apply message count limit
if len(self.conversation_history) > self.max_history_messages:
self.conversation_history = self.conversation_history[
-self.max_history_messages :
]
return
# Over token budget - trim from the front, keeping recent messages
while (
len(self.conversation_history) > 6
and sum(estimate_tokens(msg) for msg in self.conversation_history)
> self.max_history_tokens
):
# Remove pairs of messages (user + assistant) from the front
self.conversation_history = self.conversation_history[2:]
# Also enforce message count limit
if len(self.conversation_history) > self.max_history_messages:
self.conversation_history = self.conversation_history[
-self.max_history_messages :
]
def chat(self, user_message: str) -> str:
"""
Have a conversation with the user, using tools as needed
@ -288,6 +331,9 @@ When using tools, be methodical and explain each step. Always test your creation
# Prepare messages for AI
messages = [{"role": "system", "content": self._create_system_prompt()}]
# Trim history to prevent unbounded memory growth
self._trim_conversation_history()
messages.extend(self.conversation_history)
# Get AI response

View File

@ -43,6 +43,10 @@ def load_config():
"project_root": os.getenv("CLOVER_PROJECT_ROOT", "."),
"summary_file": os.getenv("CLOVER_SUMMARY_FILE", "clover.md"),
"structure_file": os.getenv("CLOVER_STRUCTURE_FILE", "structure.md"),
"max_history_messages": int(
os.getenv("CLOVER_MAX_HISTORY_MESSAGES", "20")
),
"max_history_tokens": int(os.getenv("CLOVER_MAX_HISTORY_TOKENS", "8000")),
}
# Debug output if enabled