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:
parent
214047dee1
commit
4263350c5b
2 changed files with 0 additions and 92 deletions
19
run_agent.py
19
run_agent.py
|
|
@ -4345,25 +4345,6 @@ class AIAgent:
|
||||||
if todo_snapshot:
|
if todo_snapshot:
|
||||||
compressed.append({"role": "user", "content": 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()
|
self._invalidate_system_prompt()
|
||||||
new_system_prompt = self._build_system_prompt(system_message)
|
new_system_prompt = self._build_system_prompt(system_message)
|
||||||
self._cached_system_prompt = new_system_prompt
|
self._cached_system_prompt = new_system_prompt
|
||||||
|
|
|
||||||
|
|
@ -298,79 +298,6 @@ class TestClearReadTracker(unittest.TestCase):
|
||||||
self.assertNotIn("error", result)
|
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):
|
class TestSearchLoopDetection(unittest.TestCase):
|
||||||
"""Verify that search_tool detects and blocks consecutive repeated searches."""
|
"""Verify that search_tool detects and blocks consecutive repeated searches."""
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue