{"repo":"NousResearch/hermes-agent","public_id":"10174","display_label":"Missing memory write bridge in sequential tool execution","summary":"Multiple fixes addressing the absence of an on_memory_write bridge in the _execute_tool_calls_sequential code path. The members appear to be duplicate or closely related attempts at the same patch.","subsystem":null,"counts":{"members":3,"open_issues":0,"open_issues_distinct":0,"open_prs":0,"closed_issues":1,"merged_prs":1,"attempt_prs":1},"landed_fix":{"status":"merged_to_main","basis":"structural","pr":10507,"merged_at":"2026-04-15T20:32:59+00:00"},"resolution":{"state":"resolved","by":"pr-10507"},"freshness":{"state":"stale","last_verified_at":"2026-04-15T20:32:59+00:00","is_stale":true},"grouping":{"backbone":"STRUCTURAL_SPANNING","link_density":0.6666666666666666},"fix_edges":[{"issue_number":10174,"pr_number":10507,"tier":"verified","basis":"closing_ref","confidence":0.95,"recoverable_from_timeline":true,"pr_merged_at":"2026-04-15T20:32:59+00:00"}],"members":[{"kind":"issue","number":10174,"title":"fix: on_memory_write bridge missing in _execute_tool_calls_sequential","url":null,"state":"closed"},{"kind":"pr","number":10178,"title":"fix: add on_memory_write bridge to sequential tool path","url":null,"state":"closed","close_reason":{"category":"uncategorized","implication":"needs_llm"},"merged":false,"is_fix_pr":true,"fix_tier":"suggested","fix_detail_withheld":true},{"kind":"pr","number":10507,"title":"fix: add on_memory_write bridge to sequential tool execution path","url":null,"state":"merged","close_reason":{"category":"merged"},"merged":true,"is_fix_pr":true,"fix_tier":"verified","files_changed":["run_agent.py","tests/agent/test_memory_provider.py"],"fix_hunks":[{"file":"run_agent.py","hunks":["@@ -7466,6 +7466,16 @@ def _execute_tool_calls_sequential(self, assistant_message, messages: list, effe\n                     old_text=function_args.get(\"old_text\"),\n                     store=self._memory_store,\n                 )\n+                # Bridge: notify external memory provider of built-in memory writes\n+                if self._memory_manager and function_args.get(\"action\") in (\"add\", \"replace\"):\n+                    try:\n+                        self._memory_manager.on_memory_write(\n+                            function_args.get(\"action\", \"\"),\n+                            target,\n+                            function_args.get(\"content\", \"\"),\n+                        )\n+                    except Exception:\n+                        pass\n                 tool_duration = time.time() - tool_start_time\n                 if self._should_emit_quiet_tool_messages():\n                     self._vprint(f\"  {_get_cute_tool_message_impl('memory', function_args, tool_duration, result=function_result)}\")"]},{"file":"tests/agent/test_memory_provider.py","hunks":["@@ -736,3 +736,61 @@ def test_on_session_end_tolerates_failure(self):\n         mgr.add_provider(bad)\n \n         mgr.on_session_end([])  # must not raise\n+\n+\n+# ---------------------------------------------------------------------------\n+# on_memory_write bridge — must fire from both concurrent AND sequential paths\n+# ---------------------------------------------------------------------------\n+\n+\n+class TestOnMemoryWriteBridge:\n+    \"\"\"Verify that MemoryManager.on_memory_write is called when built-in\n+    memory writes happen.  This is a regression test for #10174 where the\n+    sequential tool execution path (_execute_tool_calls_sequential) was\n+    missing the bridge call, so single memory tool calls never notified\n+    external memory providers.\n+    \"\"\"\n+\n+    def test_on_memory_write_add(self):\n+        \"\"\"on_memory_write fires for 'add' actions.\"\"\"\n+        mgr = MemoryManager()\n+        p = FakeMemoryProvider(\"ext\")\n+        mgr.add_provider(p)\n+\n+        mgr.on_memory_write(\"add\", \"memory\", \"new fact\")\n+        assert p.memory_writes == [(\"add\", \"memory\", \"new fact\")]\n+\n+    def test_on_memory_write_replace(self):\n+        \"\"\"on_memory_write fires for 'replace' actions.\"\"\"\n+        mgr = MemoryManager()\n+        p = FakeMemoryProvider(\"ext\")\n+        mgr.add_provider(p)\n+\n+        mgr.on_memory_write(\"replace\", \"user\", \"updated pref\")\n+        assert p.memory_writes == [(\"replace\", \"user\", \"updated pref\")]\n+\n+    def test_on_memory_write_remove_not_bridged(self):\n+        \"\"\"The bridge intentionally skips 'remove' — only add/replace notify.\"\"\"\n+        # This tests the contract that run_agent.py checks:\n+        #   function_args.get(\"action\") in (\"add\", \"replace\")\n+        mgr = MemoryManager()\n+        p = FakeMemoryProvider(\"ext\")\n+        mgr.add_provider(p)\n+\n+        # Manager itself doesn't filter — run_agent.py does.\n+        # But providers should handle remove gracefully.\n+        mgr.on_memory_write(\"remove\", \"memory\", \"old fact\")\n+        assert p.memory_writes == [(\"remove\", \"memory\", \"old fact\")]\n+\n+    def test_on_memory_write_tolerates_provider_failure(self):\n+        \"\"\"If a provider's on_memory_write raises, others still get notified.\"\"\"\n+        mgr = MemoryManager()\n+        bad = FakeMemoryProvider(\"builtin\")\n+        bad.on_memory_write = MagicMock(side_effect=RuntimeError(\"boom\"))\n+        good = FakeMemoryProvider(\"good\")\n+        mgr.add_provider(bad)\n+        mgr.add_provider(good)\n+\n+        mgr.on_memory_write(\"add\", \"user\", \"test\")\n+        # Good provider still received the call despite bad provider crashing\n+        assert good.memory_writes == [(\"add\", \"user\", \"test\")]\n"]}]}],"trust":{"default_tier":"verified","includes_suggested":false}}