From b07d142938420e5e658a97556dd841bdd2713e60 Mon Sep 17 00:00:00 2001 From: Alessandro <155005371+3clyp50@users.noreply.github.com> Date: Tue, 30 Jun 2026 16:35:22 +0200 Subject: [PATCH] Coerce memory load numeric args Normalize memory_load threshold and limit values from model calls before vector search so numeric strings from Responses tool arguments do not reach FAISS unchanged. Add a focused regression covering string threshold/limit values and document the memory plugin contract. --- plugins/_memory/AGENTS.md | 1 + plugins/_memory/tools/memory_load.py | 7 ++++ tests/test_tool_action_contracts.py | 48 ++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/plugins/_memory/AGENTS.md b/plugins/_memory/AGENTS.md index 622026f57..061b4a463 100644 --- a/plugins/_memory/AGENTS.md +++ b/plugins/_memory/AGENTS.md @@ -16,6 +16,7 @@ - Keep memory scoped by configured subdirectory/context. - Preserve embedding metadata needed to rebuild indexes safely. +- `memory_load` accepts numeric `threshold` and `limit` values as native numbers or numeric strings and coerces them before vector search. - Avoid storing transient action-history noise as durable memory. ## Work Guidance diff --git a/plugins/_memory/tools/memory_load.py b/plugins/_memory/tools/memory_load.py index 9305186f6..7fe419c16 100644 --- a/plugins/_memory/tools/memory_load.py +++ b/plugins/_memory/tools/memory_load.py @@ -8,6 +8,13 @@ DEFAULT_LIMIT = 10 class MemoryLoad(Tool): async def execute(self, query="", threshold=DEFAULT_THRESHOLD, limit=DEFAULT_LIMIT, filter="", **kwargs): + if threshold is None or threshold == "": + threshold = DEFAULT_THRESHOLD + if limit is None or limit == "": + limit = DEFAULT_LIMIT + threshold = float(threshold) + limit = int(limit) + db = await Memory.get(self.agent) docs = await db.search_similarity_threshold(query=query, limit=limit, threshold=threshold, filter=filter) diff --git a/tests/test_tool_action_contracts.py b/tests/test_tool_action_contracts.py index 4e964130f..ed21388cc 100644 --- a/tests/test_tool_action_contracts.py +++ b/tests/test_tool_action_contracts.py @@ -548,6 +548,54 @@ def test_memory_forget_tool_imports_plugin_memory_load(monkeypatch): ] +def test_memory_load_coerces_numeric_string_args(monkeypatch): + _install_tool_stub(monkeypatch) + monkeypatch.syspath_prepend(str(Path.cwd())) + + class FakeDb: + def __init__(self) -> None: + self.calls = [] + + async def search_similarity_threshold(self, **kwargs): + self.calls.append(kwargs) + return [] + + fake_db = FakeDb() + + async def get_memory(_agent): + return fake_db + + memory_stub = types.ModuleType("plugins._memory.helpers.memory") + memory_stub.Memory = types.SimpleNamespace(get=get_memory) + monkeypatch.setitem(sys.modules, "plugins._memory.helpers.memory", memory_stub) + + sys.modules.pop("plugins._memory.tools.memory_load", None) + module = importlib.import_module("plugins._memory.tools.memory_load") + tool = module.MemoryLoad( + _FakeAgent(), + "memory_load", + None, + { + "query": "smoke test project context", + "threshold": "0.7", + "limit": "3", + }, + "", + None, + ) + + asyncio.run(tool.execute(**tool.args)) + + assert fake_db.calls == [ + { + "query": "smoke test project context", + "threshold": 0.7, + "limit": 3, + "filter": "", + } + ] + + def test_behaviour_adjustment_normalizes_duplicate_rules(monkeypatch): _install_tool_stub(monkeypatch) monkeypatch.syspath_prepend(str(Path.cwd()))