# NousResearch/hermes-agent · Missing memory write bridge in sequential tool execution

## Open now

- Nothing open on GitHub.

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.

## TL;DR

- 3 member(s): 0 open issue(s), 1 closed issue(s), 0 open PR(s), 1 merged PR(s), 1 PR(s) closed without merging.
- A fix was merged to main in PR #10507. Verify it addresses your case.
- **Recommended:** read PR #10507 (and any competing PRs below) before filing. Confirm the merged change covers your symptom.

## Maintainer resolution verdict

- Internal-tracker verdict over this cluster's issue members: `resolved` · deciding PR(s): `pr-10507`. This is distinct from the fix status above. Weigh it yourself.

## How this cluster was grouped

- Link evidence (`STRUCTURAL_SPANNING`): every member is connected by structural evidence: shared changed files, or a closing/superseded reference.
- Pairwise link density `0.67`: the share of member pairs that carry a link, counting a structural link in full and a relevance-only link at 0.36. `1.00` means every pair is linked.

## Closed / duplicate issues

- #10174: fix: on_memory_write bridge missing in _execute_tool_calls_sequential

## Prior & competing PRs

- **#10178: fix: add on_memory_write bridge to sequential tool path**. closed without merge · fix tier `suggested`
  - maintainer direction: category `uncategorized`; implication `needs_llm`
  - _suggested-tier fix detail withheld; re-request with `?tier=all` to include it._
- **#10507: fix: add on_memory_write bridge to sequential tool execution path**. merged to main · fix tier `verified`
  - maintainer direction: category `merged`
  - files: `run_agent.py`, `tests/agent/test_memory_provider.py`
  - fix hunks:
  - `run_agent.py`

    ```diff
    @@ -7466,6 +7466,16 @@ def _execute_tool_calls_sequential(self, assistant_message, messages: list, effe
                         old_text=function_args.get("old_text"),
                         store=self._memory_store,
                     )
    +                # Bridge: notify external memory provider of built-in memory writes
    +                if self._memory_manager and function_args.get("action") in ("add", "replace"):
    +                    try:
    +                        self._memory_manager.on_memory_write(
    +                            function_args.get("action", ""),
    +                            target,
    +                            function_args.get("content", ""),
    +                        )
    +                    except Exception:
    +                        pass
                     tool_duration = time.time() - tool_start_time
                     if self._should_emit_quiet_tool_messages():
                         self._vprint(f"  {_get_cute_tool_message_impl('memory', function_args, tool_duration, result=function_result)}")
    ```
  - `tests/agent/test_memory_provider.py`

    ```diff
    @@ -736,3 +736,61 @@ def test_on_session_end_tolerates_failure(self):
             mgr.add_provider(bad)
     
             mgr.on_session_end([])  # must not raise
    +
    +
    +# ---------------------------------------------------------------------------
    +# on_memory_write bridge — must fire from both concurrent AND sequential paths
    +# ---------------------------------------------------------------------------
    +
    +
    +class TestOnMemoryWriteBridge:
    +    """Verify that MemoryManager.on_memory_write is called when built-in
    +    memory writes happen.  This is a regression test for #10174 where the
    +    sequential tool execution path (_execute_tool_calls_sequential) was
    +    missing the bridge call, so single memory tool calls never notified
    +    external memory providers.
    +    """
    +
    +    def test_on_memory_write_add(self):
    +        """on_memory_write fires for 'add' actions."""
    +        mgr = MemoryManager()
    +        p = FakeMemoryProvider("ext")
    +        mgr.add_provider(p)
    +
    +        mgr.on_memory_write("add", "memory", "new fact")
    +        assert p.memory_writes == [("add", "memory", "new fact")]
    +
    +    def test_on_memory_write_replace(self):
    +        """on_memory_write fires for 'replace' actions."""
    +        mgr = MemoryManager()
    +        p = FakeMemoryProvider("ext")
    +        mgr.add_provider(p)
    +
    +        mgr.on_memory_write("replace", "user", "updated pref")
    +        assert p.memory_writes == [("replace", "user", "updated pref")]
    +
    +    def test_on_memory_write_remove_not_bridged(self):
    +        """The bridge intentionally skips 'remove' — only add/replace notify."""
    +        # This tests the contract that run_agent.py checks:
    +        #   function_args.get("action") in ("add", "replace")
    +        mgr = MemoryManager()
    +        p = FakeMemoryProvider("ext")
    +        mgr.add_provider(p)
    +
    +        # Manager itself doesn't filter — run_agent.py does.
    +        # But providers should handle remove gracefully.
    +        mgr.on_memory_write("remove", "memory", "old fact")
    +        assert p.memory_writes == [("remove", "memory", "old fact")]
    +
    +    def test_on_memory_write_tolerates_provider_failure(self):
    +        """If a provider's on_memory_write raises, others still get notified."""
    +        mgr = MemoryManager()
    +        bad = FakeMemoryProvider("builtin")
    +        bad.on_memory_write = MagicMock(side_effect=RuntimeError("boom"))
    +        good = FakeMemoryProvider("good")
    +        mgr.add_provider(bad)
    +        mgr.add_provider(good)
    +
    +        mgr.on_memory_write("add", "user", "test")
    +        # Good provider still received the call despite bad provider crashing
    +        assert good.memory_writes == [("add", "user", "test")]
    ```

## Fix status

- Merged to main in PR #10507 (cited above). No release data is tracked. Verify downstream.
- Fix last verified (merged) `2026-04-15`. This confirmation is old and may be out of date (a revert/reopen would not have invalidated it here); re-verify.
