memor-cli 0.1.1__tar.gz → 0.3.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 (92) hide show
  1. {memor_cli-0.1.1/memor_cli.egg-info → memor_cli-0.3.0}/PKG-INFO +43 -16
  2. {memor_cli-0.1.1 → memor_cli-0.3.0}/README.md +42 -15
  3. memor_cli-0.3.0/memor/__init__.py +1 -0
  4. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/cli.py +63 -10
  5. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/daemon.py +9 -0
  6. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/dashboard/server.py +20 -0
  7. memor_cli-0.3.0/memor/dashboard/static/index.html +821 -0
  8. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/eval/runner.py +3 -2
  9. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/feedback.py +31 -26
  10. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/hook_server.py +48 -1
  11. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/ingest/claude_code.py +55 -1
  12. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/recall.py +35 -3
  13. memor_cli-0.3.0/memor/retrieve/retriever.py +122 -0
  14. memor_cli-0.3.0/memor/service.py +187 -0
  15. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/store/sqlite_store.py +164 -5
  16. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/types.py +15 -0
  17. {memor_cli-0.1.1 → memor_cli-0.3.0/memor_cli.egg-info}/PKG-INFO +43 -16
  18. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor_cli.egg-info/SOURCES.txt +3 -0
  19. {memor_cli-0.1.1 → memor_cli-0.3.0}/pyproject.toml +1 -1
  20. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_dashboard.py +3 -3
  21. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_eval_ablation.py +5 -1
  22. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_feedback.py +14 -0
  23. memor_cli-0.3.0/tests/test_hook_server.py +94 -0
  24. memor_cli-0.3.0/tests/test_hybrid_retrieval.py +213 -0
  25. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_recall_core.py +63 -3
  26. memor_cli-0.3.0/tests/test_service.py +25 -0
  27. memor_cli-0.1.1/memor/dashboard/static/index.html +0 -688
  28. memor_cli-0.1.1/memor/retrieve/retriever.py +0 -78
  29. memor_cli-0.1.1/memor/store/__init__.py +0 -0
  30. memor_cli-0.1.1/tests/test_hook_server.py +0 -42
  31. {memor_cli-0.1.1 → memor_cli-0.3.0}/LICENSE +0 -0
  32. {memor_cli-0.1.1/memor → memor_cli-0.3.0/memor/dashboard}/__init__.py +0 -0
  33. {memor_cli-0.1.1/memor/dashboard → memor_cli-0.3.0/memor/distill}/__init__.py +0 -0
  34. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/distill/distiller.py +0 -0
  35. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/distill/extractive.py +0 -0
  36. {memor_cli-0.1.1/memor/distill → memor_cli-0.3.0/memor/embed}/__init__.py +0 -0
  37. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/embed/api.py +0 -0
  38. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/embed/fake.py +0 -0
  39. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/embed/local.py +0 -0
  40. {memor_cli-0.1.1/memor/embed → memor_cli-0.3.0/memor/eval}/__init__.py +0 -0
  41. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/eval/baselines/__init__.py +0 -0
  42. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/eval/baselines/base.py +0 -0
  43. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/eval/baselines/claude_mem.py +0 -0
  44. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/eval/baselines/graphiti.py +0 -0
  45. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/eval/dataset.py +0 -0
  46. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/eval/embed_benchmark.py +0 -0
  47. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/eval/judge.py +0 -0
  48. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/eval/metrics.py +0 -0
  49. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/hook_cli.py +0 -0
  50. {memor_cli-0.1.1/memor/eval → memor_cli-0.3.0/memor/ingest}/__init__.py +0 -0
  51. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/ingest/documents.py +0 -0
  52. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/interfaces.py +0 -0
  53. {memor_cli-0.1.1/memor/ingest → memor_cli-0.3.0/memor/llm}/__init__.py +0 -0
  54. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/llm/anthropic.py +0 -0
  55. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/llm/base.py +0 -0
  56. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/llm/openai_compat.py +0 -0
  57. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/project.py +0 -0
  58. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/redact.py +0 -0
  59. {memor_cli-0.1.1/memor/llm → memor_cli-0.3.0/memor/retrieve}/__init__.py +0 -0
  60. {memor_cli-0.1.1/memor/retrieve → memor_cli-0.3.0/memor/store}/__init__.py +0 -0
  61. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor/tokencount.py +0 -0
  62. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor_cli.egg-info/dependency_links.txt +0 -0
  63. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor_cli.egg-info/entry_points.txt +0 -0
  64. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor_cli.egg-info/requires.txt +0 -0
  65. {memor_cli-0.1.1 → memor_cli-0.3.0}/memor_cli.egg-info/top_level.txt +0 -0
  66. {memor_cli-0.1.1 → memor_cli-0.3.0}/setup.cfg +0 -0
  67. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_cli_smoke.py +0 -0
  68. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_daemon.py +0 -0
  69. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_dataset_builder.py +0 -0
  70. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_dimension_safety.py +0 -0
  71. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_distiller.py +0 -0
  72. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_embed.py +0 -0
  73. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_embed_benchmark.py +0 -0
  74. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_eval_runner.py +0 -0
  75. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_external_baselines.py +0 -0
  76. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_extractive.py +0 -0
  77. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_hook.py +0 -0
  78. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_ingest_claude_code.py +0 -0
  79. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_ingest_documents.py +0 -0
  80. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_install_hook.py +0 -0
  81. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_interfaces.py +0 -0
  82. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_judge.py +0 -0
  83. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_metrics.py +0 -0
  84. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_noise_filter.py +0 -0
  85. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_project_resolver.py +0 -0
  86. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_redact.py +0 -0
  87. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_retriever.py +0 -0
  88. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_skill_recall.py +0 -0
  89. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_store.py +0 -0
  90. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_supersession.py +0 -0
  91. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_tokencount.py +0 -0
  92. {memor_cli-0.1.1 → memor_cli-0.3.0}/tests/test_types.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: memor-cli
3
- Version: 0.1.1
3
+ Version: 0.3.0
4
4
  Summary: Measured memory for coding agents. Fire and forget — no API keys needed.
5
5
  Author-email: Nimit Bhandari <nimitbhandari17@gmail.com>
6
6
  License-Expression: MIT
@@ -46,7 +46,7 @@ Dynamic: license-file
46
46
  ```
47
47
 
48
48
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
49
- [![Tests](https://img.shields.io/badge/tests-153%20passing-brightgreen.svg)]()
49
+ [![Tests](https://img.shields.io/badge/tests-179%20passing-brightgreen.svg)]()
50
50
  [![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)]()
51
51
  [![PyPI](https://img.shields.io/pypi/v/memor-cli.svg)](https://pypi.org/project/memor-cli/)
52
52
 
@@ -65,7 +65,10 @@ pipx install memor-cli
65
65
  # Install the Claude Code hook + download embedding model (~60MB)
66
66
  memor install-hook
67
67
 
68
- # Start the background daemon
68
+ # Start as a background service (macOS/Linux)
69
+ memor service install
70
+
71
+ # Or run in the foreground
69
72
  memor daemon
70
73
  ```
71
74
 
@@ -92,7 +95,13 @@ memor dashboard
92
95
  Embed query locally (model2vec, ~2ms)
93
96
  |
94
97
  v
95
- Hybrid scoring: similarity + recency + kind weight + quality
98
+ Hybrid retrieval: dense vectors + lexical BM25, fused (RRF)
99
+ |
100
+ v
101
+ Relevance gate drops off-topic matches (inject nothing if nothing fits)
102
+ |
103
+ v
104
+ Rank: similarity + recency + kind weight + quality
96
105
  |
97
106
  v
98
107
  Inject relevant context into prompt
@@ -104,20 +113,31 @@ memor dashboard
104
113
 
105
114
  **Two background processes:**
106
115
 
107
- 1. **Daemon** — polls `~/.claude/projects/` for transcripts, embeds chunks, runs distillation, analyzes feedback, compacts duplicates. All local.
116
+ 1. **Daemon** — polls `~/.claude/projects/` for transcripts, embeds chunks, runs distillation, analyzes feedback, compacts duplicates, tracks session-level token usage. All local.
108
117
  2. **Hook** — fires on every prompt, recalls relevant memories, injects them as context. Sub-15ms.
109
118
 
110
119
  **No API keys required.** Embeddings run locally via [model2vec](https://github.com/MinishLab/model2vec) (potion-base-8M, 256-dim). Vectors stored in [sqlite-vec](https://github.com/asg017/sqlite-vec). Everything runs on your machine.
111
120
 
112
121
  ---
113
122
 
114
- ## Hybrid Scoring
123
+ ## Hybrid Retrieval
124
+
125
+ Memor retrieves over two channels and fuses them, so it catches both semantic matches and exact terms:
126
+
127
+ - **Dense** — local vector similarity (model2vec) for semantic recall.
128
+ - **Lexical** — SQLite FTS5 / BM25 over the raw text, to recover exact identifiers, error strings, and API names that static embeddings blur together.
129
+
130
+ The two rankings are combined with **Reciprocal Rank Fusion (RRF)**. A **relevance gate** drops anti-correlated (off-topic) candidates *before* ranking, so an unrelated prompt injects nothing rather than the least-bad guess. The lexical channel only activates when the dense channel finds the query on-topic, preventing generic words from pulling in noise.
131
+
132
+ > Tunable via `MEMOR_MIN_SIMILARITY` (relevance floor, default 0.0) and `MEMOR_MAX_TOKENS` (injection budget, default 1500).
133
+
134
+ ## Scoring
115
135
 
116
- Memor doesn't just match keywords. Each memory is scored by four signals:
136
+ Surviving candidates are ranked by four signals:
117
137
 
118
138
  | Signal | Weight | How it works |
119
139
  |---|---|---|
120
- | **Semantic similarity** | 50% | Vector cosine distance between query and memory |
140
+ | **Semantic similarity** | 50% | Dense + lexical relevance, fused via RRF |
121
141
  | **Recency** | 25% | Exponential decay with 14-day half-life — recent decisions rank higher |
122
142
  | **Kind weight** | 15% | Distilled memories (1.3x) rank above raw session chunks (1.0x) |
123
143
  | **Quality** | 10% | Bayesian score from implicit feedback — memories the agent actually uses rank higher |
@@ -147,10 +167,11 @@ Memories are automatically classified as `decision`, `bugfix`, `lesson`, `snippe
147
167
  memor dashboard
148
168
  ```
149
169
 
150
- Shows:
151
- - **Memory bank** — session chunks, distilled memories, projects tracked
152
- - **Context efficiency** — overhead %, recall precision, quality scores per session
153
- - **Per-project breakdown** — which projects have the most context
170
+ Dark fintech-inspired UI showing:
171
+ - **Hero metrics** — total memories, recall count, avg latency, coverage — with sparkline bars
172
+ - **Daily recall activity** — stacked bar chart of hits vs misses over time
173
+ - **Session efficiency** — real token savings measured from API usage data (avg tokens/turn with vs without recall)
174
+ - **Per-project breakdown** — artifact counts, token totals, last activity
154
175
  - **Recent recalls** — every hook event with scores, latency, and status
155
176
 
156
177
  ---
@@ -162,6 +183,11 @@ memor help Print the full manual
162
183
  memor install-hook Install Claude Code hook + download model
163
184
  memor daemon Auto-ingest + distill (background watcher)
164
185
  memor dashboard Web dashboard on localhost:8420
186
+ memor version Print installed version
187
+ memor service install Run daemon as background service (launchd/systemd)
188
+ memor service stop Stop the background service
189
+ memor service uninstall Remove the background service
190
+ memor service status Check if the service is running
165
191
  memor query <text> Search memories from the CLI
166
192
  memor reingest Wipe DB and re-ingest everything
167
193
  memor reingest --project <name> Re-ingest only one project
@@ -193,16 +219,17 @@ memor/
193
219
  +-- feedback.py Implicit feedback analyzer (usage detection)
194
220
  |
195
221
  +-- retrieve/
196
- | +-- retriever.py Hybrid scoring: similarity + recency + kind + quality
222
+ | +-- retriever.py Hybrid retrieval (dense + BM25, RRF) + relevance gate + scoring
197
223
  |
198
224
  +-- store/
199
- | +-- sqlite_store.py SQLite + sqlite-vec (WAL mode, dimension safety)
225
+ | +-- sqlite_store.py SQLite + sqlite-vec + FTS5 (WAL mode, dimension safety)
200
226
  |
201
227
  +-- embed/
202
228
  | +-- local.py model2vec (potion-base-8M, 256-dim, ~60MB)
203
229
  | +-- api.py OpenAI-compatible embedding API (optional)
204
230
  | +-- fake.py Deterministic SHA-256 embedder (tests)
205
231
  |
232
+ +-- service.py Background service management (launchd/systemd)
206
233
  +-- dashboard/
207
234
  | +-- server.py FastAPI dashboard backend
208
235
  | +-- static/index.html Self-contained dashboard (no CDN deps)
@@ -227,7 +254,7 @@ skill/recall.py Standalone recall script
227
254
  **Nothing leaves your machine.** In the default configuration:
228
255
 
229
256
  - **No telemetry, no analytics, no phone-home.** Zero outbound network calls.
230
- - **Embeddings run locally** via model2vec ONNX (one-time model download from HuggingFace — no user data sent).
257
+ - **Embeddings run locally** via model2vec static token embeddings — no inference runtime, no GPU (one-time model download from HuggingFace — no user data sent).
231
258
  - **Hook transport is a Unix socket** (`~/.memor/hook.sock`), not a network port.
232
259
  - **Dashboard binds localhost only.**
233
260
 
@@ -263,7 +290,7 @@ cd memor-ai
263
290
  python3 -m venv .venv && source .venv/bin/activate
264
291
  pip install -e ".[dev]"
265
292
 
266
- pytest # 153 tests
293
+ pytest # 179 tests
267
294
  ```
268
295
 
269
296
  ---
@@ -9,7 +9,7 @@
9
9
  ```
10
10
 
11
11
  [![License: MIT](https://img.shields.io/badge/License-MIT-blue.svg)](LICENSE)
12
- [![Tests](https://img.shields.io/badge/tests-153%20passing-brightgreen.svg)]()
12
+ [![Tests](https://img.shields.io/badge/tests-179%20passing-brightgreen.svg)]()
13
13
  [![Python](https://img.shields.io/badge/python-3.11%2B-blue.svg)]()
14
14
  [![PyPI](https://img.shields.io/pypi/v/memor-cli.svg)](https://pypi.org/project/memor-cli/)
15
15
 
@@ -28,7 +28,10 @@ pipx install memor-cli
28
28
  # Install the Claude Code hook + download embedding model (~60MB)
29
29
  memor install-hook
30
30
 
31
- # Start the background daemon
31
+ # Start as a background service (macOS/Linux)
32
+ memor service install
33
+
34
+ # Or run in the foreground
32
35
  memor daemon
33
36
  ```
34
37
 
@@ -55,7 +58,13 @@ memor dashboard
55
58
  Embed query locally (model2vec, ~2ms)
56
59
  |
57
60
  v
58
- Hybrid scoring: similarity + recency + kind weight + quality
61
+ Hybrid retrieval: dense vectors + lexical BM25, fused (RRF)
62
+ |
63
+ v
64
+ Relevance gate drops off-topic matches (inject nothing if nothing fits)
65
+ |
66
+ v
67
+ Rank: similarity + recency + kind weight + quality
59
68
  |
60
69
  v
61
70
  Inject relevant context into prompt
@@ -67,20 +76,31 @@ memor dashboard
67
76
 
68
77
  **Two background processes:**
69
78
 
70
- 1. **Daemon** — polls `~/.claude/projects/` for transcripts, embeds chunks, runs distillation, analyzes feedback, compacts duplicates. All local.
79
+ 1. **Daemon** — polls `~/.claude/projects/` for transcripts, embeds chunks, runs distillation, analyzes feedback, compacts duplicates, tracks session-level token usage. All local.
71
80
  2. **Hook** — fires on every prompt, recalls relevant memories, injects them as context. Sub-15ms.
72
81
 
73
82
  **No API keys required.** Embeddings run locally via [model2vec](https://github.com/MinishLab/model2vec) (potion-base-8M, 256-dim). Vectors stored in [sqlite-vec](https://github.com/asg017/sqlite-vec). Everything runs on your machine.
74
83
 
75
84
  ---
76
85
 
77
- ## Hybrid Scoring
86
+ ## Hybrid Retrieval
87
+
88
+ Memor retrieves over two channels and fuses them, so it catches both semantic matches and exact terms:
89
+
90
+ - **Dense** — local vector similarity (model2vec) for semantic recall.
91
+ - **Lexical** — SQLite FTS5 / BM25 over the raw text, to recover exact identifiers, error strings, and API names that static embeddings blur together.
92
+
93
+ The two rankings are combined with **Reciprocal Rank Fusion (RRF)**. A **relevance gate** drops anti-correlated (off-topic) candidates *before* ranking, so an unrelated prompt injects nothing rather than the least-bad guess. The lexical channel only activates when the dense channel finds the query on-topic, preventing generic words from pulling in noise.
94
+
95
+ > Tunable via `MEMOR_MIN_SIMILARITY` (relevance floor, default 0.0) and `MEMOR_MAX_TOKENS` (injection budget, default 1500).
96
+
97
+ ## Scoring
78
98
 
79
- Memor doesn't just match keywords. Each memory is scored by four signals:
99
+ Surviving candidates are ranked by four signals:
80
100
 
81
101
  | Signal | Weight | How it works |
82
102
  |---|---|---|
83
- | **Semantic similarity** | 50% | Vector cosine distance between query and memory |
103
+ | **Semantic similarity** | 50% | Dense + lexical relevance, fused via RRF |
84
104
  | **Recency** | 25% | Exponential decay with 14-day half-life — recent decisions rank higher |
85
105
  | **Kind weight** | 15% | Distilled memories (1.3x) rank above raw session chunks (1.0x) |
86
106
  | **Quality** | 10% | Bayesian score from implicit feedback — memories the agent actually uses rank higher |
@@ -110,10 +130,11 @@ Memories are automatically classified as `decision`, `bugfix`, `lesson`, `snippe
110
130
  memor dashboard
111
131
  ```
112
132
 
113
- Shows:
114
- - **Memory bank** — session chunks, distilled memories, projects tracked
115
- - **Context efficiency** — overhead %, recall precision, quality scores per session
116
- - **Per-project breakdown** — which projects have the most context
133
+ Dark fintech-inspired UI showing:
134
+ - **Hero metrics** — total memories, recall count, avg latency, coverage — with sparkline bars
135
+ - **Daily recall activity** — stacked bar chart of hits vs misses over time
136
+ - **Session efficiency** — real token savings measured from API usage data (avg tokens/turn with vs without recall)
137
+ - **Per-project breakdown** — artifact counts, token totals, last activity
117
138
  - **Recent recalls** — every hook event with scores, latency, and status
118
139
 
119
140
  ---
@@ -125,6 +146,11 @@ memor help Print the full manual
125
146
  memor install-hook Install Claude Code hook + download model
126
147
  memor daemon Auto-ingest + distill (background watcher)
127
148
  memor dashboard Web dashboard on localhost:8420
149
+ memor version Print installed version
150
+ memor service install Run daemon as background service (launchd/systemd)
151
+ memor service stop Stop the background service
152
+ memor service uninstall Remove the background service
153
+ memor service status Check if the service is running
128
154
  memor query <text> Search memories from the CLI
129
155
  memor reingest Wipe DB and re-ingest everything
130
156
  memor reingest --project <name> Re-ingest only one project
@@ -156,16 +182,17 @@ memor/
156
182
  +-- feedback.py Implicit feedback analyzer (usage detection)
157
183
  |
158
184
  +-- retrieve/
159
- | +-- retriever.py Hybrid scoring: similarity + recency + kind + quality
185
+ | +-- retriever.py Hybrid retrieval (dense + BM25, RRF) + relevance gate + scoring
160
186
  |
161
187
  +-- store/
162
- | +-- sqlite_store.py SQLite + sqlite-vec (WAL mode, dimension safety)
188
+ | +-- sqlite_store.py SQLite + sqlite-vec + FTS5 (WAL mode, dimension safety)
163
189
  |
164
190
  +-- embed/
165
191
  | +-- local.py model2vec (potion-base-8M, 256-dim, ~60MB)
166
192
  | +-- api.py OpenAI-compatible embedding API (optional)
167
193
  | +-- fake.py Deterministic SHA-256 embedder (tests)
168
194
  |
195
+ +-- service.py Background service management (launchd/systemd)
169
196
  +-- dashboard/
170
197
  | +-- server.py FastAPI dashboard backend
171
198
  | +-- static/index.html Self-contained dashboard (no CDN deps)
@@ -190,7 +217,7 @@ skill/recall.py Standalone recall script
190
217
  **Nothing leaves your machine.** In the default configuration:
191
218
 
192
219
  - **No telemetry, no analytics, no phone-home.** Zero outbound network calls.
193
- - **Embeddings run locally** via model2vec ONNX (one-time model download from HuggingFace — no user data sent).
220
+ - **Embeddings run locally** via model2vec static token embeddings — no inference runtime, no GPU (one-time model download from HuggingFace — no user data sent).
194
221
  - **Hook transport is a Unix socket** (`~/.memor/hook.sock`), not a network port.
195
222
  - **Dashboard binds localhost only.**
196
223
 
@@ -226,7 +253,7 @@ cd memor-ai
226
253
  python3 -m venv .venv && source .venv/bin/activate
227
254
  pip install -e ".[dev]"
228
255
 
229
- pytest # 153 tests
256
+ pytest # 179 tests
230
257
  ```
231
258
 
232
259
  ---
@@ -0,0 +1 @@
1
+ __version__ = "0.3.0"
@@ -9,6 +9,8 @@ from memor.types import Scope
9
9
  from memor.ingest.claude_code import parse_transcript
10
10
 
11
11
  app = typer.Typer(no_args_is_help=True)
12
+ service_app = typer.Typer(help="Manage the memor daemon as a background service.")
13
+ app.add_typer(service_app, name="service")
12
14
 
13
15
  def _db_path(db: str) -> str:
14
16
  return str(Path(db).expanduser())
@@ -42,14 +44,21 @@ HELP_TEXT = """\
42
44
  memor — measured memory for coding agents
43
45
 
44
46
  GETTING STARTED
45
- memor install-hook Install the Claude Code recall hook + download model
46
- memor daemon Start the background watcher (ingests + distills)
47
- memor dashboard Open the web dashboard at localhost:8420
47
+ memor install-hook Install the Claude Code recall hook + download model
48
+ memor service install Start the daemon as a background service
49
+ memor dashboard Open the web dashboard at localhost:8420
50
+
51
+ SERVICE MANAGEMENT
52
+ memor service install Install and start as a background service (survives reboots)
53
+ memor service status Check if the daemon is running
54
+ memor service stop Stop the service (restarts on next login)
55
+ memor service uninstall Stop and remove the service completely
56
+ memor daemon Run the daemon in the foreground (alternative to service)
48
57
 
49
58
  QUERYING
50
- memor query <text> Search memories from the command line
51
- --project <name> Scope to a specific project
52
- --k <n> Number of results (default: 8)
59
+ memor query <text> Search memories from the command line
60
+ --project <name> Scope to a specific project
61
+ --k <n> Number of results (default: 8)
53
62
 
54
63
  INGESTION
55
64
  memor ingest-cc <file> Ingest a single transcript
@@ -66,6 +75,7 @@ MAINTENANCE
66
75
  memor scan Audit DB for leaked secrets
67
76
  memor scan --purge Redact secrets in place
68
77
  memor setup-model Download/retry the embedding model (~60MB)
78
+ memor version Print the installed version
69
79
 
70
80
  EVALUATION
71
81
  memor eval <cases.json> Run eval suite
@@ -77,10 +87,10 @@ CONFIGURATION
77
87
  No configuration needed — just install, hook, and run the daemon.
78
88
 
79
89
  EXAMPLES
80
- memor install-hook && memor daemon # one-time setup
90
+ memor install-hook && memor service install # one-time setup
81
91
  memor query "auth flow" --project my-app
82
- memor reingest --project my-app -y # refresh one project
83
- memor dashboard # see stats at localhost:8420
92
+ memor reingest --project my-app -y # refresh one project
93
+ memor dashboard # see stats at localhost:8420
84
94
  """
85
95
 
86
96
 
@@ -438,7 +448,8 @@ def install_hook():
438
448
  typer.echo(" retry later with: memor setup-model")
439
449
  typer.echo()
440
450
  typer.echo("Next steps:")
441
- typer.echo(" 1. Start the daemon: memor daemon")
451
+ typer.echo(" 1. Start the daemon: memor service install")
452
+ typer.echo(" (or foreground: memor daemon)")
442
453
  typer.echo(" 2. Open the dashboard: memor dashboard")
443
454
  typer.echo(" Everything works locally — no API keys needed.")
444
455
 
@@ -464,5 +475,47 @@ def dashboard(port: int = typer.Option(8420, help="Port to serve on"),
464
475
  uvicorn.run(app_instance, host="127.0.0.1", port=port, log_level="warning")
465
476
 
466
477
 
478
+ @app.command("version")
479
+ def version():
480
+ """Print the memor version."""
481
+ from memor import __version__
482
+ typer.echo(f"memor {__version__}")
483
+
484
+
485
+ @service_app.command("install")
486
+ def service_install():
487
+ """Install and start the daemon as a background service (survives reboots)."""
488
+ from memor.service import install
489
+ try:
490
+ typer.echo(install())
491
+ except FileNotFoundError as e:
492
+ typer.echo(str(e), err=True)
493
+ raise typer.Exit(1)
494
+ except Exception as e:
495
+ typer.echo(f"Failed to install service: {e}", err=True)
496
+ raise typer.Exit(1)
497
+
498
+
499
+ @service_app.command("uninstall")
500
+ def service_uninstall():
501
+ """Stop and remove the background service."""
502
+ from memor.service import uninstall
503
+ typer.echo(uninstall())
504
+
505
+
506
+ @service_app.command("stop")
507
+ def service_stop():
508
+ """Stop the background service (will restart on next login)."""
509
+ from memor.service import stop
510
+ typer.echo(stop())
511
+
512
+
513
+ @service_app.command("status")
514
+ def service_status():
515
+ """Show whether the daemon service is running."""
516
+ from memor.service import status
517
+ typer.echo(status())
518
+
519
+
467
520
  if __name__ == "__main__":
468
521
  app()
@@ -93,6 +93,15 @@ def ingest_file(path: Path, project: str, store: SqliteStore, embedder) -> int:
93
93
  return 0
94
94
  vecs = embedder.embed([a.text for a in arts])
95
95
  store.add_artifacts(arts, vecs)
96
+
97
+ from memor.ingest.claude_code import parse_session_usage
98
+ try:
99
+ usage = parse_session_usage(path, project)
100
+ if usage:
101
+ store.upsert_session_stats(usage)
102
+ except Exception:
103
+ pass
104
+
96
105
  return len(arts)
97
106
 
98
107
 
@@ -114,6 +114,26 @@ def create_app(db_path: str | None = None) -> FastAPI:
114
114
  })
115
115
  return result
116
116
 
117
+ @app.get("/api/session-efficiency")
118
+ def session_efficiency():
119
+ store = _store()
120
+ return store.get_session_efficiency()
121
+
122
+ @app.get("/api/recall-trend")
123
+ def recall_trend(days: int = Query(30, ge=7, le=90)):
124
+ store = _store()
125
+ rows = store.db.execute("""
126
+ SELECT date(timestamp, 'unixepoch', 'localtime') as day,
127
+ COUNT(*) as recalls,
128
+ SUM(CASE WHEN hits_count > 0 THEN 1 ELSE 0 END) as hits,
129
+ SUM(tokens_injected) as tokens,
130
+ AVG(CASE WHEN hits_count > 0 THEN top_score END) as avg_score
131
+ FROM recall_log
132
+ WHERE timestamp >= unixepoch('now', ? || ' days')
133
+ GROUP BY day ORDER BY day
134
+ """, (f"-{days}",)).fetchall()
135
+ return [dict(r) for r in rows]
136
+
117
137
  @app.get("/api/health")
118
138
  def health():
119
139
  store = _store()