yourmemory 1.2.1__tar.gz → 1.2.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (35) hide show
  1. {yourmemory-1.2.1/yourmemory.egg-info → yourmemory-1.2.2}/PKG-INFO +13 -6
  2. {yourmemory-1.2.1 → yourmemory-1.2.2}/README.md +10 -3
  3. {yourmemory-1.2.1 → yourmemory-1.2.2}/memory_mcp.py +34 -0
  4. {yourmemory-1.2.1 → yourmemory-1.2.2}/pyproject.toml +4 -3
  5. yourmemory-1.2.2/src/services/extract.py +52 -0
  6. yourmemory-1.2.2/src/services/resolve_fallback.py +192 -0
  7. {yourmemory-1.2.1 → yourmemory-1.2.2/yourmemory.egg-info}/PKG-INFO +13 -6
  8. {yourmemory-1.2.1 → yourmemory-1.2.2}/yourmemory.egg-info/SOURCES.txt +1 -0
  9. {yourmemory-1.2.1 → yourmemory-1.2.2}/yourmemory.egg-info/entry_points.txt +1 -0
  10. yourmemory-1.2.1/src/services/extract.py +0 -36
  11. {yourmemory-1.2.1 → yourmemory-1.2.2}/LICENSE +0 -0
  12. {yourmemory-1.2.1 → yourmemory-1.2.2}/setup.cfg +0 -0
  13. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/__init__.py +0 -0
  14. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/app.py +0 -0
  15. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/db/connection.py +0 -0
  16. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/db/duckdb_schema.sql +0 -0
  17. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/db/migrate.py +0 -0
  18. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/db/schema.sql +0 -0
  19. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/db/sqlite_schema.sql +0 -0
  20. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/jobs/decay_job.py +0 -0
  21. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/routes/__init__.py +0 -0
  22. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/routes/agents.py +0 -0
  23. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/routes/memories.py +0 -0
  24. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/routes/retrieve.py +0 -0
  25. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/services/__init__.py +0 -0
  26. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/services/agent_registry.py +0 -0
  27. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/services/api_keys.py +0 -0
  28. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/services/decay.py +0 -0
  29. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/services/embed.py +0 -0
  30. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/services/extract_fallback.py +0 -0
  31. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/services/resolve.py +0 -0
  32. {yourmemory-1.2.1 → yourmemory-1.2.2}/src/services/retrieve.py +0 -0
  33. {yourmemory-1.2.1 → yourmemory-1.2.2}/yourmemory.egg-info/dependency_links.txt +0 -0
  34. {yourmemory-1.2.1 → yourmemory-1.2.2}/yourmemory.egg-info/requires.txt +0 -0
  35. {yourmemory-1.2.1 → yourmemory-1.2.2}/yourmemory.egg-info/top_level.txt +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yourmemory
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: Persistent memory for Claude — Ebbinghaus forgetting curve, semantic deduplication, MCP-native
5
5
  Author-email: Sachit Misra <mishrasachit1@gmail.com>
6
6
  License: Apache License
@@ -163,8 +163,8 @@ License: Apache License
163
163
  See the License for the specific language governing permissions and
164
164
  limitations under the License.
165
165
 
166
- Project-URL: Homepage, https://github.com/sachitrafa/cognitive-ai-memory
167
- Project-URL: Repository, https://github.com/sachitrafa/cognitive-ai-memory
166
+ Project-URL: Homepage, https://github.com/sachitrafa/YourMemory
167
+ Project-URL: Repository, https://github.com/sachitrafa/YourMemory
168
168
  Keywords: mcp,claude,memory,ebbinghaus,ai,sqlite,postgresql
169
169
  Classifier: Programming Language :: Python :: 3
170
170
  Classifier: Programming Language :: Python :: 3.11
@@ -246,6 +246,8 @@ Importance additionally modulates the decay rate within each category. Memories
246
246
 
247
247
  **Zero infrastructure required** — uses DuckDB out of the box. Two commands and you're done.
248
248
 
249
+ Supports **Python 3.11, 3.12, 3.13, and 3.14**.
250
+
249
251
  ### 1. Install
250
252
 
251
253
  ```bash
@@ -286,9 +288,13 @@ Reload Claude Code (`Cmd+Shift+P` → `Developer: Reload Window`).
286
288
 
287
289
  #### Cline (VS Code)
288
290
 
289
- VS Code doesn't inherit your shell PATH, so use the **full path** from `yourmemory-path`.
291
+ VS Code doesn't inherit your shell PATH. Run this in terminal to get the exact config to paste:
292
+
293
+ ```bash
294
+ yourmemory-path
295
+ ```
290
296
 
291
- In Cline → **MCP Servers** → **Edit MCP Settings**:
297
+ Then in Cline → **MCP Servers** → **Edit MCP Settings**, paste the output. It looks like:
292
298
 
293
299
  ```json
294
300
  {
@@ -305,7 +311,7 @@ In Cline → **MCP Servers** → **Edit MCP Settings**:
305
311
  }
306
312
  ```
307
313
 
308
- Run `yourmemory-path` in terminal — it prints the exact config to paste.
314
+ Restart Cline after saving.
309
315
 
310
316
  #### Cursor
311
317
 
@@ -439,6 +445,7 @@ Runs automatically every 24 hours on startup — no cron needed. Memories below
439
445
 
440
446
  - **DuckDB** — default backend, zero setup, native vector similarity (same quality as pgvector)
441
447
  - **sentence-transformers** — local embeddings (`all-mpnet-base-v2`, 768 dims, no external service needed)
448
+ - **spaCy 3.8.13+** — local NLP for deduplication and categorization (Python 3.11–3.14 compatible)
442
449
  - **APScheduler** — automatic 24h decay job
443
450
  - **MCP** — Claude integration via Model Context Protocol
444
451
  - **PostgreSQL + pgvector** — optional, for teams / large datasets
@@ -51,6 +51,8 @@ Importance additionally modulates the decay rate within each category. Memories
51
51
 
52
52
  **Zero infrastructure required** — uses DuckDB out of the box. Two commands and you're done.
53
53
 
54
+ Supports **Python 3.11, 3.12, 3.13, and 3.14**.
55
+
54
56
  ### 1. Install
55
57
 
56
58
  ```bash
@@ -91,9 +93,13 @@ Reload Claude Code (`Cmd+Shift+P` → `Developer: Reload Window`).
91
93
 
92
94
  #### Cline (VS Code)
93
95
 
94
- VS Code doesn't inherit your shell PATH, so use the **full path** from `yourmemory-path`.
96
+ VS Code doesn't inherit your shell PATH. Run this in terminal to get the exact config to paste:
97
+
98
+ ```bash
99
+ yourmemory-path
100
+ ```
95
101
 
96
- In Cline → **MCP Servers** → **Edit MCP Settings**:
102
+ Then in Cline → **MCP Servers** → **Edit MCP Settings**, paste the output. It looks like:
97
103
 
98
104
  ```json
99
105
  {
@@ -110,7 +116,7 @@ In Cline → **MCP Servers** → **Edit MCP Settings**:
110
116
  }
111
117
  ```
112
118
 
113
- Run `yourmemory-path` in terminal — it prints the exact config to paste.
119
+ Restart Cline after saving.
114
120
 
115
121
  #### Cursor
116
122
 
@@ -244,6 +250,7 @@ Runs automatically every 24 hours on startup — no cron needed. Memories below
244
250
 
245
251
  - **DuckDB** — default backend, zero setup, native vector similarity (same quality as pgvector)
246
252
  - **sentence-transformers** — local embeddings (`all-mpnet-base-v2`, 768 dims, no external service needed)
253
+ - **spaCy 3.8.13+** — local NLP for deduplication and categorization (Python 3.11–3.14 compatible)
247
254
  - **APScheduler** — automatic 24h decay job
248
255
  - **MCP** — Claude integration via Model Context Protocol
249
256
  - **PostgreSQL + pgvector** — optional, for teams / large datasets
@@ -563,6 +563,40 @@ def print_path():
563
563
  print("Paste this into your Cline MCP settings:\n")
564
564
  print(_json.dumps(config, indent=2))
565
565
 
566
+ def setup():
567
+ """Run once after pip install to download the spaCy model."""
568
+ import subprocess
569
+ print("YourMemory setup — installing spaCy language model...")
570
+ result = subprocess.run(
571
+ [sys.executable, "-m", "spacy", "download", "en_core_web_sm"],
572
+ check=False,
573
+ )
574
+ if result.returncode == 0:
575
+ print("✓ spaCy model installed successfully.")
576
+ else:
577
+ # Fallback: install via direct wheel URL
578
+ print("Direct download fallback...")
579
+ result2 = subprocess.run(
580
+ [sys.executable, "-m", "pip", "install",
581
+ "https://github.com/explosion/spacy-models/releases/download/"
582
+ "en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl",
583
+ "--break-system-packages"],
584
+ check=False,
585
+ )
586
+ if result2.returncode == 0:
587
+ print("✓ spaCy model installed successfully.")
588
+ else:
589
+ print("✗ Could not install spaCy model automatically.")
590
+ print(" Run manually: python -m spacy download en_core_web_sm")
591
+ print(" YourMemory will still work using the built-in regex fallback.")
592
+
593
+ # Also run DB migration
594
+ from src.db.migrate import migrate
595
+ migrate()
596
+ print("✓ Database initialised.")
597
+ print("\nSetup complete. Run yourmemory-path to get your MCP config.")
598
+
599
+
566
600
  def run():
567
601
  from src.db.migrate import migrate
568
602
  migrate()
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
4
4
 
5
5
  [project]
6
6
  name = "yourmemory"
7
- version = "1.2.1"
7
+ version = "1.2.2"
8
8
  description = "Persistent memory for Claude — Ebbinghaus forgetting curve, semantic deduplication, MCP-native"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.11"
@@ -39,10 +39,11 @@ all = ["yourmemory[postgres,sse]"]
39
39
  [project.scripts]
40
40
  yourmemory = "memory_mcp:run"
41
41
  yourmemory-path = "memory_mcp:print_path"
42
+ yourmemory-setup = "memory_mcp:setup"
42
43
 
43
44
  [project.urls]
44
- Homepage = "https://github.com/sachitrafa/cognitive-ai-memory"
45
- Repository = "https://github.com/sachitrafa/cognitive-ai-memory"
45
+ Homepage = "https://github.com/sachitrafa/YourMemory"
46
+ Repository = "https://github.com/sachitrafa/YourMemory"
46
47
 
47
48
  [tool.setuptools]
48
49
  py-modules = ["memory_mcp"]
@@ -0,0 +1,52 @@
1
+ import re
2
+ import sys
3
+
4
+ _QUESTION_WORDS = {"what", "who", "where", "when", "why", "how", "which", "whose", "whom"}
5
+
6
+ _IMPERATIVE_PATTERNS = [
7
+ r'^(please|use|try|do|don\'t|make|create|add|remove|delete|update)',
8
+ r'^(convert|transform|change|modify|fix|help|show|tell)',
9
+ r'^(install|run|execute|start|stop|restart|configure)',
10
+ ]
11
+
12
+ # Load spaCy if available — falls back to regex if model not installed yet
13
+ # Run `yourmemory-setup` once after pip install to download the model
14
+ _nlp = None
15
+ try:
16
+ import spacy
17
+ _nlp = spacy.load("en_core_web_sm")
18
+ except OSError:
19
+ print(
20
+ "YourMemory: spaCy model not found. Run `yourmemory-setup` once to install it.\n"
21
+ " Falling back to built-in regex categorization.",
22
+ file=sys.stderr,
23
+ )
24
+ except Exception:
25
+ pass
26
+
27
+
28
+ def is_question(text: str) -> bool:
29
+ """Return True if the text is a question — questions are not stored as memories."""
30
+ stripped = text.strip()
31
+ if stripped.endswith("?"):
32
+ return True
33
+ first_word = re.split(r"\s+", stripped.lower())[0]
34
+ return first_word in _QUESTION_WORDS
35
+
36
+
37
+ def categorize(text: str) -> str:
38
+ """
39
+ Classify text as fact or assumption.
40
+ Uses spaCy dependency parse when available, regex heuristics otherwise.
41
+ Run `yourmemory-setup` to enable spaCy.
42
+ """
43
+ if _nlp is not None:
44
+ doc = _nlp(text)
45
+ has_subject = any(tok.dep_ in ("nsubj", "nsubjpass") for tok in doc)
46
+ return "fact" if has_subject else "assumption"
47
+
48
+ text_lower = text.lower().strip()
49
+ for pattern in _IMPERATIVE_PATTERNS:
50
+ if re.match(pattern, text_lower):
51
+ return "assumption"
52
+ return "fact"
@@ -0,0 +1,192 @@
1
+ """
2
+ Semantic deduplication for POST /memories - Fallback version without spaCy.
3
+
4
+ Detects near-duplicate memories via cosine similarity and applies one of:
5
+ - reinforce : sim ≥ 0.85 — paraphrase, bump recall_count only
6
+ - replace : 0.65–0.85 + contradiction detected — overwrite with incoming
7
+ - merge : 0.65–0.85 + no contradiction — entity-append to existing
8
+ - new : sim < 0.65 — genuinely distinct, plain INSERT
9
+ """
10
+
11
+ import json
12
+ import math
13
+ import re
14
+ from src.db.connection import get_backend
15
+
16
+ DEDUP_THRESHOLD = 0.65 # below → always new memory
17
+ REINFORCE_THRESHOLD = 0.85 # at or above → reinforce (near-identical paraphrase)
18
+
19
+ # Simple contradiction detection patterns (fallback)
20
+ _CONTRADICTION_PATTERNS = [
21
+ (r'\b(love|like|prefer|enjoy)\b', r'\b(hate|dislike|avoid)\b'),
22
+ (r'\b(start|begin|use)\b', r'\b(stop|quit|avoid)\b'),
23
+ (r'\b(want|need)\b', r'\b(refuse|reject)\b'),
24
+ (r'\b(good|great|excellent)\b', r'\b(bad|terrible|awful)\b'),
25
+ (r'\b(yes|true|correct)\b', r'\b(no|false|wrong)\b'),
26
+ ]
27
+
28
+
29
+ def _cosine(a: list, b: list) -> float:
30
+ import numpy as np
31
+ va, vb = np.array(a, dtype=float), np.array(b, dtype=float)
32
+ denom = np.linalg.norm(va) * np.linalg.norm(vb)
33
+ return float(np.dot(va, vb) / denom) if denom else 0.0
34
+
35
+
36
+ def find_near_duplicate(user_id: str, embedding: list, conn) -> dict | None:
37
+ """
38
+ Return the closest existing memory if cosine similarity >= DEDUP_THRESHOLD,
39
+ else None. Uses the caller's open connection.
40
+ """
41
+ backend = get_backend()
42
+
43
+ if backend == "postgres":
44
+ embedding_str = f"[{','.join(str(x) for x in embedding)}]"
45
+ cur = conn.cursor()
46
+ cur.execute("""
47
+ SELECT id, content, category, importance, recall_count,
48
+ 1 - (embedding <=> %s::vector) AS similarity
49
+ FROM memories
50
+ WHERE user_id = %s
51
+ ORDER BY embedding <=> %s::vector
52
+ LIMIT 1
53
+ """, (embedding_str, user_id, embedding_str))
54
+ row = cur.fetchone()
55
+ cur.close()
56
+ if row is None:
57
+ return None
58
+ sim = row[5]
59
+ if sim < DEDUP_THRESHOLD:
60
+ return None
61
+ return {"id": row[0], "content": row[1], "category": row[2],
62
+ "importance": row[3], "recall_count": row[4], "similarity": sim}
63
+
64
+ if backend == "duckdb":
65
+ from src.db.connection import duckdb_row
66
+ cur = conn.execute("""
67
+ SELECT id, content, category, importance, recall_count,
68
+ array_cosine_similarity(embedding, ?::FLOAT[768]) AS similarity
69
+ FROM memories
70
+ WHERE user_id = ?
71
+ ORDER BY similarity DESC
72
+ LIMIT 1
73
+ """, [embedding, user_id])
74
+ row = duckdb_row(cur)
75
+ if row is None or row["similarity"] < DEDUP_THRESHOLD:
76
+ return None
77
+ return row
78
+
79
+ # SQLite: numpy cosine over all user memories
80
+ cur = conn.cursor()
81
+ cur.execute("""
82
+ SELECT id, content, category, importance, recall_count, embedding
83
+ FROM memories WHERE user_id = ?
84
+ """, (user_id,))
85
+ rows = cur.fetchall()
86
+ cur.close()
87
+
88
+ best, sim = None, -1.0
89
+ for row in rows:
90
+ raw = row[5] if isinstance(row, tuple) else row["embedding"]
91
+ if raw is None:
92
+ continue
93
+ s = _cosine(embedding, json.loads(raw))
94
+ if s > sim:
95
+ sim, best = s, row
96
+ if best is None or sim < DEDUP_THRESHOLD:
97
+ return None
98
+ return {"id": best[0], "content": best[1], "category": best[2],
99
+ "importance": best[3], "recall_count": best[4], "similarity": sim}
100
+
101
+
102
+ def detect_contradiction(existing_text: str, incoming_text: str) -> bool:
103
+ """
104
+ Fallback contradiction detection using regex patterns.
105
+ Return True if the incoming text contradicts the existing one.
106
+ """
107
+ existing_lower = existing_text.lower()
108
+ incoming_lower = incoming_text.lower()
109
+
110
+ for positive_pattern, negative_pattern in _CONTRADICTION_PATTERNS:
111
+ # Check if existing has positive and incoming has negative
112
+ if re.search(positive_pattern, existing_lower) and re.search(negative_pattern, incoming_lower):
113
+ return True
114
+ # Check if existing has negative and incoming has positive
115
+ if re.search(negative_pattern, existing_lower) and re.search(positive_pattern, incoming_lower):
116
+ return True
117
+
118
+ return False
119
+
120
+
121
+ def merge_entities(existing_text: str, incoming_text: str) -> str:
122
+ """
123
+ Fallback entity merging using simple heuristics.
124
+ Append capitalized words and quoted strings from incoming that are absent from existing.
125
+ Returns the merged string, or existing_text unchanged if nothing new found.
126
+ """
127
+ existing_lower = existing_text.lower()
128
+
129
+ # Extract potential entities using simple patterns
130
+ candidates = []
131
+
132
+ # Capitalized words (potential proper nouns)
133
+ capitalized_words = re.findall(r'\b[A-Z][a-zA-Z]{2,}\b', incoming_text)
134
+ candidates.extend(capitalized_words)
135
+
136
+ # Quoted strings
137
+ quoted_strings = re.findall(r'"([^"]+)"', incoming_text)
138
+ quoted_strings.extend(re.findall(r"'([^']+)'", incoming_text))
139
+ candidates.extend(quoted_strings)
140
+
141
+ # Technical terms (words with numbers, dots, underscores)
142
+ tech_terms = re.findall(r'\b[a-zA-Z][a-zA-Z0-9._-]*[a-zA-Z0-9]\b', incoming_text)
143
+ candidates.extend([t for t in tech_terms if '.' in t or '_' in t or any(c.isdigit() for c in t)])
144
+
145
+ # Filter out terms already present in existing text
146
+ new_terms = [t for t in candidates if t.lower() not in existing_lower and len(t.strip()) > 2]
147
+
148
+ # Deduplicate while preserving order
149
+ seen, deduped = set(), []
150
+ for t in new_terms:
151
+ if t.lower() not in seen:
152
+ seen.add(t.lower())
153
+ deduped.append(t)
154
+
155
+ if not deduped:
156
+ return existing_text
157
+ if len(deduped) == 1:
158
+ return f"{existing_text} with {deduped[0]}"
159
+ return f"{existing_text} with {', '.join(deduped[:-1])} and {deduped[-1]}"
160
+
161
+
162
+ def resolve(user_id: str, content: str, embedding: list, conn) -> dict:
163
+ """
164
+ Facade: decide what to do with an incoming memory.
165
+
166
+ Returns:
167
+ {
168
+ "action": "new" | "reinforce" | "replace" | "merge",
169
+ "content": str, # final content to store/update
170
+ "existing": dict | None, # matched row if any
171
+ }
172
+ """
173
+ match = find_near_duplicate(user_id, embedding, conn)
174
+
175
+ if match is None:
176
+ return {"action": "new", "content": content, "existing": None}
177
+
178
+ sim = match["similarity"]
179
+
180
+ if sim >= REINFORCE_THRESHOLD:
181
+ return {"action": "reinforce", "content": match["content"], "existing": match}
182
+
183
+ # DEDUP_THRESHOLD ≤ sim < REINFORCE_THRESHOLD
184
+ if detect_contradiction(match["content"], content):
185
+ return {"action": "replace", "content": content, "existing": match}
186
+
187
+ merged = merge_entities(match["content"], content)
188
+ if merged == match["content"]:
189
+ # No new entities found — treat as paraphrase
190
+ return {"action": "reinforce", "content": match["content"], "existing": match}
191
+
192
+ return {"action": "merge", "content": merged, "existing": match}
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: yourmemory
3
- Version: 1.2.1
3
+ Version: 1.2.2
4
4
  Summary: Persistent memory for Claude — Ebbinghaus forgetting curve, semantic deduplication, MCP-native
5
5
  Author-email: Sachit Misra <mishrasachit1@gmail.com>
6
6
  License: Apache License
@@ -163,8 +163,8 @@ License: Apache License
163
163
  See the License for the specific language governing permissions and
164
164
  limitations under the License.
165
165
 
166
- Project-URL: Homepage, https://github.com/sachitrafa/cognitive-ai-memory
167
- Project-URL: Repository, https://github.com/sachitrafa/cognitive-ai-memory
166
+ Project-URL: Homepage, https://github.com/sachitrafa/YourMemory
167
+ Project-URL: Repository, https://github.com/sachitrafa/YourMemory
168
168
  Keywords: mcp,claude,memory,ebbinghaus,ai,sqlite,postgresql
169
169
  Classifier: Programming Language :: Python :: 3
170
170
  Classifier: Programming Language :: Python :: 3.11
@@ -246,6 +246,8 @@ Importance additionally modulates the decay rate within each category. Memories
246
246
 
247
247
  **Zero infrastructure required** — uses DuckDB out of the box. Two commands and you're done.
248
248
 
249
+ Supports **Python 3.11, 3.12, 3.13, and 3.14**.
250
+
249
251
  ### 1. Install
250
252
 
251
253
  ```bash
@@ -286,9 +288,13 @@ Reload Claude Code (`Cmd+Shift+P` → `Developer: Reload Window`).
286
288
 
287
289
  #### Cline (VS Code)
288
290
 
289
- VS Code doesn't inherit your shell PATH, so use the **full path** from `yourmemory-path`.
291
+ VS Code doesn't inherit your shell PATH. Run this in terminal to get the exact config to paste:
292
+
293
+ ```bash
294
+ yourmemory-path
295
+ ```
290
296
 
291
- In Cline → **MCP Servers** → **Edit MCP Settings**:
297
+ Then in Cline → **MCP Servers** → **Edit MCP Settings**, paste the output. It looks like:
292
298
 
293
299
  ```json
294
300
  {
@@ -305,7 +311,7 @@ In Cline → **MCP Servers** → **Edit MCP Settings**:
305
311
  }
306
312
  ```
307
313
 
308
- Run `yourmemory-path` in terminal — it prints the exact config to paste.
314
+ Restart Cline after saving.
309
315
 
310
316
  #### Cursor
311
317
 
@@ -439,6 +445,7 @@ Runs automatically every 24 hours on startup — no cron needed. Memories below
439
445
 
440
446
  - **DuckDB** — default backend, zero setup, native vector similarity (same quality as pgvector)
441
447
  - **sentence-transformers** — local embeddings (`all-mpnet-base-v2`, 768 dims, no external service needed)
448
+ - **spaCy 3.8.13+** — local NLP for deduplication and categorization (Python 3.11–3.14 compatible)
442
449
  - **APScheduler** — automatic 24h decay job
443
450
  - **MCP** — Claude integration via Model Context Protocol
444
451
  - **PostgreSQL + pgvector** — optional, for teams / large datasets
@@ -22,6 +22,7 @@ src/services/embed.py
22
22
  src/services/extract.py
23
23
  src/services/extract_fallback.py
24
24
  src/services/resolve.py
25
+ src/services/resolve_fallback.py
25
26
  src/services/retrieve.py
26
27
  yourmemory.egg-info/PKG-INFO
27
28
  yourmemory.egg-info/SOURCES.txt
@@ -1,3 +1,4 @@
1
1
  [console_scripts]
2
2
  yourmemory = memory_mcp:run
3
3
  yourmemory-path = memory_mcp:print_path
4
+ yourmemory-setup = memory_mcp:setup
@@ -1,36 +0,0 @@
1
- import re
2
- import spacy
3
-
4
- try:
5
- _nlp = spacy.load("en_core_web_sm")
6
- except OSError:
7
- import subprocess, sys
8
- subprocess.run(
9
- [sys.executable, "-m", "pip", "install",
10
- "https://github.com/explosion/spacy-models/releases/download/"
11
- "en_core_web_sm-3.8.0/en_core_web_sm-3.8.0-py3-none-any.whl"],
12
- check=True,
13
- )
14
- _nlp = spacy.load("en_core_web_sm")
15
-
16
- _QUESTION_WORDS = {"what", "who", "where", "when", "why", "how", "which", "whose", "whom"}
17
-
18
-
19
- def is_question(text: str) -> bool:
20
- """Return True if the text is a question — questions are not stored as memories."""
21
- stripped = text.strip()
22
- if stripped.endswith("?"):
23
- return True
24
- first_word = re.split(r"\s+", stripped.lower())[0]
25
- return first_word in _QUESTION_WORDS
26
-
27
-
28
- def categorize(text: str) -> str:
29
- """
30
- Use spaCy dependency parse to classify:
31
- fact — declarative sentence with an explicit subject
32
- assumption — imperative sentence with no subject (command/instruction)
33
- """
34
- doc = _nlp(text)
35
- has_subject = any(tok.dep_ in ("nsubj", "nsubjpass") for tok in doc)
36
- return "fact" if has_subject else "assumption"
File without changes
File without changes
File without changes
File without changes
File without changes
File without changes