fix: remove post-compression file-read history injection (#2226)
Remove the [Files already read — do NOT re-read these] user message that was injected into the conversation after context compression. This message used role='user' for system-generated content, creating a fake user turn that confused models about conversation state and could contribute to task-redo behavior. The file_tools.py read tracker (warn on 3rd consecutive read, block on 4th+) already handles re-read prevention inline without injecting synthetic messages. Closes #2224. Co-authored-by: Test <test@test.com>
This commit is contained in:
19
run_agent.py
19
run_agent.py
@@ -4345,25 +4345,6 @@ class AIAgent:
|
||||
if todo_snapshot:
|
||||
compressed.append({"role": "user", "content": todo_snapshot})
|
||||
|
||||
# Preserve file-read history so the model doesn't re-read files
|
||||
# it already examined before compression.
|
||||
try:
|
||||
from tools.file_tools import get_read_files_summary
|
||||
read_files = get_read_files_summary(task_id)
|
||||
if read_files:
|
||||
file_list = "\n".join(
|
||||
f" - {f['path']} ({', '.join(f['regions'])})"
|
||||
for f in read_files
|
||||
)
|
||||
compressed.append({"role": "user", "content": (
|
||||
"[Files already read in this session — do NOT re-read these]\n"
|
||||
f"{file_list}\n"
|
||||
"Use the information from the context summary above. "
|
||||
"Proceed with writing, editing, or responding."
|
||||
)})
|
||||
except Exception:
|
||||
pass # Don't break compression if file tracking fails
|
||||
|
||||
self._invalidate_system_prompt()
|
||||
new_system_prompt = self._build_system_prompt(system_message)
|
||||
self._cached_system_prompt = new_system_prompt
|
||||
|
||||
@@ -298,79 +298,6 @@ class TestClearReadTracker(unittest.TestCase):
|
||||
self.assertNotIn("error", result)
|
||||
|
||||
|
||||
class TestCompressionFileHistory(unittest.TestCase):
|
||||
"""Verify that _compress_context injects file-read history."""
|
||||
|
||||
def setUp(self):
|
||||
clear_read_tracker()
|
||||
|
||||
def tearDown(self):
|
||||
clear_read_tracker()
|
||||
|
||||
@patch("tools.file_tools._get_file_ops", return_value=_make_fake_file_ops())
|
||||
def test_compress_context_includes_read_files(self, _mock_ops):
|
||||
"""After reading files, _compress_context should inject a message
|
||||
listing which files were already read."""
|
||||
# Simulate reads
|
||||
read_file_tool("/tmp/foo.py", offset=1, limit=100, task_id="compress_test")
|
||||
read_file_tool("/tmp/bar.py", offset=1, limit=200, task_id="compress_test")
|
||||
|
||||
# Build minimal messages for compression (need enough messages)
|
||||
messages = [
|
||||
{"role": "system", "content": "You are a helpful assistant."},
|
||||
{"role": "user", "content": "Analyze the codebase."},
|
||||
{"role": "assistant", "content": "I'll read the files."},
|
||||
{"role": "user", "content": "Continue."},
|
||||
{"role": "assistant", "content": "Reading more files."},
|
||||
{"role": "user", "content": "What did you find?"},
|
||||
{"role": "assistant", "content": "Here are my findings."},
|
||||
{"role": "user", "content": "Great, write the fix."},
|
||||
{"role": "assistant", "content": "Working on it."},
|
||||
{"role": "user", "content": "Status?"},
|
||||
]
|
||||
|
||||
# Mock the compressor to return a simple compression
|
||||
mock_compressor = MagicMock()
|
||||
mock_compressor.compress.return_value = [
|
||||
messages[0], # system
|
||||
messages[1], # first user
|
||||
{"role": "user", "content": "[CONTEXT SUMMARY]: Files were analyzed."},
|
||||
messages[-1], # last user
|
||||
]
|
||||
mock_compressor.last_prompt_tokens = 1000
|
||||
|
||||
# Mock the agent's _compress_context dependencies
|
||||
mock_agent = MagicMock()
|
||||
mock_agent.context_compressor = mock_compressor
|
||||
mock_agent._todo_store.format_for_injection.return_value = None
|
||||
mock_agent._session_db = None
|
||||
mock_agent.quiet_mode = True
|
||||
mock_agent._invalidate_system_prompt = MagicMock()
|
||||
mock_agent._build_system_prompt = MagicMock(return_value="system prompt")
|
||||
mock_agent._cached_system_prompt = None
|
||||
|
||||
# Call the real _compress_context
|
||||
from run_agent import AIAgent
|
||||
result, _ = AIAgent._compress_context(
|
||||
mock_agent, messages, "system prompt",
|
||||
approx_tokens=1000, task_id="compress_test",
|
||||
)
|
||||
|
||||
# Find the injected file-read history message
|
||||
file_history_msgs = [
|
||||
m for m in result
|
||||
if isinstance(m.get("content"), str)
|
||||
and "already read" in m.get("content", "").lower()
|
||||
]
|
||||
self.assertEqual(len(file_history_msgs), 1,
|
||||
"Should inject exactly one file-read history message")
|
||||
|
||||
history_content = file_history_msgs[0]["content"]
|
||||
self.assertIn("/tmp/foo.py", history_content)
|
||||
self.assertIn("/tmp/bar.py", history_content)
|
||||
self.assertIn("do NOT re-read", history_content)
|
||||
|
||||
|
||||
class TestSearchLoopDetection(unittest.TestCase):
|
||||
"""Verify that search_tool detects and blocks consecutive repeated searches."""
|
||||
|
||||
|
||||
Reference in New Issue
Block a user