Summary
add_triple returns the existing row id when an identical still-valid triple exists, discarding any provenance supplied on the second call. tool_kg_add wraps that in a response shape identical to a genuine insert, so a caller cannot tell the two apart. tool_add_drawer already handles the equivalent case by returning reason: "already_exists".
Verified against
mempalace==3.7.0 from PyPI, Python 3.12, macOS x86_64.
Repro
from mempalace.mcp_server import tool_kg_add
a = tool_kg_add(subject="dax", predicate="likes", object="tea", source_closet="cap_1")
b = tool_kg_add(subject="dax", predicate="likes", object="tea", source_closet="cap_2")
# a and b are the same shape: {"success": True, "triple_id": ..., "fact": ...}
# b["triple_id"] == a["triple_id"], and cap_2 is not stored anywhere.
Compare tool_add_drawer, which returns {"success": True, "reason": "already_exists", "drawer_id": ...} for the same situation.
Root cause
mempalace/knowledge_graph.py, add_triple (3.7.0, lines 305-311):
existing = conn.execute(
"SELECT id FROM triples WHERE subject=? AND predicate=? AND object=? AND valid_to IS NULL",
(sub_id, pred, obj_id),
).fetchone()
if existing:
return existing["id"] # Already exists and still valid
tool_kg_add then returns {"success": True, "triple_id": triple_id, "fact": ...} at line 3795, with no field distinguishing the reused row from a new one. tool_add_drawer sets reason at line 2981.
Impact
A client that records provenance externally and keys it to the triple has no way to know its provenance was dropped, so it will believe the second capture is attributed when it is not. Silent divergence rather than a visible failure.
Suggested fix
Add "reason": "already_exists" to tool_kg_add's return when the pre-existing row is reused, matching tool_add_drawer. Deduplication itself is correct and this is not a request to change it; the ask is only that the response say what happened.
Related
#1139 modifies this same block to backfill NULL metadata on a duplicate add_triple. That is complementary rather than overlapping: it addresses what gets written, while this is about what the MCP response reports. Filing as an issue rather than a competing PR against the same function, but happy to send a patch for the reason field if that is wanted and #1139's author has no objection.
Summary
add_triplereturns the existing row id when an identical still-valid triple exists, discarding any provenance supplied on the second call.tool_kg_addwraps that in a response shape identical to a genuine insert, so a caller cannot tell the two apart.tool_add_draweralready handles the equivalent case by returningreason: "already_exists".Verified against
mempalace==3.7.0from PyPI, Python 3.12, macOS x86_64.Repro
Compare
tool_add_drawer, which returns{"success": True, "reason": "already_exists", "drawer_id": ...}for the same situation.Root cause
mempalace/knowledge_graph.py,add_triple(3.7.0, lines 305-311):tool_kg_addthen returns{"success": True, "triple_id": triple_id, "fact": ...}at line 3795, with no field distinguishing the reused row from a new one.tool_add_drawersetsreasonat line 2981.Impact
A client that records provenance externally and keys it to the triple has no way to know its provenance was dropped, so it will believe the second capture is attributed when it is not. Silent divergence rather than a visible failure.
Suggested fix
Add
"reason": "already_exists"totool_kg_add's return when the pre-existing row is reused, matchingtool_add_drawer. Deduplication itself is correct and this is not a request to change it; the ask is only that the response say what happened.Related
#1139 modifies this same block to backfill NULL metadata on a duplicate
add_triple. That is complementary rather than overlapping: it addresses what gets written, while this is about what the MCP response reports. Filing as an issue rather than a competing PR against the same function, but happy to send a patch for thereasonfield if that is wanted and #1139's author has no objection.