From e9a7c1889005b4a93113c09f0f663f1b0fa52369 Mon Sep 17 00:00:00 2001 From: flyingdoubleg Date: Mon, 13 Jul 2026 20:15:49 +0800 Subject: [PATCH] fix(memory): honor disabled toolsets for provider tools --- agent/memory_manager.py | 18 ++++++++++---- scripts/release.py | 1 + tests/agent/test_memory_provider.py | 16 ++++++++++++- tests/tools/test_refresh_agent_mcp_tools.py | 26 +++++++++++++++++++++ tools/mcp_tool.py | 8 +++++-- 5 files changed, 62 insertions(+), 7 deletions(-) diff --git a/agent/memory_manager.py b/agent/memory_manager.py index 3909f2ebcd3..6f3bbadd6f0 100644 --- a/agent/memory_manager.py +++ b/agent/memory_manager.py @@ -80,8 +80,17 @@ def normalize_tool_schema(schema: Any) -> Optional[Dict[str, Any]]: return schema -def memory_provider_tools_enabled(enabled_toolsets: Optional[List[str]]) -> bool: +def memory_provider_tools_enabled( + enabled_toolsets: Optional[List[str]], + disabled_toolsets: Optional[List[str]] = None, + *, + memory_tool_present: bool = False, +) -> bool: """Return whether external memory-provider tools should be exposed.""" + if disabled_toolsets and "memory" in disabled_toolsets: + return False + if memory_tool_present: + return True if enabled_toolsets is None: return True if not enabled_toolsets: @@ -110,9 +119,10 @@ def inject_memory_provider_tools(agent: Any) -> int: for tool in tools if isinstance(tool, dict) } - if ( - "memory" not in existing_tool_names - and not memory_provider_tools_enabled(getattr(agent, "enabled_toolsets", None)) + if not memory_provider_tools_enabled( + getattr(agent, "enabled_toolsets", None), + getattr(agent, "disabled_toolsets", None), + memory_tool_present="memory" in existing_tool_names, ): return 0 diff --git a/scripts/release.py b/scripts/release.py index 2c4f07c25f6..37c7ec6f14c 100755 --- a/scripts/release.py +++ b/scripts/release.py @@ -81,6 +81,7 @@ LEGACY_AUTHOR_MAP = { "marceloparra.hm@gmail.com": "marcelohildebrand", # PR #42346 salvage (lmstudio: JIT load mode) "qlskssk@gmail.com": "Soju06", # agent turn-latency perf PRs "m.guttmann@journaway.com": "mguttmann", # PR #63738 salvage (Anthropic setup-token pool auth normalization) + "wangzhe00zju@gmail.com": "flyingdoubleG", # PR #18166 salvage (memory-provider tools honor disabled_toolsets in initial and MCP-refresh injection) "VrtxOmega@pm.me": "VrtxOmega", # PR #43809 salvage (desktop: WSL folder-picker path bridge) "gn00742754@gmail.com": "SemonCat", # PR #56786 salvage (Slack Agent View manifests and Assistant APIs) "KCAYAAI@users.noreply.github.com": "KCAYAAI", # PR #62248 partial salvage (resume typing after clarify reply) diff --git a/tests/agent/test_memory_provider.py b/tests/agent/test_memory_provider.py index 01e68b279dd..a3cf671ab66 100644 --- a/tests/agent/test_memory_provider.py +++ b/tests/agent/test_memory_provider.py @@ -1379,17 +1379,19 @@ class TestMemoryToolToolsetGate: These tests exercise the shared gate used by agent init and ACP refreshes. The gate condition is: + disabled_toolsets includes memory → skip injection enabled_toolsets is None → no filter, inject (backward compat) selected toolsets include memory → user opted in, inject otherwise (incl. []) → skip injection """ @staticmethod - def _run_memory_injection(enabled_toolsets, memory_manager): + def _run_memory_injection(enabled_toolsets, memory_manager, disabled_toolsets=None): """Run the shared memory-tool injection helper against a fake agent.""" fake_agent = SimpleNamespace( _memory_manager=memory_manager, enabled_toolsets=enabled_toolsets, + disabled_toolsets=disabled_toolsets, tools=[], valid_tool_names=set(), ) @@ -1426,6 +1428,18 @@ class TestMemoryToolToolsetGate: assert "hindsight_recall" in names assert any(t["function"]["name"] == "hindsight_recall" for t in tools) + @pytest.mark.parametrize("enabled_toolsets", [None, ["memory"], ["all"], ["hermes-acp"]]) + def test_disabled_memory_toolset_blocks_injection(self, enabled_toolsets): + """An explicit memory disable wins over default or composite enablement.""" + mgr = self._mgr_with_tools("hindsight_recall") + tools, names = self._run_memory_injection( + enabled_toolsets, + mgr, + disabled_toolsets=["memory"], + ) + assert tools == [] + assert names == set() + def test_empty_toolsets_blocks_injection(self): """`platform_toolsets: telegram: []` must suppress memory tools. (#5544)""" mgr = self._mgr_with_tools("fact_store") diff --git a/tests/tools/test_refresh_agent_mcp_tools.py b/tests/tools/test_refresh_agent_mcp_tools.py index da349474a33..30f97910450 100644 --- a/tests/tools/test_refresh_agent_mcp_tools.py +++ b/tests/tools/test_refresh_agent_mcp_tools.py @@ -140,6 +140,32 @@ def test_refresh_preserves_memory_provider_and_context_engine_tools(monkeypatch) assert added == {"mcp_new_server_tool"} +def test_refresh_does_not_reinject_disabled_memory_provider_tools(monkeypatch): + """A refresh removes stale provider tools when memory becomes disabled.""" + agent = _agent( + ["read_file", "memory_search"], + enabled=["all"], + disabled=["memory"], + ) + agent._memory_manager = types.SimpleNamespace( + get_all_tool_schemas=lambda: [ + {"name": "memory_search", "description": "", "parameters": {}} + ] + ) + + import model_tools + monkeypatch.setattr( + model_tools, + "get_tool_definitions", + lambda **kw: [_tool("read_file")], + ) + + mcp_tool.refresh_agent_mcp_tools(agent) + + assert "memory_search" not in agent.valid_tool_names + assert all(t["function"]["name"] != "memory_search" for t in agent.tools) + + def test_refresh_respects_context_engine_toolset_gate(monkeypatch): """#5544: context-engine tools must NOT be re-injected on a restricted toolset. A platform with enabled_toolsets that excludes context_engine diff --git a/tools/mcp_tool.py b/tools/mcp_tool.py index 7d18ceb1525..35382471a14 100644 --- a/tools/mcp_tool.py +++ b/tools/mcp_tool.py @@ -6134,9 +6134,13 @@ def _reinject_post_build_tools(agent, tools_list: list, name_set: set) -> set: memory_manager = getattr(agent, "_memory_manager", None) get_mem_schemas = getattr(memory_manager, "get_all_tool_schemas", None) if memory_manager else None if callable(get_mem_schemas): - # Honor the same enablement gate inject_memory_provider_tools uses. + # Honor the same toolset gate inject_memory_provider_tools uses. from agent.memory_manager import memory_provider_tools_enabled - if "memory" in name_set or memory_provider_tools_enabled(getattr(agent, "enabled_toolsets", None)): + if memory_provider_tools_enabled( + getattr(agent, "enabled_toolsets", None), + getattr(agent, "disabled_toolsets", None), + memory_tool_present="memory" in name_set, + ): for schema in get_mem_schemas(): if isinstance(schema, dict): _add(schema)