Skip to content

feat: upsert_note tool — semantic dedup before write using existing embeddings #1259

Description

@84matte84

Problem

When an AI agent writes to basic-memory across multiple sessions, it frequently creates duplicate notes on the same topic with slightly different titles. For example:

  • Session 1 creates reference_bu_product_mapping about BU-to-product mapping
  • Session 3 creates bu-mapping-analysis-2026-08-15 with updated findings on the same topic

Now search_notes("BU mapping") returns both, potentially with conflicting information. The agent (or human) has no way to know which is current. Over time, the knowledge graph accumulates contradictory entries that degrade retrieval quality.

The write_note tool with overwrite=True only helps when the caller knows the exact title of the existing note. In practice, agents don't — they know the topic but not whether a note already exists or what it's called.

Proposed Solution

A new upsert_note tool that leverages the existing FastEmbed embeddings to check for semantic duplicates before writing:

upsert_note(
    title: str,
    content: str,
    directory: str,
    project: str = None,
    similarity_threshold: float = 0.85,  # cosine similarity
    on_match: str = "update",  # "update" | "merge" | "ask"
)

Behavior:

  1. Compute embedding for the new note's title + first paragraph
  2. Search existing notes using the same vector index already used by search_notes
  3. If a note exceeds similarity_threshold:
    • on_match="update": overwrite the matched note with new content (like write_note with overwrite=True, but the caller doesn't need to know the existing title)
    • on_match="merge": append new content to the matched note
    • on_match="ask": return the match and let the caller decide
  4. If no match: create a new note (same as write_note)

Return value should indicate what happened:

{
  "action": "updated_existing",
  "matched_note": "reference_bu_product_mapping",
  "similarity": 0.91,
  "path": "reference_bu_product_mapping.md"
}

Why This Fits basic-memory's Architecture

  • No LLM needed — uses the FastEmbed embeddings already computed and indexed for search_notes. Pure cosine similarity, same as the existing search pipeline.
  • Zero new dependencies — the vector search infrastructure is already there.
  • Backward compatiblewrite_note stays unchanged. upsert_note is additive.
  • Consistent with existing patternswrite_note already has overwrite logic; this just makes it semantic-aware.

Use Case

AI agents (Claude Code, Cursor, etc.) that use basic-memory as a persistent knowledge store across hundreds of sessions. Without server-side dedup, the only defense is prompting the agent to "search before write" — which works ~95% of the time but fails when context is long, the agent is in a hurry, or a subagent doesn't inherit the parent's rules.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions