fix(mcp): case-insensitive agent name in diary_write/diary_read (#1243)
`tool_diary_write` stored the `agent` metadata verbatim after `sanitize_name` (which preserves case), while `tool_diary_read` filtered by exact match — so writing as "Claude" and reading as "claude" silently returned zero rows. Both endpoints now lowercase `agent_name` immediately after sanitization. The default per-agent wing slug is also stable across casings since it's derived from the same normalized form. Behavior change: entries written prior to this fix under mixed-case agent names will not match the new lowercase filter; documented under v3.3.5 in CHANGELOG with a `mempalace repair` pointer. Adds a regression test (`test_diary_read_case_insensitive_agent`) and updates the existing `test_diary_write_and_read` to assert the new lowercase agent identity. Closes #1243 Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -476,9 +476,9 @@ class TestWriteTools:
|
||||
|
||||
assert result1["success"] is True
|
||||
assert result2["success"] is True
|
||||
assert (
|
||||
result1["drawer_id"] != result2["drawer_id"]
|
||||
), "Documents with shared header but different content must have distinct drawer IDs"
|
||||
assert result1["drawer_id"] != result2["drawer_id"], (
|
||||
"Documents with shared header but different content must have distinct drawer IDs"
|
||||
)
|
||||
|
||||
def test_delete_drawer(self, monkeypatch, config, palace_path, seeded_collection, kg):
|
||||
_patch_mcp_server(monkeypatch, config, kg)
|
||||
@@ -682,7 +682,8 @@ class TestDiaryTools:
|
||||
topic="architecture",
|
||||
)
|
||||
assert w["success"] is True
|
||||
assert w["agent"] == "TestAgent"
|
||||
# agent_name is normalized to lowercase on write (#1243).
|
||||
assert w["agent"] == "testagent"
|
||||
|
||||
r = tool_diary_read(agent_name="TestAgent")
|
||||
assert r["total"] == 1
|
||||
@@ -774,6 +775,50 @@ class TestDiaryTools:
|
||||
assert r_scoped["total"] == 1
|
||||
assert r_scoped["entries"][0]["content"] == "project-wing entry"
|
||||
|
||||
def test_diary_read_case_insensitive_agent(self, monkeypatch, config, palace_path, kg):
|
||||
"""Regression for #1243: diary_read must be case-insensitive over
|
||||
agent_name. Writing as "Claude" and reading as "claude" (or vice
|
||||
versa) must surface the same entries — sanitize_name preserved
|
||||
case, which silently dropped reads when the agent name's casing
|
||||
differed from the write."""
|
||||
_patch_mcp_server(monkeypatch, config, kg)
|
||||
_client, _col = _get_collection(palace_path, create=True)
|
||||
del _client
|
||||
from mempalace.mcp_server import tool_diary_read, tool_diary_write
|
||||
|
||||
# Write as "Claude" → read as "claude" should match.
|
||||
w1 = tool_diary_write(
|
||||
agent_name="Claude",
|
||||
entry="entry written as Claude",
|
||||
topic="general",
|
||||
)
|
||||
assert w1["success"]
|
||||
|
||||
r1 = tool_diary_read(agent_name="claude")
|
||||
assert "entries" in r1, r1
|
||||
contents1 = {e["content"] for e in r1["entries"]}
|
||||
assert "entry written as Claude" in contents1
|
||||
|
||||
# Write as "CLAUDE" → read as "Claude" should also match the
|
||||
# same agent. After normalization both writes target the same
|
||||
# lowercase agent identity, so both entries are returned.
|
||||
w2 = tool_diary_write(
|
||||
agent_name="CLAUDE",
|
||||
entry="entry written as CLAUDE",
|
||||
topic="general",
|
||||
)
|
||||
assert w2["success"]
|
||||
|
||||
r2 = tool_diary_read(agent_name="Claude")
|
||||
contents2 = {e["content"] for e in r2["entries"]}
|
||||
assert "entry written as Claude" in contents2
|
||||
assert "entry written as CLAUDE" in contents2
|
||||
|
||||
# The stored agent metadata is the lowercase form, and the
|
||||
# default wing is derived from that lowercase form too.
|
||||
assert w1["agent"] == "claude"
|
||||
assert w2["agent"] == "claude"
|
||||
|
||||
|
||||
# ── Cache Invalidation (inode/mtime) ──────────────────────────────────
|
||||
|
||||
@@ -960,9 +1005,9 @@ class TestCacheInvalidation:
|
||||
all_calls = captured["get"] + captured["create"]
|
||||
assert all_calls, "expected get_collection or create_collection to be called"
|
||||
for kwargs in all_calls:
|
||||
assert (
|
||||
"embedding_function" in kwargs
|
||||
), f"missing embedding_function= in chromadb call: {kwargs}"
|
||||
assert "embedding_function" in kwargs, (
|
||||
f"missing embedding_function= in chromadb call: {kwargs}"
|
||||
)
|
||||
assert kwargs["embedding_function"] is not None
|
||||
|
||||
# Same expectation on the create=False (cache-miss) reopen path.
|
||||
|
||||
Reference in New Issue
Block a user