{"repo":"NousResearch/hermes-agent","public_id":"2784","display_label":"PersistentShell polling causes excessive CPU on WSL","summary":"A fixed 10ms poll interval in PersistentShellMixin causes excessive resource consumption and high CPU usage, particularly on WSL/WSL2. Multiple fixes propose replacing it with exponential backoff.","subsystem":"environments","counts":{"members":5,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":2,"merged_prs":1,"attempt_prs":2},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":2996},"resolution":{"state":"partially_resolved","by":"pr-2996"},"freshness":{"state":"stale","last_verified_at":"2026-03-25T17:56:48+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_PARTIAL","link_density":0.33599999999999997},"fix_edges":[{"issue_number":2784,"pr_number":2996,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true}],"members":[{"kind":"issue","number":2784,"title":"[Bug]: PersistentShellMixin 10ms poll interval causes excessive resource consumption on WSL","url":null,"state":"closed"},{"kind":"issue","number":31513,"title":"[WSL2] Gateway idle causes 100% CPU from wslhost on Windows 10","url":null,"state":"closed"},{"kind":"pr","number":2786,"title":"fix(shell): replace fixed 10ms poll interval with exponential backoff to reduce WSL2 resource consumption","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#2996","number":2996}],"category":"merged_via","implication":"superseded"},"merged":false},{"kind":"pr","number":2787,"title":"fix(tools):  exponential backoff for PersistentShellMixin poll interval   ","url":null,"state":"closed","close_reason":{"refs":[{"kind":"pr","value":"#2786","number":2786}],"category":"duplicate_of","implication":"duplicate"},"merged":false},{"kind":"pr","number":2996,"title":"fix(shell): exponential backoff for persistent shell polling","url":null,"state":"merged","close_reason":{"category":"merged","implication":"superseded"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["tools/environments/persistent_shell.py","tools/environments/ssh.py"],"fix_hunks":[{"file":"tools/environments/persistent_shell.py","hunks":["@@ -40,7 +40,8 @@ def _execute_oneshot(self, command: str, cwd: str, *,\n     def _cleanup_temp_files(self): ...\n \n     _session_id: str = \"\"\n-    _poll_interval: float = 0.01\n+    _poll_interval_start: float = 0.01  # initial poll interval (10ms)\n+    _poll_interval_max: float = 0.25    # max poll interval (250ms) — reduces I/O for long commands\n \n     @property\n     def _temp_prefix(self) -> str:","@@ -224,7 +225,7 @@ def _execute_persistent_locked(self, command: str, cwd: str,\n         )\n         self._send_to_shell(ipc_script)\n         deadline = time.monotonic() + timeout\n-        poll_interval = self._poll_interval\n+        poll_interval = self._poll_interval_start  # starts at 10ms, backs off to 250ms\n \n         while True:\n             if is_interrupted():","@@ -256,6 +257,10 @@ def _execute_persistent_locked(self, command: str, cwd: str,\n                 break\n \n             time.sleep(poll_interval)\n+            # Exponential backoff: fast start (10ms) for quick commands,\n+            # ramps up to 250ms for long-running commands — reduces I/O by 10-25x\n+            # on WSL2 where polling keeps the VM hot and memory pressure high.\n+            poll_interval = min(poll_interval * 1.5, self._poll_interval_max)\n \n         output, exit_code, new_cwd = self._read_persistent_output()\n         if new_cwd:"]},{"file":"tools/environments/ssh.py","hunks":["@@ -87,7 +87,7 @@ def _establish_connection(self):\n         except subprocess.TimeoutExpired:\n             raise RuntimeError(f\"SSH connection to {self.user}@{self.host} timed out\")\n \n-    _poll_interval: float = 0.15\n+    _poll_interval_start: float = 0.15  # SSH: higher initial interval (150ms) for network latency\n \n     @property\n     def _temp_prefix(self) -> str:\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}