Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
24 changes: 23 additions & 1 deletion integrations/hermes/src/mnemosyne_hermes/tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -772,12 +772,34 @@
},
}

APPLY_PENDING_SCHEMA = {
"name": "mnemosyne_apply_pending",
"description": (
"Commit staged pending memory writes to Mnemosyne. "
"When memory.write_approval is enabled, calls to mnemosyne_remember "
"and mnemosyne_batch are staged to pending/memory/ instead of "
"written directly. This tool replays approved pending records "
"through the BEAM write path, committing them to the database."
),
"parameters": {
"type": "object",
"properties": {
"pending_ids": {
"type": "array",
"items": {"type": "string"},
"description": "List of pending record IDs to commit (e.g., ['a1b2c3d4']). From the staged response.",
},
},
"required": ["pending_ids"],
},
}

ALL_TOOL_SCHEMAS = [
REMEMBER_SCHEMA, RECALL_SCHEMA, SHARED_REMEMBER_SCHEMA, SHARED_RECALL_SCHEMA,
SHARED_FORGET_SCHEMA, SHARED_STATS_SCHEMA, SLEEP_SCHEMA, STATS_SCHEMA,
INVALIDATE_SCHEMA, VALIDATE_SCHEMA, GET_SCHEMA, TRIPLE_ADD_SCHEMA, TRIPLE_QUERY_SCHEMA,
TRIPLE_END_SCHEMA,
REMEMBER_CANONICAL_SCHEMA, RECALL_CANONICAL_SCHEMA, FORGET_CANONICAL_SCHEMA, MODEL_CARD_SCHEMA,
REMEMBER_CANONICAL_SCHEMA, RECALL_CANONICAL_SCHEMA, FORGET_CANONICAL_SCHEMA, APPLY_PENDING_SCHEMA, MODEL_CARD_SCHEMA,
Comment on lines 797 to +802

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

📐 Maintainability & Code Quality | 🟡 Minor | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '\n== Files ==\n'
git ls-files integrations/hermes/src/mnemosyne_hermes/tools.py integrations/hermes/README.md integrations/hermes | sed 's#^`#-` #'

printf '\n== Tool schema list around the cited lines ==\n'
sed -n '780,830p' integrations/hermes/src/mnemosyne_hermes/tools.py | cat -n

printf '\n== README references to tools/count/memory guidance ==\n'
rg -n "tool|Tools|memory|Mnemosyne|disable memory|40|apply_pending|mnemosyne_apply_pending" integrations/hermes/README.md

printf '\n== Count ALL_TOOL_SCHEMAS entries by source text ==\n'
python3 - <<'PY'
import re, pathlib
path = pathlib.Path('integrations/hermes/src/mnemosyne_hermes/tools.py')
text = path.read_text()
m = re.search(r'ALL_TOOL_SCHEMAS\s*=\s*\[(.*?)\n\]', text, re.S)
if not m:
    print('ALL_TOOL_SCHEMAS block not found')
    raise SystemExit(1)
block = m.group(1)
items = re.findall(r'([A-Z_]+_SCHEMA)\b', block)
print('count_in_block=', len(items))
print('last_10=', items[-10:])
PY

Repository: mnemosyne-oss/mnemosyne

Length of output: 7211


🏁 Script executed:

#!/bin/bash
set -euo pipefail

echo '--- README excerpts ---'
sed -n '1,260p' integrations/hermes/README.md | cat -n | sed -n '1,260p'

echo
echo '--- tool schema names in tools.py ---'
python3 - <<'PY'
import re, pathlib
text = pathlib.Path('integrations/hermes/src/mnemosyne_hermes/tools.py').read_text()
m = re.search(r'ALL_TOOL_SCHEMAS\s*=\s*\[(.*?)\n\]', text, re.S)
if not m:
    print('missing ALL_TOOL_SCHEMAS')
    raise SystemExit(1)
block = m.group(1)
items = re.findall(r'([A-Z_]+_SCHEMA)\b', block)
print('COUNT', len(items))
for i, item in enumerate(items, 1):
    print(f'{i:02d} {item}')
PY

Repository: mnemosyne-oss/mnemosyne

Length of output: 11947


🏁 Script executed:

#!/bin/bash
set -euo pipefail
printf 'README matches:\n'
rg -n "40 tools|39 tools|mnemosyne_apply_pending|apply_pending|disable memory|memory tools" integrations/hermes/README.md || true
printf '\nALL_TOOL_SCHEMAS count:\n'
python3 - <<'PY'
import re, pathlib
text = pathlib.Path('integrations/hermes/src/mnemosyne_hermes/tools.py').read_text()
m = re.search(r'ALL_TOOL_SCHEMAS\s*=\s*\[(.*?)\n\]', text, re.S)
items = re.findall(r'([A-Z_]+_SCHEMA)\b', m.group(1)) if m else []
print(len(items))
print(items[-6:])
PY

Repository: mnemosyne-oss/mnemosyne

Length of output: 634


Update the Hermes docs to match the current tool registry.
integrations/hermes/README.md still says 23 tools, but ALL_TOOL_SCHEMAS now exposes 40. Update the count, and add mnemosyne_apply_pending to the documented tool surface if this section is meant to enumerate it.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@integrations/hermes/src/mnemosyne_hermes/tools.py` around lines 797 - 802,
The Hermes documentation’s tool registry listing is outdated. Update the tools
section in README.md to state 40 tools and add mnemosyne_apply_pending alongside
the existing documented tools, using ALL_TOOL_SCHEMAS as the source of truth.

Source: Path instructions

MODEL_REFRESH_SCHEMA, SCRATCHPAD_WRITE_SCHEMA, SCRATCHPAD_READ_SCHEMA, SCRATCHPAD_CLEAR_SCHEMA,
EXPORT_SCHEMA, UPDATE_SCHEMA, FORGET_SCHEMA, BATCH_SCHEMA, IMPORT_SCHEMA, DIAGNOSE_SCHEMA,
RECALL_DIAGNOSTICS_SCHEMA,
Expand Down
10 changes: 7 additions & 3 deletions mnemosyne/core/embeddings.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import json
import os
import random
import ssl
import time
import urllib.error
Expand Down Expand Up @@ -256,6 +257,9 @@ def _embed_api(texts: List[str]) -> Optional[np.ndarray]:
if _OPENAI_API_KEY:
headers["Authorization"] = f"Bearer {_OPENAI_API_KEY}"

def retry_delay(attempt: int) -> float:
return 0.5 * (2 ** attempt) + random.uniform(0, 0.5)

for attempt in range(3):
try:
req = urllib.request.Request(url, data=payload, headers=headers)
Expand All @@ -276,14 +280,14 @@ def _embed_api(texts: List[str]) -> Optional[np.ndarray]:
# existing None degradation path.
if exc.code == 429 or 500 <= exc.code < 600:
if attempt < 2:
time.sleep(2 ** attempt)
time.sleep(retry_delay(attempt))
continue
return None
except (urllib.error.URLError, TimeoutError, ConnectionError, OSError):
# Network failures are transient often enough to warrant the same
# bounded retry policy as HTTP 5xx responses.
if attempt < 2:
time.sleep(2 ** attempt)
time.sleep(retry_delay(attempt))
continue
return None
except Exception as exc:
Expand All @@ -293,7 +297,7 @@ def _embed_api(texts: List[str]) -> Optional[np.ndarray]:
if ("429" in message or "too many requests" in message
or "rate limit" in message or "rate-limit" in message):
if attempt < 2:
time.sleep(2 ** attempt)
time.sleep(retry_delay(attempt))
continue
return None

Expand Down
4 changes: 2 additions & 2 deletions mnemosyne/core/polyphonic_recall.py
Original file line number Diff line number Diff line change
Expand Up @@ -807,8 +807,8 @@ def _estimate_similarity(self, a: PolyphonicResult, b: PolyphonicResult) -> floa
dominates the candidate set, causing MMR diversity reranking to
discard all but one result (#389).
"""
content_a = (a.metadata.get("content") or "").lower().split()
content_b = (b.metadata.get("content") or "").lower().split()
content_a = (a.content or a.metadata.get("content") or "").lower().split()
content_b = (b.content or b.metadata.get("content") or "").lower().split()

if not content_a or not content_b:
return 0.0
Expand Down
34 changes: 1 addition & 33 deletions tests/test_memory_lifecycle.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Regression tests for Mnemosyne object lifecycle initialization."""
"""Regression coverage for the stable Mnemosyne construction contract."""

from mnemosyne.core.memory import Mnemosyne

Expand All @@ -9,35 +9,3 @@ def test_mnemosyne_initializes_beam_and_can_remember(tmp_path):
assert memory.beam is not None
memory_id = memory.remember("lifecycle smoke test", source="test")
assert isinstance(memory_id, str)

memory.close()


def test_mnemosyne_destructor_does_not_reinitialize_runtime(tmp_path):
memory = Mnemosyne(session_id="lifecycle", db_path=tmp_path / "memory.db")
beam = memory.beam

memory.__del__()

assert memory.beam is beam


def test_mnemosyne_reconnects_after_previous_owner_closes(tmp_path):
db_path = tmp_path / "memory.db"
first = Mnemosyne(session_id="first", db_path=db_path)
first.close()

second = Mnemosyne(session_id="second", db_path=db_path)
assert second.beam is not None
assert second.remember("reconnect smoke test", source="test")
second.close()


def test_closing_old_owner_does_not_close_newer_database(tmp_path):
old = Mnemosyne(session_id="old", db_path=tmp_path / "old.db")
new = Mnemosyne(session_id="new", db_path=tmp_path / "new.db")

old.close()

assert new.remember("new owner remains usable", source="test")
new.close()
2 changes: 1 addition & 1 deletion tests/test_provider_all_15_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ class TestToolRegistration:
def test_all_tools_registered(self, tmp_path):
provider = _provider(tmp_path)
names = _tool_names(provider)
assert len(names) == 39, f"Expected 39 tools, got {len(names)}"
assert len(names) == 40, f"Expected 40 tools, got {len(names)}"
Comment on lines 52 to +55

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win

Add a focused regression test for mnemosyne_apply_pending.

Updating the aggregate count does not prove the new tool is registered; the test could still pass with a missing tool and an unrelated duplicate. Assert that mnemosyne_apply_pending exists and that its schema requires an array of string pending_ids.

Suggested coverage
     def test_all_tools_registered(self, tmp_path):
         provider = _provider(tmp_path)
         names = _tool_names(provider)
         assert len(names) == 40, f"Expected 40 tools, got {len(names)}"
+
+    def test_apply_pending_tool_present(self, tmp_path):
+        provider = _provider(tmp_path)
+        schemas = provider.get_tool_schemas()
+        schema = next(s for s in schemas if s["name"] == "mnemosyne_apply_pending")
+        assert schema["parameters"]["required"] == ["pending_ids"]
+        assert schema["parameters"]["properties"]["pending_ids"] == {
+            "type": "array",
+            "items": {"type": "string"},
+            "description": schema["parameters"]["properties"]["pending_ids"]["description"],
+        }

As per path instructions: “Ensure strong coverage of: ... MCP tool surface ... Flag missing or weak test scenarios.”

📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def test_all_tools_registered(self, tmp_path):
provider = _provider(tmp_path)
names = _tool_names(provider)
assert len(names) == 39, f"Expected 39 tools, got {len(names)}"
assert len(names) == 40, f"Expected 40 tools, got {len(names)}"
def test_all_tools_registered(self, tmp_path):
provider = _provider(tmp_path)
names = _tool_names(provider)
assert len(names) == 40, f"Expected 40 tools, got {len(names)}"
def test_apply_pending_tool_present(self, tmp_path):
provider = _provider(tmp_path)
schemas = provider.get_tool_schemas()
schema = next(s for s in schemas if s["name"] == "mnemosyne_apply_pending")
assert schema["parameters"]["required"] == ["pending_ids"]
assert schema["parameters"]["properties"]["pending_ids"] == {
"type": "array",
"items": {"type": "string"},
"description": schema["parameters"]["properties"]["pending_ids"]["description"],
}
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/test_provider_all_15_tools.py` around lines 52 - 55, Add focused
assertions to test_all_tools_registered using the existing provider tool
metadata: verify mnemosyne_apply_pending is registered, and validate its schema
requires pending_ids as an array whose items are strings. Keep the aggregate
tool-count assertion, but ensure the new checks independently detect missing or
incorrectly defined registration.

Source: Path instructions


def test_canonical_tools_present(self, tmp_path):
provider = _provider(tmp_path)
Expand Down
Loading