#755 Phase 5. create_note / create_task now BLOCK a near-duplicate instead of
silently inserting: they return {"duplicate": true, "existing_id", message}
pointing at the record to UPDATE. Fights store bloat and stale competing copies
that semantic search (RAG) would otherwise resurface for reconciliation. A
force=true override creates anyway for genuinely-distinct records.
- services/dedup.py: find_duplicate_note — two signals, scoped to owner + same
project + same kind: (1) normalized-title exact match (cheap, always); (2)
semantic cosine ≥ 0.90 but ONLY when body ≥ 200 chars (short/title-only
embeddings false-positive — the pre-pivot lesson). Project-less (orphan)
records compare only to other orphans on BOTH signals (orphan_only on the
semantic call) — they're not matched across every project.
- Gate wired into the MCP create_note/create_task tools (the LLM write path)
with force override; _INSTRUCTIONS documents the duplicate response + force.
- Opt-in by design: the service helper is only called from the interactive
create tools. Internal/programmatic creates (recurrence spawn, imports) go
straight through services.create_note and are NOT gated — a recurring task
spawning its next same-titled instance must not be blocked.
- Scope v1: MCP tools only. REST/web (human CRUD, needs a UI affordance) and
create_rule (not a RAG surface; _INSTRUCTIONS already steer it) are follow-ups.
- tests: dedup service (title/semantic/body-gate/type-filter) + tool gate
(blocks, force bypasses) for notes and tasks.
Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>