yourmemory 1.2.0__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.
- {yourmemory-1.2.0/yourmemory.egg-info → yourmemory-1.2.2}/PKG-INFO +70 -57
- {yourmemory-1.2.0 → yourmemory-1.2.2}/README.md +66 -53
- {yourmemory-1.2.0 → yourmemory-1.2.2}/memory_mcp.py +34 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/pyproject.toml +5 -4
- yourmemory-1.2.2/src/services/extract.py +52 -0
- yourmemory-1.2.2/src/services/extract_fallback.py +34 -0
- yourmemory-1.2.2/src/services/resolve_fallback.py +192 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2/yourmemory.egg-info}/PKG-INFO +70 -57
- {yourmemory-1.2.0 → yourmemory-1.2.2}/yourmemory.egg-info/SOURCES.txt +2 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/yourmemory.egg-info/entry_points.txt +1 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/yourmemory.egg-info/requires.txt +1 -1
- yourmemory-1.2.0/src/services/extract.py +0 -38
- {yourmemory-1.2.0 → yourmemory-1.2.2}/LICENSE +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/setup.cfg +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/__init__.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/app.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/db/connection.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/db/duckdb_schema.sql +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/db/migrate.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/db/schema.sql +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/db/sqlite_schema.sql +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/jobs/decay_job.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/routes/__init__.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/routes/agents.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/routes/memories.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/routes/retrieve.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/services/__init__.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/services/agent_registry.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/services/api_keys.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/services/decay.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/services/embed.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/services/resolve.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/src/services/retrieve.py +0 -0
- {yourmemory-1.2.0 → yourmemory-1.2.2}/yourmemory.egg-info/dependency_links.txt +0 -0
- {yourmemory-1.2.0 → 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.
|
|
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/
|
|
167
|
-
Project-URL: Repository, https://github.com/sachitrafa/
|
|
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
|
|
@@ -181,7 +181,7 @@ Requires-Dist: numpy
|
|
|
181
181
|
Requires-Dist: python-dateutil
|
|
182
182
|
Requires-Dist: duckdb>=0.10.0
|
|
183
183
|
Requires-Dist: apscheduler
|
|
184
|
-
Requires-Dist: spacy<4.0,>=3.
|
|
184
|
+
Requires-Dist: spacy<4.0,>=3.8.13
|
|
185
185
|
Provides-Extra: postgres
|
|
186
186
|
Requires-Dist: psycopg2-binary; extra == "postgres"
|
|
187
187
|
Requires-Dist: pgvector; extra == "postgres"
|
|
@@ -197,7 +197,7 @@ Dynamic: license-file
|
|
|
197
197
|
|
|
198
198
|
**+16pp better recall than Mem0 on LoCoMo. 100% stale memory precision. Biologically-inspired memory decay for AI agents.**
|
|
199
199
|
|
|
200
|
-
Persistent memory for Claude
|
|
200
|
+
Persistent memory for Claude and any MCP-compatible AI — works like human memory. Important things stick, forgotten things fade, outdated facts get pruned automatically.
|
|
201
201
|
|
|
202
202
|
> Early stage — feedback and ideas welcome.
|
|
203
203
|
|
|
@@ -244,7 +244,9 @@ Importance additionally modulates the decay rate within each category. Memories
|
|
|
244
244
|
|
|
245
245
|
## Setup
|
|
246
246
|
|
|
247
|
-
**Zero infrastructure required** — uses
|
|
247
|
+
**Zero infrastructure required** — uses DuckDB out of the box. Two commands and you're done.
|
|
248
|
+
|
|
249
|
+
Supports **Python 3.11, 3.12, 3.13, and 3.14**.
|
|
248
250
|
|
|
249
251
|
### 1. Install
|
|
250
252
|
|
|
@@ -252,11 +254,21 @@ Importance additionally modulates the decay rate within each category. Memories
|
|
|
252
254
|
pip install yourmemory
|
|
253
255
|
```
|
|
254
256
|
|
|
255
|
-
All dependencies
|
|
257
|
+
All dependencies installed automatically. No clone, no Docker, no database setup.
|
|
258
|
+
|
|
259
|
+
### 2. Get your config
|
|
260
|
+
|
|
261
|
+
Run this once to get your exact config:
|
|
256
262
|
|
|
257
|
-
|
|
263
|
+
```bash
|
|
264
|
+
yourmemory-path
|
|
265
|
+
```
|
|
258
266
|
|
|
259
|
-
|
|
267
|
+
It prints your full executable path and a ready-to-paste config for any MCP client. Copy it.
|
|
268
|
+
|
|
269
|
+
### 3. Wire into your AI client
|
|
270
|
+
|
|
271
|
+
The database is created automatically at `~/.yourmemory/memories.duckdb` on first use.
|
|
260
272
|
|
|
261
273
|
#### Claude Code
|
|
262
274
|
|
|
@@ -276,26 +288,49 @@ Reload Claude Code (`Cmd+Shift+P` → `Developer: Reload Window`).
|
|
|
276
288
|
|
|
277
289
|
#### Cline (VS Code)
|
|
278
290
|
|
|
279
|
-
|
|
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
|
+
```
|
|
296
|
+
|
|
297
|
+
Then in Cline → **MCP Servers** → **Edit MCP Settings**, paste the output. It looks like:
|
|
280
298
|
|
|
281
299
|
```json
|
|
282
300
|
{
|
|
283
301
|
"mcpServers": {
|
|
284
302
|
"yourmemory": {
|
|
285
|
-
"command": "yourmemory",
|
|
303
|
+
"command": "/full/path/to/yourmemory",
|
|
286
304
|
"args": [],
|
|
287
305
|
"env": {
|
|
288
|
-
"
|
|
289
|
-
"
|
|
306
|
+
"YOURMEMORY_USER": "your_name",
|
|
307
|
+
"DATABASE_URL": ""
|
|
290
308
|
}
|
|
291
309
|
}
|
|
292
310
|
}
|
|
293
311
|
}
|
|
294
312
|
```
|
|
295
313
|
|
|
296
|
-
|
|
314
|
+
Restart Cline after saving.
|
|
315
|
+
|
|
316
|
+
#### Cursor
|
|
297
317
|
|
|
298
|
-
|
|
318
|
+
Add to `~/.cursor/mcp.json`:
|
|
319
|
+
|
|
320
|
+
```json
|
|
321
|
+
{
|
|
322
|
+
"mcpServers": {
|
|
323
|
+
"yourmemory": {
|
|
324
|
+
"command": "/full/path/to/yourmemory",
|
|
325
|
+
"args": [],
|
|
326
|
+
"env": {
|
|
327
|
+
"YOURMEMORY_USER": "your_name",
|
|
328
|
+
"DATABASE_URL": ""
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
299
334
|
|
|
300
335
|
#### Claude Desktop
|
|
301
336
|
|
|
@@ -315,9 +350,9 @@ Restart Claude Desktop.
|
|
|
315
350
|
|
|
316
351
|
#### Any MCP-compatible client
|
|
317
352
|
|
|
318
|
-
YourMemory is a standard stdio MCP server.
|
|
353
|
+
YourMemory is a standard stdio MCP server. Works with Claude Code, Claude Desktop, Cline, Cursor, Windsurf, Continue, and Zed. Use the full path from `yourmemory-path` if the client doesn't inherit shell PATH.
|
|
319
354
|
|
|
320
|
-
###
|
|
355
|
+
### 4. Add memory instructions to your project
|
|
321
356
|
|
|
322
357
|
Copy `sample_CLAUDE.md` into your project root as `CLAUDE.md` and replace:
|
|
323
358
|
- `YOUR_NAME` — your name (e.g. `Alice`)
|
|
@@ -329,13 +364,19 @@ Claude will now follow the recall → store → update workflow automatically on
|
|
|
329
364
|
|
|
330
365
|
### PostgreSQL (optional — for teams or large datasets)
|
|
331
366
|
|
|
332
|
-
|
|
367
|
+
Install with Postgres support:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
pip install yourmemory[postgres]
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Then create a `.env` file:
|
|
333
374
|
|
|
334
375
|
```bash
|
|
335
376
|
DATABASE_URL=postgresql://YOUR_USER@localhost:5432/yourmemory
|
|
336
377
|
```
|
|
337
378
|
|
|
338
|
-
The backend is selected automatically — `postgresql://` in `DATABASE_URL` → Postgres + pgvector, anything else →
|
|
379
|
+
The backend is selected automatically — `postgresql://` in `DATABASE_URL` → Postgres + pgvector, anything else → DuckDB.
|
|
339
380
|
|
|
340
381
|
**macOS**
|
|
341
382
|
```bash
|
|
@@ -349,8 +390,6 @@ sudo apt install postgresql postgresql-contrib postgresql-16-pgvector
|
|
|
349
390
|
createdb yourmemory
|
|
350
391
|
```
|
|
351
392
|
|
|
352
|
-
> **One-liner setup script** (macOS/Linux): `bash scripts/setup_db.sh` handles install + DB creation automatically.
|
|
353
|
-
|
|
354
393
|
---
|
|
355
394
|
|
|
356
395
|
## MCP Tools
|
|
@@ -402,47 +441,21 @@ Runs automatically every 24 hours on startup — no cron needed. Memories below
|
|
|
402
441
|
|
|
403
442
|
---
|
|
404
443
|
|
|
405
|
-
## REST API
|
|
406
|
-
|
|
407
|
-
```bash
|
|
408
|
-
# Store
|
|
409
|
-
curl -X POST http://localhost:8000/memories \
|
|
410
|
-
-H "Content-Type: application/json" \
|
|
411
|
-
-d '{"userId":"u1","content":"Prefers dark mode","importance":0.8}'
|
|
412
|
-
|
|
413
|
-
# Retrieve
|
|
414
|
-
curl -X POST http://localhost:8000/retrieve \
|
|
415
|
-
-H "Content-Type: application/json" \
|
|
416
|
-
-d '{"userId":"u1","query":"UI preferences"}'
|
|
417
|
-
|
|
418
|
-
# List all
|
|
419
|
-
curl "http://localhost:8000/memories?userId=u1"
|
|
420
|
-
|
|
421
|
-
# Update
|
|
422
|
-
curl -X PUT http://localhost:8000/memories/42 \
|
|
423
|
-
-H "Content-Type: application/json" \
|
|
424
|
-
-d '{"content":"Prefers dark mode in all apps","importance":0.85}'
|
|
425
|
-
|
|
426
|
-
# Delete
|
|
427
|
-
curl -X DELETE http://localhost:8000/memories/42
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
---
|
|
431
|
-
|
|
432
444
|
## Stack
|
|
433
445
|
|
|
434
|
-
- **
|
|
446
|
+
- **DuckDB** — default backend, zero setup, native vector similarity (same quality as pgvector)
|
|
435
447
|
- **sentence-transformers** — local embeddings (`all-mpnet-base-v2`, 768 dims, no external service needed)
|
|
436
|
-
- **
|
|
448
|
+
- **spaCy 3.8.13+** — local NLP for deduplication and categorization (Python 3.11–3.14 compatible)
|
|
437
449
|
- **APScheduler** — automatic 24h decay job
|
|
438
450
|
- **MCP** — Claude integration via Model Context Protocol
|
|
451
|
+
- **PostgreSQL + pgvector** — optional, for teams / large datasets
|
|
439
452
|
|
|
440
453
|
---
|
|
441
454
|
|
|
442
455
|
## Architecture
|
|
443
456
|
|
|
444
457
|
```
|
|
445
|
-
Claude
|
|
458
|
+
Claude / Cline / Cursor / Any MCP client
|
|
446
459
|
│
|
|
447
460
|
├── recall_memory(query)
|
|
448
461
|
│ └── embed → cosine similarity → score = sim × strength → top-k
|
|
@@ -455,12 +468,12 @@ Claude Code
|
|
|
455
468
|
└── update_memory(id, new_content)
|
|
456
469
|
└── embed(new_content) → UPDATE memories
|
|
457
470
|
|
|
458
|
-
PostgreSQL (
|
|
459
|
-
└── memories
|
|
460
|
-
├── embedding vector(768)
|
|
461
|
-
├── importance float
|
|
462
|
-
├── recall_count int
|
|
463
|
-
└── last_accessed_at
|
|
471
|
+
DuckDB (default) PostgreSQL + pgvector (optional)
|
|
472
|
+
└── memories.duckdb └── memories table
|
|
473
|
+
├── embedding FLOAT[768] ├── embedding vector(768)
|
|
474
|
+
├── importance FLOAT ├── importance float
|
|
475
|
+
├── recall_count INTEGER ├── recall_count int
|
|
476
|
+
└── last_accessed_at └── last_accessed_at
|
|
464
477
|
```
|
|
465
478
|
|
|
466
479
|
---
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
**+16pp better recall than Mem0 on LoCoMo. 100% stale memory precision. Biologically-inspired memory decay for AI agents.**
|
|
4
4
|
|
|
5
|
-
Persistent memory for Claude
|
|
5
|
+
Persistent memory for Claude and any MCP-compatible AI — works like human memory. Important things stick, forgotten things fade, outdated facts get pruned automatically.
|
|
6
6
|
|
|
7
7
|
> Early stage — feedback and ideas welcome.
|
|
8
8
|
|
|
@@ -49,7 +49,9 @@ Importance additionally modulates the decay rate within each category. Memories
|
|
|
49
49
|
|
|
50
50
|
## Setup
|
|
51
51
|
|
|
52
|
-
**Zero infrastructure required** — uses
|
|
52
|
+
**Zero infrastructure required** — uses DuckDB out of the box. Two commands and you're done.
|
|
53
|
+
|
|
54
|
+
Supports **Python 3.11, 3.12, 3.13, and 3.14**.
|
|
53
55
|
|
|
54
56
|
### 1. Install
|
|
55
57
|
|
|
@@ -57,11 +59,21 @@ Importance additionally modulates the decay rate within each category. Memories
|
|
|
57
59
|
pip install yourmemory
|
|
58
60
|
```
|
|
59
61
|
|
|
60
|
-
All dependencies
|
|
62
|
+
All dependencies installed automatically. No clone, no Docker, no database setup.
|
|
63
|
+
|
|
64
|
+
### 2. Get your config
|
|
65
|
+
|
|
66
|
+
Run this once to get your exact config:
|
|
61
67
|
|
|
62
|
-
|
|
68
|
+
```bash
|
|
69
|
+
yourmemory-path
|
|
70
|
+
```
|
|
63
71
|
|
|
64
|
-
|
|
72
|
+
It prints your full executable path and a ready-to-paste config for any MCP client. Copy it.
|
|
73
|
+
|
|
74
|
+
### 3. Wire into your AI client
|
|
75
|
+
|
|
76
|
+
The database is created automatically at `~/.yourmemory/memories.duckdb` on first use.
|
|
65
77
|
|
|
66
78
|
#### Claude Code
|
|
67
79
|
|
|
@@ -81,26 +93,49 @@ Reload Claude Code (`Cmd+Shift+P` → `Developer: Reload Window`).
|
|
|
81
93
|
|
|
82
94
|
#### Cline (VS Code)
|
|
83
95
|
|
|
84
|
-
|
|
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
|
+
```
|
|
101
|
+
|
|
102
|
+
Then in Cline → **MCP Servers** → **Edit MCP Settings**, paste the output. It looks like:
|
|
85
103
|
|
|
86
104
|
```json
|
|
87
105
|
{
|
|
88
106
|
"mcpServers": {
|
|
89
107
|
"yourmemory": {
|
|
90
|
-
"command": "yourmemory",
|
|
108
|
+
"command": "/full/path/to/yourmemory",
|
|
91
109
|
"args": [],
|
|
92
110
|
"env": {
|
|
93
|
-
"
|
|
94
|
-
"
|
|
111
|
+
"YOURMEMORY_USER": "your_name",
|
|
112
|
+
"DATABASE_URL": ""
|
|
95
113
|
}
|
|
96
114
|
}
|
|
97
115
|
}
|
|
98
116
|
}
|
|
99
117
|
```
|
|
100
118
|
|
|
101
|
-
|
|
119
|
+
Restart Cline after saving.
|
|
120
|
+
|
|
121
|
+
#### Cursor
|
|
102
122
|
|
|
103
|
-
|
|
123
|
+
Add to `~/.cursor/mcp.json`:
|
|
124
|
+
|
|
125
|
+
```json
|
|
126
|
+
{
|
|
127
|
+
"mcpServers": {
|
|
128
|
+
"yourmemory": {
|
|
129
|
+
"command": "/full/path/to/yourmemory",
|
|
130
|
+
"args": [],
|
|
131
|
+
"env": {
|
|
132
|
+
"YOURMEMORY_USER": "your_name",
|
|
133
|
+
"DATABASE_URL": ""
|
|
134
|
+
}
|
|
135
|
+
}
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
```
|
|
104
139
|
|
|
105
140
|
#### Claude Desktop
|
|
106
141
|
|
|
@@ -120,9 +155,9 @@ Restart Claude Desktop.
|
|
|
120
155
|
|
|
121
156
|
#### Any MCP-compatible client
|
|
122
157
|
|
|
123
|
-
YourMemory is a standard stdio MCP server.
|
|
158
|
+
YourMemory is a standard stdio MCP server. Works with Claude Code, Claude Desktop, Cline, Cursor, Windsurf, Continue, and Zed. Use the full path from `yourmemory-path` if the client doesn't inherit shell PATH.
|
|
124
159
|
|
|
125
|
-
###
|
|
160
|
+
### 4. Add memory instructions to your project
|
|
126
161
|
|
|
127
162
|
Copy `sample_CLAUDE.md` into your project root as `CLAUDE.md` and replace:
|
|
128
163
|
- `YOUR_NAME` — your name (e.g. `Alice`)
|
|
@@ -134,13 +169,19 @@ Claude will now follow the recall → store → update workflow automatically on
|
|
|
134
169
|
|
|
135
170
|
### PostgreSQL (optional — for teams or large datasets)
|
|
136
171
|
|
|
137
|
-
|
|
172
|
+
Install with Postgres support:
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
pip install yourmemory[postgres]
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
Then create a `.env` file:
|
|
138
179
|
|
|
139
180
|
```bash
|
|
140
181
|
DATABASE_URL=postgresql://YOUR_USER@localhost:5432/yourmemory
|
|
141
182
|
```
|
|
142
183
|
|
|
143
|
-
The backend is selected automatically — `postgresql://` in `DATABASE_URL` → Postgres + pgvector, anything else →
|
|
184
|
+
The backend is selected automatically — `postgresql://` in `DATABASE_URL` → Postgres + pgvector, anything else → DuckDB.
|
|
144
185
|
|
|
145
186
|
**macOS**
|
|
146
187
|
```bash
|
|
@@ -154,8 +195,6 @@ sudo apt install postgresql postgresql-contrib postgresql-16-pgvector
|
|
|
154
195
|
createdb yourmemory
|
|
155
196
|
```
|
|
156
197
|
|
|
157
|
-
> **One-liner setup script** (macOS/Linux): `bash scripts/setup_db.sh` handles install + DB creation automatically.
|
|
158
|
-
|
|
159
198
|
---
|
|
160
199
|
|
|
161
200
|
## MCP Tools
|
|
@@ -207,47 +246,21 @@ Runs automatically every 24 hours on startup — no cron needed. Memories below
|
|
|
207
246
|
|
|
208
247
|
---
|
|
209
248
|
|
|
210
|
-
## REST API
|
|
211
|
-
|
|
212
|
-
```bash
|
|
213
|
-
# Store
|
|
214
|
-
curl -X POST http://localhost:8000/memories \
|
|
215
|
-
-H "Content-Type: application/json" \
|
|
216
|
-
-d '{"userId":"u1","content":"Prefers dark mode","importance":0.8}'
|
|
217
|
-
|
|
218
|
-
# Retrieve
|
|
219
|
-
curl -X POST http://localhost:8000/retrieve \
|
|
220
|
-
-H "Content-Type: application/json" \
|
|
221
|
-
-d '{"userId":"u1","query":"UI preferences"}'
|
|
222
|
-
|
|
223
|
-
# List all
|
|
224
|
-
curl "http://localhost:8000/memories?userId=u1"
|
|
225
|
-
|
|
226
|
-
# Update
|
|
227
|
-
curl -X PUT http://localhost:8000/memories/42 \
|
|
228
|
-
-H "Content-Type: application/json" \
|
|
229
|
-
-d '{"content":"Prefers dark mode in all apps","importance":0.85}'
|
|
230
|
-
|
|
231
|
-
# Delete
|
|
232
|
-
curl -X DELETE http://localhost:8000/memories/42
|
|
233
|
-
```
|
|
234
|
-
|
|
235
|
-
---
|
|
236
|
-
|
|
237
249
|
## Stack
|
|
238
250
|
|
|
239
|
-
- **
|
|
251
|
+
- **DuckDB** — default backend, zero setup, native vector similarity (same quality as pgvector)
|
|
240
252
|
- **sentence-transformers** — local embeddings (`all-mpnet-base-v2`, 768 dims, no external service needed)
|
|
241
|
-
- **
|
|
253
|
+
- **spaCy 3.8.13+** — local NLP for deduplication and categorization (Python 3.11–3.14 compatible)
|
|
242
254
|
- **APScheduler** — automatic 24h decay job
|
|
243
255
|
- **MCP** — Claude integration via Model Context Protocol
|
|
256
|
+
- **PostgreSQL + pgvector** — optional, for teams / large datasets
|
|
244
257
|
|
|
245
258
|
---
|
|
246
259
|
|
|
247
260
|
## Architecture
|
|
248
261
|
|
|
249
262
|
```
|
|
250
|
-
Claude
|
|
263
|
+
Claude / Cline / Cursor / Any MCP client
|
|
251
264
|
│
|
|
252
265
|
├── recall_memory(query)
|
|
253
266
|
│ └── embed → cosine similarity → score = sim × strength → top-k
|
|
@@ -260,12 +273,12 @@ Claude Code
|
|
|
260
273
|
└── update_memory(id, new_content)
|
|
261
274
|
└── embed(new_content) → UPDATE memories
|
|
262
275
|
|
|
263
|
-
PostgreSQL (
|
|
264
|
-
└── memories
|
|
265
|
-
├── embedding vector(768)
|
|
266
|
-
├── importance float
|
|
267
|
-
├── recall_count int
|
|
268
|
-
└── last_accessed_at
|
|
276
|
+
DuckDB (default) PostgreSQL + pgvector (optional)
|
|
277
|
+
└── memories.duckdb └── memories table
|
|
278
|
+
├── embedding FLOAT[768] ├── embedding vector(768)
|
|
279
|
+
├── importance FLOAT ├── importance float
|
|
280
|
+
├── recall_count INTEGER ├── recall_count int
|
|
281
|
+
└── last_accessed_at └── last_accessed_at
|
|
269
282
|
```
|
|
270
283
|
|
|
271
284
|
---
|
|
@@ -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.
|
|
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"
|
|
@@ -28,7 +28,7 @@ dependencies = [
|
|
|
28
28
|
"python-dateutil",
|
|
29
29
|
"duckdb>=0.10.0",
|
|
30
30
|
"apscheduler",
|
|
31
|
-
"spacy>=3.
|
|
31
|
+
"spacy>=3.8.13,<4.0",
|
|
32
32
|
]
|
|
33
33
|
|
|
34
34
|
[project.optional-dependencies]
|
|
@@ -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/
|
|
45
|
-
Repository = "https://github.com/sachitrafa/
|
|
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,34 @@
|
|
|
1
|
+
import re
|
|
2
|
+
|
|
3
|
+
_QUESTION_WORDS = {"what", "who", "where", "when", "why", "how", "which", "whose", "whom"}
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
def is_question(text: str) -> bool:
|
|
7
|
+
"""Return True if the text is a question — questions are not stored as memories."""
|
|
8
|
+
stripped = text.strip()
|
|
9
|
+
if stripped.endswith("?"):
|
|
10
|
+
return True
|
|
11
|
+
first_word = re.split(r"\s+", stripped.lower())[0]
|
|
12
|
+
return first_word in _QUESTION_WORDS
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def categorize(text: str) -> str:
|
|
16
|
+
"""
|
|
17
|
+
Fallback categorization without spaCy:
|
|
18
|
+
Simple heuristics to classify as fact vs assumption
|
|
19
|
+
"""
|
|
20
|
+
text_lower = text.lower().strip()
|
|
21
|
+
|
|
22
|
+
# Commands/imperatives typically start with verbs or "please"
|
|
23
|
+
imperative_patterns = [
|
|
24
|
+
r'^(please|use|try|do|don\'t|make|create|add|remove|delete|update)',
|
|
25
|
+
r'^(convert|transform|change|modify|fix|help|show|tell)',
|
|
26
|
+
r'^(install|run|execute|start|stop|restart|configure)'
|
|
27
|
+
]
|
|
28
|
+
|
|
29
|
+
for pattern in imperative_patterns:
|
|
30
|
+
if re.match(pattern, text_lower):
|
|
31
|
+
return "assumption"
|
|
32
|
+
|
|
33
|
+
# Default to fact for declarative statements
|
|
34
|
+
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.
|
|
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/
|
|
167
|
-
Project-URL: Repository, https://github.com/sachitrafa/
|
|
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
|
|
@@ -181,7 +181,7 @@ Requires-Dist: numpy
|
|
|
181
181
|
Requires-Dist: python-dateutil
|
|
182
182
|
Requires-Dist: duckdb>=0.10.0
|
|
183
183
|
Requires-Dist: apscheduler
|
|
184
|
-
Requires-Dist: spacy<4.0,>=3.
|
|
184
|
+
Requires-Dist: spacy<4.0,>=3.8.13
|
|
185
185
|
Provides-Extra: postgres
|
|
186
186
|
Requires-Dist: psycopg2-binary; extra == "postgres"
|
|
187
187
|
Requires-Dist: pgvector; extra == "postgres"
|
|
@@ -197,7 +197,7 @@ Dynamic: license-file
|
|
|
197
197
|
|
|
198
198
|
**+16pp better recall than Mem0 on LoCoMo. 100% stale memory precision. Biologically-inspired memory decay for AI agents.**
|
|
199
199
|
|
|
200
|
-
Persistent memory for Claude
|
|
200
|
+
Persistent memory for Claude and any MCP-compatible AI — works like human memory. Important things stick, forgotten things fade, outdated facts get pruned automatically.
|
|
201
201
|
|
|
202
202
|
> Early stage — feedback and ideas welcome.
|
|
203
203
|
|
|
@@ -244,7 +244,9 @@ Importance additionally modulates the decay rate within each category. Memories
|
|
|
244
244
|
|
|
245
245
|
## Setup
|
|
246
246
|
|
|
247
|
-
**Zero infrastructure required** — uses
|
|
247
|
+
**Zero infrastructure required** — uses DuckDB out of the box. Two commands and you're done.
|
|
248
|
+
|
|
249
|
+
Supports **Python 3.11, 3.12, 3.13, and 3.14**.
|
|
248
250
|
|
|
249
251
|
### 1. Install
|
|
250
252
|
|
|
@@ -252,11 +254,21 @@ Importance additionally modulates the decay rate within each category. Memories
|
|
|
252
254
|
pip install yourmemory
|
|
253
255
|
```
|
|
254
256
|
|
|
255
|
-
All dependencies
|
|
257
|
+
All dependencies installed automatically. No clone, no Docker, no database setup.
|
|
258
|
+
|
|
259
|
+
### 2. Get your config
|
|
260
|
+
|
|
261
|
+
Run this once to get your exact config:
|
|
256
262
|
|
|
257
|
-
|
|
263
|
+
```bash
|
|
264
|
+
yourmemory-path
|
|
265
|
+
```
|
|
258
266
|
|
|
259
|
-
|
|
267
|
+
It prints your full executable path and a ready-to-paste config for any MCP client. Copy it.
|
|
268
|
+
|
|
269
|
+
### 3. Wire into your AI client
|
|
270
|
+
|
|
271
|
+
The database is created automatically at `~/.yourmemory/memories.duckdb` on first use.
|
|
260
272
|
|
|
261
273
|
#### Claude Code
|
|
262
274
|
|
|
@@ -276,26 +288,49 @@ Reload Claude Code (`Cmd+Shift+P` → `Developer: Reload Window`).
|
|
|
276
288
|
|
|
277
289
|
#### Cline (VS Code)
|
|
278
290
|
|
|
279
|
-
|
|
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
|
+
```
|
|
296
|
+
|
|
297
|
+
Then in Cline → **MCP Servers** → **Edit MCP Settings**, paste the output. It looks like:
|
|
280
298
|
|
|
281
299
|
```json
|
|
282
300
|
{
|
|
283
301
|
"mcpServers": {
|
|
284
302
|
"yourmemory": {
|
|
285
|
-
"command": "yourmemory",
|
|
303
|
+
"command": "/full/path/to/yourmemory",
|
|
286
304
|
"args": [],
|
|
287
305
|
"env": {
|
|
288
|
-
"
|
|
289
|
-
"
|
|
306
|
+
"YOURMEMORY_USER": "your_name",
|
|
307
|
+
"DATABASE_URL": ""
|
|
290
308
|
}
|
|
291
309
|
}
|
|
292
310
|
}
|
|
293
311
|
}
|
|
294
312
|
```
|
|
295
313
|
|
|
296
|
-
|
|
314
|
+
Restart Cline after saving.
|
|
315
|
+
|
|
316
|
+
#### Cursor
|
|
297
317
|
|
|
298
|
-
|
|
318
|
+
Add to `~/.cursor/mcp.json`:
|
|
319
|
+
|
|
320
|
+
```json
|
|
321
|
+
{
|
|
322
|
+
"mcpServers": {
|
|
323
|
+
"yourmemory": {
|
|
324
|
+
"command": "/full/path/to/yourmemory",
|
|
325
|
+
"args": [],
|
|
326
|
+
"env": {
|
|
327
|
+
"YOURMEMORY_USER": "your_name",
|
|
328
|
+
"DATABASE_URL": ""
|
|
329
|
+
}
|
|
330
|
+
}
|
|
331
|
+
}
|
|
332
|
+
}
|
|
333
|
+
```
|
|
299
334
|
|
|
300
335
|
#### Claude Desktop
|
|
301
336
|
|
|
@@ -315,9 +350,9 @@ Restart Claude Desktop.
|
|
|
315
350
|
|
|
316
351
|
#### Any MCP-compatible client
|
|
317
352
|
|
|
318
|
-
YourMemory is a standard stdio MCP server.
|
|
353
|
+
YourMemory is a standard stdio MCP server. Works with Claude Code, Claude Desktop, Cline, Cursor, Windsurf, Continue, and Zed. Use the full path from `yourmemory-path` if the client doesn't inherit shell PATH.
|
|
319
354
|
|
|
320
|
-
###
|
|
355
|
+
### 4. Add memory instructions to your project
|
|
321
356
|
|
|
322
357
|
Copy `sample_CLAUDE.md` into your project root as `CLAUDE.md` and replace:
|
|
323
358
|
- `YOUR_NAME` — your name (e.g. `Alice`)
|
|
@@ -329,13 +364,19 @@ Claude will now follow the recall → store → update workflow automatically on
|
|
|
329
364
|
|
|
330
365
|
### PostgreSQL (optional — for teams or large datasets)
|
|
331
366
|
|
|
332
|
-
|
|
367
|
+
Install with Postgres support:
|
|
368
|
+
|
|
369
|
+
```bash
|
|
370
|
+
pip install yourmemory[postgres]
|
|
371
|
+
```
|
|
372
|
+
|
|
373
|
+
Then create a `.env` file:
|
|
333
374
|
|
|
334
375
|
```bash
|
|
335
376
|
DATABASE_URL=postgresql://YOUR_USER@localhost:5432/yourmemory
|
|
336
377
|
```
|
|
337
378
|
|
|
338
|
-
The backend is selected automatically — `postgresql://` in `DATABASE_URL` → Postgres + pgvector, anything else →
|
|
379
|
+
The backend is selected automatically — `postgresql://` in `DATABASE_URL` → Postgres + pgvector, anything else → DuckDB.
|
|
339
380
|
|
|
340
381
|
**macOS**
|
|
341
382
|
```bash
|
|
@@ -349,8 +390,6 @@ sudo apt install postgresql postgresql-contrib postgresql-16-pgvector
|
|
|
349
390
|
createdb yourmemory
|
|
350
391
|
```
|
|
351
392
|
|
|
352
|
-
> **One-liner setup script** (macOS/Linux): `bash scripts/setup_db.sh` handles install + DB creation automatically.
|
|
353
|
-
|
|
354
393
|
---
|
|
355
394
|
|
|
356
395
|
## MCP Tools
|
|
@@ -402,47 +441,21 @@ Runs automatically every 24 hours on startup — no cron needed. Memories below
|
|
|
402
441
|
|
|
403
442
|
---
|
|
404
443
|
|
|
405
|
-
## REST API
|
|
406
|
-
|
|
407
|
-
```bash
|
|
408
|
-
# Store
|
|
409
|
-
curl -X POST http://localhost:8000/memories \
|
|
410
|
-
-H "Content-Type: application/json" \
|
|
411
|
-
-d '{"userId":"u1","content":"Prefers dark mode","importance":0.8}'
|
|
412
|
-
|
|
413
|
-
# Retrieve
|
|
414
|
-
curl -X POST http://localhost:8000/retrieve \
|
|
415
|
-
-H "Content-Type: application/json" \
|
|
416
|
-
-d '{"userId":"u1","query":"UI preferences"}'
|
|
417
|
-
|
|
418
|
-
# List all
|
|
419
|
-
curl "http://localhost:8000/memories?userId=u1"
|
|
420
|
-
|
|
421
|
-
# Update
|
|
422
|
-
curl -X PUT http://localhost:8000/memories/42 \
|
|
423
|
-
-H "Content-Type: application/json" \
|
|
424
|
-
-d '{"content":"Prefers dark mode in all apps","importance":0.85}'
|
|
425
|
-
|
|
426
|
-
# Delete
|
|
427
|
-
curl -X DELETE http://localhost:8000/memories/42
|
|
428
|
-
```
|
|
429
|
-
|
|
430
|
-
---
|
|
431
|
-
|
|
432
444
|
## Stack
|
|
433
445
|
|
|
434
|
-
- **
|
|
446
|
+
- **DuckDB** — default backend, zero setup, native vector similarity (same quality as pgvector)
|
|
435
447
|
- **sentence-transformers** — local embeddings (`all-mpnet-base-v2`, 768 dims, no external service needed)
|
|
436
|
-
- **
|
|
448
|
+
- **spaCy 3.8.13+** — local NLP for deduplication and categorization (Python 3.11–3.14 compatible)
|
|
437
449
|
- **APScheduler** — automatic 24h decay job
|
|
438
450
|
- **MCP** — Claude integration via Model Context Protocol
|
|
451
|
+
- **PostgreSQL + pgvector** — optional, for teams / large datasets
|
|
439
452
|
|
|
440
453
|
---
|
|
441
454
|
|
|
442
455
|
## Architecture
|
|
443
456
|
|
|
444
457
|
```
|
|
445
|
-
Claude
|
|
458
|
+
Claude / Cline / Cursor / Any MCP client
|
|
446
459
|
│
|
|
447
460
|
├── recall_memory(query)
|
|
448
461
|
│ └── embed → cosine similarity → score = sim × strength → top-k
|
|
@@ -455,12 +468,12 @@ Claude Code
|
|
|
455
468
|
└── update_memory(id, new_content)
|
|
456
469
|
└── embed(new_content) → UPDATE memories
|
|
457
470
|
|
|
458
|
-
PostgreSQL (
|
|
459
|
-
└── memories
|
|
460
|
-
├── embedding vector(768)
|
|
461
|
-
├── importance float
|
|
462
|
-
├── recall_count int
|
|
463
|
-
└── last_accessed_at
|
|
471
|
+
DuckDB (default) PostgreSQL + pgvector (optional)
|
|
472
|
+
└── memories.duckdb └── memories table
|
|
473
|
+
├── embedding FLOAT[768] ├── embedding vector(768)
|
|
474
|
+
├── importance FLOAT ├── importance float
|
|
475
|
+
├── recall_count INTEGER ├── recall_count int
|
|
476
|
+
└── last_accessed_at └── last_accessed_at
|
|
464
477
|
```
|
|
465
478
|
|
|
466
479
|
---
|
|
@@ -20,7 +20,9 @@ src/services/api_keys.py
|
|
|
20
20
|
src/services/decay.py
|
|
21
21
|
src/services/embed.py
|
|
22
22
|
src/services/extract.py
|
|
23
|
+
src/services/extract_fallback.py
|
|
23
24
|
src/services/resolve.py
|
|
25
|
+
src/services/resolve_fallback.py
|
|
24
26
|
src/services/retrieve.py
|
|
25
27
|
yourmemory.egg-info/PKG-INFO
|
|
26
28
|
yourmemory.egg-info/SOURCES.txt
|
|
@@ -1,38 +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
|
-
e.g. "Novak is world number 1", "I love Python", "My name is Sachit"
|
|
33
|
-
assumption — imperative sentence with no subject (command/instruction)
|
|
34
|
-
e.g. "Use python instead of JS", "Please convert this image"
|
|
35
|
-
"""
|
|
36
|
-
doc = _nlp(text)
|
|
37
|
-
has_subject = any(tok.dep_ in ("nsubj", "nsubjpass") for tok in doc)
|
|
38
|
-
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
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|