Skip to content

add_triple discards provenance on a duplicate fact and kg_add reports it as a fresh insert #2268

Description

@rubicon

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.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions