graph-mind-memory 0.5.2__tar.gz → 0.5.4__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 (66) hide show
  1. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4}/PKG-INFO +16 -18
  2. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4}/README.md +15 -17
  3. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4}/pyproject.toml +3 -2
  4. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/brain_log.py +23 -3
  5. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/graph_mind_mcp_server.py +25 -12
  6. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/graph_mind_memory.egg-info/PKG-INFO +16 -18
  7. graph_mind_memory-0.5.4/src/graph_mind_memory.egg-info/SOURCES.txt +63 -0
  8. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/install.py +21 -15
  9. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/local_brain.py +2 -0
  10. graph_mind_memory-0.5.4/tests/test_associative_memory.py +120 -0
  11. graph_mind_memory-0.5.4/tests/test_automatic_capture.py +101 -0
  12. graph_mind_memory-0.5.4/tests/test_capture_health.py +36 -0
  13. graph_mind_memory-0.5.4/tests/test_coding_memory.py +75 -0
  14. graph_mind_memory-0.5.4/tests/test_cold_start.py +77 -0
  15. graph_mind_memory-0.5.4/tests/test_concurrent_use.py +40 -0
  16. graph_mind_memory-0.5.4/tests/test_conversation_memory.py +191 -0
  17. graph_mind_memory-0.5.4/tests/test_executable_memory.py +105 -0
  18. graph_mind_memory-0.5.4/tests/test_executable_plan_v2_diagnostic.py +52 -0
  19. graph_mind_memory-0.5.4/tests/test_forget.py +125 -0
  20. graph_mind_memory-0.5.4/tests/test_forget_conversation.py +100 -0
  21. graph_mind_memory-0.5.4/tests/test_graph_mind_mcp_server.py +118 -0
  22. graph_mind_memory-0.5.4/tests/test_ingestion_gate.py +67 -0
  23. graph_mind_memory-0.5.4/tests/test_ingestion_synonyms.py +70 -0
  24. graph_mind_memory-0.5.4/tests/test_install.py +160 -0
  25. graph_mind_memory-0.5.4/tests/test_llm_concept_planner.py +87 -0
  26. graph_mind_memory-0.5.4/tests/test_local_brain.py +60 -0
  27. graph_mind_memory-0.5.4/tests/test_longmemeval_adapter.py +49 -0
  28. graph_mind_memory-0.5.4/tests/test_longmemeval_ingestion_calibration.py +45 -0
  29. graph_mind_memory-0.5.4/tests/test_longmemeval_ingestion_preflight.py +22 -0
  30. graph_mind_memory-0.5.4/tests/test_longmemeval_ingestion_v7.py +55 -0
  31. graph_mind_memory-0.5.4/tests/test_memory_algebra.py +45 -0
  32. graph_mind_memory-0.5.4/tests/test_precondition_verifier.py +39 -0
  33. graph_mind_memory-0.5.4/tests/test_recent_recall.py +115 -0
  34. graph_mind_memory-0.5.4/tests/test_resume_retries.py +26 -0
  35. graph_mind_memory-0.5.4/tests/test_secret_masking.py +76 -0
  36. graph_mind_memory-0.5.4/tests/test_semantic_llm_extractor.py +102 -0
  37. graph_mind_memory-0.5.4/tests/test_semantic_memory.py +69 -0
  38. graph_mind_memory-0.5.4/tests/test_semantic_recall.py +89 -0
  39. graph_mind_memory-0.5.4/tests/test_shared_brain.py +269 -0
  40. graph_mind_memory-0.5.4/tests/test_temporal_state.py +151 -0
  41. graph_mind_memory-0.5.4/tests/test_universal_personal_memory.py +73 -0
  42. graph_mind_memory-0.5.2/graph_mind_memory.egg-info/SOURCES.txt +0 -31
  43. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4}/LICENSE +0 -0
  44. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4}/setup.cfg +0 -0
  45. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/associative_memory.py +0 -0
  46. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/automatic_capture.py +0 -0
  47. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/automatic_capture_cli.py +0 -0
  48. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/clock.py +0 -0
  49. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/coding_memory.py +0 -0
  50. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/conversation_memory.py +0 -0
  51. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/development_memory.py +0 -0
  52. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/development_paths.py +0 -0
  53. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/embedding_warmup.py +0 -0
  54. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/entity_timeline.py +0 -0
  55. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/forget.py +0 -0
  56. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/graph_mind_memory.egg-info/dependency_links.txt +0 -0
  57. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/graph_mind_memory.egg-info/entry_points.txt +0 -0
  58. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/graph_mind_memory.egg-info/requires.txt +0 -0
  59. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/graph_mind_memory.egg-info/top_level.txt +0 -0
  60. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/local_embedder.py +0 -0
  61. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/semantic_llm_extractor.py +0 -0
  62. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/semantic_memory.py +0 -0
  63. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/semantic_recall.py +0 -0
  64. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/temporal_resolver.py +0 -0
  65. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/universal_personal_memory.py +0 -0
  66. {graph_mind_memory-0.5.2 → graph_mind_memory-0.5.4/src}/vector_cache.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graph-mind-memory
3
- Version: 0.5.2
3
+ Version: 0.5.4
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
@@ -78,8 +78,8 @@ calls Graph-MIND over MCP and gets back a few thousand tokens of the conversatio
78
78
  ## Benchmarks
79
79
 
80
80
  All numbers below come from files in this repository: the pre-registrations, each question's
81
- answer, and the judge's verdict on it, under [`runs/`](runs). The method is in
82
- [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.
83
83
 
84
84
  **LongMemEval_S: answer accuracy, 500 questions.** The answer model is gpt-5-mini; the judge is the
85
85
  official gpt-4o-2024-08-06.
@@ -139,7 +139,7 @@ or from source:
139
139
  ```bash
140
140
  git clone https://github.com/goyohan0611-png/graph-mind.git
141
141
  cd graph-mind
142
- python install.py
142
+ python src/install.py
143
143
  ```
144
144
 
145
145
  The installer:
@@ -233,7 +233,7 @@ On the PC that holds the memory, tell its AI:
233
233
  It replies with a connection code (`gm1.…`). On each other PC:
234
234
 
235
235
  ```bash
236
- graph-mind-install --join gm1.… # or: python install.py --join gm1.…
236
+ graph-mind-install --join gm1.… # or: python src/install.py --join gm1.…
237
237
  ```
238
238
 
239
239
  The memory then lives in a Postgres server on the first PC, which Graph-MIND sets up itself. Other
@@ -254,16 +254,17 @@ each PC.
254
254
  ## Reproducing the benchmarks
255
255
 
256
256
  1. Download `longmemeval_s_cleaned.json` from [LongMemEval](https://github.com/xiaowu0162/LongMemEval)
257
- into `external/longmemeval/`.
257
+ into `research/external/longmemeval/`.
258
258
  2. Set `OPENAI_API_KEY` for the answer model and the judge.
259
- 3. Run the commands in [REPORT.md §10](REPORT.md#10-reproducing).
259
+ 3. `pip install -e .`, then from `research/` run the commands in
260
+ [REPORT.md §10](research/REPORT.md#10-reproducing).
260
261
 
261
262
  A full 500-question run costs about US$3.
262
263
 
263
264
  ## Tests
264
265
 
265
266
  ```bash
266
- python -m unittest discover -p "test_*.py"
267
+ python -m unittest discover -s tests -p "test_*.py"
267
268
  ```
268
269
 
269
270
  ---
@@ -271,7 +272,7 @@ python -m unittest discover -p "test_*.py"
271
272
  ## Known limits
272
273
 
273
274
  - **For the first few seconds after an AI app starts, answers use word search only** while the
274
- embedding model loads in the background (6-9 s on a slow, synced disk; it was 50-70 s before
275
+ embedding model loads in the background (6-30 s on a slow, synced disk, depending on load; it was 50-70 s before
275
276
  0.4). The first answer never waits for it; later answers take well under a second.
276
277
  - **Daily use on Windows only so far.** macOS and Linux pass the tests in CI but have not seen real
277
278
  use.
@@ -279,20 +280,17 @@ python -m unittest discover -p "test_*.py"
279
280
  can stop capture until Graph-MIND is updated.
280
281
  - **Multi-session questions are the weakest type at 80%.** These are questions that count or
281
282
  combine facts across many conversations.
282
- - **The repository still holds the research-phase experiments** next to the product (see
283
- [Repository layout](#repository-layout)); the installed package carries only the 22 product
284
- modules.
285
283
 
286
- 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).
287
285
 
288
286
  ## Repository layout
289
287
 
290
- | | files |
288
+ | folder | what is in it |
291
289
  |---|---|
292
- | **Product** (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), `local_brain.py` / `conversation_memory.py` / `coding_memory.py` / `development_memory.py` (stores), `semantic_recall.py` / `local_embedder.py` / `vector_cache.py` / `embedding_warmup.py` (search), and their helpers |
293
- | **Benchmarks** | `product_answer_eval.py`, `official_judge_v073.py`, `rival_mem0.py`, `rival_mempalace.py`, `rival_clean_prereg.py`, and the result files under `runs/` |
294
- | **Research phase** | the other modules: earlier extraction pipelines and analyses that REPORT.md cites |
295
- | **Tests** | `test_*.py` |
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 |
296
294
 
297
295
  ## Security
298
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.
@@ -111,7 +111,7 @@ or from source:
111
111
  ```bash
112
112
  git clone https://github.com/goyohan0611-png/graph-mind.git
113
113
  cd graph-mind
114
- python install.py
114
+ python src/install.py
115
115
  ```
116
116
 
117
117
  The installer:
@@ -205,7 +205,7 @@ On the PC that holds the memory, tell its AI:
205
205
  It replies with a connection code (`gm1.…`). On each other PC:
206
206
 
207
207
  ```bash
208
- graph-mind-install --join gm1.… # or: python install.py --join gm1.…
208
+ graph-mind-install --join gm1.… # or: python src/install.py --join gm1.…
209
209
  ```
210
210
 
211
211
  The memory then lives in a Postgres server on the first PC, which Graph-MIND sets up itself. Other
@@ -226,16 +226,17 @@ each PC.
226
226
  ## Reproducing the benchmarks
227
227
 
228
228
  1. Download `longmemeval_s_cleaned.json` from [LongMemEval](https://github.com/xiaowu0162/LongMemEval)
229
- into `external/longmemeval/`.
229
+ into `research/external/longmemeval/`.
230
230
  2. Set `OPENAI_API_KEY` for the answer model and the judge.
231
- 3. Run the commands in [REPORT.md §10](REPORT.md#10-reproducing).
231
+ 3. `pip install -e .`, then from `research/` run the commands in
232
+ [REPORT.md §10](research/REPORT.md#10-reproducing).
232
233
 
233
234
  A full 500-question run costs about US$3.
234
235
 
235
236
  ## Tests
236
237
 
237
238
  ```bash
238
- python -m unittest discover -p "test_*.py"
239
+ python -m unittest discover -s tests -p "test_*.py"
239
240
  ```
240
241
 
241
242
  ---
@@ -243,7 +244,7 @@ python -m unittest discover -p "test_*.py"
243
244
  ## Known limits
244
245
 
245
246
  - **For the first few seconds after an AI app starts, answers use word search only** while the
246
- embedding model loads in the background (6-9 s on a slow, synced disk; it was 50-70 s before
247
+ embedding model loads in the background (6-30 s on a slow, synced disk, depending on load; it was 50-70 s before
247
248
  0.4). The first answer never waits for it; later answers take well under a second.
248
249
  - **Daily use on Windows only so far.** macOS and Linux pass the tests in CI but have not seen real
249
250
  use.
@@ -251,20 +252,17 @@ python -m unittest discover -p "test_*.py"
251
252
  can stop capture until Graph-MIND is updated.
252
253
  - **Multi-session questions are the weakest type at 80%.** These are questions that count or
253
254
  combine facts across many conversations.
254
- - **The repository still holds the research-phase experiments** next to the product (see
255
- [Repository layout](#repository-layout)); the installed package carries only the 22 product
256
- modules.
257
255
 
258
- 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).
259
257
 
260
258
  ## Repository layout
261
259
 
262
- | | files |
260
+ | folder | what is in it |
263
261
  |---|---|
264
- | **Product** (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), `local_brain.py` / `conversation_memory.py` / `coding_memory.py` / `development_memory.py` (stores), `semantic_recall.py` / `local_embedder.py` / `vector_cache.py` / `embedding_warmup.py` (search), and their helpers |
265
- | **Benchmarks** | `product_answer_eval.py`, `official_judge_v073.py`, `rival_mem0.py`, `rival_mempalace.py`, `rival_clean_prereg.py`, and the result files under `runs/` |
266
- | **Research phase** | the other modules: earlier extraction pipelines and analyses that REPORT.md cites |
267
- | **Tests** | `test_*.py` |
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 |
268
266
 
269
267
  ## Security
270
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.2"
7
+ version = "0.5.4"
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"
@@ -45,7 +45,8 @@ graph-mind-capture = "automatic_capture_cli:main"
45
45
  graph-mind-forget = "forget:main"
46
46
 
47
47
  [tool.setuptools]
48
- # ponytail: flat modules, as in the repository; a graph_mind/ package is the upgrade path.
48
+ # ponytail: flat modules under src/; a graph_mind/ package is the upgrade path.
49
+ package-dir = { "" = "src" }
49
50
  py-modules = [
50
51
  "associative_memory",
51
52
  "automatic_capture",
@@ -461,6 +461,27 @@ def _pg(url):
461
461
  return connection
462
462
 
463
463
 
464
+ _READERS: dict = {}
465
+
466
+
467
+ def _read_rows(url, query, parameters):
468
+ """A read on one connection kept open per hub: the sync before every answer paid a fresh
469
+ connection (password handshake included) each time, 0.2 s of a 0.9 s answer. A connection
470
+ the hub dropped (it restarted) is replaced once."""
471
+ import psycopg
472
+ for attempt in (1, 2):
473
+ connection = _READERS.get(url)
474
+ if connection is None or connection.closed:
475
+ connection = _READERS[url] = _pg(url)
476
+ connection.autocommit = True
477
+ try:
478
+ return connection.execute(query, parameters).fetchall()
479
+ except psycopg.OperationalError:
480
+ _READERS.pop(url, None)
481
+ if attempt == 2:
482
+ raise
483
+
484
+
464
485
  def _outbox() -> Path:
465
486
  from development_paths import graph_mind_home
466
487
  return graph_mind_home() / "outbox.jsonl"
@@ -514,9 +535,8 @@ def _new_lines(db: sqlite3.Connection, shared) -> list[str]:
514
535
  source = "postgres"
515
536
  row = db.execute("SELECT offset FROM brain_log_cursor WHERE source=?",
516
537
  (source,)).fetchone()
517
- with _pg(shared) as connection:
518
- rows = connection.execute("SELECT seq, line FROM brain_log WHERE seq > %s ORDER BY seq",
519
- (row[0] if row else 0,)).fetchall()
538
+ rows = _read_rows(shared, "SELECT seq, line FROM brain_log WHERE seq > %s ORDER BY seq",
539
+ (row[0] if row else 0,))
520
540
  if rows:
521
541
  with db:
522
542
  db.execute("INSERT OR REPLACE INTO brain_log_cursor VALUES(?,?)",
@@ -26,12 +26,13 @@ if hasattr(sys.stderr, "reconfigure"):
26
26
 
27
27
 
28
28
  def _version() -> str:
29
- """The package version: pyproject.toml beside this file in a source checkout, else the
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__).with_name("pyproject.toml")
33
- if project.exists():
34
- found = re.search(r'^version = "([^"]+)"', project.read_text(encoding="utf-8"), re.M)
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
- "Remember proactively. Whenever the user mentions something about their life, work, "
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 "
@@ -563,12 +573,15 @@ def build_server(db_path=None):
563
573
  return {"status": "SKIPPED", "route": route,
564
574
  "scope_completeness": "NOT_APPLICABLE", "char_budget": max_chars,
565
575
  "used_chars": 0, "estimated_tokens": 0, "selected_items": 0,
566
- "omitted_items": 0, "items": [], "context": ""}
576
+ "omitted_items": 0, "ids": [], "context": ""}
567
577
  recalled = gather(database, query, scopes=scopes, as_of=as_of or _now(),
568
578
  knowledge_cutoff=knowledge_cutoff, limit=limit, recent=recent,
569
579
  since=since, about_assistant=about_assistant, around=around)
570
580
  packet = build_context_pack(recalled, max_chars=max_chars,
571
581
  order="time" if recent else "relevance")
582
+ # `context` already carries each item's kind, date, title, text and source; the item
583
+ # list repeated all of it and was 2/3 of what the model had to read (43K -> 16K chars)
584
+ packet["ids"] = [item["id"] for item in packet.pop("items")]
572
585
  packet["route"] = route
573
586
  warnings = capture_warnings()
574
587
  if warnings:
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: graph-mind-memory
3
- Version: 0.5.2
3
+ Version: 0.5.4
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
@@ -78,8 +78,8 @@ calls Graph-MIND over MCP and gets back a few thousand tokens of the conversatio
78
78
  ## Benchmarks
79
79
 
80
80
  All numbers below come from files in this repository: the pre-registrations, each question's
81
- answer, and the judge's verdict on it, under [`runs/`](runs). The method is in
82
- [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.
83
83
 
84
84
  **LongMemEval_S: answer accuracy, 500 questions.** The answer model is gpt-5-mini; the judge is the
85
85
  official gpt-4o-2024-08-06.
@@ -139,7 +139,7 @@ or from source:
139
139
  ```bash
140
140
  git clone https://github.com/goyohan0611-png/graph-mind.git
141
141
  cd graph-mind
142
- python install.py
142
+ python src/install.py
143
143
  ```
144
144
 
145
145
  The installer:
@@ -233,7 +233,7 @@ On the PC that holds the memory, tell its AI:
233
233
  It replies with a connection code (`gm1.…`). On each other PC:
234
234
 
235
235
  ```bash
236
- graph-mind-install --join gm1.… # or: python install.py --join gm1.…
236
+ graph-mind-install --join gm1.… # or: python src/install.py --join gm1.…
237
237
  ```
238
238
 
239
239
  The memory then lives in a Postgres server on the first PC, which Graph-MIND sets up itself. Other
@@ -254,16 +254,17 @@ each PC.
254
254
  ## Reproducing the benchmarks
255
255
 
256
256
  1. Download `longmemeval_s_cleaned.json` from [LongMemEval](https://github.com/xiaowu0162/LongMemEval)
257
- into `external/longmemeval/`.
257
+ into `research/external/longmemeval/`.
258
258
  2. Set `OPENAI_API_KEY` for the answer model and the judge.
259
- 3. Run the commands in [REPORT.md §10](REPORT.md#10-reproducing).
259
+ 3. `pip install -e .`, then from `research/` run the commands in
260
+ [REPORT.md §10](research/REPORT.md#10-reproducing).
260
261
 
261
262
  A full 500-question run costs about US$3.
262
263
 
263
264
  ## Tests
264
265
 
265
266
  ```bash
266
- python -m unittest discover -p "test_*.py"
267
+ python -m unittest discover -s tests -p "test_*.py"
267
268
  ```
268
269
 
269
270
  ---
@@ -271,7 +272,7 @@ python -m unittest discover -p "test_*.py"
271
272
  ## Known limits
272
273
 
273
274
  - **For the first few seconds after an AI app starts, answers use word search only** while the
274
- embedding model loads in the background (6-9 s on a slow, synced disk; it was 50-70 s before
275
+ embedding model loads in the background (6-30 s on a slow, synced disk, depending on load; it was 50-70 s before
275
276
  0.4). The first answer never waits for it; later answers take well under a second.
276
277
  - **Daily use on Windows only so far.** macOS and Linux pass the tests in CI but have not seen real
277
278
  use.
@@ -279,20 +280,17 @@ python -m unittest discover -p "test_*.py"
279
280
  can stop capture until Graph-MIND is updated.
280
281
  - **Multi-session questions are the weakest type at 80%.** These are questions that count or
281
282
  combine facts across many conversations.
282
- - **The repository still holds the research-phase experiments** next to the product (see
283
- [Repository layout](#repository-layout)); the installed package carries only the 22 product
284
- modules.
285
283
 
286
- 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).
287
285
 
288
286
  ## Repository layout
289
287
 
290
- | | files |
288
+ | folder | what is in it |
291
289
  |---|---|
292
- | **Product** (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), `local_brain.py` / `conversation_memory.py` / `coding_memory.py` / `development_memory.py` (stores), `semantic_recall.py` / `local_embedder.py` / `vector_cache.py` / `embedding_warmup.py` (search), and their helpers |
293
- | **Benchmarks** | `product_answer_eval.py`, `official_judge_v073.py`, `rival_mem0.py`, `rival_mempalace.py`, `rival_clean_prereg.py`, and the result files under `runs/` |
294
- | **Research phase** | the other modules: earlier extraction pipelines and analyses that REPORT.md cites |
295
- | **Tests** | `test_*.py` |
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 |
296
294
 
297
295
  ## Security
298
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", {}), **ENV} # keep the user's own settings
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
- subprocess.run([claude, "mcp", "remove", "-s", "user", NAME], capture_output=True)
59
- environment = [flag for key, value in ENV.items() for flag in ("-e", f"{key}={value}")]
60
- # the name before -e: the CLI's -e takes every value that follows, the name included
61
- subprocess.run([claude, "mcp", "add", "-s", "user", NAME, *environment, "--",
62
- sys.executable, str(SERVER)], check=True, capture_output=True)
63
- return "Claude Code"
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 ENV.items())
162
- # required: otherwise Codex leaves out a server still starting when the first question comes
163
- text += (f"\n{header}\nrequired = true\nstartup_timeout_sec = 60\n"
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()
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
  from development_paths import use_wal
5
5
 
6
6
  from pathlib import Path
7
+ import functools
7
8
  import hashlib
8
9
  import json
9
10
  import re
@@ -39,6 +40,7 @@ def _string_list(value, error):
39
40
  return list(dict.fromkeys(item.strip() for item in value))
40
41
 
41
42
 
43
+ @functools.lru_cache(maxsize=65536) # every memory's title and tags, on every question
42
44
  def _normalized(value):
43
45
  text = unicodedata.normalize("NFKC", value).casefold()
44
46
  return " ".join(re.findall(r"[^\W_]+", text, flags=re.UNICODE))