hmem-mcp 6.3.0 → 6.3.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/dist/cli-context-inject.js +8 -1
- package/dist/cli-context-inject.js.map +1 -1
- package/dist/cli-hook-startup.js +12 -9
- package/dist/cli-hook-startup.js.map +1 -1
- package/dist/cli-init.js +96 -1
- package/dist/cli-init.js.map +1 -1
- package/dist/cli-log-exchange.js +9 -4
- package/dist/cli-log-exchange.js.map +1 -1
- package/dist/hmem-config.d.ts +7 -1
- package/dist/hmem-config.js +4 -1
- package/dist/hmem-config.js.map +1 -1
- package/dist/hmem-store.d.ts +10 -31
- package/dist/hmem-store.js +22 -187
- package/dist/hmem-store.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp-server.js +195 -481
- package/dist/mcp-server.js.map +1 -1
- package/opencode-plugin/hmem.js +105 -0
- package/package.json +2 -1
- package/skills/hmem-config/SKILL.md +77 -0
- package/skills/hmem-curate/SKILL.md +158 -134
- package/skills/hmem-migrate-o/SKILL.md +1 -1
- package/skills/hmem-read/SKILL.md +1 -1
- package/skills/hmem-release/SKILL.md +2 -3
- package/skills/hmem-update/SKILL.md +48 -1
- package/skills/hmem-wipe/SKILL.md +6 -4
- package/skills/hmem-write/SKILL.md +28 -5
- package/skills/hmem-self-curate/SKILL.md +0 -194
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* hmem OpenCode plugin
|
|
3
|
+
*
|
|
4
|
+
* Bridges OpenCode's plugin event system into hmem's CLI hook commands.
|
|
5
|
+
* Drop this file into ~/.config/opencode/plugins/ (global) or
|
|
6
|
+
* .opencode/plugins/ (project-local) and OpenCode will auto-load it.
|
|
7
|
+
*
|
|
8
|
+
* Mapping:
|
|
9
|
+
* chat.message → capture user prompt + write session marker
|
|
10
|
+
* event: message.part.updated → accumulate assistant text per session
|
|
11
|
+
* event: session.idle → spawn `hmem log-exchange` + `hmem checkpoint`
|
|
12
|
+
* experimental.session.compacting → inject hmem context into compaction prompt
|
|
13
|
+
*
|
|
14
|
+
* Requires `hmem` CLI on PATH and HMEM_PATH env var (auto-detected if unset).
|
|
15
|
+
*/
|
|
16
|
+
|
|
17
|
+
import { spawn, spawnSync } from "node:child_process";
|
|
18
|
+
|
|
19
|
+
function spawnHmem(args, stdinJson) {
|
|
20
|
+
try {
|
|
21
|
+
const child = spawn("hmem", args, {
|
|
22
|
+
detached: true,
|
|
23
|
+
stdio: ["pipe", "ignore", "ignore"],
|
|
24
|
+
env: process.env,
|
|
25
|
+
});
|
|
26
|
+
if (stdinJson) {
|
|
27
|
+
child.stdin.end(stdinJson);
|
|
28
|
+
} else {
|
|
29
|
+
child.stdin.end();
|
|
30
|
+
}
|
|
31
|
+
child.unref();
|
|
32
|
+
} catch {
|
|
33
|
+
// hmem not installed or not on PATH — silently no-op
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
function partsToText(parts) {
|
|
38
|
+
if (!Array.isArray(parts)) return "";
|
|
39
|
+
return parts
|
|
40
|
+
.filter(p => p && p.type === "text" && typeof p.text === "string")
|
|
41
|
+
.map(p => p.text)
|
|
42
|
+
.join("\n")
|
|
43
|
+
.trim();
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
export default {
|
|
47
|
+
id: "hmem",
|
|
48
|
+
server: async () => {
|
|
49
|
+
const userMessages = new Map();
|
|
50
|
+
const assistantBuffers = new Map();
|
|
51
|
+
|
|
52
|
+
return {
|
|
53
|
+
"chat.message": async ({ sessionID }, { parts }) => {
|
|
54
|
+
const text = partsToText(parts);
|
|
55
|
+
if (text) userMessages.set(sessionID, text);
|
|
56
|
+
assistantBuffers.set(sessionID, "");
|
|
57
|
+
spawnHmem(["hook-startup"], JSON.stringify({ session_id: sessionID }));
|
|
58
|
+
},
|
|
59
|
+
|
|
60
|
+
event: async ({ event }) => {
|
|
61
|
+
if (!event || typeof event !== "object") return;
|
|
62
|
+
|
|
63
|
+
if (event.type === "message.part.updated") {
|
|
64
|
+
const part = event.properties?.part;
|
|
65
|
+
if (!part || part.type !== "text" || typeof part.text !== "string") return;
|
|
66
|
+
const sid = part.sessionID;
|
|
67
|
+
if (!sid) return;
|
|
68
|
+
assistantBuffers.set(sid, part.text);
|
|
69
|
+
return;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
if (event.type === "session.idle") {
|
|
73
|
+
const sid = event.properties?.sessionID;
|
|
74
|
+
if (!sid) return;
|
|
75
|
+
const userMessage = userMessages.get(sid);
|
|
76
|
+
const assistantMessage = assistantBuffers.get(sid);
|
|
77
|
+
if (!userMessage || !assistantMessage) return;
|
|
78
|
+
userMessages.delete(sid);
|
|
79
|
+
assistantBuffers.delete(sid);
|
|
80
|
+
spawnHmem(["log-exchange"], JSON.stringify({
|
|
81
|
+
session_id: sid,
|
|
82
|
+
last_user_message: userMessage,
|
|
83
|
+
last_assistant_message: assistantMessage,
|
|
84
|
+
}));
|
|
85
|
+
spawnHmem(["checkpoint"], JSON.stringify({ session_id: sid }));
|
|
86
|
+
}
|
|
87
|
+
},
|
|
88
|
+
|
|
89
|
+
"experimental.session.compacting": async ({ sessionID }, output) => {
|
|
90
|
+
try {
|
|
91
|
+
const result = spawnSync("hmem", ["context-inject"], {
|
|
92
|
+
input: JSON.stringify({ session_id: sessionID }),
|
|
93
|
+
encoding: "utf8",
|
|
94
|
+
timeout: 5000,
|
|
95
|
+
env: process.env,
|
|
96
|
+
});
|
|
97
|
+
const text = (result.stdout || "").trim();
|
|
98
|
+
if (text) output.context.push(text);
|
|
99
|
+
} catch {
|
|
100
|
+
// hmem not available — skip
|
|
101
|
+
}
|
|
102
|
+
},
|
|
103
|
+
};
|
|
104
|
+
},
|
|
105
|
+
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "hmem-mcp",
|
|
3
|
-
"version": "6.3.
|
|
3
|
+
"version": "6.3.2",
|
|
4
4
|
"description": "Humanlike memory for AI agents — MCP server with 5-level lazy-loaded SQLite memory",
|
|
5
5
|
"author": "Bumblebiber",
|
|
6
6
|
"license": "MIT",
|
|
@@ -32,6 +32,7 @@
|
|
|
32
32
|
"dist",
|
|
33
33
|
"skills",
|
|
34
34
|
"scripts",
|
|
35
|
+
"opencode-plugin",
|
|
35
36
|
"prebuilds",
|
|
36
37
|
"server.json",
|
|
37
38
|
"README.md",
|
|
@@ -183,3 +183,80 @@ npx hmem-sync connect
|
|
|
183
183
|
| 401 Token verification failed | Passphrase has special chars — set `HMEM_SYNC_PASSPHRASE` in .mcp.json env |
|
|
184
184
|
| 0 entries after pull | `HMEM_PATH` filename must match between devices |
|
|
185
185
|
| Update | `npm update -g hmem-sync` (always global, never inside a project) |
|
|
186
|
+
|
|
187
|
+
## Hook configuration on Windows (REQUIRED)
|
|
188
|
+
|
|
189
|
+
On Windows, hook execution is fragile out of the box. Two issues bite every new user:
|
|
190
|
+
|
|
191
|
+
**1. Git Bash routing** — On systems with Git for Windows installed, Claude Code may route hook commands through Git Bash (`bash.exe`). Its MSYS2 runtime crashes transiently with `add_item ("\??\C:\Program Files\Git", "/", ...) failed, errno 1` during cygheap init, killing the hook **before the command is even parsed**. Symptom: `UserPromptSubmit hook error` or `Stop hook error` with a bash.exe stacktrace.
|
|
192
|
+
|
|
193
|
+
**2. Unix inline env-var syntax** — Commands like `HMEM_PATH=C:/... node ...` work in bash but break in cmd.exe and PowerShell. Symptom: `"HMEM_PATH" is not recognized as a command`.
|
|
194
|
+
|
|
195
|
+
**The fix for Windows users (apply to every hook + statusLine):**
|
|
196
|
+
|
|
197
|
+
```json
|
|
198
|
+
{
|
|
199
|
+
"env": {
|
|
200
|
+
"HMEM_PATH": "C:/Users/<you>/.hmem/Agents/<AGENT>/<AGENT>.hmem"
|
|
201
|
+
},
|
|
202
|
+
"hooks": {
|
|
203
|
+
"UserPromptSubmit": [
|
|
204
|
+
{
|
|
205
|
+
"matcher": "",
|
|
206
|
+
"hooks": [
|
|
207
|
+
{
|
|
208
|
+
"type": "command",
|
|
209
|
+
"command": "node C:/Users/<you>/AppData/Roaming/npm/node_modules/hmem-mcp/dist/cli.js hook-startup",
|
|
210
|
+
"shell": "powershell"
|
|
211
|
+
}
|
|
212
|
+
]
|
|
213
|
+
}
|
|
214
|
+
],
|
|
215
|
+
"Stop": [
|
|
216
|
+
{
|
|
217
|
+
"matcher": "",
|
|
218
|
+
"hooks": [
|
|
219
|
+
{
|
|
220
|
+
"type": "command",
|
|
221
|
+
"command": "node C:/Users/<you>/AppData/Roaming/npm/node_modules/hmem-mcp/dist/cli.js log-exchange",
|
|
222
|
+
"shell": "powershell"
|
|
223
|
+
}
|
|
224
|
+
]
|
|
225
|
+
}
|
|
226
|
+
],
|
|
227
|
+
"SessionStart": [
|
|
228
|
+
{
|
|
229
|
+
"matcher": "clear",
|
|
230
|
+
"hooks": [
|
|
231
|
+
{
|
|
232
|
+
"type": "command",
|
|
233
|
+
"command": "node C:/Users/<you>/AppData/Roaming/npm/node_modules/hmem-mcp/dist/cli.js context-inject",
|
|
234
|
+
"shell": "powershell"
|
|
235
|
+
}
|
|
236
|
+
]
|
|
237
|
+
}
|
|
238
|
+
]
|
|
239
|
+
},
|
|
240
|
+
"statusLine": {
|
|
241
|
+
"type": "command",
|
|
242
|
+
"command": "node C:/Users/<you>/AppData/Roaming/npm/node_modules/hmem-mcp/dist/cli.js statusline",
|
|
243
|
+
"shell": "powershell"
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
```
|
|
247
|
+
|
|
248
|
+
Two things to notice:
|
|
249
|
+
- `"shell": "powershell"` on **every** hook command and on statusLine — forces native PowerShell, bypasses Git Bash entirely.
|
|
250
|
+
- `HMEM_PATH` lives in the top-level `env` block, **not** inline in the command. Claude Code inherits the env block to every hook subprocess, regardless of shell.
|
|
251
|
+
|
|
252
|
+
**Never use inline env-var syntax in hook commands on Windows.** `VAR=value command` is bash-only syntax and will silently break under cmd.exe or PowerShell.
|
|
253
|
+
|
|
254
|
+
**Troubleshooting matrix:**
|
|
255
|
+
|
|
256
|
+
| Symptom | Cause | Fix |
|
|
257
|
+
|---------|-------|-----|
|
|
258
|
+
| `UserPromptSubmit hook error` (no stacktrace) | Inline `VAR=value` in command + cmd.exe parses it as a command name | Move env vars to `env` block, remove inline prefix |
|
|
259
|
+
| `bash.exe: *** fatal error - add_item ... errno 1` | Git Bash MSYS2 runtime crashing at startup | Add `"shell": "powershell"` to every hook command |
|
|
260
|
+
| Hooks silently do nothing (no errors) | Wrong shell interpreting the command, or project not active for session logging | Verify `"shell": "powershell"`, call `load_project(id="P00XX")` every session |
|
|
261
|
+
|
|
262
|
+
**Note on `load_project` per-session:** The `active` flag on a P-entry persists in the database, but the "currently active project for session logging" is a per-session attribute. After every Claude Code restart, the agent must call `load_project(id="P00XX")` again, or exchanges will be logged to O0000 (no-project fallback) instead of the project's O-entry. Consider adding this to your project briefing or session-start routine.
|
|
@@ -1,206 +1,213 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: hmem-curate
|
|
3
|
-
description:
|
|
4
|
-
Memory curation workflow. Use when asked to curate, audit, or clean up agent memories.
|
|
5
|
-
Processes one agent at a time — read, fix, mark audited, summarize, terminate.
|
|
6
|
-
Requires role: ceo.
|
|
3
|
+
description: Curate hmem memory — your own or a foreign .hmem file. Systematically review entries, mark obsolete/irrelevant/favorite, fix titles, consolidate duplicates. Use when asked to "aufräumen", "clean up memory", "curate", "Speicher bereinigen", "tidy up", or when memory_health() shows issues.
|
|
7
4
|
---
|
|
8
5
|
|
|
9
|
-
#
|
|
6
|
+
# hmem Curation
|
|
10
7
|
|
|
11
|
-
|
|
8
|
+
Curate hmem memory — mark obsolete/irrelevant/favorite, fix titles, consolidate duplicates, fix broken links.
|
|
9
|
+
|
|
10
|
+
**Two modes:**
|
|
11
|
+
- **Self-curation** (default): you curate your own memory. No extra params.
|
|
12
|
+
- **Foreign-file curation**: pass `hmem_path=/absolute/path/to/file.hmem` to `read_memory`, `update_memory`, `memory_health`, `find_related`. Sync and session cache are disabled. All updates land in that file.
|
|
12
13
|
|
|
13
14
|
---
|
|
14
15
|
|
|
15
|
-
## Step
|
|
16
|
+
## Step 0: Health Check First
|
|
16
17
|
|
|
17
18
|
```
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
19
|
+
memory_health() # own store
|
|
20
|
+
memory_health(hmem_path="...") # foreign file
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
Shows:
|
|
24
|
+
- **Broken links** — entries with refs to deleted IDs
|
|
25
|
+
- **Orphaned entries** — roots with no sub-nodes (likely draft stubs)
|
|
26
|
+
- **Stale favorites/pinned** — not accessed in >60 days (demote or verify)
|
|
27
|
+
- **Broken obsolete chains** — `[✓ID]` pointing to deleted entries
|
|
21
28
|
|
|
22
|
-
|
|
23
|
-
|
|
29
|
+
Useful before starting:
|
|
30
|
+
```
|
|
31
|
+
memory_stats()
|
|
32
|
+
read_memory(stale_days=60) # stale entries in own store
|
|
33
|
+
read_memory(stale_days=60, hmem_path="...") # same, foreign file
|
|
34
|
+
```
|
|
24
35
|
|
|
25
|
-
|
|
36
|
+
---
|
|
26
37
|
|
|
27
|
-
|
|
38
|
+
## Workflow: Prefix by Prefix
|
|
28
39
|
|
|
29
|
-
|
|
30
|
-
"- **AGENTNAME**: N entries — [OK | fixed L0003 | marked E0002 obsolete (dup) | consolidated P0004+P0007→P0004]"
|
|
40
|
+
Work one prefix at a time. Load all entries of a prefix with full depth:
|
|
31
41
|
|
|
32
|
-
6. Terminate.
|
|
33
42
|
```
|
|
43
|
+
read_memory(prefix="P", show_all=true)
|
|
44
|
+
read_memory(prefix="P", show_all=true, hmem_path="...")
|
|
45
|
+
```
|
|
46
|
+
|
|
47
|
+
`show_all=true` bypasses the bulk-read algorithm and session cache — every entry is expanded with L2+L3 children visible. Review the output directly.
|
|
48
|
+
|
|
49
|
+
**Order:** Start with the prefix with the most entries (usually P), then L, E, D, etc.
|
|
50
|
+
|
|
51
|
+
If context overflows mid-prefix, continue with the remaining entries — memory survives compression.
|
|
34
52
|
|
|
35
53
|
---
|
|
36
54
|
|
|
37
|
-
##
|
|
55
|
+
## For Each Entry: Decide and Act
|
|
38
56
|
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
|
43
|
-
|
|
|
44
|
-
|
|
|
45
|
-
|
|
|
57
|
+
| Decision | Action |
|
|
58
|
+
|----------|--------|
|
|
59
|
+
| Still valid and useful | Skip |
|
|
60
|
+
| Important reference (every session) | `update_memory(id="X", content="...", favorite=true)` |
|
|
61
|
+
| Outdated — a better entry exists | Mark obsolete (see below) |
|
|
62
|
+
| Just noise — not wrong, but irrelevant | `update_memory(id="X", content="...", irrelevant=true)` |
|
|
63
|
+
| Title vague or misleading | `update_memory(id="X", content="Better wording")` |
|
|
64
|
+
| Sub-node has valuable reference info | `update_memory(id="X.N", content="...", favorite=true)` |
|
|
46
65
|
|
|
47
|
-
|
|
48
|
-
Memory content lives in `memory_nodes` — not in flat `level_2/3` fields.
|
|
49
|
-
To fix an L2 or deeper node, use the compound ID: `fix_agent_memory(agent_name, "L0003.2", content="corrected text")`.
|
|
50
|
-
To navigate the tree: `read_memory` shows node IDs like `L0003.2`, `L0003.2.1` — use those directly.
|
|
66
|
+
Add `hmem_path="..."` to every call when curating a foreign file.
|
|
51
67
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
68
|
+
---
|
|
69
|
+
|
|
70
|
+
## Marking Obsolete
|
|
71
|
+
|
|
72
|
+
Obsolete requires a correction reference. Three patterns:
|
|
55
73
|
|
|
56
|
-
**
|
|
74
|
+
**A: Replacement exists already**
|
|
75
|
+
```
|
|
76
|
+
update_memory(id="E0023", content="Wrong approach — see [✓E0076]", obsolete=true)
|
|
77
|
+
```
|
|
57
78
|
|
|
79
|
+
**B: No replacement exists yet**
|
|
80
|
+
```
|
|
81
|
+
write_memory(prefix="L", content="Correct approach is XYZ\n\tDetails...") # -> L0090
|
|
82
|
+
update_memory(id="L0042", content="Superseded — see [✓L0090]", obsolete=true)
|
|
58
83
|
```
|
|
59
|
-
# Curator can bypass [✓ID] enforcement:
|
|
60
|
-
fix_agent_memory(agent_name, id, obsolete=true)
|
|
61
84
|
|
|
62
|
-
|
|
63
|
-
|
|
85
|
+
**C: Just stale, no correction needed**
|
|
86
|
+
```
|
|
87
|
+
update_memory(id="T0005", content="...", irrelevant=true)
|
|
64
88
|
```
|
|
65
89
|
|
|
66
|
-
|
|
90
|
+
Foreign file: curator may mark obsolete without `[✓ID]` for stale entries where no correction exists.
|
|
67
91
|
|
|
68
|
-
|
|
69
|
-
1. Read both entries fully (`read_memory(id=X)` for each)
|
|
70
|
-
2. Pick the **keeper** (usually the older/more informative one)
|
|
71
|
-
3. Fix the keeper's L1 if needed: `fix_agent_memory(agent_name, keeper_id, content="Broader title")`
|
|
72
|
-
4. Carry over the best content from the entry to be deleted:
|
|
73
|
-
- Existing nodes with better wording → `fix_agent_memory(agent_name, "KEEPER.2", content="improved text")`
|
|
74
|
-
- Content that only exists in the entry to be deleted → `append_agent_memory(agent_name, keeper_id, content="carried-over detail\n\tsub-detail")`
|
|
75
|
-
5. `fix_agent_memory(agent_name, fragment_id, obsolete=true)` once content is carried over
|
|
92
|
+
---
|
|
76
93
|
|
|
77
|
-
|
|
78
|
-
- Same workflow. Pick oldest as keeper.
|
|
79
|
-
- Goal: one P entry per project, growing over time.
|
|
94
|
+
## Consolidate Duplicates
|
|
80
95
|
|
|
81
|
-
|
|
96
|
+
1. Pick the **keeper** (more complete, usually older)
|
|
97
|
+
2. Copy unique info: `append_memory(id="P0029", content="Carry-over\n\tDetail")`
|
|
98
|
+
3. Mark duplicate obsolete: `update_memory(id="P0031", content="Merged into [✓P0029]", obsolete=true)`
|
|
82
99
|
|
|
83
|
-
|
|
100
|
+
**Fragmented P-entries (same project, multiple entries):** same workflow. One P per project.
|
|
101
|
+
|
|
102
|
+
---
|
|
84
103
|
|
|
85
|
-
|
|
104
|
+
## Links — Cross-References
|
|
105
|
+
|
|
106
|
+
When two entries have a clear causal/contextual relationship (e.g. a P and the L/E entries that resulted from it), add links at **both** so drill-down resolves them:
|
|
86
107
|
|
|
87
108
|
```
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
fix_agent_memory(agent_name, "E0009", links=["P0001"])
|
|
109
|
+
update_memory(id="P0001", content="...", links=["L0023", "E0009"])
|
|
110
|
+
update_memory(id="L0023", content="...", links=["P0001"])
|
|
91
111
|
```
|
|
92
112
|
|
|
93
|
-
|
|
113
|
+
Don't over-link — only where navigation benefits.
|
|
94
114
|
|
|
95
|
-
|
|
115
|
+
---
|
|
96
116
|
|
|
97
|
-
|
|
117
|
+
## Title/Body Quality
|
|
98
118
|
|
|
99
|
-
|
|
119
|
+
Every node has a **title** (short, ~50 chars) and optional **body** (blank line separator). During curation:
|
|
100
120
|
|
|
101
121
|
**Root entries (L1):**
|
|
102
|
-
-
|
|
122
|
+
- Auto-title truncated/meaningless? Rewrite with explicit title + body:
|
|
103
123
|
```
|
|
104
|
-
|
|
124
|
+
update_memory(id="L0042", content="Clear title\n\nDetailed L1 body that was too long for a title")
|
|
105
125
|
```
|
|
106
|
-
- Old entries without body separation still work — the title is auto-extracted from `level_1`. Only rewrite if the auto-title is genuinely bad.
|
|
107
126
|
|
|
108
127
|
**Child nodes (L2+):**
|
|
109
|
-
-
|
|
128
|
+
- Dense content? Split into title + body:
|
|
110
129
|
```
|
|
111
|
-
|
|
130
|
+
update_memory(id="L0003.2", content="Short node title\n\nDetailed explanation")
|
|
112
131
|
```
|
|
113
|
-
- Nodes with short, clear content don't need body separation — leave them as-is.
|
|
114
132
|
|
|
115
|
-
**
|
|
116
|
-
|
|
117
|
-
- Node has >200 chars of content crammed into one line
|
|
118
|
-
- Content is valuable but hard to scan in listings (title = full text)
|
|
133
|
+
**Rewrite when:** auto-title is truncated mid-word, node has >200 chars crammed in one line, content is valuable but unscannable.
|
|
134
|
+
**Don't rewrite when:** title is already clear, or entry has low access count and marginal value.
|
|
119
135
|
|
|
120
|
-
|
|
121
|
-
- Title is already clear and navigable
|
|
122
|
-
- Entry has low access count and marginal value (not worth the effort)
|
|
136
|
+
---
|
|
123
137
|
|
|
124
|
-
|
|
138
|
+
## P-Entry Standard-Schema (R0009)
|
|
125
139
|
|
|
126
|
-
|
|
140
|
+
P-entries follow the standard L2 structure:
|
|
127
141
|
`.1 Overview`, `.2 Codebase`, `.3 Usage`, `.4 Context`, `.5 Deployment`, `.6 Bugs`, `.7 Protocol`, `.8 Open tasks`, `.9 Ideas`
|
|
128
142
|
|
|
129
|
-
|
|
130
|
-
- **Missing
|
|
131
|
-
- **Wrong order:**
|
|
132
|
-
- **Empty
|
|
133
|
-
- **L1 body:**
|
|
134
|
-
|
|
135
|
-
### O-entry curation (v5.1.2+)
|
|
136
|
-
|
|
137
|
-
O-entries may need curation for titles, tags, and summaries:
|
|
143
|
+
Check P-entries during curation:
|
|
144
|
+
- **Missing section:** `append_memory(id="P00XX", content="\tOverview\n\t\tCurrent state: ...")`
|
|
145
|
+
- **Wrong order:** Restructure — order is fixed per R0009
|
|
146
|
+
- **Empty section:** OK to omit, but content must be in the right section if present
|
|
147
|
+
- **L1 body:** One-line project summary: `Name | Status | Stack | Description`
|
|
138
148
|
|
|
139
|
-
|
|
140
|
-
- Old O-entries may have title "unassigned" or generic titles like "hmem-mcp". Fix with `fix_agent_memory(agent_name, id, content="Descriptive session title")`.
|
|
141
|
-
- Good title: "Title/Body Separation design + v5.1.0 release". Bad: "hmem-mcp".
|
|
149
|
+
---
|
|
142
150
|
|
|
143
|
-
|
|
144
|
-
- O-entries should have a `#session` tag and optionally topic tags (e.g. `#npm`, `#release`, `#refactor`).
|
|
145
|
-
- Add missing tags: `fix_agent_memory(agent_name, id, tags=["#session", "#release"])`
|
|
151
|
+
## O-Entries (Session Logs)
|
|
146
152
|
|
|
147
|
-
**
|
|
148
|
-
- O-entries with many exchanges (>10) should have a `[CP]` checkpoint summary. If missing, write one:
|
|
149
|
-
`append_agent_memory(agent_name, "O00XX", content="\t[CP] Factual 3-8 sentence summary of the session")`
|
|
150
|
-
Then tag it: the auto-tagger picks up `[CP]` prefixed nodes on the next checkpoint run.
|
|
153
|
+
O-entries accumulate via the Stop hook. They're excluded from bulk reads by default — **leave them alone**. Focus curation time on L, E, D, P.
|
|
151
154
|
|
|
152
155
|
**Special tagged nodes — do not modify:**
|
|
153
|
-
-
|
|
154
|
-
-
|
|
156
|
+
- `#checkpoint-summary` — auto-generated `[CP]` summaries
|
|
157
|
+
- `#skill-dialog` — skill activation exchanges
|
|
155
158
|
|
|
156
|
-
|
|
159
|
+
**Exception — old O-entries with bad titles/missing tags:**
|
|
160
|
+
- `update_memory(id="O0042", content="Descriptive session title")`
|
|
161
|
+
- `update_memory(id="O0042", content="...", tags=["#session", "#release"])`
|
|
157
162
|
|
|
158
|
-
|
|
163
|
+
---
|
|
159
164
|
|
|
160
|
-
|
|
161
|
-
fix_agent_memory(agent_name, id, obsolete=true)
|
|
162
|
-
```
|
|
165
|
+
## Bulk Operations
|
|
163
166
|
|
|
164
|
-
|
|
167
|
+
For large-scale changes across many entries:
|
|
165
168
|
|
|
166
|
-
|
|
169
|
+
| Tool | Purpose |
|
|
170
|
+
|------|---------|
|
|
171
|
+
| `update_many(updates=[...])` | Batch flag updates across multiple IDs |
|
|
172
|
+
| `tag_bulk(ids=[...], add_tags=[...], remove_tags=[...])` | Add/remove tags across many entries |
|
|
173
|
+
| `tag_rename(old_tag, new_tag)` | Rename a tag globally |
|
|
167
174
|
|
|
168
|
-
|
|
169
|
-
Navigator entries go stale when code moves. Check: does the file/line referenced still exist?
|
|
170
|
-
If stale and the agent hasn't updated it: mark obsolete via `fix_agent_memory(agent_name, id, obsolete=true)`.
|
|
171
|
-
Do NOT fix stale N entries yourself — the agent who wrote them must verify and update.
|
|
175
|
+
(These operate on your own store only — for foreign files, iterate manually with `update_memory(hmem_path=...)`.)
|
|
172
176
|
|
|
173
177
|
---
|
|
174
178
|
|
|
175
|
-
|
|
179
|
+
## Relocate Misplaced Nodes
|
|
176
180
|
|
|
177
|
-
|
|
181
|
+
`move_memory` cuts and re-inserts a sub-node under a new parent, rewriting all IDs + links + `[✓ID]` refs.
|
|
182
|
+
|
|
183
|
+
```
|
|
184
|
+
move_memory(source_id="P0029.15", target_parent_id="L0074")
|
|
185
|
+
move_memory(source_id="P0029.15", target_parent_id="P0029.20")
|
|
186
|
+
```
|
|
187
|
+
|
|
188
|
+
**Constraints:** source must be a sub-node (not root); cannot move into own subtree. Operates on own store only.
|
|
178
189
|
|
|
179
190
|
---
|
|
180
191
|
|
|
181
|
-
##
|
|
192
|
+
## Favorite Audit
|
|
182
193
|
|
|
183
|
-
|
|
194
|
+
- **Too many?** >10% favorites → demote less important: `update_memory(id="X", content="...", favorite=false)`
|
|
195
|
+
- **Missing?** Reference entries (API endpoints, key decisions, patterns) should be favorites.
|
|
196
|
+
- **Sub-node better than root?** Favorite the sub-node instead.
|
|
184
197
|
|
|
185
|
-
|
|
186
|
-
## Project experiences and summaries (5 entries)
|
|
198
|
+
---
|
|
187
199
|
|
|
188
|
-
|
|
189
|
-
2.1 Architecture: Node.js polling daemon with file-based IPC
|
|
190
|
-
2.2 Key decisions: SQLite for hmem, MCP for tool protocol
|
|
191
|
-
[+7 more → P0001]
|
|
192
|
-
Links: L0045, D0003
|
|
200
|
+
## Stale Entries
|
|
193
201
|
|
|
194
|
-
|
|
195
|
-
...
|
|
202
|
+
Entries older than 1 month with `access_count = 0`: mark obsolete.
|
|
196
203
|
|
|
197
|
-
|
|
204
|
+
```
|
|
205
|
+
update_memory(id="L0042", obsolete=true)
|
|
198
206
|
```
|
|
199
207
|
|
|
200
|
-
**
|
|
201
|
-
**Non-expanded entries** show latest child + `[+N more → ID]` hint.
|
|
208
|
+
**Exception:** unique lessons or error patterns with no equivalent — keep even if never accessed.
|
|
202
209
|
|
|
203
|
-
|
|
210
|
+
The V2 algorithm uses time-weighted scoring (`access_count / log2(age_in_days + 2)`) — old+low-access entries naturally sink; old+high-access stay visible.
|
|
204
211
|
|
|
205
212
|
---
|
|
206
213
|
|
|
@@ -208,22 +215,39 @@ Use `read_memory(show_obsolete=true)` to see all obsolete entries.
|
|
|
208
215
|
|
|
209
216
|
| Store | Max entries | Action when over |
|
|
210
217
|
|-------|-------------|-----------------|
|
|
211
|
-
| Personal | 300 | Triage: duplicates → low-access old
|
|
218
|
+
| Personal | 300 | Triage: duplicates → low-access old → generic lessons |
|
|
212
219
|
| Company | 200 | Same |
|
|
213
220
|
|
|
214
|
-
**Triage order (
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
221
|
+
**Triage order:** exact duplicates → stale (access=0, >1 month) → fragmented P entries → borderline.
|
|
222
|
+
|
|
223
|
+
---
|
|
224
|
+
|
|
225
|
+
## Quick Reference
|
|
226
|
+
|
|
227
|
+
| Tool | When |
|
|
228
|
+
|------|------|
|
|
229
|
+
| `memory_health()` | **Start here** — broken links, orphans, stale favorites |
|
|
230
|
+
| `memory_stats()` | Overview before starting |
|
|
231
|
+
| `read_memory(stale_days=60)` | Prime curation targets |
|
|
232
|
+
| `read_memory(prefix="X", show_all=true)` | Load entire prefix |
|
|
233
|
+
| `update_memory(id, content, favorite=true)` | Always-show reference |
|
|
234
|
+
| `update_memory(id, content, irrelevant=true)` | Hide from bulk reads |
|
|
235
|
+
| `update_memory(id, content, obsolete=true)` | Mark wrong (needs [✓ID]) |
|
|
236
|
+
| `append_memory(id, content)` | Merge info into keeper |
|
|
237
|
+
| `move_memory(source_id, target_parent_id)` | Relocate misplaced sub-node |
|
|
238
|
+
| `update_many(updates=[...])` | Batch flag updates |
|
|
239
|
+
| `tag_bulk / tag_rename` | Tag maintenance |
|
|
240
|
+
| `read_memory(show_obsolete=true)` | Review already-obsolete |
|
|
241
|
+
| `find_related(id)` | Discover connections / spot duplicates |
|
|
242
|
+
|
|
243
|
+
All four read/update/health/find tools accept `hmem_path="..."` for foreign-file curation.
|
|
219
244
|
|
|
220
245
|
---
|
|
221
246
|
|
|
222
247
|
## Rules
|
|
223
248
|
|
|
224
249
|
- Never invent or fabricate memories.
|
|
225
|
-
-
|
|
226
|
-
-
|
|
227
|
-
-
|
|
228
|
-
- One
|
|
229
|
-
- Always write to LAST_CURATION.md, even for clean runs ("OK — nothing to fix").
|
|
250
|
+
- Prefer fix/consolidate/mark-obsolete over deletion. Obsolete entries are hidden from bulk reads anyway.
|
|
251
|
+
- **When in doubt, skip.** False obsolete/irrelevant is harder to undo than leaving an entry alone.
|
|
252
|
+
- **Preserve learning value.** E (errors) and L (lessons) about *why* something failed stay valuable even after the bug is fixed — only mark obsolete if the analysis is wrong.
|
|
253
|
+
- **One prefix per batch.** Don't try all 200+ entries at once.
|
|
@@ -136,7 +136,7 @@ The migration tags old O-entries as `#legacy`. These still use the old flat form
|
|
|
136
136
|
|
|
137
137
|
1. **Keep for now** — they don't hurt anything, just take space
|
|
138
138
|
2. **Mark irrelevant** — `update_memory(id="O0042", irrelevant=true)` for entries you don't need
|
|
139
|
-
3. **Delete later** — run `/hmem-
|
|
139
|
+
3. **Delete later** — run `/hmem-curate` to review and clean up `#legacy` entries
|
|
140
140
|
4. **Auto-purge** — irrelevant entries older than 30 days are automatically deleted
|
|
141
141
|
|
|
142
142
|
Recommendation: keep them for a week to make sure everything works, then curate.
|
|
@@ -382,7 +382,7 @@ After any `read_memory()` call, scan for:
|
|
|
382
382
|
- Don't curate during time-critical tasks (the user is waiting for a bug fix, not curation)
|
|
383
383
|
- Don't mark entries irrelevant if you're unsure — ask the user first
|
|
384
384
|
|
|
385
|
-
For a thorough deep-clean, use the `/hmem-
|
|
385
|
+
For a thorough deep-clean, use the `/hmem-curate` skill.
|
|
386
386
|
|
|
387
387
|
---
|
|
388
388
|
|
|
@@ -47,8 +47,7 @@ Every code change can affect skill documentation. Check each skill against the c
|
|
|
47
47
|
| **hmem-read** | read_memory output format, load_project display, O-entry format | Changes to `formatBulkRead`, `formatRecentOEntries`, `load_project` rendering |
|
|
48
48
|
| **hmem-config** | New config parameters, changed defaults, removed options | Changes to `HmemConfig` interface, `DEFAULT_CONFIG`, `loadHmemConfig` |
|
|
49
49
|
| **hmem-update** | New migration steps, new post-update checks | Any schema change, new features that need post-update setup |
|
|
50
|
-
| **hmem-curate** | Curation rules, new node types, new tags | New tagged node types (#checkpoint-summary, #skill-dialog), schema changes |
|
|
51
|
-
| **hmem-self-curate** | Same as curate but for agent self-curation | Same triggers as hmem-curate |
|
|
50
|
+
| **hmem-curate** | Curation rules (self + foreign-file), hmem_path param, new node types, new tags | New tagged node types (#checkpoint-summary, #skill-dialog), schema changes, hmem_path-capable tool changes |
|
|
52
51
|
| **hmem-new-project** | P-entry schema (R0009), write_memory format | Changes to P-entry structure or write format |
|
|
53
52
|
| **hmem-setup** | Hook scripts, init flow, MCP config format | Changes to hooks, CLI commands, environment variables |
|
|
54
53
|
| **hmem-wipe** | Checkpoint references, context threshold | Changes to checkpointMode, contextTokenThreshold |
|
|
@@ -131,7 +130,7 @@ git push
|
|
|
131
130
|
| Code area | Skills to check |
|
|
132
131
|
|-----------|----------------|
|
|
133
132
|
| hmem-store.ts (write/read) | hmem-write, hmem-read, hmem-curate |
|
|
134
|
-
| hmem-store.ts (O-entries) | hmem-read, hmem-
|
|
133
|
+
| hmem-store.ts (O-entries) | hmem-read, hmem-curate |
|
|
135
134
|
| hmem-config.ts | hmem-config, hmem-update, hmem-setup |
|
|
136
135
|
| mcp-server.ts (tools) | hmem-write, hmem-read (tool params) |
|
|
137
136
|
| mcp-server.ts (load_project) | hmem-read, hmem-new-project |
|