From 7a61b66dacc33b0bf3dd505fdf3eacf6f2f01c9f Mon Sep 17 00:00:00 2001 From: MorAlekss Date: Wed, 15 Jul 2026 06:05:47 -0700 Subject: [PATCH] fix(conversation): use resolve_agent_cwd() and Platform line for stored-prompt staleness check --- agent/conversation_loop.py | 17 ++++-- agent/system_prompt.py | 2 + .../run_agent/test_compression_persistence.py | 58 ++++++++++++++++--- 3 files changed, 63 insertions(+), 14 deletions(-) diff --git a/agent/conversation_loop.py b/agent/conversation_loop.py index 0c0660e9cdcc..b9067a378726 100644 --- a/agent/conversation_loop.py +++ b/agent/conversation_loop.py @@ -48,6 +48,7 @@ from agent.turn_context import ( reanchor_current_turn_user_idx, ) from agent.turn_retry_state import TurnRetryState +from agent.runtime_cwd import resolve_agent_cwd from agent.message_sanitization import ( close_interrupted_tool_sequence, _repair_tool_call_arguments, @@ -561,16 +562,20 @@ def _stored_prompt_matches_runtime(agent, prompt: str) -> bool: # Detect cwd drift: if the stored prompt was built in a different working # directory, reuse would silently inject a stale path into the prefix cache. + # Compare against resolve_agent_cwd() — the SAME resolver used to build the + # prompt — so gateway/TUI sessions that set TERMINAL_CWD are not falsely + # rejected (they would always differ from the launch dir's os.getcwd()). stored_cwd = line_value("Current working directory") if stored_cwd: - import os as _os - if stored_cwd != _os.getcwd(): + if stored_cwd != str(resolve_agent_cwd()): return False - # Detect runtime surface drift: desktop GUI vs non-desktop. - has_desktop_marker = "Runtime surface: you're running inside the Hermes desktop GUI app." in prompt - in_desktop = (os.environ.get("HERMES_DESKTOP", "").strip().lower() in {"1", "true", "yes"}) - if has_desktop_marker != in_desktop: + # Detect runtime-surface drift: the stored prompt records which platform it + # was built for (e.g. "desktop" vs "cli"). Reusing a desktop-built prompt on + # a terminal session (or vice versa) would inject the wrong runtime hints. + stored_platform = line_value("Platform") + current_platform = str(getattr(agent, "platform", "") or "").strip() + if stored_platform and current_platform and stored_platform != current_platform: return False return True diff --git a/agent/system_prompt.py b/agent/system_prompt.py index 6cc7554b9866..d92072d1ac1e 100644 --- a/agent/system_prompt.py +++ b/agent/system_prompt.py @@ -532,6 +532,8 @@ def build_system_prompt_parts(agent: Any, system_message: Optional[str] = None) timestamp_line += f"\nModel: {agent.model}" if agent.provider: timestamp_line += f"\nProvider: {agent.provider}" + if agent.platform: + timestamp_line += f"\nPlatform: {agent.platform}" volatile_parts.append(timestamp_line) return { diff --git a/tests/run_agent/test_compression_persistence.py b/tests/run_agent/test_compression_persistence.py index 4ae3f623401d..9fae8ddfe967 100644 --- a/tests/run_agent/test_compression_persistence.py +++ b/tests/run_agent/test_compression_persistence.py @@ -487,19 +487,61 @@ class TestStoredPromptCwdDrift: ) def test_stored_prompt_stale_when_runtime_surface_differs(self): - """Desktop GUI marker in stored prompt must not be reused when running in terminal.""" - from unittest.mock import patch + """A stored prompt built for a different platform must not be reused.""" from agent.conversation_loop import _stored_prompt_matches_runtime - agent = self._make_agent() stored_prompt = ( - "Runtime surface: you're running inside the Hermes desktop GUI app.\n" + "Platform: desktop\n" "Model: test/model\n" "Provider: openrouter\n" ) + agent = self._make_agent() + agent.platform = "cli" + assert _stored_prompt_matches_runtime(agent, stored_prompt) is False, ( + "Expected False when stored prompt is for 'desktop' but the " + "current session runs on 'cli'" + ) - # Current runtime is terminal/CLI: no desktop marker. - with patch.dict(os.environ, {"HERMES_DESKTOP": ""}, clear=False): - assert _stored_prompt_matches_runtime(agent, stored_prompt) is False, ( - "Expected False when stored prompt claims desktop but HERMES_DESKTOP is unset" + def test_stored_prompt_fresh_when_platform_matches(self): + """Matching platform allows prompt reuse.""" + from agent.conversation_loop import _stored_prompt_matches_runtime + + agent = self._make_agent() + agent.platform = "desktop" + stored_prompt = ( + "Platform: desktop\n" + "Model: test/model\n" + "Provider: openrouter\n" + ) + assert _stored_prompt_matches_runtime(agent, stored_prompt) is True, ( + "Expected True when stored platform matches the current platform" + ) + + def test_built_prompt_contains_platform_line(self): + """The built system prompt must carry a Platform: line so drift detection works.""" + import tempfile + from pathlib import Path + from unittest.mock import patch + from hermes_state import SessionDB + from run_agent import AIAgent + from agent.system_prompt import build_system_prompt_parts + + with tempfile.TemporaryDirectory() as tmpdir: + db = SessionDB(db_path=Path(tmpdir) / "test.db") + with patch.dict(os.environ, {"OPENROUTER_API_KEY": "test-key"}): + agent = AIAgent( + api_key="test-key", + base_url="https://openrouter.ai/api/v1", + model="test/model", + provider="openrouter", + quiet_mode=True, + session_db=db, + session_id="platform-test", + skip_context_files=True, + skip_memory=True, + ) + agent.platform = "cli" + parts = build_system_prompt_parts(agent) + assert "Platform: cli" in parts["volatile"], ( + "Built prompt missing 'Platform: cli' — drift detection cannot read it" )