memor-cli 0.6.0__tar.gz → 0.7.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 (106) hide show
  1. {memor_cli-0.6.0/memor_cli.egg-info → memor_cli-0.7.0}/PKG-INFO +33 -16
  2. {memor_cli-0.6.0 → memor_cli-0.7.0}/README.md +32 -15
  3. memor_cli-0.7.0/memor/__init__.py +1 -0
  4. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/cli.py +132 -10
  5. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/daemon.py +33 -0
  6. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/dashboard/server.py +17 -3
  7. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/dashboard/static/index.html +50 -2
  8. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/hook_cli.py +3 -7
  9. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/hook_server.py +29 -20
  10. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/store/sqlite_store.py +95 -5
  11. {memor_cli-0.6.0 → memor_cli-0.7.0/memor_cli.egg-info}/PKG-INFO +33 -16
  12. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor_cli.egg-info/SOURCES.txt +3 -1
  13. {memor_cli-0.6.0 → memor_cli-0.7.0}/pyproject.toml +1 -1
  14. memor_cli-0.7.0/tests/test_multi_agent_hook.py +199 -0
  15. memor_cli-0.7.0/tests/test_vec_compaction.py +202 -0
  16. memor_cli-0.6.0/memor/__init__.py +0 -1
  17. {memor_cli-0.6.0 → memor_cli-0.7.0}/LICENSE +0 -0
  18. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/dashboard/__init__.py +0 -0
  19. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/distill/__init__.py +0 -0
  20. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/distill/distiller.py +0 -0
  21. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/distill/extractive.py +0 -0
  22. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/embed/__init__.py +0 -0
  23. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/embed/api.py +0 -0
  24. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/embed/fake.py +0 -0
  25. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/embed/local.py +0 -0
  26. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/eval/__init__.py +0 -0
  27. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/eval/baselines/__init__.py +0 -0
  28. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/eval/baselines/base.py +0 -0
  29. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/eval/baselines/claude_mem.py +0 -0
  30. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/eval/baselines/graphiti.py +0 -0
  31. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/eval/counterfactual.py +0 -0
  32. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/eval/dataset.py +0 -0
  33. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/eval/embed_benchmark.py +0 -0
  34. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/eval/judge.py +0 -0
  35. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/eval/metrics.py +0 -0
  36. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/eval/runner.py +0 -0
  37. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/feedback.py +0 -0
  38. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/global_memories.py +0 -0
  39. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/ingest/__init__.py +0 -0
  40. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/ingest/claude_code.py +0 -0
  41. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/ingest/documents.py +0 -0
  42. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/interfaces.py +0 -0
  43. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/llm/__init__.py +0 -0
  44. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/llm/anthropic.py +0 -0
  45. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/llm/base.py +0 -0
  46. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/llm/openai_compat.py +0 -0
  47. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/project.py +0 -0
  48. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/query_complexity.py +0 -0
  49. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/recall.py +0 -0
  50. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/redact.py +0 -0
  51. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/retrieve/__init__.py +0 -0
  52. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/retrieve/retriever.py +0 -0
  53. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/service.py +0 -0
  54. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/session_context.py +0 -0
  55. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/store/__init__.py +0 -0
  56. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/tokencount.py +0 -0
  57. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/turn_metrics.py +0 -0
  58. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor/types.py +0 -0
  59. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor_cli.egg-info/dependency_links.txt +0 -0
  60. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor_cli.egg-info/entry_points.txt +0 -0
  61. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor_cli.egg-info/requires.txt +0 -0
  62. {memor_cli-0.6.0 → memor_cli-0.7.0}/memor_cli.egg-info/top_level.txt +0 -0
  63. {memor_cli-0.6.0 → memor_cli-0.7.0}/setup.cfg +0 -0
  64. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_cli_smoke.py +0 -0
  65. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_counterfactual.py +0 -0
  66. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_daemon.py +0 -0
  67. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_dashboard.py +0 -0
  68. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_dashboard_quality.py +0 -0
  69. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_dataset_builder.py +0 -0
  70. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_dimension_safety.py +0 -0
  71. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_distiller.py +0 -0
  72. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_embed.py +0 -0
  73. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_embed_benchmark.py +0 -0
  74. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_eval_ablation.py +0 -0
  75. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_eval_runner.py +0 -0
  76. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_external_baselines.py +0 -0
  77. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_extractive.py +0 -0
  78. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_feedback.py +0 -0
  79. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_global_memories.py +0 -0
  80. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_hook.py +0 -0
  81. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_hook_server.py +0 -0
  82. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_hybrid_retrieval.py +0 -0
  83. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_ingest_claude_code.py +0 -0
  84. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_ingest_documents.py +0 -0
  85. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_install_hook.py +0 -0
  86. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_interfaces.py +0 -0
  87. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_judge.py +0 -0
  88. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_metrics.py +0 -0
  89. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_negative_feedback.py +0 -0
  90. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_noise_filter.py +0 -0
  91. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_project_resolver.py +0 -0
  92. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_quality_gate.py +0 -0
  93. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_query_complexity.py +0 -0
  94. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_recall_core.py +0 -0
  95. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_redact.py +0 -0
  96. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_retriever.py +0 -0
  97. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_roi_trend.py +0 -0
  98. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_semantic_feedback.py +0 -0
  99. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_service.py +0 -0
  100. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_session_context.py +0 -0
  101. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_skill_recall.py +0 -0
  102. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_store.py +0 -0
  103. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_supersession.py +0 -0
  104. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_tokencount.py +0 -0
  105. {memor_cli-0.6.0 → memor_cli-0.7.0}/tests/test_turn_metrics.py +0 -0
  106. {memor_cli-0.6.0 → memor_cli-0.7.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.6.0
3
+ Version: 0.7.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,13 +46,13 @@ 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-256%20passing-brightgreen.svg)]()
49
+ [![Tests](https://img.shields.io/badge/tests-285%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
 
53
- **Automatic background memory for Claude Code.** Fire and forget — no API keys needed.
53
+ **Automatic background memory for Claude Code, Codex, and Copilot.** Fire and forget — no API keys needed.
54
54
 
55
- Memor watches your coding sessions, extracts decisions and patterns, and recalls relevant context on every prompt. Zero configuration. One install. Your agent remembers everything.
55
+ Memor watches your coding sessions, extracts decisions and patterns, and recalls relevant context on every prompt. Works with Claude Code, OpenAI Codex CLI, and GitHub Copilot CLI. Zero configuration. One install. Your agent remembers everything.
56
56
 
57
57
  ---
58
58
 
@@ -62,8 +62,9 @@ Memor watches your coding sessions, extracts decisions and patterns, and recalls
62
62
  # Install globally (recommended)
63
63
  pipx install memor-cli
64
64
 
65
- # Install the Claude Code hook + download embedding model (~60MB)
66
- memor install-hook
65
+ # Install the hook + download embedding model (~60MB)
66
+ memor install-hook # interactive — pick Claude Code, Codex, or Copilot
67
+ memor install-hook --agent claude # or pass directly
67
68
 
68
69
  # Start as a background service (macOS/Linux)
69
70
  memor service install
@@ -72,7 +73,7 @@ memor service install
72
73
  memor daemon
73
74
  ```
74
75
 
75
- That's it. Every Claude Code prompt now gets automatic context recall. Open the dashboard to see it working:
76
+ That's it. Every prompt now gets automatic context recall. Open the dashboard to see it working:
76
77
 
77
78
  ```bash
78
79
  memor dashboard
@@ -86,10 +87,10 @@ memor dashboard
86
87
  ## How It Works
87
88
 
88
89
  ```
89
- You type a prompt in Claude Code
90
+ You type a prompt (Claude Code / Codex / Copilot)
90
91
  |
91
92
  v
92
- Hook fires (UserPromptSubmit)
93
+ Hook fires — auto-detects which agent
93
94
  |
94
95
  v
95
96
  Embed query locally (model2vec, ~2ms)
@@ -107,14 +108,26 @@ memor dashboard
107
108
  Inject relevant context into prompt
108
109
  |
109
110
  v
110
- Claude sees your past decisions, bugfixes,
111
+ Your agent sees past decisions, bugfixes,
111
112
  architecture choices — without you re-explaining
112
113
  ```
113
114
 
115
+ ### Supported Agents
116
+
117
+ | Agent | Hook protocol | Config location | Install |
118
+ |---|---|---|---|
119
+ | **Claude Code** | `UserPromptSubmit` + `additionalContext` | `~/.claude/settings.json` | `memor install-hook --agent claude` |
120
+ | **Codex CLI** | `UserPromptSubmit` + `additionalContext` | `~/.codex/hooks/hooks.json` | `memor install-hook --agent codex` |
121
+ | **Copilot CLI** | `userPromptSubmitted` + `additionalContext` | `~/.copilot/hooks/memor.json` | `memor install-hook --agent copilot` |
122
+
123
+ A single `memor-hook` binary auto-detects which agent is calling it — no separate entry points needed. The dashboard tracks recalls per agent so you can see usage across all your environments.
124
+
125
+ > **Note:** Cloud-hosted agents (Codex cloud, Copilot cloud agent) run in remote sandboxes and cannot reach local hooks. MCP server support for sandboxed agents is planned ([#26](https://github.com/bnimit/memor-ai/issues/26)).
126
+
114
127
  **Two background processes:**
115
128
 
116
- 1. **Daemon** — polls `~/.claude/projects/` for transcripts, embeds chunks, runs distillation, analyzes feedback (positive and negative), promotes cross-project patterns to global scope, compacts duplicates, tracks session-level token usage. All local.
117
- 2. **Hook** — fires on every prompt, recalls relevant memories, injects them as context. Sub-15ms.
129
+ 1. **Daemon** — polls `~/.claude/projects/` for transcripts, embeds chunks, runs distillation, analyzes feedback (positive and negative), promotes cross-project patterns to global scope, compacts duplicates, auto-compacts the vector index when bloated, tracks session-level token usage. All local.
130
+ 2. **Hook** — fires on every prompt, recalls relevant memories, injects them as context. Sub-15ms. Works across Claude Code, Codex, and Copilot.
118
131
 
119
132
  **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.
120
133
 
@@ -188,10 +201,11 @@ memor dashboard
188
201
 
189
202
  Dark fintech-inspired UI showing:
190
203
  - **Hero metrics** — total memories, recall count, avg latency, coverage — with sparkline bars
204
+ - **Agent breakdown** — per-agent recall stats (Claude, Codex, Copilot) with hit rates
191
205
  - **Daily recall activity** — stacked bar chart of hits vs misses over time
192
206
  - **Session efficiency** — real token savings measured from API usage data (avg tokens/turn with vs without recall)
193
207
  - **Per-project breakdown** — artifact counts, token totals, last activity
194
- - **Recent recalls** — every hook event with scores, latency, and status
208
+ - **Recent recalls** — every hook event with agent badge, scores, latency, and status
195
209
 
196
210
  ---
197
211
 
@@ -199,7 +213,8 @@ Dark fintech-inspired UI showing:
199
213
 
200
214
  ```
201
215
  memor help Print the full manual
202
- memor install-hook Install Claude Code hook + download model
216
+ memor install-hook Install hook + download model (interactive agent picker)
217
+ --agent claude|codex|copilot Choose agent directly
203
218
  memor daemon Auto-ingest + distill (background watcher)
204
219
  memor dashboard Web dashboard on localhost:8420
205
220
  memor version Print installed version
@@ -211,6 +226,7 @@ memor query <text> Search memories from the CLI
211
226
  memor reingest Wipe DB and re-ingest everything
212
227
  memor reingest --project <name> Re-ingest only one project
213
228
  memor forget-stale Deactivate memories unused for 30+ days
229
+ memor compact Rebuild vector index, reclaim wasted space
214
230
  memor scan Audit DB for leaked secrets
215
231
  memor scan --purge Redact secrets in place
216
232
  memor setup-model Download/retry the embedding model
@@ -264,7 +280,8 @@ memor/
264
280
  +-- judge.py LLM-as-judge evaluation
265
281
  +-- embed_benchmark.py Embedding model comparison
266
282
 
267
- memor/hook_cli.py Claude Code hook entry point (thin client)
283
+ memor/hook_cli.py Hook entry point — auto-detects Claude/Codex/Copilot
284
+ memor/hook_server.py Hook server with agent detection + response formatting
268
285
  skill/recall.py Standalone recall script
269
286
  ```
270
287
 
@@ -311,7 +328,7 @@ cd memor-ai
311
328
  python3 -m venv .venv && source .venv/bin/activate
312
329
  pip install -e ".[dev]"
313
330
 
314
- pytest # 251 tests
331
+ pytest # 285 tests
315
332
  ```
316
333
 
317
334
  ---
@@ -9,13 +9,13 @@
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-256%20passing-brightgreen.svg)]()
12
+ [![Tests](https://img.shields.io/badge/tests-285%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
 
16
- **Automatic background memory for Claude Code.** Fire and forget — no API keys needed.
16
+ **Automatic background memory for Claude Code, Codex, and Copilot.** Fire and forget — no API keys needed.
17
17
 
18
- Memor watches your coding sessions, extracts decisions and patterns, and recalls relevant context on every prompt. Zero configuration. One install. Your agent remembers everything.
18
+ Memor watches your coding sessions, extracts decisions and patterns, and recalls relevant context on every prompt. Works with Claude Code, OpenAI Codex CLI, and GitHub Copilot CLI. Zero configuration. One install. Your agent remembers everything.
19
19
 
20
20
  ---
21
21
 
@@ -25,8 +25,9 @@ Memor watches your coding sessions, extracts decisions and patterns, and recalls
25
25
  # Install globally (recommended)
26
26
  pipx install memor-cli
27
27
 
28
- # Install the Claude Code hook + download embedding model (~60MB)
29
- memor install-hook
28
+ # Install the hook + download embedding model (~60MB)
29
+ memor install-hook # interactive — pick Claude Code, Codex, or Copilot
30
+ memor install-hook --agent claude # or pass directly
30
31
 
31
32
  # Start as a background service (macOS/Linux)
32
33
  memor service install
@@ -35,7 +36,7 @@ memor service install
35
36
  memor daemon
36
37
  ```
37
38
 
38
- That's it. Every Claude Code prompt now gets automatic context recall. Open the dashboard to see it working:
39
+ That's it. Every prompt now gets automatic context recall. Open the dashboard to see it working:
39
40
 
40
41
  ```bash
41
42
  memor dashboard
@@ -49,10 +50,10 @@ memor dashboard
49
50
  ## How It Works
50
51
 
51
52
  ```
52
- You type a prompt in Claude Code
53
+ You type a prompt (Claude Code / Codex / Copilot)
53
54
  |
54
55
  v
55
- Hook fires (UserPromptSubmit)
56
+ Hook fires — auto-detects which agent
56
57
  |
57
58
  v
58
59
  Embed query locally (model2vec, ~2ms)
@@ -70,14 +71,26 @@ memor dashboard
70
71
  Inject relevant context into prompt
71
72
  |
72
73
  v
73
- Claude sees your past decisions, bugfixes,
74
+ Your agent sees past decisions, bugfixes,
74
75
  architecture choices — without you re-explaining
75
76
  ```
76
77
 
78
+ ### Supported Agents
79
+
80
+ | Agent | Hook protocol | Config location | Install |
81
+ |---|---|---|---|
82
+ | **Claude Code** | `UserPromptSubmit` + `additionalContext` | `~/.claude/settings.json` | `memor install-hook --agent claude` |
83
+ | **Codex CLI** | `UserPromptSubmit` + `additionalContext` | `~/.codex/hooks/hooks.json` | `memor install-hook --agent codex` |
84
+ | **Copilot CLI** | `userPromptSubmitted` + `additionalContext` | `~/.copilot/hooks/memor.json` | `memor install-hook --agent copilot` |
85
+
86
+ A single `memor-hook` binary auto-detects which agent is calling it — no separate entry points needed. The dashboard tracks recalls per agent so you can see usage across all your environments.
87
+
88
+ > **Note:** Cloud-hosted agents (Codex cloud, Copilot cloud agent) run in remote sandboxes and cannot reach local hooks. MCP server support for sandboxed agents is planned ([#26](https://github.com/bnimit/memor-ai/issues/26)).
89
+
77
90
  **Two background processes:**
78
91
 
79
- 1. **Daemon** — polls `~/.claude/projects/` for transcripts, embeds chunks, runs distillation, analyzes feedback (positive and negative), promotes cross-project patterns to global scope, compacts duplicates, tracks session-level token usage. All local.
80
- 2. **Hook** — fires on every prompt, recalls relevant memories, injects them as context. Sub-15ms.
92
+ 1. **Daemon** — polls `~/.claude/projects/` for transcripts, embeds chunks, runs distillation, analyzes feedback (positive and negative), promotes cross-project patterns to global scope, compacts duplicates, auto-compacts the vector index when bloated, tracks session-level token usage. All local.
93
+ 2. **Hook** — fires on every prompt, recalls relevant memories, injects them as context. Sub-15ms. Works across Claude Code, Codex, and Copilot.
81
94
 
82
95
  **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.
83
96
 
@@ -151,10 +164,11 @@ memor dashboard
151
164
 
152
165
  Dark fintech-inspired UI showing:
153
166
  - **Hero metrics** — total memories, recall count, avg latency, coverage — with sparkline bars
167
+ - **Agent breakdown** — per-agent recall stats (Claude, Codex, Copilot) with hit rates
154
168
  - **Daily recall activity** — stacked bar chart of hits vs misses over time
155
169
  - **Session efficiency** — real token savings measured from API usage data (avg tokens/turn with vs without recall)
156
170
  - **Per-project breakdown** — artifact counts, token totals, last activity
157
- - **Recent recalls** — every hook event with scores, latency, and status
171
+ - **Recent recalls** — every hook event with agent badge, scores, latency, and status
158
172
 
159
173
  ---
160
174
 
@@ -162,7 +176,8 @@ Dark fintech-inspired UI showing:
162
176
 
163
177
  ```
164
178
  memor help Print the full manual
165
- memor install-hook Install Claude Code hook + download model
179
+ memor install-hook Install hook + download model (interactive agent picker)
180
+ --agent claude|codex|copilot Choose agent directly
166
181
  memor daemon Auto-ingest + distill (background watcher)
167
182
  memor dashboard Web dashboard on localhost:8420
168
183
  memor version Print installed version
@@ -174,6 +189,7 @@ memor query <text> Search memories from the CLI
174
189
  memor reingest Wipe DB and re-ingest everything
175
190
  memor reingest --project <name> Re-ingest only one project
176
191
  memor forget-stale Deactivate memories unused for 30+ days
192
+ memor compact Rebuild vector index, reclaim wasted space
177
193
  memor scan Audit DB for leaked secrets
178
194
  memor scan --purge Redact secrets in place
179
195
  memor setup-model Download/retry the embedding model
@@ -227,7 +243,8 @@ memor/
227
243
  +-- judge.py LLM-as-judge evaluation
228
244
  +-- embed_benchmark.py Embedding model comparison
229
245
 
230
- memor/hook_cli.py Claude Code hook entry point (thin client)
246
+ memor/hook_cli.py Hook entry point — auto-detects Claude/Codex/Copilot
247
+ memor/hook_server.py Hook server with agent detection + response formatting
231
248
  skill/recall.py Standalone recall script
232
249
  ```
233
250
 
@@ -274,7 +291,7 @@ cd memor-ai
274
291
  python3 -m venv .venv && source .venv/bin/activate
275
292
  pip install -e ".[dev]"
276
293
 
277
- pytest # 251 tests
294
+ pytest # 285 tests
278
295
  ```
279
296
 
280
297
  ---
@@ -0,0 +1 @@
1
+ __version__ = "0.7.0"
@@ -44,9 +44,10 @@ HELP_TEXT = """\
44
44
  memor — measured memory for coding agents
45
45
 
46
46
  GETTING STARTED
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
47
+ memor install-hook Install the recall hook + download model
48
+ --agent claude|codex|copilot Choose your agent (default: claude)
49
+ memor service install Start the daemon as a background service
50
+ memor dashboard Open the web dashboard at localhost:8420
50
51
 
51
52
  SERVICE MANAGEMENT
52
53
  memor service install Install and start as a background service (survives reboots)
@@ -72,6 +73,7 @@ MAINTENANCE
72
73
  memor reingest --project <name> Re-ingest only one project
73
74
  memor distill --project <name> Run distillation manually
74
75
  memor forget-stale Deactivate memories not recalled in 30 days
76
+ memor compact Rebuild vector index, reclaim space
75
77
  memor scan Audit DB for leaked secrets
76
78
  memor scan --purge Redact secrets in place
77
79
  memor setup-model Download/retry the embedding model (~60MB)
@@ -88,7 +90,9 @@ CONFIGURATION
88
90
  No configuration needed — just install, hook, and run the daemon.
89
91
 
90
92
  EXAMPLES
91
- memor install-hook && memor service install # one-time setup
93
+ memor install-hook && memor service install # Claude Code (default)
94
+ memor install-hook --agent codex && memor service install # Codex CLI
95
+ memor install-hook --agent copilot && memor service install # Copilot CLI
92
96
  memor query "auth flow" --project my-app
93
97
  memor reingest --project my-app -y # refresh one project
94
98
  memor dashboard # see stats at localhost:8420
@@ -391,6 +395,36 @@ def forget_stale(days: int = typer.Option(30, help="Deactivate memories not reca
391
395
  typer.echo(f"Deactivated {count} stale memories.")
392
396
 
393
397
 
398
+ @app.command("compact")
399
+ def compact(db: str = typer.Option(str(Path.home() / ".memor" / "memor.db")),
400
+ fake: bool = False,
401
+ confirm: bool = typer.Option(False, "--yes", "-y", help="Skip confirmation")):
402
+ """Rebuild the vector index to reclaim wasted space."""
403
+ db_path = _db_path(db)
404
+ if not Path(db_path).exists():
405
+ typer.echo("No database found.")
406
+ raise typer.Exit(1)
407
+ dim = _get_dim(db_path)
408
+ embedder = _embedder(fake)
409
+ if embedder.dim != dim:
410
+ typer.echo(f"Warning: embedder dim={embedder.dim} != DB dim={dim}. Using DB dim.")
411
+ from memor.embed.fake import FakeEmbedder
412
+ embedder = FakeEmbedder(dim=dim)
413
+ store = SqliteStore(db_path, dim=dim)
414
+ chunk_count = store.db.execute("SELECT COUNT(*) as c FROM vec_artifacts_chunks").fetchone()["c"]
415
+ active_count = store.db.execute("SELECT COUNT(*) as c FROM artifacts WHERE active=1").fetchone()["c"]
416
+ db_size_mb = round(Path(db_path).stat().st_size / 1_048_576, 1)
417
+ typer.echo(f"Current: {chunk_count} chunks, {active_count} active vectors, {db_size_mb} MB")
418
+ if not confirm:
419
+ typer.confirm("Rebuild vector index?", abort=True)
420
+ typer.echo("Compacting...")
421
+ result = store.rebuild_vec_index(embedder)
422
+ db_size_after = round(Path(db_path).stat().st_size / 1_048_576, 1)
423
+ typer.echo(f"Done: {result['vectors_reindexed']} vectors reindexed in {result['duration_ms']}ms")
424
+ typer.echo(f"Chunks: {result['before_chunks']} -> {result['after_chunks']} (chunk_size={result['chunk_size']})")
425
+ typer.echo(f"DB size: {db_size_mb} MB -> {db_size_after} MB")
426
+
427
+
394
428
  @app.command("scan")
395
429
  def scan(db: str = typer.Option(str(Path.home() / ".memor" / "memor.db")),
396
430
  purge: bool = typer.Option(False, "--purge", help="Redact secrets in place"),
@@ -456,20 +490,108 @@ def _install_hook_logic(settings_path: Path, hook_command: str) -> None:
456
490
  settings_path.write_text(json.dumps(data, indent=2))
457
491
 
458
492
 
493
+ def _install_hook_logic_codex(hooks_path: Path, hook_command: str) -> None:
494
+ if hooks_path.exists():
495
+ data = json.loads(hooks_path.read_text())
496
+ else:
497
+ data = {}
498
+ hooks = data.setdefault("hooks", {})
499
+ prompt_hooks = hooks.setdefault("UserPromptSubmit", [])
500
+ entry = {"command": hook_command, "timeout": 5}
501
+ existing_idx = None
502
+ for i, h in enumerate(prompt_hooks):
503
+ if "memor-hook" in h.get("command", ""):
504
+ existing_idx = i
505
+ break
506
+ if existing_idx is not None:
507
+ prompt_hooks[existing_idx] = entry
508
+ else:
509
+ prompt_hooks.append(entry)
510
+ hooks_path.parent.mkdir(parents=True, exist_ok=True)
511
+ hooks_path.write_text(json.dumps(data, indent=2))
512
+
513
+
514
+ def _install_hook_logic_copilot(hooks_path: Path, hook_command: str) -> None:
515
+ if hooks_path.exists():
516
+ data = json.loads(hooks_path.read_text())
517
+ else:
518
+ data = {"version": 1, "hooks": {}}
519
+ data.setdefault("version", 1)
520
+ hooks = data.setdefault("hooks", {})
521
+ prompt_hooks = hooks.setdefault("userPromptSubmitted", [])
522
+ entry = {"type": "command", "bash": hook_command, "timeoutSec": 5}
523
+ existing_idx = None
524
+ for i, h in enumerate(prompt_hooks):
525
+ if "memor-hook" in h.get("bash", ""):
526
+ existing_idx = i
527
+ break
528
+ if existing_idx is not None:
529
+ prompt_hooks[existing_idx] = entry
530
+ else:
531
+ prompt_hooks.append(entry)
532
+ hooks_path.parent.mkdir(parents=True, exist_ok=True)
533
+ hooks_path.write_text(json.dumps(data, indent=2))
534
+
535
+
536
+ AGENT_CHOICES = [
537
+ ("claude", "~/.claude/settings.json", "Claude Code"),
538
+ ("codex", "~/.codex/hooks/hooks.json", "Codex CLI"),
539
+ ("copilot", "~/.copilot/hooks/memor.json", "Copilot CLI"),
540
+ ]
541
+
542
+
543
+ def _prompt_agent() -> str:
544
+ typer.echo("Which agent do you want to install the recall hook for?\n")
545
+ for i, (key, _, label) in enumerate(AGENT_CHOICES, 1):
546
+ typer.echo(f" {i}. {label}")
547
+ typer.echo()
548
+ while True:
549
+ choice = typer.prompt("Choose (1-3)", default="1")
550
+ try:
551
+ idx = int(choice) - 1
552
+ if 0 <= idx < len(AGENT_CHOICES):
553
+ return AGENT_CHOICES[idx][0]
554
+ except ValueError:
555
+ pass
556
+ typer.echo("Invalid choice. Enter 1, 2, or 3.")
557
+
558
+
459
559
  @app.command("install-hook")
460
- def install_hook():
461
- """Install the Claude Code recall hook into ~/.claude/settings.json."""
560
+ def install_hook(agent: str | None = typer.Option(None, help="Agent: claude, codex, copilot (interactive if omitted)")):
561
+ """Install the recall hook for Claude Code, Codex, or Copilot."""
462
562
  import shutil
563
+ agent_keys = {key for key, _, _ in AGENT_CHOICES}
564
+
565
+ if agent is None:
566
+ agent = _prompt_agent()
567
+ else:
568
+ agent = agent.lower()
569
+
570
+ if agent not in agent_keys:
571
+ typer.echo(f"Unknown agent '{agent}'. Choose: claude, codex, copilot", err=True)
572
+ raise typer.Exit(1)
573
+
463
574
  hook_bin = shutil.which("memor-hook")
464
575
  if not hook_bin:
465
576
  typer.echo("Error: 'memor-hook' not found on PATH.", err=True)
466
577
  typer.echo(" If you installed with pipx: pipx install memor-cli", err=True)
467
578
  typer.echo(" If developing locally: pip install -e .", err=True)
468
579
  raise typer.Exit(1)
469
- settings_path = Path.home() / ".claude" / "settings.json"
470
- _install_hook_logic(settings_path, hook_bin)
471
- typer.echo(f"Hook installed: {hook_bin}")
472
- typer.echo(f"Settings updated: {settings_path}")
580
+
581
+ config_path_str, agent_name = next(
582
+ (p, n) for k, p, n in AGENT_CHOICES if k == agent
583
+ )
584
+ config_path = Path(config_path_str).expanduser()
585
+
586
+ if agent == "claude":
587
+ _install_hook_logic(config_path, hook_bin)
588
+ elif agent == "codex":
589
+ _install_hook_logic_codex(config_path, hook_bin)
590
+ elif agent == "copilot":
591
+ _install_hook_logic_copilot(config_path, hook_bin)
592
+
593
+ typer.echo(f"\nHook installed for {agent_name}: {hook_bin}")
594
+ typer.echo(f"Config updated: {config_path}")
473
595
  typer.echo()
474
596
  typer.echo("Pre-downloading embedding model...")
475
597
  try:
@@ -186,6 +186,29 @@ def compact_memories(store: SqliteStore, embedder) -> int:
186
186
  return deactivated
187
187
 
188
188
 
189
+ def should_compact(store: SqliteStore) -> bool:
190
+ try:
191
+ chunk_count = store.db.execute(
192
+ "SELECT COUNT(*) as c FROM vec_artifacts_chunks").fetchone()["c"]
193
+ active_count = store.db.execute(
194
+ "SELECT COUNT(*) as c FROM artifacts WHERE active=1").fetchone()["c"]
195
+ except Exception as e:
196
+ print(f" should_compact error: {e}")
197
+ return False
198
+ if chunk_count == 0 or active_count == 0:
199
+ return False
200
+ from memor.store.sqlite_store import _choose_chunk_size
201
+ chunk_size = _choose_chunk_size(active_count)
202
+ ideal_chunks = max(1, active_count // chunk_size + 1)
203
+ return chunk_count > ideal_chunks * 2
204
+
205
+
206
+ def auto_compact(store: SqliteStore, embedder) -> dict | None:
207
+ if not should_compact(store):
208
+ return None
209
+ return store.rebuild_vec_index(embedder, vacuum=False)
210
+
211
+
189
212
  def run_poll_cycle(
190
213
  state: dict[str, float],
191
214
  store: SqliteStore,
@@ -286,6 +309,16 @@ def run_poll_cycle(
286
309
  except Exception:
287
310
  pass
288
311
 
312
+ # Auto-compact vec index if bloated
313
+ if new_ingested:
314
+ try:
315
+ result = auto_compact(store, embedder)
316
+ if result:
317
+ print(f" vec compact: {result['before_chunks']} -> {result['after_chunks']} chunks "
318
+ f"({result['vectors_reindexed']} vectors, {result['duration_ms']}ms)")
319
+ except Exception:
320
+ pass
321
+
289
322
  return state, distilled
290
323
 
291
324
 
@@ -14,9 +14,13 @@ def create_app(db_path: str | None = None) -> FastAPI:
14
14
 
15
15
  app = FastAPI(title="Memor Dashboard")
16
16
  _db_path = db_path
17
+ _cached_store: SqliteStore | None = None
17
18
 
18
19
  def _store() -> SqliteStore:
19
- return SqliteStore(_db_path, dim=_get_dim(_db_path))
20
+ nonlocal _cached_store
21
+ if _cached_store is None:
22
+ _cached_store = SqliteStore(_db_path, dim=_get_dim(_db_path))
23
+ return _cached_store
20
24
 
21
25
  @app.get("/", response_class=HTMLResponse)
22
26
  def index():
@@ -66,11 +70,16 @@ def create_app(db_path: str | None = None) -> FastAPI:
66
70
  ORDER BY artifacts DESC
67
71
  """).fetchall()
68
72
 
73
+ active_rows = [
74
+ r for r in artifact_rows
75
+ if (r["memories"] or 0) > 0 or r["project"] in recall_projects
76
+ ]
77
+
69
78
  if not recall_stats:
70
- return [dict(r) for r in artifact_rows]
79
+ return [dict(r) for r in active_rows]
71
80
 
72
81
  result = list(recall_stats)
73
- for r in artifact_rows:
82
+ for r in active_rows:
74
83
  if r["project"] not in recall_projects:
75
84
  result.append(dict(r))
76
85
  return result
@@ -168,6 +177,11 @@ def create_app(db_path: str | None = None) -> FastAPI:
168
177
  return {"status": "no_runs"}
169
178
  return result
170
179
 
180
+ @app.get("/api/agent-breakdown")
181
+ def agent_breakdown():
182
+ store = _store()
183
+ return store.get_agent_breakdown()
184
+
171
185
  @app.get("/api/version")
172
186
  def version():
173
187
  from memor import __version__
@@ -304,6 +304,12 @@
304
304
  .badge-global::before { background: #a090ff; }
305
305
  .badge-negative { background: rgba(232,84,84,0.12); color: var(--error); }
306
306
  .badge-negative::before { background: var(--error); }
307
+ .badge-claude { background: var(--accent-dim); color: var(--accent); }
308
+ .badge-claude::before { background: var(--accent); }
309
+ .badge-codex { background: rgba(61,214,140,0.12); color: #3dd68c; }
310
+ .badge-codex::before { background: #3dd68c; }
311
+ .badge-copilot { background: rgba(100,160,255,0.12); color: #64a0ff; }
312
+ .badge-copilot::before { background: #64a0ff; }
307
313
 
308
314
  .quality-bar { display: flex; height: 6px; border-radius: 3px; overflow: hidden; background: var(--surface3); width: 100%; min-width: 60px; }
309
315
  .quality-fill { height: 100%; border-radius: 3px; transition: width 0.3s; }
@@ -398,6 +404,12 @@
398
404
  </div>
399
405
  </section>
400
406
 
407
+ <!-- ── Agent breakdown ─────────────────────────────────── -->
408
+ <section id="agent-section" style="display:none;">
409
+ <div class="section-label"><span class="dot"></span> Agent Breakdown</div>
410
+ <div class="metric-grid" id="agent-grid" style="grid-template-columns:repeat(auto-fit,minmax(180px,1fr));"></div>
411
+ </section>
412
+
401
413
  <!-- ── Token ROI hero ─────────────────────────────────── -->
402
414
  <section>
403
415
  <div class="chart-card" id="roi-card" style="display:none;">
@@ -538,6 +550,7 @@
538
550
  <thead>
539
551
  <tr>
540
552
  <th>Time</th>
553
+ <th>Agent</th>
541
554
  <th>Project</th>
542
555
  <th>Query</th>
543
556
  <th>Hits</th>
@@ -595,6 +608,16 @@
595
608
  return '<span class="badge ' + m[0] + '">' + m[1] + '</span>';
596
609
  }
597
610
 
611
+ function agentBadge(agent) {
612
+ var map = {
613
+ claude: ['badge-claude', 'Claude'],
614
+ codex: ['badge-codex', 'Codex'],
615
+ copilot: ['badge-copilot', 'Copilot'],
616
+ };
617
+ var m = map[agent || 'claude'] || ['badge-claude', agent || 'claude'];
618
+ return '<span class="badge ' + m[0] + '">' + m[1] + '</span>';
619
+ }
620
+
598
621
  function el(tag, attrs, inner) {
599
622
  var e = document.createElement(tag);
600
623
  for (var k in (attrs || {})) e.setAttribute(k, attrs[k]);
@@ -860,13 +883,14 @@
860
883
  var tbody = document.getElementById('recalls-body');
861
884
  tbody.innerHTML = '';
862
885
  if (!data.length) {
863
- tbody.innerHTML = '<tr><td colspan="8" class="empty-state">No recall events yet.</td></tr>';
886
+ tbody.innerHTML = '<tr><td colspan="9" class="empty-state">No recall events yet.</td></tr>';
864
887
  return;
865
888
  }
866
889
  data.forEach(function(row) {
867
890
  var tr = el('tr', {});
868
891
  tr.innerHTML =
869
892
  '<td class="mono" style="white-space:nowrap;color:var(--text-muted)">' + relativeTime(row.timestamp) + '</td>' +
893
+ '<td>' + agentBadge(row.agent) + '</td>' +
870
894
  '<td class="truncate mono" style="max-width:120px" title="' + esc(row.project) + '">' + esc(row.project) + '</td>' +
871
895
  '<td class="truncate" style="max-width:220px" title="' + esc(row.query_preview) + '">' + esc(row.query_preview) + '</td>' +
872
896
  '<td>' + (row.hits_count||0) + '</td>' +
@@ -1002,6 +1026,29 @@
1002
1026
  });
1003
1027
  }
1004
1028
 
1029
+ /* ── Agent breakdown ────────────────────────────────────── */
1030
+ function renderAgentBreakdown(data) {
1031
+ var section = document.getElementById('agent-section');
1032
+ var grid = document.getElementById('agent-grid');
1033
+ if (!data || data.length <= 1) { section.style.display = 'none'; return; }
1034
+ section.style.display = '';
1035
+ grid.innerHTML = '';
1036
+ var colors = { claude: 'var(--accent)', codex: '#3dd68c', copilot: '#64a0ff' };
1037
+ data.forEach(function(row) {
1038
+ var agent = row.agent || 'claude';
1039
+ var color = colors[agent] || 'var(--text-muted)';
1040
+ var hitRate = row.recalls > 0 ? ((row.hits / row.recalls) * 100) : 0;
1041
+ var card = el('div', {class: 'metric-card'});
1042
+ card.innerHTML =
1043
+ '<div class="metric-info">' +
1044
+ '<div class="metric-label" style="color:' + color + ';">' + esc(agent.charAt(0).toUpperCase() + agent.slice(1)) + '</div>' +
1045
+ '<div class="metric-value">' + fmt(row.recalls) + '</div>' +
1046
+ '<div class="metric-sub">' + fmtPct(hitRate) + ' hit rate · ' + fmtMs(row.avg_latency) + '</div>' +
1047
+ '</div>';
1048
+ grid.appendChild(card);
1049
+ });
1050
+ }
1051
+
1005
1052
  /* ── Data loaders ──────────────────────────────────────── */
1006
1053
  async function loadSummary() { try { renderSummary(await api('/api/summary')); } catch(e) { console.warn('summary',e); } }
1007
1054
  async function loadProjects() { try { renderProjects(await api('/api/projects')); } catch(e) { console.warn('projects',e); } }
@@ -1012,6 +1059,7 @@
1012
1059
  async function loadROI() { try { renderROI(await api('/api/roi')); } catch(e) { console.warn('roi',e); } }
1013
1060
  async function loadROITrend() { try { renderROITrend(await api('/api/roi-trend')); } catch(e) { console.warn('roi-trend',e); } }
1014
1061
  async function loadQuality() { try { renderQuality(await api('/api/quality')); } catch(e) { console.warn('quality',e); } }
1062
+ async function loadAgentBreakdown() { try { renderAgentBreakdown(await api('/api/agent-breakdown')); } catch(e) { console.warn('agent-breakdown',e); } }
1015
1063
  async function loadRecalls() {
1016
1064
  try {
1017
1065
  var url = '/api/recalls?limit=50' + (projectFilter ? '&project=' + encodeURIComponent(projectFilter) : '');
@@ -1020,7 +1068,7 @@
1020
1068
  }
1021
1069
 
1022
1070
  async function refresh() {
1023
- await Promise.allSettled([loadSummary(), loadProjects(), loadEfficiency(), loadSessionEfficiency(), loadHealth(), loadTrend(), loadRecalls(), loadROI(), loadROITrend(), loadQuality()]);
1071
+ await Promise.allSettled([loadSummary(), loadProjects(), loadEfficiency(), loadSessionEfficiency(), loadHealth(), loadTrend(), loadRecalls(), loadROI(), loadROITrend(), loadQuality(), loadAgentBreakdown()]);
1024
1072
  document.getElementById('last-updated').textContent = new Date().toLocaleTimeString();
1025
1073
 
1026
1074
  renderMiniBars('mb-chunks', null);