mlx-memo 0.5.0__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 (111) hide show
  1. mlx_memo-0.5.0/.claude-plugin/marketplace.json +17 -0
  2. mlx_memo-0.5.0/.claude-plugin/plugin.json +13 -0
  3. mlx_memo-0.5.0/.github/workflows/test.yml +34 -0
  4. mlx_memo-0.5.0/.gitignore +39 -0
  5. mlx_memo-0.5.0/.mcp.json +10 -0
  6. mlx_memo-0.5.0/CHANGELOG.md +301 -0
  7. mlx_memo-0.5.0/CLAUDE.md +124 -0
  8. mlx_memo-0.5.0/LICENSE +21 -0
  9. mlx_memo-0.5.0/PKG-INFO +540 -0
  10. mlx_memo-0.5.0/README.md +503 -0
  11. mlx_memo-0.5.0/docs/architecture.svg +162 -0
  12. mlx_memo-0.5.0/docs/tui-dashboard.png +0 -0
  13. mlx_memo-0.5.0/hooks/hooks.json +58 -0
  14. mlx_memo-0.5.0/integrations/paperclip-plugin-memo/.gitignore +3 -0
  15. mlx_memo-0.5.0/integrations/paperclip-plugin-memo/README.md +71 -0
  16. mlx_memo-0.5.0/integrations/paperclip-plugin-memo/esbuild.config.mjs +17 -0
  17. mlx_memo-0.5.0/integrations/paperclip-plugin-memo/package.json +48 -0
  18. mlx_memo-0.5.0/integrations/paperclip-plugin-memo/pnpm-lock.yaml +1242 -0
  19. mlx_memo-0.5.0/integrations/paperclip-plugin-memo/rollup.config.mjs +28 -0
  20. mlx_memo-0.5.0/integrations/paperclip-plugin-memo/scripts/install.sh +24 -0
  21. mlx_memo-0.5.0/integrations/paperclip-plugin-memo/src/manifest.ts +167 -0
  22. mlx_memo-0.5.0/integrations/paperclip-plugin-memo/src/ui/index.tsx +44 -0
  23. mlx_memo-0.5.0/integrations/paperclip-plugin-memo/src/worker.ts +275 -0
  24. mlx_memo-0.5.0/integrations/paperclip-plugin-memo/tests/plugin.spec.ts +51 -0
  25. mlx_memo-0.5.0/integrations/paperclip-plugin-memo/tsconfig.json +27 -0
  26. mlx_memo-0.5.0/integrations/paperclip-plugin-memo/vitest.config.ts +8 -0
  27. mlx_memo-0.5.0/pyproject.toml +79 -0
  28. mlx_memo-0.5.0/scripts/audit-data-integrity.py +121 -0
  29. mlx_memo-0.5.0/scripts/clean-ingest-leaks.py +64 -0
  30. mlx_memo-0.5.0/scripts/migrate-from-mem-vault.py +176 -0
  31. mlx_memo-0.5.0/skills/memo/SKILL.md +251 -0
  32. mlx_memo-0.5.0/src/memo/__init__.py +30 -0
  33. mlx_memo-0.5.0/src/memo/agent.py +377 -0
  34. mlx_memo-0.5.0/src/memo/analytics.py +332 -0
  35. mlx_memo-0.5.0/src/memo/capture.py +364 -0
  36. mlx_memo-0.5.0/src/memo/chunker.py +210 -0
  37. mlx_memo-0.5.0/src/memo/cli.py +5045 -0
  38. mlx_memo-0.5.0/src/memo/cognitive.py +419 -0
  39. mlx_memo-0.5.0/src/memo/collaborative.py +451 -0
  40. mlx_memo-0.5.0/src/memo/config.py +279 -0
  41. mlx_memo-0.5.0/src/memo/consolidation.py +350 -0
  42. mlx_memo-0.5.0/src/memo/contextual.py +289 -0
  43. mlx_memo-0.5.0/src/memo/crossref.py +308 -0
  44. mlx_memo-0.5.0/src/memo/dashboard.py +506 -0
  45. mlx_memo-0.5.0/src/memo/embedder.py +261 -0
  46. mlx_memo-0.5.0/src/memo/encryption.py +381 -0
  47. mlx_memo-0.5.0/src/memo/federation.py +242 -0
  48. mlx_memo-0.5.0/src/memo/graph.py +253 -0
  49. mlx_memo-0.5.0/src/memo/history.py +160 -0
  50. mlx_memo-0.5.0/src/memo/import_export.py +338 -0
  51. mlx_memo-0.5.0/src/memo/lifecycle.py +342 -0
  52. mlx_memo-0.5.0/src/memo/llm.py +144 -0
  53. mlx_memo-0.5.0/src/memo/memory.py +1535 -0
  54. mlx_memo-0.5.0/src/memo/multimodal.py +495 -0
  55. mlx_memo-0.5.0/src/memo/navigation.py +372 -0
  56. mlx_memo-0.5.0/src/memo/proactive.py +247 -0
  57. mlx_memo-0.5.0/src/memo/project.py +71 -0
  58. mlx_memo-0.5.0/src/memo/queries.py +247 -0
  59. mlx_memo-0.5.0/src/memo/reranker.py +216 -0
  60. mlx_memo-0.5.0/src/memo/server.py +1846 -0
  61. mlx_memo-0.5.0/src/memo/session.py +401 -0
  62. mlx_memo-0.5.0/src/memo/setup/__init__.py +26 -0
  63. mlx_memo-0.5.0/src/memo/setup/config_io.py +81 -0
  64. mlx_memo-0.5.0/src/memo/setup/picker.py +109 -0
  65. mlx_memo-0.5.0/src/memo/setup/vaults.py +72 -0
  66. mlx_memo-0.5.0/src/memo/sharing.py +459 -0
  67. mlx_memo-0.5.0/src/memo/store.py +455 -0
  68. mlx_memo-0.5.0/src/memo/sync.py +300 -0
  69. mlx_memo-0.5.0/src/memo/temporal.py +394 -0
  70. mlx_memo-0.5.0/src/memo/transcript_miner.py +238 -0
  71. mlx_memo-0.5.0/src/memo/versioning.py +261 -0
  72. mlx_memo-0.5.0/src/memo/watcher.py +213 -0
  73. mlx_memo-0.5.0/tests/__init__.py +0 -0
  74. mlx_memo-0.5.0/tests/conftest.py +59 -0
  75. mlx_memo-0.5.0/tests/test_agent.py +170 -0
  76. mlx_memo-0.5.0/tests/test_analytics.py +166 -0
  77. mlx_memo-0.5.0/tests/test_capture.py +260 -0
  78. mlx_memo-0.5.0/tests/test_cli_init.py +98 -0
  79. mlx_memo-0.5.0/tests/test_cli_install_wrapper.py +113 -0
  80. mlx_memo-0.5.0/tests/test_cli_migrate_vault.py +123 -0
  81. mlx_memo-0.5.0/tests/test_cognitive.py +271 -0
  82. mlx_memo-0.5.0/tests/test_collaborative.py +276 -0
  83. mlx_memo-0.5.0/tests/test_config.py +120 -0
  84. mlx_memo-0.5.0/tests/test_consolidation.py +207 -0
  85. mlx_memo-0.5.0/tests/test_contextual.py +237 -0
  86. mlx_memo-0.5.0/tests/test_crossref.py +228 -0
  87. mlx_memo-0.5.0/tests/test_dashboard.py +129 -0
  88. mlx_memo-0.5.0/tests/test_encryption.py +266 -0
  89. mlx_memo-0.5.0/tests/test_federation.py +227 -0
  90. mlx_memo-0.5.0/tests/test_import_export.py +190 -0
  91. mlx_memo-0.5.0/tests/test_lifecycle.py +256 -0
  92. mlx_memo-0.5.0/tests/test_memory.py +715 -0
  93. mlx_memo-0.5.0/tests/test_memory_project_tag.py +73 -0
  94. mlx_memo-0.5.0/tests/test_multimodal.py +250 -0
  95. mlx_memo-0.5.0/tests/test_navigation.py +251 -0
  96. mlx_memo-0.5.0/tests/test_proactive.py +149 -0
  97. mlx_memo-0.5.0/tests/test_project.py +69 -0
  98. mlx_memo-0.5.0/tests/test_queries.py +270 -0
  99. mlx_memo-0.5.0/tests/test_reranker.py +174 -0
  100. mlx_memo-0.5.0/tests/test_server.py +92 -0
  101. mlx_memo-0.5.0/tests/test_session.py +344 -0
  102. mlx_memo-0.5.0/tests/test_setup_config_io.py +69 -0
  103. mlx_memo-0.5.0/tests/test_setup_vaults.py +80 -0
  104. mlx_memo-0.5.0/tests/test_sharing.py +293 -0
  105. mlx_memo-0.5.0/tests/test_smoke_mlx.py +101 -0
  106. mlx_memo-0.5.0/tests/test_store.py +125 -0
  107. mlx_memo-0.5.0/tests/test_sync.py +169 -0
  108. mlx_memo-0.5.0/tests/test_temporal.py +197 -0
  109. mlx_memo-0.5.0/tests/test_transcript_miner.py +100 -0
  110. mlx_memo-0.5.0/tests/test_versioning.py +258 -0
  111. mlx_memo-0.5.0/tests/test_watcher.py +71 -0
@@ -0,0 +1,17 @@
1
+ {
2
+ "$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
3
+ "name": "memo",
4
+ "description": "Local MCP memory for AI agents on Apple Silicon. MLX-native, sqlite-vec, markdown-on-disk.",
5
+ "owner": {
6
+ "name": "Fernando Ferrari",
7
+ "url": "https://github.com/jagoff"
8
+ },
9
+ "plugins": [
10
+ {
11
+ "name": "memo",
12
+ "description": "Persistent semantic memory. Slash command `/memo` + MCP tools `mcp__memo__memory_*`.",
13
+ "source": "./",
14
+ "category": "memory"
15
+ }
16
+ ]
17
+ }
@@ -0,0 +1,13 @@
1
+ {
2
+ "name": "memo",
3
+ "description": "Local MCP memory for AI agents — MLX-native on Apple Silicon, sqlite-vec store, markdown-on-disk in your Obsidian vault. Zero Ollama, zero cloud APIs.",
4
+ "version": "0.5.0",
5
+ "author": {
6
+ "name": "Fernando Ferrari",
7
+ "url": "https://github.com/jagoff"
8
+ },
9
+ "homepage": "https://github.com/jagoff/memo",
10
+ "repository": "https://github.com/jagoff/memo",
11
+ "license": "MIT",
12
+ "keywords": ["memory", "mcp", "mlx", "obsidian", "rag", "agents", "local-first"]
13
+ }
@@ -0,0 +1,34 @@
1
+ name: test
2
+
3
+ on:
4
+ push:
5
+ branches: [master]
6
+ pull_request:
7
+ branches: [master]
8
+
9
+ jobs:
10
+ test:
11
+ # Linux runner is enough — `mlx`/`mlx-lm` deps are gated to
12
+ # `darwin/arm64` in pyproject.toml and the real-MLX smoke tests
13
+ # auto-skip via the `requires_mlx` marker (see conftest.py) when
14
+ # `mlx_lm` isn't importable.
15
+ runs-on: ubuntu-latest
16
+ strategy:
17
+ matrix:
18
+ python-version: ["3.13"]
19
+ steps:
20
+ - uses: actions/checkout@v4
21
+ - name: Set up Python ${{ matrix.python-version }}
22
+ uses: actions/setup-python@v5
23
+ with:
24
+ python-version: ${{ matrix.python-version }}
25
+ - name: Install uv
26
+ run: pip install uv
27
+ - name: Install package + dev deps
28
+ run: |
29
+ uv venv .venv
30
+ uv pip install -e '.[dev]'
31
+ - name: Lint (ruff)
32
+ run: .venv/bin/ruff check src/ tests/
33
+ - name: Tests (excluding slow real-MLX smoke)
34
+ run: .venv/bin/python -m pytest -q -m "not slow"
@@ -0,0 +1,39 @@
1
+ # Python
2
+ __pycache__/
3
+ *.py[cod]
4
+ *.egg-info/
5
+ *.egg
6
+ .pytest_cache/
7
+ .mypy_cache/
8
+ .ruff_cache/
9
+ .coverage
10
+ htmlcov/
11
+
12
+ # Build artifacts
13
+ build/
14
+ dist/
15
+ dist.v*/
16
+
17
+ # Environments
18
+ .venv/
19
+ venv/
20
+ .env
21
+ .env.local
22
+
23
+ # Local DB / state
24
+ *.db
25
+ *.db-shm
26
+ *.db-wal
27
+
28
+ # Editor / IDE / OS
29
+ .DS_Store
30
+ .claude/
31
+ .windsurf/
32
+ .idea/
33
+ .vscode/
34
+
35
+ # Node (integrations)
36
+ node_modules/
37
+ integrations/**/node_modules/
38
+ integrations/**/dist/
39
+ integrations/**/.paperclip-sdk/
@@ -0,0 +1,10 @@
1
+ {
2
+ "mcpServers": {
3
+ "memo": {
4
+ "type": "stdio",
5
+ "command": "memo-mcp",
6
+ "args": [],
7
+ "env": {}
8
+ }
9
+ }
10
+ }
@@ -0,0 +1,301 @@
1
+ # Changelog
2
+
3
+ All notable changes to this project will be documented in this file.
4
+
5
+ The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
6
+ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
7
+
8
+ ## [0.5.0] - 2026-05-12
9
+
10
+ PyPI dist rename + TUI follow-ups (q/ESC quit, smaller layout).
11
+
12
+ ### Changed
13
+
14
+ - **PyPI distribution renamed `memo-mcp` → `mlx-memo`.** The previous
15
+ name collided with [`milasd/memo-mcp`](https://github.com/milasd/memo-mcp)
16
+ (a ChromaDB-backed journal MCP) and risked install ambiguity for new
17
+ users. The Python module (`memo`), CLI binary (`memo`), MCP server
18
+ binary (`memo-mcp`), and the GitHub repo all keep their names — only
19
+ the PyPI dist moved. Existing `pip install memo-mcp ≤ 0.4.3` users
20
+ keep working; new installs use `pip install mlx-memo`. README +
21
+ CLAUDE.md updated, badges repointed.
22
+
23
+ ### Added (0.4.2 + 0.4.3 follow-ups, merged into 0.5.0)
24
+
25
+ - **`q` / `ESC` exit** in `memo tui`. Background stdin reader in
26
+ cbreak mode sets a stop event; main loop polls. Falls back to
27
+ Ctrl+C-only when stdin isn't a TTY. Footer advertises the keys.
28
+ - **TUI layout shrunk to ~18 rows**. Removed the panel-framed hero
29
+ (now an inline footer status line); corpus and runtime collapsed
30
+ to single-line summaries.
31
+
32
+ ### Fixed
33
+
34
+ - **Legacy-path warning** (`stored path(s) don't resolve…`) now
35
+ silenced inside `memo tui` via `MEMO_SUPPRESS_LEGACY_WARN=1`. The
36
+ user can't act on it from inside the alt screen anyway. Outside the
37
+ TUI the message was rephrased ("heads-up: tu índice apunta a paths
38
+ antiguos") and explicitly labeled as not-an-error.
39
+
40
+ ## [0.4.1] - 2026-05-12
41
+
42
+ Adds a live terminal dashboard and the recall-log plumbing that feeds it.
43
+
44
+ ### Added
45
+
46
+ - **`memo tui`** — live, colored Rich-Live dashboard. Six panels:
47
+ corpus stats (totals + per-type breakdown + project count), runtime
48
+ (MLX warm/cold flags for embedder / reranker / chat, vault size,
49
+ watcher status from `launchctl print`), recent saves (last 10 from
50
+ `history.db`), recent recalls (last 8 from the new recall log), top
51
+ tags (project tags highlighted), and 14-day saves/recalls
52
+ sparklines (`▁▂▃▄▅▆▇█`). Refresh `--refresh N` (default 1.0 s).
53
+ Ctrl+C to exit. Zero new deps — Rich was already in.
54
+ - **Recall log JSONL** at `~/.local/share/memo/recall.log`. The
55
+ `memo recall-hook` appends `{ts, prompt, hits[]}` per invocation
56
+ (best-effort, failures swallowed). Auto-rotates at ~200 KB to the
57
+ last 200 entries. Powers the TUI's recall panel; failures here can
58
+ never affect the hook's output.
59
+ - **`/memo tui`** routing in `skills/memo/SKILL.md` (along with
60
+ `/memo watch`, `/memo install-watcher`, `/memo mine-history`).
61
+
62
+ ### Changed
63
+
64
+ - New module `memo.dashboard` (~370 lines). Public API:
65
+ `run_tui`, `render`, `sparkline`, `append_recall_log`,
66
+ `read_recall_log`.
67
+
68
+ ## [0.4.0] - 2026-05-12
69
+
70
+ Minor release — five "gamechanger" features land alongside a major
71
+ repo-hygiene pass to make the project public-ready.
72
+
73
+ ### Added
74
+
75
+ - **Project-scoped recall.** `memo save` now auto-attaches a
76
+ `project:<repo>` tag derived from the git toplevel of the caller's
77
+ cwd (or the `MEMO_PROJECT_TAG` env var). The recall hook reads `cwd`
78
+ from the Claude Code hook payload and additively boosts the score of
79
+ memorias that share the current project tag by
80
+ `MEMO_RECALL_PROJECT_BOOST` (default `0.15`). Opt out per-call with
81
+ `memo save --no-project-tag` or globally with
82
+ `MEMO_AUTO_PROJECT_TAG=0`. New module: `memo.project`.
83
+ - **Token-budget-aware recall.** `MEMO_RECALL_TOKEN_BUDGET` (default
84
+ `0` = off) packs memorias greedily by score until the budget is
85
+ reached; the final memoria gets body-truncated to fit instead of
86
+ being dropped wholesale. Token estimate is `len(text)//4` — no
87
+ tiktoken dep.
88
+ - **`memo mine-history`** — bulk-mine past Claude Code transcripts
89
+ under `~/.claude/projects/<hash>/*.jsonl` for insights, running the
90
+ same prefilter → helper-LLM extract → embedding-dedup pipeline as
91
+ the live capture hook. Resumable per-file via
92
+ `~/.local/share/memo/mine-history.json`. Flags:
93
+ `--path / --since / --limit / --dry-run / --debug / --json`.
94
+ New module: `memo.transcript_miner`.
95
+ - **MCP resources** — `memo://recent` (top-20 by `updated` desc, with
96
+ per-memoria `memo://memory/<id>` links) and `memo://memory/{id}`
97
+ (full record, accepts prefix ≥4 chars). Clients can pin / drag
98
+ memorias into context without paying tool-call overhead per access.
99
+ - **`memo watch`** — file-watcher daemon. Watches `cfg.memory_dir`
100
+ recursively via `watchdog`/FSEvents and triggers a debounced
101
+ `Memory.reindex()` (`--delay` configurable, default 2 s) when `.md`
102
+ files are modified. Bundled installer:
103
+ - `memo install-watcher` writes
104
+ `~/Library/LaunchAgents/com.fer.memo.watch.plist`, loads it via
105
+ `launchctl bootstrap`, and verifies. Logs to
106
+ `~/Library/Logs/memo/`. `KeepAlive=true`.
107
+ - `memo uninstall-watcher` boots out the job and removes the plist.
108
+ New module: `memo.watcher`. New dep: `watchdog>=4.0`.
109
+
110
+ ### Changed
111
+
112
+ - **README rewritten for a public audience.** Cleaner hero, expanded
113
+ alternatives matrix (mem0 / letta / cognee / supermemory / mem-vault
114
+ / MCP-memory reference / engram) with the seven differentiators
115
+ spelled out in plain terms, and an updated `docs/architecture.svg`
116
+ that covers clients → MCP/CLI → core → MLX → storage rather than
117
+ just the recall pipeline.
118
+ - **`.gitignore` expanded** to cover `dist.v*/`, `.claude/`,
119
+ `.windsurf/`, `integrations/**/node_modules/`,
120
+ `integrations/**/dist/`, `integrations/**/.paperclip-sdk/`, and
121
+ common IDE dirs.
122
+ - **Test fixture default** now sets `MEMO_AUTO_PROJECT_TAG=0` so tests
123
+ asserting exact tag sets aren't polluted by the cwd-derived
124
+ project tag. Tests exercising the auto-tag flow opt back in
125
+ explicitly via `monkeypatch.setenv`.
126
+
127
+ ### Removed
128
+
129
+ - Stale `dist.v0.2.0/`, `dist.v0.3.0/`, `dist.v0.3.1/` build snapshots
130
+ and accumulated `.DS_Store` files from the repo. `dist/` was already
131
+ gitignored; the dated variants weren't.
132
+
133
+ ## [0.3.3] - 2026-05-08
134
+
135
+ Patch release — install-from-git fixes surfaced while validating the
136
+ distributed-install flow on a clean machine.
137
+
138
+ ### Fixed
139
+
140
+ - **`memo --version` crashed** with `RuntimeError: 'memo' is not
141
+ installed. Try passing 'package_name' instead.` because click's
142
+ `version_option` defaulted to `package_name="memo"` while the actual
143
+ PyPI/wheel dist is `memo-mcp`. Pinned the lookup explicitly.
144
+ - **`DEFAULT_MEMORY_SUBDIR` pointed at the deprecated archive path**
145
+ (`04-Archive/99-obsidian-system/99-AI/memory`). Updated to the
146
+ current `99-obsidian/99-AI/memory` location, matching the user-facing
147
+ vault reorganization done on 2026-05-08. Existing installs that
148
+ override via `MEMO_MEMORY_SUBDIR` are unaffected.
149
+
150
+ ## [0.3.2] - 2026-05-07
151
+
152
+ Patch release — BM25 recall fix.
153
+
154
+ ### Fixed
155
+
156
+ - **BM25 search wrapped query in phrase quotes** (`"foo bar"`) which
157
+ required the words to appear consecutively. This killed recall on
158
+ natural multi-word Spanish queries:
159
+ - `"Astor terapia ocupacional"` did NOT match the document titled
160
+ `"Informe Terapia Ocupacional — Astor Ferrari"` because the words
161
+ don't appear in that exact consecutive order.
162
+
163
+ Fix: tokenize via `\w+` (Unicode-aware), wrap each token in its own
164
+ phrase quotes, join with whitespace (FTS5's implicit AND). Now the
165
+ query is `"Astor" "terapia" "ocupacional"` — matches any doc
166
+ containing all 3 words anywhere, any order.
167
+
168
+ ### Verified
169
+
170
+ Same query post-fix:
171
+ - BM25: `Astor — Informe Terapia Ocupacional feb 2026` returns first hit ✓
172
+ - Hybrid (vec+BM25 RRF): same doc in top-3 ✓
173
+
174
+ Other corpus queries that benefit:
175
+ - `MLX migration` → `obsidian-rag: migración Ollama → MLX` (was missing pre-fix)
176
+ - `obsidian-rag bug fix` → `Bug pattern — sqlite3 'database is locked'` (was missing pre-fix)
177
+
178
+ ## [0.3.1] - 2026-05-07
179
+
180
+ Patch release — bug fixes for the v0.3.0 ingest pipeline.
181
+
182
+ ### Added
183
+
184
+ - `memo ingest <vault-path>` CLI command — bulk-ingest .md files from any
185
+ Obsidian vault into the memo index. Synthesizes ids from path hash so
186
+ user .md files are not modified. Idempotent. See README "Ambient memory"
187
+ section for usage.
188
+ - `MEMO_INGEST_MIN_CHARS` env var (default 200) — skip notes shorter than
189
+ this threshold. Tag-only stubs (`#tagA #tagB` + 1-line question)
190
+ produce noisy embeddings near the corpus centroid that match generic
191
+ queries with high false-positive rate. Filtering them improves recall
192
+ precision on queries with proper nouns.
193
+
194
+ ### Fixed
195
+
196
+ - **Embedder API misuse** in `recall-hook`, `prewarm`, and `ingest`:
197
+ `MLXEmbedder.embed()` is batched (signature `Sequence[str] →
198
+ list[list[float]]`); passing a bare string iterated per-char,
199
+ producing variable-dim outputs (135, 512, 2465...) instead of 1024.
200
+ Cascade Metal GPU error after several mismatches. Fix: wrap input
201
+ in `[composed]` and take `[0]`.
202
+ - **Plugin install path** — bumping from 0.3.0 to 0.3.1 so users on the
203
+ Claude Code plugin marketplace pick up these fixes via `/plugin update`.
204
+
205
+ ## [0.3.0] - 2026-05-07
206
+
207
+ **Game-changer release**: memo turns into an *ambient* context layer.
208
+ Memorias auto-inject as `additionalContext` on every Claude Code prompt,
209
+ without the user invoking `/memo` at all.
210
+
211
+ ### Added
212
+
213
+ - `memo recall-hook` CLI command — Claude Code `UserPromptSubmit` hook that
214
+ embeds the user prompt via the MLX embedder, runs vec-only search against
215
+ the memo index, and emits relevant memorias as `additionalContext` markdown
216
+ on stdout. Sub-1.7s warm latency on a 223-doc corpus, well within the
217
+ default 5s hook timeout.
218
+ - `memo prewarm` CLI command — `SessionStart` hook that pre-loads the MLX
219
+ embedder so the first `recall-hook` invocation of the session is fast
220
+ (warm load: ~500ms vs ~2s cold).
221
+ - `hooks/hooks.json` bundled in the plugin — auto-wires the two hooks above
222
+ when users install via `/plugin install memo@memo`.
223
+ - 6 env vars to tune ambient memory behaviour:
224
+ - `MEMO_RECALL_DISABLE` — kill switch (default: enabled).
225
+ - `MEMO_RECALL_TOP_K` — default 3.
226
+ - `MEMO_RECALL_MIN_SIM` — cosine similarity floor, default 0.6.
227
+ - `MEMO_RECALL_MIN_PROMPT_CHARS` — default 12.
228
+ - `MEMO_RECALL_BODY_CHARS` — snippet length, default 240.
229
+ - `MEMO_RECALL_SKIP_SLASH` — skip recall on `/` prompts, default 1.
230
+ - `MEMO_RECALL_DEBUG` — print failure reasons to stderr, default 0.
231
+
232
+ ### Why this is the game changer
233
+
234
+ Before: user types `/memo save 'X'` to save and `/memo search 'Y'` to recall.
235
+ Friction = adoption blocker; memory only helps when remembered to invoke.
236
+
237
+ After: memo silently consults the user's past on every prompt and injects
238
+ the most relevant 3 memorias if any score above 0.6 cosine similarity.
239
+ Zero `/memo` invocations needed for the recall side. The agent "knows
240
+ your past" automatically.
241
+
242
+ ### Empirical tuning
243
+
244
+ Threshold 0.6 was picked after testing the 223-doc corpus:
245
+ - "qué decidí sobre MLX vs Ollama" → 3 hits at 0.71-0.74 (all relevant).
246
+ - "how to bake apple pie" (corpus has zero food memorias) → 0 hits at 0.6
247
+ (3 noise hits at 0.5-0.6 cut by the floor).
248
+ - "qué hice con whatsapp" → 3 hits at 0.6-0.75 (whatsapp work).
249
+
250
+ ### Privacy / local-first
251
+
252
+ Hot path is 100% MLX in-process. Embedder = `Qwen3-Embedding-0.6B-4bit-DWQ`,
253
+ search = `sqlite-vec`. Zero network calls, zero cloud APIs, zero telemetry.
254
+ The hook input (your prompt) never leaves the machine.
255
+
256
+ ### Save side (Phase B)
257
+
258
+ Passive memory extraction (auto-`memo save` from chat transcripts) is NOT
259
+ in 0.3.0 — recall side first to validate the architecture, save side in
260
+ 0.4.0 once we have signal on whether the recall hook is helpful in real use.
261
+
262
+ ## [0.2.0] - 2026-05-07
263
+
264
+ First public release. Distribution name on PyPI is `memo-mcp`
265
+ (`memo` was already taken).
266
+
267
+ ### Added
268
+
269
+ - Public PyPI distribution as [`memo-mcp`](https://pypi.org/project/memo-mcp/).
270
+ - Claude Code plugin format (`.claude-plugin/plugin.json`) — single-step install
271
+ via `/plugin install memo@jagoff/memo`.
272
+ - `.mcp.json` bundled in repo root so MCP-aware clients can auto-register.
273
+ - `skills/memo/SKILL.md` bundled in repo — slash-command UX layer for Claude
274
+ Code CLI users (optional).
275
+ - LICENSE file (MIT).
276
+ - README polish: install, quickstart, architecture diagram, comparison vs
277
+ `mem-vault` / `mem0` / `engram`.
278
+ - Migration script `scripts/migrate-from-mem-vault.py` (already shipped in
279
+ 0.1.0 development; documented in README for this release).
280
+
281
+ ### Changed
282
+
283
+ - Bumped Development Status from `3 - Alpha` to `4 - Beta` in pyproject classifiers.
284
+ - Author name expanded from "Fer" to "Fernando Ferrari" for PyPI metadata clarity.
285
+
286
+ ## [0.1.0] - 2026-04-28
287
+
288
+ Initial development release (private — not on PyPI).
289
+
290
+ ### Added
291
+
292
+ - MLX-native memory MCP for Apple Silicon. Stack: `mlx-lm` + `mlx`
293
+ (Qwen2.5-7B/3B-Instruct-4bit + Qwen3-Embedding-0.6B-4bit-DWQ),
294
+ `sqlite-vec` for vectors, markdown files in Obsidian vault for storage.
295
+ - Tools exposed: `memory_save`, `memory_search`, `memory_list`, `memory_get`,
296
+ `memory_update`, `memory_delete`, `memory_stats`, `memory_reindex`,
297
+ `memory_ask` (RAG over memorias with inline citations), `memory_consolidate`
298
+ (cluster + LLM merge proposals), graph queries (entity extraction).
299
+ - CLI (`memo`) with subcommands matching MCP tools.
300
+ - MCP server entry point (`memo-mcp`) using FastMCP framework.
301
+ - History tracking via SQLite for memory edits + accesses.
@@ -0,0 +1,124 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## What this is
6
+
7
+ `memo` (PyPI dist name: `mlx-memo` as of 0.5.0; previously `memo-mcp`) is a local MCP memory server for Apple Silicon. Two entry points share the same `Memory` API:
8
+
9
+ - `memo` — Click-based CLI in `src/memo/cli.py` (~25 commands).
10
+ - `memo-mcp` — FastMCP stdio server in `src/memo/server.py` (~13 tools, all prefixed `memory_`).
11
+
12
+ The Python module, CLI binary, and GitHub repo are all named `memo`. Only the PyPI distribution is `memo-mcp` (because `memo` 0.2.4 was already taken). Don't conflate the two when grepping or renaming.
13
+
14
+ ## Common commands
15
+
16
+ ```bash
17
+ # Dev install (the project requires Python ≥ 3.13; the Linux/x86 path is gated by pyproject markers)
18
+ uv pip install -e '.[dev]'
19
+
20
+ # Tests — fast suite (skips slow real-MLX smoke)
21
+ .venv/bin/python -m pytest -q -m "not slow"
22
+
23
+ # Single test
24
+ .venv/bin/python -m pytest tests/test_memory.py::test_save_then_search -q
25
+
26
+ # Real-MLX smoke (Apple Silicon only — auto-skips elsewhere via conftest)
27
+ .venv/bin/python -m pytest -q -m requires_mlx
28
+
29
+ # Lint
30
+ .venv/bin/ruff check src/ tests/
31
+
32
+ # CLI smoke against the real vault
33
+ memo doctor
34
+ memo stats
35
+ ```
36
+
37
+ CI (`.github/workflows/test.yml`) runs ruff + `pytest -m "not slow"` on Ubuntu — this works because `mlx`/`mlx-lm` deps are gated to `darwin`/`arm64` in `pyproject.toml`, and `tests/conftest.py` auto-skips `requires_mlx` tests when `mlx_lm` isn't importable.
38
+
39
+ Pytest markers (declared in `pyproject.toml`):
40
+ - `requires_mlx` — loads real MLX models. Auto-skipped without `mlx_lm`. Default run includes them on Apple Silicon.
41
+ - `slow` — anything >1s. Default run excludes via `-m "not slow"`.
42
+
43
+ ## Architecture
44
+
45
+ Layered, with the `.md` file in the Obsidian vault as the storage of record. Three sqlite files intentionally split (different WAL, independent reset).
46
+
47
+ ```
48
+ CLI / MCP server
49
+
50
+
51
+ memory.py (Memory: save / search / list / get / update / delete / reindex / consolidate / ask)
52
+
53
+ ┌───┼─────────────┬──────────────┬──────────────┐
54
+ ▼ ▼ ▼ ▼ ▼
55
+ embedder.py store.py history.py graph.py llm.py
56
+ (MLX (sqlite-vec (events DB) (entities (MLX two-tier:
57
+ embed) memvec.db graph.db) Qwen2.5-7B chat
58
+ + 3B helper)
59
+
60
+
61
+ markdown files in <vault>/99-obsidian/99-AI/memory/ ← source of truth
62
+ ```
63
+
64
+ ### Storage of record vs index
65
+
66
+ The `.md` files are authoritative. `memvec.db` (sqlite-vec) is a rebuildable index. The user can edit memorias directly in Obsidian; on next `memo reindex` (or any tool boot), `body_hash` mismatches drive re-embedding. `rm ~/.local/share/memo/memvec.db && memo reindex` is a safe full rebuild — it never touches the `.md` files.
67
+
68
+ Three separate sqlite files in `~/.local/share/memo/`:
69
+ - `memvec.db` — `meta` table + `vec0` virtual table (sqlite-vec). Hot-path read.
70
+ - `history.db` — append-only audit of save/update/delete events.
71
+ - `graph.db` — entity index (proper-noun NER over memorias).
72
+
73
+ Splitting them avoids WAL contention between hot vec reads and batch writes (entity extraction, history). Don't merge them back without good reason.
74
+
75
+ ### MLX in-process — Apple Silicon only
76
+
77
+ `embedder.py` uses `mlx_lm.load()` to load a Qwen3-Embedding model, bypasses `lm_head`, last-token-pools the hidden states, and L2-normalises. `llm.py` runs Qwen2.5-Instruct in two tiers (7B chat / 3B helper) via `mlx_lm.generate()`. Both lazy-load under `_load_lock`.
78
+
79
+ Key invariants — don't break these:
80
+ - **Asymmetric retrieval.** Queries get a `Instruct: ...\nQuery: ...` prefix prepended in `embedder.py`; documents go raw. Without the prefix, cosine collapses toward 0. The constant lives in `_QUERY_INSTRUCTION_PREFIX`.
81
+ - **`MLXEmbedder.embed()` is batched.** Signature is `Sequence[str] → list[list[float]]`. Passing a bare string iterates per-character (Python's string-as-iterable), produces variable-dim outputs, and cascades into a Metal GPU error. Always wrap: `embedder.embed([text])[0]`. This was the v0.3.1 bug — don't reintroduce it.
82
+ - **`MEMO_EMBEDDER_DIMS` must match the model.** Asserted at load (1024 for 0.6B, 2560 for 4B, 4096 for 8B). The vec0 schema bakes in `FLOAT[1024]`; swapping models requires `rm memvec.db && memo reindex` — see README "Upgrading the embedder".
83
+ - **`mlx`/`mlx-lm` imports are deferred.** Module-level imports would break Linux CI. Defer until `_ensure_loaded()` (already the convention in `embedder.py` / `llm.py`).
84
+
85
+ ### Search modes
86
+
87
+ `Memory.search(query, mode=...)` supports three modes (default `hybrid`):
88
+ - `vec` — cosine over sqlite-vec embeddings.
89
+ - `bm25` — FTS5 over title + tags + body. Uses unicode61 + diacritic stripping for Spanish. The query tokenizer wraps each `\w+` token in its own phrase quotes so multi-word queries become AND-of-tokens, not phrase-match — see v0.3.2 changelog. If you touch BM25 tokenization, re-run `tests/test_memory.py` BM25 cases against multi-word Spanish queries (`"Astor terapia ocupacional"`).
90
+ - `hybrid` — Reciprocal Rank Fusion of vec + BM25 (default).
91
+
92
+ ### Ambient memory hooks (`hooks/hooks.json`)
93
+
94
+ When the plugin is installed, two Claude Code hooks are wired:
95
+ - `SessionStart` (matcher `startup|clear`) → `memo prewarm` (async, 30s timeout). Pre-loads MLX so the first recall is fast.
96
+ - `UserPromptSubmit` → `memo recall-hook` (5s timeout). Embeds the prompt, runs vec-only search, prints the top-3 memorias above `MEMO_RECALL_MIN_SIM` (default 0.6) as `additionalContext` markdown on stdout.
97
+
98
+ The 5s timeout is tight. Cold MLX load is ~2s. If you add work to the hook, measure end-to-end latency against the 5s budget with `MEMO_RECALL_DEBUG=1`.
99
+
100
+ ## Testing conventions
101
+
102
+ `tests/conftest.py` enforces two rules — preserve them:
103
+
104
+ - **Never touch the real vault.** The `tmp_cfg` fixture builds a `Config` with `data_dir`, `vault_path`, and `state_dir` under pytest's `tmp_path`, and pins `MEMO_CONFIG_FILE` so `Config.from_env()` doesn't read the dev's real `~/.config/memo/config.toml`. Any new test must depend on `tmp_cfg` (or build its own isolated `Config`) — never call `Config.from_env()` in a test without controlling these env vars.
105
+ - **CliRunner-based tests must set `MEMO_NONINTERACTIVE=1`** in the `env=` arg, otherwise the CLI's first-run gate may try to fire the picker mid-test. Also override `MEMO_DATA_DIR`/`MEMO_STATE_DIR` to keep state under `tmp_path`. If your test exercises the embedder via the stub (`monkeypatch.setattr("memo.embedder.MLXEmbedder.embed", ...)`), also pin `MEMO_EMBEDDER_DIMS` to match the stub's output dim — the dev's shell may have `MEMO_EMBEDDER_DIMS=2560` (4B model) exported and CliRunner inherits it.
106
+ - **`requires_mlx` is auto-applied.** A test marked `@pytest.mark.requires_mlx` skips automatically when `mlx_lm` can't be imported. Use it for anything that does a real forward pass; everything else should monkeypatch `MLXEmbedder.embed` / `MLXChat.chat`.
107
+
108
+ `tests/test_smoke_mlx.py` is the canonical example of `requires_mlx` smoke tests.
109
+
110
+ ## Versioning + release
111
+
112
+ Version lives in three places — bump together:
113
+ - `pyproject.toml` `[project] version`
114
+ - `.claude-plugin/plugin.json` `version`
115
+ - `CHANGELOG.md` (Keep-a-Changelog format)
116
+
117
+ `src/memo/__init__.py:__version__` is currently pinned at `"0.1.0"` and is not the source of truth — `pyproject.toml` is.
118
+
119
+ ## Conventions specific to this repo
120
+
121
+ - **The memoria storage location is user-configurable.** The default is `~/Documents/memo/` (a standard macOS path so the tool works for users who don't use Obsidian). On first interactive run, `memo` prompts an arrow-key picker (`memo init` re-runs it). The chosen path is persisted in `~/.config/memo/config.toml` `[storage] data_dir`. Tests must override via `tmp_cfg` (which sets `data_dir`, `vault_path`, `state_dir` and pins `MEMO_CONFIG_FILE` to a test-only path so the developer's real config doesn't leak in). Resolution order in `Config.from_env()`: explicit kwargs → `MEMO_*` env vars → config file → legacy `MEMO_VAULT_PATH` + `MEMO_MEMORY_SUBDIR` → default. `vault_path` is now optional and only used by `memo ingest`.
122
+ - **Frontmatter schema is fixed.** `id`, `title`, `type`, `tags`, `created`, `updated`. `type` is one of `decision | fact | bug | feedback | preference | note | manual`. Adding a new type means updating the Click `Choice` in `cli.py:save`/`update` AND the docstring in `server.py:memory_save`.
123
+ - **Filename slug is `<YYYY-MM-DD>-<slug>.md`.** Mirrors obsidian-rag's conversation writer. The slugifier lives in `memory.py`.
124
+ - **No Ollama anywhere.** `pyproject.toml` does not declare it; `doctor` does not probe `:11434`. The README leans on this as a selling point — don't reintroduce it as a dependency.
mlx_memo-0.5.0/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Fernando Ferrari
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.