graph-mind-memory 0.5.1__tar.gz → 0.5.3__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.
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3}/PKG-INFO +24 -20
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3}/README.md +22 -19
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3}/pyproject.toml +4 -2
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/conversation_memory.py +9 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/graph_mind_mcp_server.py +21 -11
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/graph_mind_memory.egg-info/PKG-INFO +24 -20
- graph_mind_memory-0.5.3/src/graph_mind_memory.egg-info/SOURCES.txt +63 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/install.py +21 -15
- graph_mind_memory-0.5.3/tests/test_associative_memory.py +120 -0
- graph_mind_memory-0.5.3/tests/test_automatic_capture.py +101 -0
- graph_mind_memory-0.5.3/tests/test_capture_health.py +36 -0
- graph_mind_memory-0.5.3/tests/test_coding_memory.py +75 -0
- graph_mind_memory-0.5.3/tests/test_cold_start.py +77 -0
- graph_mind_memory-0.5.3/tests/test_concurrent_use.py +40 -0
- graph_mind_memory-0.5.3/tests/test_conversation_memory.py +191 -0
- graph_mind_memory-0.5.3/tests/test_executable_memory.py +105 -0
- graph_mind_memory-0.5.3/tests/test_executable_plan_v2_diagnostic.py +52 -0
- graph_mind_memory-0.5.3/tests/test_forget.py +125 -0
- graph_mind_memory-0.5.3/tests/test_forget_conversation.py +100 -0
- graph_mind_memory-0.5.3/tests/test_graph_mind_mcp_server.py +118 -0
- graph_mind_memory-0.5.3/tests/test_ingestion_gate.py +67 -0
- graph_mind_memory-0.5.3/tests/test_ingestion_synonyms.py +70 -0
- graph_mind_memory-0.5.3/tests/test_install.py +160 -0
- graph_mind_memory-0.5.3/tests/test_llm_concept_planner.py +87 -0
- graph_mind_memory-0.5.3/tests/test_local_brain.py +60 -0
- graph_mind_memory-0.5.3/tests/test_longmemeval_adapter.py +49 -0
- graph_mind_memory-0.5.3/tests/test_longmemeval_ingestion_calibration.py +45 -0
- graph_mind_memory-0.5.3/tests/test_longmemeval_ingestion_preflight.py +22 -0
- graph_mind_memory-0.5.3/tests/test_longmemeval_ingestion_v7.py +55 -0
- graph_mind_memory-0.5.3/tests/test_memory_algebra.py +45 -0
- graph_mind_memory-0.5.3/tests/test_precondition_verifier.py +39 -0
- graph_mind_memory-0.5.3/tests/test_recent_recall.py +115 -0
- graph_mind_memory-0.5.3/tests/test_resume_retries.py +26 -0
- graph_mind_memory-0.5.3/tests/test_secret_masking.py +76 -0
- graph_mind_memory-0.5.3/tests/test_semantic_llm_extractor.py +102 -0
- graph_mind_memory-0.5.3/tests/test_semantic_memory.py +69 -0
- graph_mind_memory-0.5.3/tests/test_semantic_recall.py +89 -0
- graph_mind_memory-0.5.3/tests/test_shared_brain.py +269 -0
- graph_mind_memory-0.5.3/tests/test_temporal_state.py +151 -0
- graph_mind_memory-0.5.3/tests/test_universal_personal_memory.py +73 -0
- graph_mind_memory-0.5.1/graph_mind_memory.egg-info/SOURCES.txt +0 -31
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3}/LICENSE +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3}/setup.cfg +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/associative_memory.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/automatic_capture.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/automatic_capture_cli.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/brain_log.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/clock.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/coding_memory.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/development_memory.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/development_paths.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/embedding_warmup.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/entity_timeline.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/forget.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/graph_mind_memory.egg-info/dependency_links.txt +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/graph_mind_memory.egg-info/entry_points.txt +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/graph_mind_memory.egg-info/requires.txt +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/graph_mind_memory.egg-info/top_level.txt +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/local_brain.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/local_embedder.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/semantic_llm_extractor.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/semantic_memory.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/semantic_recall.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/temporal_resolver.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/universal_personal_memory.py +0 -0
- {graph_mind_memory-0.5.1 → graph_mind_memory-0.5.3/src}/vector_cache.py +0 -0
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graph-mind-memory
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Local-first memory for AI coding assistants: verbatim, on your own PC, no LLM calls at write time.
|
|
5
5
|
Author-email: Yohan Ko <goyohan0611@gmail.com>
|
|
6
6
|
License-Expression: AGPL-3.0-only
|
|
7
7
|
Project-URL: Homepage, https://github.com/goyohan0611-png/graph-mind
|
|
8
8
|
Project-URL: Issues, https://github.com/goyohan0611-png/graph-mind/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/goyohan0611-png/graph-mind/blob/main/CHANGELOG.md
|
|
9
10
|
Keywords: mcp,memory,claude,codex,llm,longmemeval,local-first
|
|
10
11
|
Classifier: Development Status :: 3 - Alpha
|
|
11
12
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -77,8 +78,8 @@ calls Graph-MIND over MCP and gets back a few thousand tokens of the conversatio
|
|
|
77
78
|
## Benchmarks
|
|
78
79
|
|
|
79
80
|
All numbers below come from files in this repository: the pre-registrations, each question's
|
|
80
|
-
answer, and the judge's verdict on it, under [`runs/`](runs). The method is in
|
|
81
|
-
[REPORT.md](REPORT.md), including the failures and the corrections.
|
|
81
|
+
answer, and the judge's verdict on it, under [`research/runs/`](research/runs). The method is in
|
|
82
|
+
[REPORT.md](research/REPORT.md), including the failures and the corrections.
|
|
82
83
|
|
|
83
84
|
**LongMemEval_S: answer accuracy, 500 questions.** The answer model is gpt-5-mini; the judge is the
|
|
84
85
|
official gpt-4o-2024-08-06.
|
|
@@ -120,6 +121,8 @@ Requires Python 3.10+ (64-bit). Windows, macOS or Linux. Hosting a brain that ot
|
|
|
120
121
|
needs Python 3.12 or older on that one PC (its Postgres helper, pgserver, has no newer build yet);
|
|
121
122
|
everything else, joining included, works on 3.13 and 3.14 too.
|
|
122
123
|
|
|
124
|
+
What changed in each release: [CHANGELOG.md](CHANGELOG.md).
|
|
125
|
+
|
|
123
126
|
```bash
|
|
124
127
|
pip install graph-mind-memory
|
|
125
128
|
graph-mind-install
|
|
@@ -136,7 +139,7 @@ or from source:
|
|
|
136
139
|
```bash
|
|
137
140
|
git clone https://github.com/goyohan0611-png/graph-mind.git
|
|
138
141
|
cd graph-mind
|
|
139
|
-
python install.py
|
|
142
|
+
python src/install.py
|
|
140
143
|
```
|
|
141
144
|
|
|
142
145
|
The installer:
|
|
@@ -180,9 +183,12 @@ random 20-64 character tokens caught; in 246,750 ordinary chat turns it fired 92
|
|
|
180
183
|
all on real ids and tokens). Commit ids, hashes and UUIDs are kept, since you recall those on
|
|
181
184
|
purpose.
|
|
182
185
|
|
|
183
|
-
|
|
184
|
-
so a stored key would leave your PC the first time it is useful. To have Graph-MIND
|
|
185
|
-
tell it **where the key is**, not the key: *"my OpenAI key is in 1Password, under
|
|
186
|
+
Keep masking on if you use a cloud model: a recalled memory goes to your model's provider with the
|
|
187
|
+
question, so a stored key would leave your PC the first time it is useful. To have Graph-MIND
|
|
188
|
+
remember a key, tell it **where the key is**, not the key: *"my OpenAI key is in 1Password, under
|
|
189
|
+
Dev"*. If your model runs on your own machines and you want keys remembered as typed, add
|
|
190
|
+
`"mask_secrets": false` to `~/.graph-mind/config.json` (applies from the next message; already
|
|
191
|
+
masked text stays masked).
|
|
186
192
|
|
|
187
193
|
Only what you actually send is captured: the service reads each app's transcript, which is written
|
|
188
194
|
after you press Enter, so a paste you delete before sending never reaches it. A plain password
|
|
@@ -227,7 +233,7 @@ On the PC that holds the memory, tell its AI:
|
|
|
227
233
|
It replies with a connection code (`gm1.…`). On each other PC:
|
|
228
234
|
|
|
229
235
|
```bash
|
|
230
|
-
graph-mind-install --join gm1.… # or: python install.py --join gm1.…
|
|
236
|
+
graph-mind-install --join gm1.… # or: python src/install.py --join gm1.…
|
|
231
237
|
```
|
|
232
238
|
|
|
233
239
|
The memory then lives in a Postgres server on the first PC, which Graph-MIND sets up itself. Other
|
|
@@ -248,16 +254,17 @@ each PC.
|
|
|
248
254
|
## Reproducing the benchmarks
|
|
249
255
|
|
|
250
256
|
1. Download `longmemeval_s_cleaned.json` from [LongMemEval](https://github.com/xiaowu0162/LongMemEval)
|
|
251
|
-
into `external/longmemeval/`.
|
|
257
|
+
into `research/external/longmemeval/`.
|
|
252
258
|
2. Set `OPENAI_API_KEY` for the answer model and the judge.
|
|
253
|
-
3.
|
|
259
|
+
3. `pip install -e .`, then from `research/` run the commands in
|
|
260
|
+
[REPORT.md §10](research/REPORT.md#10-reproducing).
|
|
254
261
|
|
|
255
262
|
A full 500-question run costs about US$3.
|
|
256
263
|
|
|
257
264
|
## Tests
|
|
258
265
|
|
|
259
266
|
```bash
|
|
260
|
-
python -m unittest discover -p "test_*.py"
|
|
267
|
+
python -m unittest discover -s tests -p "test_*.py"
|
|
261
268
|
```
|
|
262
269
|
|
|
263
270
|
---
|
|
@@ -273,20 +280,17 @@ python -m unittest discover -p "test_*.py"
|
|
|
273
280
|
can stop capture until Graph-MIND is updated.
|
|
274
281
|
- **Multi-session questions are the weakest type at 80%.** These are questions that count or
|
|
275
282
|
combine facts across many conversations.
|
|
276
|
-
- **The repository still holds the research-phase experiments** next to the product (see
|
|
277
|
-
[Repository layout](#repository-layout)); the installed package carries only the 22 product
|
|
278
|
-
modules.
|
|
279
283
|
|
|
280
|
-
The full list is in [REPORT.md §9](REPORT.md#9-known-limits).
|
|
284
|
+
The full list is in [REPORT.md §9](research/REPORT.md#9-known-limits).
|
|
281
285
|
|
|
282
286
|
## Repository layout
|
|
283
287
|
|
|
284
|
-
| |
|
|
288
|
+
| folder | what is in it |
|
|
285
289
|
|---|---|
|
|
286
|
-
|
|
|
287
|
-
|
|
|
288
|
-
|
|
|
289
|
-
|
|
|
290
|
+
| [`src/`](src) | the product, exactly what `pip install graph-mind-memory` installs: `graph_mind_mcp_server.py` (MCP server), `automatic_capture*.py` (capture service), `install.py`, `brain_log.py` (sharing across PCs), `forget.py` (deleting), the stores, and search (`semantic_recall.py`, `local_embedder.py`) |
|
|
291
|
+
| [`tests/`](tests) | unit tests, run in CI on Windows, Linux and macOS |
|
|
292
|
+
| [`research/`](research) | the benchmark and research-phase scripts, [REPORT.md](research/REPORT.md), and every run's results under `runs/` |
|
|
293
|
+
| [`demo/`](demo) | the README's GIF and the script that records it |
|
|
290
294
|
|
|
291
295
|
## Security
|
|
292
296
|
|
|
@@ -50,8 +50,8 @@ calls Graph-MIND over MCP and gets back a few thousand tokens of the conversatio
|
|
|
50
50
|
## Benchmarks
|
|
51
51
|
|
|
52
52
|
All numbers below come from files in this repository: the pre-registrations, each question's
|
|
53
|
-
answer, and the judge's verdict on it, under [`runs/`](runs). The method is in
|
|
54
|
-
[REPORT.md](REPORT.md), including the failures and the corrections.
|
|
53
|
+
answer, and the judge's verdict on it, under [`research/runs/`](research/runs). The method is in
|
|
54
|
+
[REPORT.md](research/REPORT.md), including the failures and the corrections.
|
|
55
55
|
|
|
56
56
|
**LongMemEval_S: answer accuracy, 500 questions.** The answer model is gpt-5-mini; the judge is the
|
|
57
57
|
official gpt-4o-2024-08-06.
|
|
@@ -93,6 +93,8 @@ Requires Python 3.10+ (64-bit). Windows, macOS or Linux. Hosting a brain that ot
|
|
|
93
93
|
needs Python 3.12 or older on that one PC (its Postgres helper, pgserver, has no newer build yet);
|
|
94
94
|
everything else, joining included, works on 3.13 and 3.14 too.
|
|
95
95
|
|
|
96
|
+
What changed in each release: [CHANGELOG.md](CHANGELOG.md).
|
|
97
|
+
|
|
96
98
|
```bash
|
|
97
99
|
pip install graph-mind-memory
|
|
98
100
|
graph-mind-install
|
|
@@ -109,7 +111,7 @@ or from source:
|
|
|
109
111
|
```bash
|
|
110
112
|
git clone https://github.com/goyohan0611-png/graph-mind.git
|
|
111
113
|
cd graph-mind
|
|
112
|
-
python install.py
|
|
114
|
+
python src/install.py
|
|
113
115
|
```
|
|
114
116
|
|
|
115
117
|
The installer:
|
|
@@ -153,9 +155,12 @@ random 20-64 character tokens caught; in 246,750 ordinary chat turns it fired 92
|
|
|
153
155
|
all on real ids and tokens). Commit ids, hashes and UUIDs are kept, since you recall those on
|
|
154
156
|
purpose.
|
|
155
157
|
|
|
156
|
-
|
|
157
|
-
so a stored key would leave your PC the first time it is useful. To have Graph-MIND
|
|
158
|
-
tell it **where the key is**, not the key: *"my OpenAI key is in 1Password, under
|
|
158
|
+
Keep masking on if you use a cloud model: a recalled memory goes to your model's provider with the
|
|
159
|
+
question, so a stored key would leave your PC the first time it is useful. To have Graph-MIND
|
|
160
|
+
remember a key, tell it **where the key is**, not the key: *"my OpenAI key is in 1Password, under
|
|
161
|
+
Dev"*. If your model runs on your own machines and you want keys remembered as typed, add
|
|
162
|
+
`"mask_secrets": false` to `~/.graph-mind/config.json` (applies from the next message; already
|
|
163
|
+
masked text stays masked).
|
|
159
164
|
|
|
160
165
|
Only what you actually send is captured: the service reads each app's transcript, which is written
|
|
161
166
|
after you press Enter, so a paste you delete before sending never reaches it. A plain password
|
|
@@ -200,7 +205,7 @@ On the PC that holds the memory, tell its AI:
|
|
|
200
205
|
It replies with a connection code (`gm1.…`). On each other PC:
|
|
201
206
|
|
|
202
207
|
```bash
|
|
203
|
-
graph-mind-install --join gm1.… # or: python install.py --join gm1.…
|
|
208
|
+
graph-mind-install --join gm1.… # or: python src/install.py --join gm1.…
|
|
204
209
|
```
|
|
205
210
|
|
|
206
211
|
The memory then lives in a Postgres server on the first PC, which Graph-MIND sets up itself. Other
|
|
@@ -221,16 +226,17 @@ each PC.
|
|
|
221
226
|
## Reproducing the benchmarks
|
|
222
227
|
|
|
223
228
|
1. Download `longmemeval_s_cleaned.json` from [LongMemEval](https://github.com/xiaowu0162/LongMemEval)
|
|
224
|
-
into `external/longmemeval/`.
|
|
229
|
+
into `research/external/longmemeval/`.
|
|
225
230
|
2. Set `OPENAI_API_KEY` for the answer model and the judge.
|
|
226
|
-
3.
|
|
231
|
+
3. `pip install -e .`, then from `research/` run the commands in
|
|
232
|
+
[REPORT.md §10](research/REPORT.md#10-reproducing).
|
|
227
233
|
|
|
228
234
|
A full 500-question run costs about US$3.
|
|
229
235
|
|
|
230
236
|
## Tests
|
|
231
237
|
|
|
232
238
|
```bash
|
|
233
|
-
python -m unittest discover -p "test_*.py"
|
|
239
|
+
python -m unittest discover -s tests -p "test_*.py"
|
|
234
240
|
```
|
|
235
241
|
|
|
236
242
|
---
|
|
@@ -246,20 +252,17 @@ python -m unittest discover -p "test_*.py"
|
|
|
246
252
|
can stop capture until Graph-MIND is updated.
|
|
247
253
|
- **Multi-session questions are the weakest type at 80%.** These are questions that count or
|
|
248
254
|
combine facts across many conversations.
|
|
249
|
-
- **The repository still holds the research-phase experiments** next to the product (see
|
|
250
|
-
[Repository layout](#repository-layout)); the installed package carries only the 22 product
|
|
251
|
-
modules.
|
|
252
255
|
|
|
253
|
-
The full list is in [REPORT.md §9](REPORT.md#9-known-limits).
|
|
256
|
+
The full list is in [REPORT.md §9](research/REPORT.md#9-known-limits).
|
|
254
257
|
|
|
255
258
|
## Repository layout
|
|
256
259
|
|
|
257
|
-
| |
|
|
260
|
+
| folder | what is in it |
|
|
258
261
|
|---|---|
|
|
259
|
-
|
|
|
260
|
-
|
|
|
261
|
-
|
|
|
262
|
-
|
|
|
262
|
+
| [`src/`](src) | the product, exactly what `pip install graph-mind-memory` installs: `graph_mind_mcp_server.py` (MCP server), `automatic_capture*.py` (capture service), `install.py`, `brain_log.py` (sharing across PCs), `forget.py` (deleting), the stores, and search (`semantic_recall.py`, `local_embedder.py`) |
|
|
263
|
+
| [`tests/`](tests) | unit tests, run in CI on Windows, Linux and macOS |
|
|
264
|
+
| [`research/`](research) | the benchmark and research-phase scripts, [REPORT.md](research/REPORT.md), and every run's results under `runs/` |
|
|
265
|
+
| [`demo/`](demo) | the README's GIF and the script that records it |
|
|
263
266
|
|
|
264
267
|
## Security
|
|
265
268
|
|
|
@@ -4,7 +4,7 @@ build-backend = "setuptools.build_meta"
|
|
|
4
4
|
|
|
5
5
|
[project]
|
|
6
6
|
name = "graph-mind-memory"
|
|
7
|
-
version = "0.5.
|
|
7
|
+
version = "0.5.3"
|
|
8
8
|
description = "Local-first memory for AI coding assistants: verbatim, on your own PC, no LLM calls at write time."
|
|
9
9
|
readme = "README.md"
|
|
10
10
|
requires-python = ">=3.10"
|
|
@@ -35,6 +35,7 @@ dependencies = [
|
|
|
35
35
|
[project.urls]
|
|
36
36
|
Homepage = "https://github.com/goyohan0611-png/graph-mind"
|
|
37
37
|
Issues = "https://github.com/goyohan0611-png/graph-mind/issues"
|
|
38
|
+
Changelog = "https://github.com/goyohan0611-png/graph-mind/blob/main/CHANGELOG.md"
|
|
38
39
|
|
|
39
40
|
[project.scripts]
|
|
40
41
|
graph-mind = "graph_mind_mcp_server:main"
|
|
@@ -44,7 +45,8 @@ graph-mind-capture = "automatic_capture_cli:main"
|
|
|
44
45
|
graph-mind-forget = "forget:main"
|
|
45
46
|
|
|
46
47
|
[tool.setuptools]
|
|
47
|
-
# ponytail: flat modules
|
|
48
|
+
# ponytail: flat modules under src/; a graph_mind/ package is the upgrade path.
|
|
49
|
+
package-dir = { "" = "src" }
|
|
48
50
|
py-modules = [
|
|
49
51
|
"associative_memory",
|
|
50
52
|
"automatic_capture",
|
|
@@ -79,8 +79,17 @@ AGENT_INSTRUCTIONS = re.compile(
|
|
|
79
79
|
r"(?:#\s*AGENTS\.md instructions[^\n]*\s*)?<INSTRUCTIONS>.*?</INSTRUCTIONS>", flags=re.DOTALL)
|
|
80
80
|
|
|
81
81
|
|
|
82
|
+
def masking_on():
|
|
83
|
+
"""On unless ~/.graph-mind/config.json says "mask_secrets": false: someone running a model on
|
|
84
|
+
their own machines may want keys kept in memory verbatim."""
|
|
85
|
+
from brain_log import _config
|
|
86
|
+
return _config().get("mask_secrets", True) is not False
|
|
87
|
+
|
|
88
|
+
|
|
82
89
|
def _redact(value):
|
|
83
90
|
cleaned = AGENT_INSTRUCTIONS.sub("", AUTOMATIC_BLOCKS.sub("", value))
|
|
91
|
+
if not masking_on():
|
|
92
|
+
return cleaned.strip(), 0
|
|
84
93
|
count = 0
|
|
85
94
|
for pattern, replacement in SECRET_PATTERNS:
|
|
86
95
|
cleaned, replacements = pattern.subn(replacement, cleaned)
|
|
@@ -26,12 +26,13 @@ if hasattr(sys.stderr, "reconfigure"):
|
|
|
26
26
|
|
|
27
27
|
|
|
28
28
|
def _version() -> str:
|
|
29
|
-
"""The package version: pyproject.toml
|
|
29
|
+
"""The package version: the checkout's pyproject.toml (one folder up from src/), else the
|
|
30
30
|
installed distribution's. A hand-kept constant here had drifted to 0.5.0 at release 0.3.1."""
|
|
31
31
|
import re
|
|
32
|
-
project = Path(__file__).
|
|
33
|
-
if project.exists()
|
|
34
|
-
|
|
32
|
+
project = Path(__file__).resolve().parent.parent / "pyproject.toml"
|
|
33
|
+
text = project.read_text(encoding="utf-8") if project.exists() else ""
|
|
34
|
+
if 'name = "graph-mind-memory"' in text:
|
|
35
|
+
found = re.search(r'^version = "([^"]+)"', text, re.M)
|
|
35
36
|
if found:
|
|
36
37
|
return found.group(1)
|
|
37
38
|
try:
|
|
@@ -358,6 +359,21 @@ def capture_warnings() -> list[str]:
|
|
|
358
359
|
return []
|
|
359
360
|
|
|
360
361
|
|
|
362
|
+
PROACTIVE_NOTE = (
|
|
363
|
+
"Remember proactively. Whenever the user mentions something about their life, work, "
|
|
364
|
+
"plans, preferences, people, places, meals, purchases, health, decisions or events, "
|
|
365
|
+
"call brain_remember right away with their own words, without asking first and "
|
|
366
|
+
"without waiting to be told to remember it. Saving too much costs nothing; forgetting "
|
|
367
|
+
"is what the user notices. Do it silently: never tell the user that you saved, "
|
|
368
|
+
"stored or noted something, and do not add lines like 'I saved that to memory'. "
|
|
369
|
+
"Mention memory only when the user asks what you remember or whether it was saved. ")
|
|
370
|
+
# set by the installer for apps whose transcripts the capture service already saves
|
|
371
|
+
CAPTURED_NOTE = (
|
|
372
|
+
"Every turn of this conversation is saved automatically. Do not call brain_remember for "
|
|
373
|
+
"ordinary chat; call it only when the user explicitly asks you to remember something or "
|
|
374
|
+
"to record a decision. ")
|
|
375
|
+
|
|
376
|
+
|
|
361
377
|
def build_server(db_path=None):
|
|
362
378
|
database = Path(db_path).expanduser() if db_path is not None else configured_db_path()
|
|
363
379
|
server = MCPServer(
|
|
@@ -365,13 +381,7 @@ def build_server(db_path=None):
|
|
|
365
381
|
title="Graph-MIND Universal Personal Memory",
|
|
366
382
|
description="User-owned local brain shared across models, sessions and applications.",
|
|
367
383
|
instructions=(
|
|
368
|
-
|
|
369
|
-
"plans, preferences, people, places, meals, purchases, health, decisions or events, "
|
|
370
|
-
"call brain_remember right away with their own words, without asking first and "
|
|
371
|
-
"without waiting to be told to remember it. Saving too much costs nothing; forgetting "
|
|
372
|
-
"is what the user notices. Do it silently: never tell the user that you saved, "
|
|
373
|
-
"stored or noted something, and do not add lines like 'I saved that to memory'. "
|
|
374
|
-
"Mention memory only when the user asks what you remember or whether it was saved. "
|
|
384
|
+
(CAPTURED_NOTE if os.environ.get("GRAPH_MIND_CAPTURED") else PROACTIVE_NOTE) +
|
|
375
385
|
"Call brain_context first when a request may depend on the user's prior conversations, "
|
|
376
386
|
"facts, preferences, decisions, projects or work, including 'where did we leave off?'. "
|
|
377
387
|
"It skips unrelated requests and returns a bounded evidence packet instead of "
|
|
@@ -1,11 +1,12 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: graph-mind-memory
|
|
3
|
-
Version: 0.5.
|
|
3
|
+
Version: 0.5.3
|
|
4
4
|
Summary: Local-first memory for AI coding assistants: verbatim, on your own PC, no LLM calls at write time.
|
|
5
5
|
Author-email: Yohan Ko <goyohan0611@gmail.com>
|
|
6
6
|
License-Expression: AGPL-3.0-only
|
|
7
7
|
Project-URL: Homepage, https://github.com/goyohan0611-png/graph-mind
|
|
8
8
|
Project-URL: Issues, https://github.com/goyohan0611-png/graph-mind/issues
|
|
9
|
+
Project-URL: Changelog, https://github.com/goyohan0611-png/graph-mind/blob/main/CHANGELOG.md
|
|
9
10
|
Keywords: mcp,memory,claude,codex,llm,longmemeval,local-first
|
|
10
11
|
Classifier: Development Status :: 3 - Alpha
|
|
11
12
|
Classifier: Programming Language :: Python :: 3
|
|
@@ -77,8 +78,8 @@ calls Graph-MIND over MCP and gets back a few thousand tokens of the conversatio
|
|
|
77
78
|
## Benchmarks
|
|
78
79
|
|
|
79
80
|
All numbers below come from files in this repository: the pre-registrations, each question's
|
|
80
|
-
answer, and the judge's verdict on it, under [`runs/`](runs). The method is in
|
|
81
|
-
[REPORT.md](REPORT.md), including the failures and the corrections.
|
|
81
|
+
answer, and the judge's verdict on it, under [`research/runs/`](research/runs). The method is in
|
|
82
|
+
[REPORT.md](research/REPORT.md), including the failures and the corrections.
|
|
82
83
|
|
|
83
84
|
**LongMemEval_S: answer accuracy, 500 questions.** The answer model is gpt-5-mini; the judge is the
|
|
84
85
|
official gpt-4o-2024-08-06.
|
|
@@ -120,6 +121,8 @@ Requires Python 3.10+ (64-bit). Windows, macOS or Linux. Hosting a brain that ot
|
|
|
120
121
|
needs Python 3.12 or older on that one PC (its Postgres helper, pgserver, has no newer build yet);
|
|
121
122
|
everything else, joining included, works on 3.13 and 3.14 too.
|
|
122
123
|
|
|
124
|
+
What changed in each release: [CHANGELOG.md](CHANGELOG.md).
|
|
125
|
+
|
|
123
126
|
```bash
|
|
124
127
|
pip install graph-mind-memory
|
|
125
128
|
graph-mind-install
|
|
@@ -136,7 +139,7 @@ or from source:
|
|
|
136
139
|
```bash
|
|
137
140
|
git clone https://github.com/goyohan0611-png/graph-mind.git
|
|
138
141
|
cd graph-mind
|
|
139
|
-
python install.py
|
|
142
|
+
python src/install.py
|
|
140
143
|
```
|
|
141
144
|
|
|
142
145
|
The installer:
|
|
@@ -180,9 +183,12 @@ random 20-64 character tokens caught; in 246,750 ordinary chat turns it fired 92
|
|
|
180
183
|
all on real ids and tokens). Commit ids, hashes and UUIDs are kept, since you recall those on
|
|
181
184
|
purpose.
|
|
182
185
|
|
|
183
|
-
|
|
184
|
-
so a stored key would leave your PC the first time it is useful. To have Graph-MIND
|
|
185
|
-
tell it **where the key is**, not the key: *"my OpenAI key is in 1Password, under
|
|
186
|
+
Keep masking on if you use a cloud model: a recalled memory goes to your model's provider with the
|
|
187
|
+
question, so a stored key would leave your PC the first time it is useful. To have Graph-MIND
|
|
188
|
+
remember a key, tell it **where the key is**, not the key: *"my OpenAI key is in 1Password, under
|
|
189
|
+
Dev"*. If your model runs on your own machines and you want keys remembered as typed, add
|
|
190
|
+
`"mask_secrets": false` to `~/.graph-mind/config.json` (applies from the next message; already
|
|
191
|
+
masked text stays masked).
|
|
186
192
|
|
|
187
193
|
Only what you actually send is captured: the service reads each app's transcript, which is written
|
|
188
194
|
after you press Enter, so a paste you delete before sending never reaches it. A plain password
|
|
@@ -227,7 +233,7 @@ On the PC that holds the memory, tell its AI:
|
|
|
227
233
|
It replies with a connection code (`gm1.…`). On each other PC:
|
|
228
234
|
|
|
229
235
|
```bash
|
|
230
|
-
graph-mind-install --join gm1.… # or: python install.py --join gm1.…
|
|
236
|
+
graph-mind-install --join gm1.… # or: python src/install.py --join gm1.…
|
|
231
237
|
```
|
|
232
238
|
|
|
233
239
|
The memory then lives in a Postgres server on the first PC, which Graph-MIND sets up itself. Other
|
|
@@ -248,16 +254,17 @@ each PC.
|
|
|
248
254
|
## Reproducing the benchmarks
|
|
249
255
|
|
|
250
256
|
1. Download `longmemeval_s_cleaned.json` from [LongMemEval](https://github.com/xiaowu0162/LongMemEval)
|
|
251
|
-
into `external/longmemeval/`.
|
|
257
|
+
into `research/external/longmemeval/`.
|
|
252
258
|
2. Set `OPENAI_API_KEY` for the answer model and the judge.
|
|
253
|
-
3.
|
|
259
|
+
3. `pip install -e .`, then from `research/` run the commands in
|
|
260
|
+
[REPORT.md §10](research/REPORT.md#10-reproducing).
|
|
254
261
|
|
|
255
262
|
A full 500-question run costs about US$3.
|
|
256
263
|
|
|
257
264
|
## Tests
|
|
258
265
|
|
|
259
266
|
```bash
|
|
260
|
-
python -m unittest discover -p "test_*.py"
|
|
267
|
+
python -m unittest discover -s tests -p "test_*.py"
|
|
261
268
|
```
|
|
262
269
|
|
|
263
270
|
---
|
|
@@ -273,20 +280,17 @@ python -m unittest discover -p "test_*.py"
|
|
|
273
280
|
can stop capture until Graph-MIND is updated.
|
|
274
281
|
- **Multi-session questions are the weakest type at 80%.** These are questions that count or
|
|
275
282
|
combine facts across many conversations.
|
|
276
|
-
- **The repository still holds the research-phase experiments** next to the product (see
|
|
277
|
-
[Repository layout](#repository-layout)); the installed package carries only the 22 product
|
|
278
|
-
modules.
|
|
279
283
|
|
|
280
|
-
The full list is in [REPORT.md §9](REPORT.md#9-known-limits).
|
|
284
|
+
The full list is in [REPORT.md §9](research/REPORT.md#9-known-limits).
|
|
281
285
|
|
|
282
286
|
## Repository layout
|
|
283
287
|
|
|
284
|
-
| |
|
|
288
|
+
| folder | what is in it |
|
|
285
289
|
|---|---|
|
|
286
|
-
|
|
|
287
|
-
|
|
|
288
|
-
|
|
|
289
|
-
|
|
|
290
|
+
| [`src/`](src) | the product, exactly what `pip install graph-mind-memory` installs: `graph_mind_mcp_server.py` (MCP server), `automatic_capture*.py` (capture service), `install.py`, `brain_log.py` (sharing across PCs), `forget.py` (deleting), the stores, and search (`semantic_recall.py`, `local_embedder.py`) |
|
|
291
|
+
| [`tests/`](tests) | unit tests, run in CI on Windows, Linux and macOS |
|
|
292
|
+
| [`research/`](research) | the benchmark and research-phase scripts, [REPORT.md](research/REPORT.md), and every run's results under `runs/` |
|
|
293
|
+
| [`demo/`](demo) | the README's GIF and the script that records it |
|
|
290
294
|
|
|
291
295
|
## Security
|
|
292
296
|
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
LICENSE
|
|
2
|
+
README.md
|
|
3
|
+
pyproject.toml
|
|
4
|
+
src/associative_memory.py
|
|
5
|
+
src/automatic_capture.py
|
|
6
|
+
src/automatic_capture_cli.py
|
|
7
|
+
src/brain_log.py
|
|
8
|
+
src/clock.py
|
|
9
|
+
src/coding_memory.py
|
|
10
|
+
src/conversation_memory.py
|
|
11
|
+
src/development_memory.py
|
|
12
|
+
src/development_paths.py
|
|
13
|
+
src/embedding_warmup.py
|
|
14
|
+
src/entity_timeline.py
|
|
15
|
+
src/forget.py
|
|
16
|
+
src/graph_mind_mcp_server.py
|
|
17
|
+
src/install.py
|
|
18
|
+
src/local_brain.py
|
|
19
|
+
src/local_embedder.py
|
|
20
|
+
src/semantic_llm_extractor.py
|
|
21
|
+
src/semantic_memory.py
|
|
22
|
+
src/semantic_recall.py
|
|
23
|
+
src/temporal_resolver.py
|
|
24
|
+
src/universal_personal_memory.py
|
|
25
|
+
src/vector_cache.py
|
|
26
|
+
src/graph_mind_memory.egg-info/PKG-INFO
|
|
27
|
+
src/graph_mind_memory.egg-info/SOURCES.txt
|
|
28
|
+
src/graph_mind_memory.egg-info/dependency_links.txt
|
|
29
|
+
src/graph_mind_memory.egg-info/entry_points.txt
|
|
30
|
+
src/graph_mind_memory.egg-info/requires.txt
|
|
31
|
+
src/graph_mind_memory.egg-info/top_level.txt
|
|
32
|
+
tests/test_associative_memory.py
|
|
33
|
+
tests/test_automatic_capture.py
|
|
34
|
+
tests/test_capture_health.py
|
|
35
|
+
tests/test_coding_memory.py
|
|
36
|
+
tests/test_cold_start.py
|
|
37
|
+
tests/test_concurrent_use.py
|
|
38
|
+
tests/test_conversation_memory.py
|
|
39
|
+
tests/test_executable_memory.py
|
|
40
|
+
tests/test_executable_plan_v2_diagnostic.py
|
|
41
|
+
tests/test_forget.py
|
|
42
|
+
tests/test_forget_conversation.py
|
|
43
|
+
tests/test_graph_mind_mcp_server.py
|
|
44
|
+
tests/test_ingestion_gate.py
|
|
45
|
+
tests/test_ingestion_synonyms.py
|
|
46
|
+
tests/test_install.py
|
|
47
|
+
tests/test_llm_concept_planner.py
|
|
48
|
+
tests/test_local_brain.py
|
|
49
|
+
tests/test_longmemeval_adapter.py
|
|
50
|
+
tests/test_longmemeval_ingestion_calibration.py
|
|
51
|
+
tests/test_longmemeval_ingestion_preflight.py
|
|
52
|
+
tests/test_longmemeval_ingestion_v7.py
|
|
53
|
+
tests/test_memory_algebra.py
|
|
54
|
+
tests/test_precondition_verifier.py
|
|
55
|
+
tests/test_recent_recall.py
|
|
56
|
+
tests/test_resume_retries.py
|
|
57
|
+
tests/test_secret_masking.py
|
|
58
|
+
tests/test_semantic_llm_extractor.py
|
|
59
|
+
tests/test_semantic_memory.py
|
|
60
|
+
tests/test_semantic_recall.py
|
|
61
|
+
tests/test_shared_brain.py
|
|
62
|
+
tests/test_temporal_state.py
|
|
63
|
+
tests/test_universal_personal_memory.py
|
|
@@ -23,6 +23,9 @@ HERE = Path(__file__).resolve().parent
|
|
|
23
23
|
SERVER = HERE / "graph_mind_mcp_server.py"
|
|
24
24
|
NAME = "graph-mind-memory"
|
|
25
25
|
ENV = {"PYTHONUTF8": "1", "PYTHONIOENCODING": "utf-8"}
|
|
26
|
+
# apps whose transcripts the capture service reads: every turn is saved without the model,
|
|
27
|
+
# so the model need not spend a tool call on brain_remember for ordinary chat
|
|
28
|
+
CAPTURED = {**ENV, "GRAPH_MIND_CAPTURED": "1"}
|
|
26
29
|
|
|
27
30
|
|
|
28
31
|
def step(message):
|
|
@@ -31,7 +34,7 @@ def step(message):
|
|
|
31
34
|
|
|
32
35
|
def packages():
|
|
33
36
|
done = subprocess.run([sys.executable, "-m", "pip", "install", "--disable-pip-version-check",
|
|
34
|
-
"-q", "-r", str(HERE / "requirements.txt")])
|
|
37
|
+
"-q", "-r", str(HERE.parent / "requirements.txt")])
|
|
35
38
|
if done.returncode:
|
|
36
39
|
# PyTorch ships files nested deep enough to pass Windows' 260-character path limit when
|
|
37
40
|
# Python lives under a long folder: pip then fails halfway with "No such file".
|
|
@@ -42,10 +45,10 @@ def packages():
|
|
|
42
45
|
" or install Python or this folder under a shorter path, e.g. C:\\graph-mind")
|
|
43
46
|
|
|
44
47
|
|
|
45
|
-
def _json_entry(path: Path, key: str = "mcpServers", **extra) -> None:
|
|
48
|
+
def _json_entry(path: Path, key: str = "mcpServers", env=ENV, **extra) -> None:
|
|
46
49
|
settings = json.loads(path.read_text(encoding="utf-8")) if path.exists() else {}
|
|
47
50
|
servers = settings.setdefault(key, {})
|
|
48
|
-
environment = {**servers.get(NAME, {}).get("env", {}), **
|
|
51
|
+
environment = {**servers.get(NAME, {}).get("env", {}), **env} # keep the user's own settings
|
|
49
52
|
servers[NAME] = {**extra, "command": sys.executable, "args": [str(SERVER)], "env": environment}
|
|
50
53
|
temporary = path.with_suffix(".graph-mind.tmp")
|
|
51
54
|
temporary.write_text(json.dumps(settings, ensure_ascii=False, indent=2), encoding="utf-8")
|
|
@@ -55,14 +58,17 @@ def _json_entry(path: Path, key: str = "mcpServers", **extra) -> None:
|
|
|
55
58
|
def claude_code(home: Path) -> str:
|
|
56
59
|
claude = shutil.which("claude") # full path: an npm install is claude.CMD, which
|
|
57
60
|
if claude: # Windows will not launch by its bare name
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
61
|
+
environment = [flag for key, value in CAPTURED.items() for flag in ("-e", f"{key}={value}")]
|
|
62
|
+
try:
|
|
63
|
+
subprocess.run([claude, "mcp", "remove", "-s", "user", NAME], capture_output=True)
|
|
64
|
+
# the name before -e: the CLI's -e takes every value that follows, the name included
|
|
65
|
+
subprocess.run([claude, "mcp", "add", "-s", "user", NAME, *environment, "--",
|
|
66
|
+
sys.executable, str(SERVER)], check=True, capture_output=True)
|
|
67
|
+
return "Claude Code"
|
|
68
|
+
except (OSError, subprocess.CalledProcessError):
|
|
69
|
+
pass # Windows refused to launch it (access denied, seen live): write the file
|
|
64
70
|
if (home / ".claude.json").exists():
|
|
65
|
-
_json_entry(home / ".claude.json", type="stdio")
|
|
71
|
+
_json_entry(home / ".claude.json", env=CAPTURED, type="stdio")
|
|
66
72
|
return "Claude Code"
|
|
67
73
|
return ""
|
|
68
74
|
|
|
@@ -158,9 +164,9 @@ def codex(home: Path) -> str:
|
|
|
158
164
|
text = config.read_text(encoding="utf-8") if config.exists() else ""
|
|
159
165
|
header = f"[mcp_servers.{NAME}]"
|
|
160
166
|
text = _without_codex_block(text)
|
|
161
|
-
environment = "\n".join(f"{key} = '{value}'" for key, value in
|
|
162
|
-
# required:
|
|
163
|
-
text += (f"\n{header}\
|
|
167
|
+
environment = "\n".join(f"{key} = '{value}'" for key, value in CAPTURED.items())
|
|
168
|
+
# not required: a memory server that fails to start must never keep a chat from starting
|
|
169
|
+
text += (f"\n{header}\nstartup_timeout_sec = 60\n"
|
|
164
170
|
f"command = '{sys.executable}'\nargs = ['{SERVER}']\n\n"
|
|
165
171
|
f"[mcp_servers.{NAME}.env]\n{environment}\n")
|
|
166
172
|
config.write_text(text, encoding="utf-8")
|
|
@@ -171,7 +177,7 @@ def capture_service(start: bool) -> str:
|
|
|
171
177
|
sys.path.insert(0, str(HERE))
|
|
172
178
|
from automatic_capture import _atomic_json, default_config_path, default_service_state_dir, make_config
|
|
173
179
|
if not default_config_path().exists():
|
|
174
|
-
config = make_config(workspace=str(HERE), project_id="project:graph-mind",
|
|
180
|
+
config = make_config(workspace=str(HERE.parent), project_id="project:graph-mind",
|
|
175
181
|
session_id="service:automatic-capture-v0.2",
|
|
176
182
|
sessions_root=str(Path.home() / ".codex" / "sessions"))
|
|
177
183
|
# Conversations only. Watching a code folder is opt-in: this folder is Graph-MIND itself,
|
|
@@ -263,7 +269,7 @@ def main(argv=None):
|
|
|
263
269
|
sys.path.insert(0, str(HERE))
|
|
264
270
|
from brain_log import _decode
|
|
265
271
|
_decode(args.join)
|
|
266
|
-
if not args.skip_packages and (HERE / "requirements.txt").exists(): # pip: already done
|
|
272
|
+
if not args.skip_packages and (HERE.parent / "requirements.txt").exists(): # pip: already done
|
|
267
273
|
step("installing packages (the first time takes a few minutes)")
|
|
268
274
|
packages()
|
|
269
275
|
home = Path.home()
|