Skip to content

Commit 4b49438

Browse files
author
Your Name
committed
Better similarity detection with basic windowed pattern recognition
1 parent 527e606 commit 4b49438

2 files changed

Lines changed: 78 additions & 50 deletions

File tree

cecli/coders/agent_coder.py

Lines changed: 75 additions & 47 deletions
Original file line numberDiff line numberDiff line change
@@ -46,11 +46,11 @@ class AgentCoder(Coder):
4646
def __init__(self, *args, **kwargs):
4747
self.recently_removed = {}
4848
self.tool_usage_history = []
49-
self.tool_usage_retries = 10
49+
self.tool_usage_retries = 20
5050
self.last_round_tools = []
5151
self.tool_call_vectors = []
5252
self.tool_similarity_threshold = 0.90
53-
self.max_tool_vector_history = 10
53+
self.max_tool_vector_history = 20
5454
self.read_tools = {
5555
"command",
5656
"commandinteractive",
@@ -867,66 +867,91 @@ async def _execute_tool_with_registry(self, norm_tool_name, params):
867867
def _get_repetitive_tools(self):
868868
"""
869869
Identifies repetitive tool usage patterns from rounds of tool calls.
870-
871-
This method uses similarity-based detection:
872-
1. If the last round contained a write tool, it assumes progress and returns no repetitive tools.
873-
2. It checks for similarity-based repetition using cosine similarity on tool call strings.
874-
875-
It avoids flagging repetition if a "write" tool was used recently,
876-
as that suggests progress is being made.
877870
"""
878871
history_len = len(self.tool_usage_history)
879872
if history_len < 5:
880873
return set()
874+
881875
similarity_repetitive_tools = self._get_repetitive_tools_by_similarity()
876+
882877
if self.last_round_tools:
883878
last_round_has_write = any(
884879
tool.lower() in self.write_tools for tool in self.last_round_tools
885880
)
886881
if last_round_has_write:
887-
self.tool_usage_history = []
888-
# Filter similarity_repetitive_tools to only include tools in read_tools or write_tools
889-
filtered_similarity_tools = {
890-
tool
891-
for tool in similarity_repetitive_tools
892-
if tool.lower() in self.read_tools or tool.lower() in self.write_tools
893-
}
894-
return filtered_similarity_tools if len(filtered_similarity_tools) else set()
895-
# Filter similarity_repetitive_tools to only include tools in read_tools or write_tools
896-
filtered_similarity_tools = {
882+
# Remove half of the history when a write tool is used
883+
half = len(self.tool_usage_history) // 2
884+
self.tool_usage_history = self.tool_usage_history[half:]
885+
self.tool_call_vectors = self.tool_call_vectors[half:]
886+
887+
# Filter to only include tools in read_tools or write_tools
888+
return {
897889
tool
898890
for tool in similarity_repetitive_tools
899891
if tool.lower() in self.read_tools or tool.lower() in self.write_tools
900892
}
901-
if filtered_similarity_tools:
902-
return filtered_similarity_tools
903-
return set()
904893

905894
def _get_repetitive_tools_by_similarity(self):
906895
"""
907-
Identifies repetitive tool usage patterns using cosine similarity on tool call strings.
908-
909-
This method checks if the latest tool calls are highly similar (>0.99 threshold)
910-
to historical tool calls using bigram vector similarity.
911-
912-
Returns:
913-
set: Set of tool names that are repetitive based on similarity
896+
Identifies repetitive tool usage patterns using cosine similarity and windowed patterns.
914897
"""
915898
if not self.tool_usage_history or len(self.tool_call_vectors) < 2:
916899
return set()
900+
901+
repetitive_tools = set()
917902
latest_vector = self.tool_call_vectors[-1]
903+
similarity_triggered = False
904+
905+
# Store similarity scores by index (similarity between latest vector and each historical vector)
906+
similarity_scores = []
907+
908+
# 1. Similarity-based detection
918909
for i, historical_vector in enumerate(self.tool_call_vectors[:-1]):
919910
similarity = cosine_similarity(latest_vector, historical_vector)
920-
if similarity >= self.tool_similarity_threshold:
911+
similarity_scores.append(similarity)
912+
913+
# Flag immediately if similarity is very high (> 0.99)
914+
if similarity > 0.99:
921915
if i < len(self.tool_usage_history):
922-
tool_name = self.tool_usage_history[i]
923-
# Only return tools that are in read_tools or write_tools
924-
if (
925-
tool_name.lower() in self.read_tools
926-
or tool_name.lower() in self.write_tools
927-
):
928-
return {tool_name}
929-
return set()
916+
repetitive_tools.add(self.tool_usage_history[i])
917+
918+
# Standard similarity threshold triggers windowed check
919+
elif similarity >= self.tool_similarity_threshold:
920+
similarity_triggered = True
921+
922+
# 2. Windowed pattern detection (window size 3)
923+
# Only runs if similarity threshold was met or high similarity was found
924+
if similarity_triggered or repetitive_tools:
925+
window_size = 3
926+
if len(self.tool_usage_history) >= window_size * 2:
927+
latest_window = tuple(self.tool_usage_history[-window_size:])
928+
latest_window_vectors = self.tool_call_vectors[-window_size:]
929+
930+
for i in range(len(self.tool_usage_history) - (window_size * 2) + 1):
931+
historical_window = tuple(self.tool_usage_history[i : i + window_size])
932+
historical_window_vectors = self.tool_call_vectors[i : i + window_size]
933+
934+
if latest_window == historical_window:
935+
# Check if at least one tool in the window has similarity above threshold
936+
# We compare each tool in the historical window with its counterpart in the latest window
937+
window_has_high_similarity = False
938+
for j in range(window_size):
939+
# Compare historical tool at position i+j with latest tool at position -window_size+j
940+
hist_idx = i + j
941+
latest_idx = -window_size + j
942+
943+
if hist_idx < len(self.tool_call_vectors) and latest_idx < 0:
944+
similarity = cosine_similarity(
945+
historical_window_vectors[j], latest_window_vectors[j]
946+
)
947+
if similarity >= self.tool_similarity_threshold:
948+
window_has_high_similarity = True
949+
break
950+
951+
if window_has_high_similarity:
952+
repetitive_tools.update(latest_window)
953+
954+
return repetitive_tools
930955

931956
def _generate_tool_context(self, repetitive_tools):
932957
"""
@@ -947,8 +972,8 @@ def _generate_tool_context(self, repetitive_tools):
947972
context_parts.append("## Recent Tool Usage History")
948973

949974
if len(self.tool_usage_history) > 10:
950-
recent_history = self.tool_usage_history[-10:]
951-
context_parts.append("(Showing last 10 tools)")
975+
recent_history = self.tool_usage_history[-20:]
976+
context_parts.append("(Showing last 20 tools)")
952977
else:
953978
recent_history = self.tool_usage_history
954979
for i, tool in enumerate(recent_history, 1):
@@ -973,7 +998,7 @@ def _generate_tool_context(self, repetitive_tools):
973998
self.model_kwargs["temperature"] = min(temperature + 0.1, 2)
974999
self.model_kwargs["frequency_penalty"] = min(freq_penalty + 0.1, 1)
9751000

976-
if random.random() < 0.25:
1001+
if random.random() < 0.2:
9771002
self.model_kwargs["temperature"] = min(
9781003
(
9791004
1
@@ -984,21 +1009,21 @@ def _generate_tool_context(self, repetitive_tools):
9841009
)
9851010
self.model_kwargs["frequency_penalty"] = min(0, max(freq_penalty - 0.15, 0))
9861011

987-
# One tenth of the time, just straight reset the randomness
988-
if random.random() < 0.1:
1012+
# One twentieth of the time, just straight reset the randomness
1013+
if random.random() < 0.05:
9891014
self.model_kwargs = {}
9901015

991-
if self.turn_count - self._last_repetitive_warning_turn > 2:
1016+
if self.turn_count - self._last_repetitive_warning_turn > 1:
9921017
self._last_repetitive_warning_turn = self.turn_count
9931018
self._last_repetitive_warning_severity += 1
9941019

9951020
repetition_warning = f"""
9961021
## Repetition Detected: Strategy Adjustment Required
997-
I have detected repetitive usage of the following tools: {', '.join([f'`{t}`' for t in repetitive_tools])}.
998-
**Constraint:** Do not repeat the exact same parameters for these tools in your next turn.
1022+
You have been using the following tools repetitively: {', '.join([f'`{t}`' for t in repetitive_tools])}.
1023+
**Constraint:** Do not repeat the same parameters for these tools in your next turns. Try something different.
9991024
"""
10001025

1001-
if self._last_repetitive_warning_severity > 2:
1026+
if self._last_repetitive_warning_severity > 5:
10021027
self._last_repetitive_warning_severity = 0
10031028

10041029
fruit = random.choice(
@@ -1056,6 +1081,9 @@ def _generate_tool_context(self, repetitive_tools):
10561081
context_parts.append(repetition_warning)
10571082
else:
10581083
self.model_kwargs = {}
1084+
self._last_repetitive_warning_severity = min(
1085+
self._last_repetitive_warning_severity - 1, 0
1086+
)
10591087

10601088
context_parts.append("</context>")
10611089
return "\n".join(context_parts)

cecli/prompts/agent.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -25,8 +25,8 @@ main_system: |
2525
2626
<context name="workflow_and_tool_usage">
2727
## Core Workflow
28-
1. **Plan**: Start by using `UpdateTodoList` to outline the task. Always begin a complex interaction by setting or updating the roadmap.
29-
2. **Explore**: Use `Grep` for broad searches, but if results exceed 50 matches, refine your pattern immediately. Use discovery tools to add files as read-only context.
28+
1. **Plan**: Start by using `UpdateTodoList` to outline the task.
29+
2. **Explore**: Use `Grep` for broad searches, but if results exceed 50 matches, refine your pattern immediately. Use discovery tools to add files as context.
3030
3. **Execute**: Use the appropriate editing tool. Mark files as editable with `ContextManager` when needed. Proactively use skills if they are available.
3131
4. **Verify & Recover**: Review every diff. If an edit fails or introduces errors, prioritize `UndoChange` to restore a known good state before attempting a fix.
3232
5. **Finished**: Use the `Finished` tool only after verifying the solution. Briefly summarize the changes for the user.
@@ -56,7 +56,7 @@ system_reminder: |
5656
- **Context Hygiene**: Remove files or skills from context using `ContextManager` or `RemoveSkill` once they are no longer needed to save tokens and prevent confusion.
5757
- **Turn Management**: Tool calls trigger the next turn. Do not include tool calls in your final summary to the user.
5858
- **Sandbox**: Use `.cecli/workspace` for all verification and temporary logic.
59-
- **Novelty**: Do not repeat phrases in your responses to the user. You do not need to declare you understand the task. Simply proceed.
59+
- **Novelty**: Do not repeat phrases in your responses to the user. You do not need to declare you understand the task. Simply proceed. Only speak when you have something new to say.
6060
{lazy_prompt}
6161
{shell_cmd_reminder}
6262
</context>

0 commit comments

Comments
 (0)