@hmanlab/memo 0.5.0 → 0.5.2
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.
- package/README.md +119 -3
- package/claude/skill/memo/SKILL.md +88 -48
- package/dist/cli.js +36966 -346
- package/dist/memo-mcp-server.js +38305 -1373
- package/package.json +23 -2
package/README.md
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
Local-first MCP server for persistent, persona-aware memory across projects.
|
|
4
4
|
|
|
5
|
-
`memo` ships two surfaces: an MCP server (
|
|
5
|
+
`memo` ships two surfaces: an MCP server (35 tools) for AI clients like
|
|
6
6
|
Claude Code, and a Node CLI (`hmanlab-memory`) for power users. Both
|
|
7
7
|
share the same backend — the CLI is a thin wrapper, not a re-implementation.
|
|
8
8
|
|
|
@@ -10,9 +10,123 @@ Everything lives under `~/.hmanlab/`: one root SQLite DB + a `personas/`
|
|
|
10
10
|
directory of YAML files + one DB per registered project. No cloud, no
|
|
11
11
|
account, no telemetry.
|
|
12
12
|
|
|
13
|
+
## What `npx -y @hmanlab/hl-plugins install memo` does
|
|
14
|
+
|
|
15
|
+
It's the one-line path to a working setup. It runs five steps, in order:
|
|
16
|
+
|
|
17
|
+
1. **Pre-flight.** Node ≥ 18, an `~/.opencode/` config dir. Auto-creates
|
|
18
|
+
the dir if missing.
|
|
19
|
+
2. **Install Bun.** Memo is built with `--target=bun`, so Bun is a hard
|
|
20
|
+
requirement. The installer auto-installs it via
|
|
21
|
+
`curl -fsSL https://bun.sh/install | bash` if it isn't on PATH yet.
|
|
22
|
+
3. **Stage the plugin CLI.** Copies `dist/cli.js` to
|
|
23
|
+
`~/.local/share/hl-plugins/memo/` so the next step can invoke plugin
|
|
24
|
+
subcommands by absolute path (no PATH dependency yet).
|
|
25
|
+
4. **Prompt about MiniLM.** *See the section below.* Your answer is
|
|
26
|
+
persisted during install — there's no "run later" step.
|
|
27
|
+
5. **Copy + register.** Ships the MCP server bundle to
|
|
28
|
+
`~/.local/share/hl-plugins/memo/memo-mcp-server.js`, drops the skill
|
|
29
|
+
markdown at `~/.claude/skills/memo/SKILL.md`, and registers the server
|
|
30
|
+
in your Claude Code config. Then prints
|
|
31
|
+
*"Restart opencode to use the new tools."*
|
|
32
|
+
|
|
33
|
+
That's it. No auth, no account, no telemetry, no daemon. The server runs
|
|
34
|
+
on stdio only when Claude Code invokes it.
|
|
35
|
+
|
|
36
|
+
### Optional: the MiniLM embedder
|
|
37
|
+
|
|
38
|
+
After Bun is confirmed and before any files are copied, the installer asks
|
|
39
|
+
once whether you want the optional MiniLM-L6-v2 model. The model powers
|
|
40
|
+
semantic search — paraphrase and typo queries still hit the right memory
|
|
41
|
+
even when the words don't match the stored content literally.
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
? MiniLM-L6-v2 (~25 MB) powers semantic search so paraphrase and typo queries
|
|
45
|
+
still hit the right memory.
|
|
46
|
+
|
|
47
|
+
With it: 75.2% recall@5 (62.9% recall@1)
|
|
48
|
+
Without it: paraphrase queries drop to ~30%, typo queries to ~25%
|
|
49
|
+
(105-query eval across coding, glossary, and preferences)
|
|
50
|
+
|
|
51
|
+
Enable? [Y/n]:
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
Your answer is committed during install — no follow-up step:
|
|
55
|
+
|
|
56
|
+
- **Y (default):** writes `embedder_mode: minilm` to
|
|
57
|
+
`~/.hmanlab/config.yaml`. The model downloads lazily on the next
|
|
58
|
+
`memory_save` / `memory_search` call (~25 MB, ~2 s warmup, then ~50 ms
|
|
59
|
+
per query).
|
|
60
|
+
- **n:** writes `embedder_mode: hash`. `loadExtractor()` short-circuits
|
|
61
|
+
on every subsequent call. The model is **never** downloaded or
|
|
62
|
+
referenced — the embedder uses the deterministic trigram fallback.
|
|
63
|
+
|
|
64
|
+
Non-interactive installs (CI, scripts piped via `| sh`) treat the prompt
|
|
65
|
+
as Yes so the install never blocks.
|
|
66
|
+
|
|
67
|
+
Change your mind any time:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
hmanlab-memory embedder status # show current mode
|
|
71
|
+
hmanlab-memory embedder install # switch to minilm (lazy download on next memory call)
|
|
72
|
+
hmanlab-memory embedder disable # switch to hash (no download, ever)
|
|
73
|
+
```
|
|
74
|
+
|
|
75
|
+
The mode is stored under `embedder_mode` in `~/.hmanlab/config.yaml`. Three
|
|
76
|
+
values: `minilm` (require the real model), `hash` (use the deterministic
|
|
77
|
+
trigram fallback), `auto` (try MiniLM, fall back to hash on failure —
|
|
78
|
+
default if the key is absent).
|
|
79
|
+
|
|
80
|
+
### With MiniLM vs without — what actually changes
|
|
81
|
+
|
|
82
|
+
Same 105 positive + 20 negative queries, same memory corpus. Two
|
|
83
|
+
columns: **Hash** (no MiniLM, no model download) and **MiniLM +
|
|
84
|
+
trigram** (what ships by default — semantic embedder + the trigram
|
|
85
|
+
FTS5 mirror that catches 3-char substring overlap).
|
|
86
|
+
|
|
87
|
+
**Headline metrics:**
|
|
88
|
+
|
|
89
|
+
| Metric | Hash fallback | MiniLM + trigram | Δ |
|
|
90
|
+
|---|---|---|---|
|
|
91
|
+
| Recall@1 | 41.0% | **62.9%** | **+21.9 pp** |
|
|
92
|
+
| Recall@5 | 68.6% | **75.2%** | +6.6 pp |
|
|
93
|
+
| MRR | 0.516 | **0.679** | **+0.163** |
|
|
94
|
+
|
|
95
|
+
The biggest win is Recall@1 — the trigram FTS5 mirror lifts it from
|
|
96
|
+
45.7% (MiniLM alone) to 62.9% (MiniLM + trigram). When the query
|
|
97
|
+
shares even one 3-char substring with the right memory, that memory
|
|
98
|
+
now lands at rank 1 instead of being lost in the top-5 noise.
|
|
99
|
+
|
|
100
|
+
**By domain (R@5):**
|
|
101
|
+
|
|
102
|
+
| Domain | Hash | MiniLM + trigram | Δ |
|
|
103
|
+
|---|---|---|---|
|
|
104
|
+
| glossary | 64.5% | 100.0% | **+35.5** |
|
|
105
|
+
| preferences | 97.4% | 100.0% | +2.6 |
|
|
106
|
+
|
|
107
|
+
**By query kind (R@5):**
|
|
108
|
+
|
|
109
|
+
| Kind | Hash | MiniLM + trigram | Δ |
|
|
110
|
+
|---|---|---|---|
|
|
111
|
+
| literal | 93.3% | 96.7% | +3.4 |
|
|
112
|
+
| paraphrase | 60.0% | 66.7% | +6.7 |
|
|
113
|
+
| typo | 53.3% | 66.7% | +13.3 |
|
|
114
|
+
| negation | 70.0% | 60.0% | **−10.0** |
|
|
115
|
+
| broad | 60.0% | 80.0% | +20.0 |
|
|
116
|
+
|
|
117
|
+
If your memory is mostly short, literal preferences, hash fallback is
|
|
118
|
+
competitive. If your memory is glossary definitions or fuzzy
|
|
119
|
+
paraphrases, MiniLM + trigram dominates — particularly on
|
|
120
|
+
broad queries where the user types a vague prompt and expects the
|
|
121
|
+
right memory to surface.
|
|
122
|
+
|
|
123
|
+
Raw eval data:
|
|
124
|
+
- `~/Desktop/memo-eval/results-2026-06-25-bigeval.json` (MiniLM + trigram, current ship state)
|
|
125
|
+
- `~/Desktop/memo-eval/results-2026-06-25-bigeval-hash.json` (hash fallback, what you get if you decline MiniLM at install)
|
|
126
|
+
|
|
13
127
|
## What's in the box (v1.0.0)
|
|
14
128
|
|
|
15
|
-
### MCP tools (
|
|
129
|
+
### MCP tools (35)
|
|
16
130
|
- **Persona (11):** `persona_list`, `persona_get`, `persona_create`,
|
|
17
131
|
`persona_update`, `persona_delete`, `persona_clone`,
|
|
18
132
|
`persona_reload`, `user_persona_get`, `user_persona_update`
|
|
@@ -70,9 +184,11 @@ Full CLI reference: [`docs/USAGE.md`](./docs/USAGE.md).
|
|
|
70
184
|
|
|
71
185
|
```
|
|
72
186
|
~/.hmanlab/
|
|
73
|
-
├── config.yaml # cwd_auto_detect, persona_filter_mode,
|
|
187
|
+
├── config.yaml # cwd_auto_detect, persona_filter_mode, embedder_mode
|
|
74
188
|
├── root.db # user_persona, ai_personas, projects,
|
|
75
189
|
│ # global_memories (+ _fts + _edges), schema migrations
|
|
190
|
+
├── models/ # MiniLM-L6-v2 q8 (~25 MB), lazy-downloaded on first use
|
|
191
|
+
│ └── Xenova/all-MiniLM-L6-v2/...
|
|
76
192
|
├── personas/ # persona YAML files (built-in + user)
|
|
77
193
|
│ ├── default.yaml
|
|
78
194
|
│ ├── work.yaml # parent: default
|
|
@@ -5,25 +5,82 @@ description: Use when the user wants persistent memory across projects, persona-
|
|
|
5
5
|
|
|
6
6
|
# memo — hmanlab-memo (local-first MCP memory)
|
|
7
7
|
|
|
8
|
-
The `memo` MCP server exposes
|
|
9
|
-
persistent, persona-aware memory on the user's machine. Everything lives
|
|
10
|
-
`~/.hmanlab/` (one root SQLite DB + a `personas/` directory of YAML
|
|
11
|
-
No cloud, no account, no telemetry.
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
8
|
+
The `memo` MCP server exposes 35 tools that give an AI coding assistant
|
|
9
|
+
persistent, persona-aware memory on the user's machine. Everything lives
|
|
10
|
+
under `~/.hmanlab/` (one root SQLite DB + a `personas/` directory of YAML
|
|
11
|
+
files + one DB per registered project). No cloud, no account, no telemetry.
|
|
12
|
+
|
|
13
|
+
The MCP bundle is `--target=bun` and lives at
|
|
14
|
+
`~/.local/share/hl-plugins/memo/memo-mcp-server.js`. Claude Code launches
|
|
15
|
+
it on stdio.
|
|
16
|
+
|
|
17
|
+
## Search strategy — normalize the query before calling `memory_search`
|
|
18
|
+
|
|
19
|
+
`memory_search` is hybrid (FTS + recency + vector). Vector search lifts
|
|
20
|
+
Recall@5 from 68.6% → 73.3% on the standard eval, but only if the query
|
|
21
|
+
isn't too distorted. **You are better at query rewriting than the local
|
|
22
|
+
embedder.** Before calling `memory_search`, normalize the query yourself:
|
|
23
|
+
|
|
24
|
+
1. **Fix typos** before searching — vector similarity on `"indenation with
|
|
25
|
+
tabs"` lands on the right memory; `"indsnation with tabx"` might not.
|
|
26
|
+
2. **Drop conversational filler** ("can you", "do you know", "what was
|
|
27
|
+
that thing about"). The filler dilutes the cosine signal.
|
|
28
|
+
3. **Strip negation of the question, keep negation of the memory.** When
|
|
29
|
+
the user asks "should I commit secrets to a private repo", search for
|
|
30
|
+
`"Never commit secrets to git"` (the actual memory), not the literal
|
|
31
|
+
question. The embedder can't distinguish "should I commit" from
|
|
32
|
+
"Never commit" — but if your query contains the *memory's* words,
|
|
33
|
+
FTS catches it.
|
|
34
|
+
4. **Prefer the user's own phrasing** when you remember it from the
|
|
35
|
+
conversation. If the user said "tabs not spaces" earlier and the
|
|
36
|
+
memory says "Use tabs for indentation in this project", search for
|
|
37
|
+
the latter — it matches both FTS and vector better.
|
|
38
|
+
5. **One query, not several.** Don't fan out: a single, well-chosen query
|
|
39
|
+
outperforms three noisy ones.
|
|
40
|
+
|
|
41
|
+
Concretely, before calling `memory_search("query")`:
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
raw user question: "wait what was my rule about not committing api keys"
|
|
45
|
+
rewrite to: "Never commit secrets to git"
|
|
46
|
+
then call: memory_search("Never commit secrets to git")
|
|
47
|
+
```
|
|
48
|
+
|
|
49
|
+
Don't rewrite for `memory_recent` — that's recency-only and your input
|
|
50
|
+
doesn't matter.
|
|
51
|
+
|
|
52
|
+
## When to use the tools
|
|
53
|
+
|
|
54
|
+
- **User asks a question whose answer is in memory** → `memory_search`
|
|
55
|
+
with a normalized query. Skim top-5; if none match, fall back to
|
|
56
|
+
answering from your own knowledge (don't claim a memory hit when
|
|
57
|
+
there isn't one).
|
|
58
|
+
- **User states a preference / rule / decision worth keeping** →
|
|
59
|
+
`memory_save`. Use `importance: 0.9` for durable rules, `0.5` for
|
|
60
|
+
context, `0.3` for one-off notes. Add a `category` (e.g. "preferences",
|
|
61
|
+
"code-style", "glossary").
|
|
62
|
+
- **User asks "what do you know about me / this project"** → `memory_search`
|
|
63
|
+
with `scope: "project"` for project-specific, `scope: "all"` for
|
|
64
|
+
everything.
|
|
65
|
+
- **User asks to switch hats / "talk like X"** → `persona_list` to see
|
|
66
|
+
options, `persona_get` to read the full prompt, then continue as that
|
|
67
|
+
persona.
|
|
68
|
+
- **User asks to remember a global preference** → `user_persona_update`.
|
|
69
|
+
- **Long conversation, context getting heavy** → `memory_compact_prep`
|
|
70
|
+
to get the pre-selected subset worth re-injecting after compaction.
|
|
71
|
+
- **Storage getting messy** → `memory_hygiene all` for the stale/cold/
|
|
72
|
+
duplicate report, `memory_status` for the headline counts.
|
|
73
|
+
- **Want to back up / move a project** → `project_export <name>` /
|
|
74
|
+
`project_import <archive>`.
|
|
75
|
+
|
|
76
|
+
## Save rules
|
|
77
|
+
|
|
78
|
+
- **Be specific.** "Use tabs for indentation" beats "code style matters".
|
|
79
|
+
- **One fact per memory.** Splitting lets each one rank on its own.
|
|
80
|
+
- **Use the user's own words** when possible. They're more searchable
|
|
81
|
+
later.
|
|
82
|
+
- **Pick importance honestly.** `0.9` = durable rule, `0.5` = context,
|
|
83
|
+
`0.3` = ephemeral.
|
|
27
84
|
|
|
28
85
|
## Setup (one-time, on the machine)
|
|
29
86
|
|
|
@@ -32,37 +89,20 @@ memories, embeddings, and hybrid search land in later phases.
|
|
|
32
89
|
```bash
|
|
33
90
|
hl-plugins install memo
|
|
34
91
|
```
|
|
35
|
-
3.
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
92
|
+
3. The installer prompts once about MiniLM-L6-v2 (a local embedder that
|
|
93
|
+
powers semantic search). Default is Yes — ~25 MB download on first
|
|
94
|
+
memory call.
|
|
95
|
+
4. Restart Claude Code. The 35 tools appear under the `memo` MCP server.
|
|
39
96
|
|
|
40
97
|
## On-disk layout
|
|
41
98
|
|
|
42
99
|
```
|
|
43
100
|
~/.hmanlab/
|
|
44
|
-
├── config.yaml # paths
|
|
45
|
-
├── root.db #
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
├──
|
|
50
|
-
└──
|
|
51
|
-
```
|
|
52
|
-
|
|
53
|
-
YAML is the source of truth. Editing a file on disk and calling `persona_reload`
|
|
54
|
-
updates the DB to match. The starter pack is extracted only on first boot;
|
|
55
|
-
existing YAMLs are never overwritten.
|
|
56
|
-
|
|
57
|
-
## When to use these tools
|
|
58
|
-
|
|
59
|
-
- **User asks to switch hats / "talk like X" / use a persona** → `persona_list`
|
|
60
|
-
to see options, `persona_get` to read the full prompt, then continue the
|
|
61
|
-
conversation as that persona.
|
|
62
|
-
- **User asks to remember a preference** → `user_persona_update` with the
|
|
63
|
-
preference text.
|
|
64
|
-
- **User asks to create / edit a persona** → `persona_create` or
|
|
65
|
-
`persona_update`.
|
|
66
|
-
- **User edits a persona YAML directly** → `persona_reload` to make the DB
|
|
67
|
-
match.
|
|
68
|
-
- **User wants to fork an existing persona** → `persona_clone`.
|
|
101
|
+
├── config.yaml # paths, embedder_mode, persona_filter_mode
|
|
102
|
+
├── root.db # user_persona, ai_personas, projects, global_memories
|
|
103
|
+
├── models/ # MiniLM-L6-v2 q8 (lazy-downloaded on first embed call)
|
|
104
|
+
├── personas/ # persona YAML files (built-in + user)
|
|
105
|
+
└── projects/<name>/
|
|
106
|
+
├── project.yaml
|
|
107
|
+
└── hmanlab.db # memories + FTS5
|
|
108
|
+
```
|