Skip to content

Commit 1fd16da

Browse files
committed
fix(mcp): diary_read(wing='') spans all wings for agent (#1145)
#1097 fixed mempalace_search to treat empty-string wing/room as no filter, matching how LLM agents default to filling every optional parameter with ''. The same pattern wasn't applied to diary_read: passing wing='' defaulted to wing_<agent_name>, siloing away entries that hooks had written to project-derived wings per #659. When wing is empty/omitted, filter only on agent + room=diary so callers get a unified view of the agent's journal across every wing it has written to. Explicit wing=<name> continues to scope reads to that wing only. Adds test covering empty-wing read after writing to both the default and a non-default wing.
1 parent d158375 commit 1fd16da

2 files changed

Lines changed: 47 additions & 13 deletions

File tree

mempalace/mcp_server.py

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -995,10 +995,11 @@ def tool_diary_read(agent_name: str, last_n: int = 10, wing: str = ""):
995995
Read an agent's recent diary entries. Returns the last N entries
996996
in chronological order — the agent's personal journal.
997997
998-
When ``wing`` is provided, reads from that wing instead of the
999-
agent's default ``wing_<agent_name>`` wing. This lets hooks
1000-
direct diary reads to a project-specific wing derived from
1001-
the transcript path.
998+
When ``wing`` is provided, reads only from that wing. When ``wing``
999+
is empty or omitted, returns entries from every wing this agent has
1000+
written to. Diary writes from hooks land in project-derived wings
1001+
(``wing_<project>``), so requiring a specific wing on read would
1002+
silo those entries from agent-initiated reads.
10021003
"""
10031004
try:
10041005
agent_name = sanitize_name(agent_name, "agent_name")
@@ -1007,21 +1008,20 @@ def tool_diary_read(agent_name: str, last_n: int = 10, wing: str = ""):
10071008
except ValueError as e:
10081009
return {"error": str(e)}
10091010
last_n = max(1, min(last_n, 100))
1010-
if not wing:
1011-
wing = f"wing_{agent_name.lower().replace(' ', '_')}"
10121011
col = _get_collection()
10131012
if not col:
10141013
return _no_palace()
10151014

1015+
# Build filter: always scope by agent + room=diary. Wing is optional —
1016+
# when empty, return entries across all wings for this agent (matches
1017+
# the #1097 empty-string-as-no-filter convention for LLM ergonomics).
1018+
conditions = [{"room": "diary"}, {"agent": agent_name}]
1019+
if wing:
1020+
conditions.insert(0, {"wing": wing})
1021+
10161022
try:
10171023
results = col.get(
1018-
where={
1019-
"$and": [
1020-
{"wing": wing},
1021-
{"room": "diary"},
1022-
{"agent": agent_name},
1023-
]
1024-
},
1024+
where={"$and": conditions},
10251025
include=["documents", "metadatas"],
10261026
limit=10000,
10271027
)

tests/test_mcp_server.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -740,6 +740,40 @@ def now(cls):
740740
assert entry1 in contents
741741
assert entry2 in contents
742742

743+
def test_diary_read_empty_wing_spans_all_wings(self, monkeypatch, config, palace_path, kg):
744+
"""diary_read(wing='') must return entries from every wing this agent
745+
wrote to. Hooks write to project-derived wings (#659); a reader that
746+
silos by default wing would never see those entries."""
747+
_patch_mcp_server(monkeypatch, config, kg)
748+
_client, _col = _get_collection(palace_path, create=True)
749+
del _client
750+
from mempalace.mcp_server import tool_diary_read, tool_diary_write
751+
752+
w1 = tool_diary_write(
753+
agent_name="TestAgent",
754+
entry="default-wing entry",
755+
topic="general",
756+
)
757+
w2 = tool_diary_write(
758+
agent_name="TestAgent",
759+
entry="project-wing entry",
760+
topic="general",
761+
wing="wing_someproject",
762+
)
763+
assert w1["success"] and w2["success"]
764+
765+
# Empty wing → return both entries
766+
r = tool_diary_read(agent_name="TestAgent", wing="")
767+
assert r["total"] == 2
768+
contents = {e["content"] for e in r["entries"]}
769+
assert "default-wing entry" in contents
770+
assert "project-wing entry" in contents
771+
772+
# Explicit wing → return only that wing's entries
773+
r_scoped = tool_diary_read(agent_name="TestAgent", wing="wing_someproject")
774+
assert r_scoped["total"] == 1
775+
assert r_scoped["entries"][0]["content"] == "project-wing entry"
776+
743777

744778
# ── Cache Invalidation (inode/mtime) ──────────────────────────────────
745779

0 commit comments

Comments
 (0)