hmem-mcp 2.2.0 → 2.4.0
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 +83 -50
- package/dist/hmem-config.d.ts +9 -2
- package/dist/hmem-config.js +19 -0
- package/dist/hmem-config.js.map +1 -1
- package/dist/hmem-store.d.ts +88 -8
- package/dist/hmem-store.js +656 -52
- package/dist/hmem-store.js.map +1 -1
- package/dist/mcp-server.js +187 -50
- package/dist/mcp-server.js.map +1 -1
- package/dist/session-cache.d.ts +20 -40
- package/dist/session-cache.js +42 -47
- package/dist/session-cache.js.map +1 -1
- package/package.json +1 -1
- package/skills/hmem-read/SKILL.md +13 -12
package/README.md
CHANGED
|
@@ -2,8 +2,7 @@
|
|
|
2
2
|
|
|
3
3
|
> AI agents forget everything when a session ends. hmem changes that.
|
|
4
4
|
|
|
5
|
-
>
|
|
6
|
-
> may still change. Feedback and bug reports welcome. Also the parameters I chose need to be tested and tweaked.
|
|
5
|
+
> hmem is actively used in production. APIs are stable since v2.0. Feedback and bug reports welcome.
|
|
7
6
|
|
|
8
7
|
**hmem** is a Model Context Protocol (MCP) server that gives AI agents persistent, humanlike memory — modeled after how human memory actually works.
|
|
9
8
|
|
|
@@ -71,12 +70,13 @@ Each node gets a compound ID (`L0003.2.1`) so any branch is individually address
|
|
|
71
70
|
Entries can be updated without deleting and recreating them:
|
|
72
71
|
|
|
73
72
|
```
|
|
74
|
-
update_memory(id="L0003", content="Corrected L1 summary")
|
|
75
|
-
update_memory(id="L0003
|
|
73
|
+
update_memory(id="L0003", content="Corrected L1 summary") # update text
|
|
74
|
+
update_memory(id="L0003", favorite=true) # toggle flag only
|
|
75
|
+
update_memory(id="L0003.2", content="Fixed sub-node text") # fix a sub-node
|
|
76
76
|
append_memory(id="L0003", content="New finding\n\tSub-detail")
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
`update_memory` replaces the text of a single node (children preserved). `append_memory` adds new child nodes to an existing entry.
|
|
79
|
+
`update_memory` replaces the text of a single node (children preserved). Content is optional — pass only flags to toggle them without repeating the text. `append_memory` adds new child nodes to an existing entry.
|
|
80
80
|
|
|
81
81
|
### Obsolete Entries
|
|
82
82
|
|
|
@@ -104,13 +104,20 @@ A dedicated curator agent runs periodically to maintain memory health. It detect
|
|
|
104
104
|
|
|
105
105
|
- **Hierarchical retrieval** — lazy loading of detail levels saves tokens
|
|
106
106
|
- **True tree structure** — multiple siblings at the same depth (not just one chain)
|
|
107
|
+
- **Compact output** — child IDs render as `.7` instead of `P0029.7`; dates shown only when differing from parent
|
|
107
108
|
- **Persistent across sessions** — agents remember previous work even after restart
|
|
108
|
-
- **Editable without deletion** — `update_memory` and `append_memory` modify entries in place
|
|
109
|
-
- **
|
|
110
|
-
- **
|
|
111
|
-
- **
|
|
112
|
-
- **
|
|
113
|
-
- **
|
|
109
|
+
- **Editable without deletion** — `update_memory` and `append_memory` modify entries in place; content is optional when toggling flags
|
|
110
|
+
- **Markers** — `[♥]` favorite, `[P]` pinned, `[!]` obsolete, `[-]` irrelevant, `[*]` active, `[s]` secret — on root entries and sub-nodes
|
|
111
|
+
- **Pinned entries** — super-favorites that show all children titles in bulk reads (not just the latest); use for reference entries you need in full every session
|
|
112
|
+
- **Hashtags** — cross-cutting tags (`#hmem`, `#security`) for filtering and discovering related entries across prefixes
|
|
113
|
+
- **Import/Export** — `export_memory` as Markdown or `.hmem` SQLite clone (excluding secrets); `import_memory` with L1 deduplication, sub-node merge, and automatic ID remapping on conflict
|
|
114
|
+
- **Obsolete chain resolution** — mark entries/sub-nodes obsolete with `[✓ID]` reference; `read_memory` auto-follows the chain to the current version
|
|
115
|
+
- **Access-count promotion** — most-accessed entries get expanded automatically (`[★]`); most-referenced sub-nodes shown as "Hot Nodes"
|
|
116
|
+
- **Session cache** — bulk reads suppress already-seen entries with Fibonacci decay; two modes: `discover` (newest-heavy) and `essentials` (importance-heavy)
|
|
117
|
+
- **Active-prefix filtering** — mark entries as `[*]` active to focus bulk reads on what matters now; non-active entries still show as compact titles
|
|
118
|
+
- **Secret entries** — `[s]` entries/nodes excluded from `export_memory`
|
|
119
|
+
- **Titles & compact views** — auto-extracted titles; `titles_only` mode for table-of-contents view
|
|
120
|
+
- **Effective-date sorting** — entries with recent appends surface to the top
|
|
114
121
|
- **Per-agent memory** — each agent has its own `.hmem` file (SQLite)
|
|
115
122
|
- **Skill-file driven** — agents are instructed via skill files, no hardcoded logic
|
|
116
123
|
- **MCP-native** — works with Claude Code, Gemini CLI, OpenCode, and any MCP-compatible tool
|
|
@@ -134,7 +141,7 @@ That's it. The interactive installer will:
|
|
|
134
141
|
|
|
135
142
|
After the installer finishes, restart your AI tool and call `read_memory()` to verify.
|
|
136
143
|
|
|
137
|
-
> **Example memory:** The installer includes `hmem_developer.hmem` — a real `.hmem` database with 67 entries and 287 nodes from actual hmem development. It contains lessons learned, architecture decisions, error fixes, and project milestones — a great way to see how hmem works in practice before writing your own entries. You can explore it immediately with `read_memory()` after install, or browse it in the TUI viewer with `python3 hmem.py path/to/memory.hmem`.
|
|
144
|
+
> **Example memory:** The installer includes `hmem_developer.hmem` — a real `.hmem` database with 67 entries and 287 nodes from actual hmem development. It contains lessons learned, architecture decisions, error fixes, and project milestones — a great way to see how hmem works in practice before writing your own entries. You can explore it immediately with `read_memory()` after install, or browse it in the TUI viewer with `python3 hmem-reader.py path/to/memory.hmem`.
|
|
138
145
|
|
|
139
146
|
> **Don't forget the skill files!** The MCP server provides the tools (read_memory, write_memory, etc.), but the slash commands (`/hmem-save`, `/hmem-read`) require skill files to be copied to your tool's skills directory. See the [Skill Files](#skill-files) section below — it's a one-time copy-paste.
|
|
140
147
|
>
|
|
@@ -271,10 +278,13 @@ done
|
|
|
271
278
|
|
|
272
279
|
| Tool | Description |
|
|
273
280
|
|------|-------------|
|
|
274
|
-
| `read_memory` | Read
|
|
281
|
+
| `read_memory` | Read memories — L1 summaries, drill by ID, filter by prefix, search by time |
|
|
275
282
|
| `write_memory` | Save new memory entries with tab-indented hierarchy |
|
|
276
|
-
| `update_memory` | Update
|
|
277
|
-
| `append_memory` | Append new child nodes to an existing entry
|
|
283
|
+
| `update_memory` | Update text and/or flags of an entry or sub-node (content optional) |
|
|
284
|
+
| `append_memory` | Append new child nodes to an existing entry or sub-node |
|
|
285
|
+
| `export_memory` | Export non-secret entries as Markdown text or `.hmem` SQLite file |
|
|
286
|
+
| `import_memory` | Import entries from a `.hmem` file with deduplication and ID remapping |
|
|
287
|
+
| `reset_memory_cache` | Clear session cache so all entries are treated as unseen |
|
|
278
288
|
| `search_memory` | Full-text search across all agent `.hmem` databases |
|
|
279
289
|
|
|
280
290
|
### Curator Tools (role: ceo)
|
|
@@ -342,11 +352,13 @@ Place an optional `hmem.config.json` in your `HMEM_PROJECT_DIR` to tune behavior
|
|
|
342
352
|
"maxL1Chars": 120,
|
|
343
353
|
"maxLnChars": 50000,
|
|
344
354
|
"maxDepth": 5,
|
|
355
|
+
"maxTitleChars": 50,
|
|
345
356
|
"accessCountTopN": 5,
|
|
346
|
-
"
|
|
347
|
-
|
|
348
|
-
|
|
349
|
-
|
|
357
|
+
"bulkReadV2": {
|
|
358
|
+
"topNewestCount": 5,
|
|
359
|
+
"topAccessCount": 3,
|
|
360
|
+
"topObsoleteCount": 3
|
|
361
|
+
},
|
|
350
362
|
"prefixes": {
|
|
351
363
|
"R": "Research"
|
|
352
364
|
}
|
|
@@ -375,13 +387,41 @@ To add your own, add entries to the `"prefixes"` key in `hmem.config.json`. Cust
|
|
|
375
387
|
Any entry can be marked as a **favorite** — regardless of its prefix category. Favorites always appear with their L2 detail in bulk reads, marked with `[♥]`.
|
|
376
388
|
|
|
377
389
|
```
|
|
378
|
-
write_memory(prefix="D", content="...", favorite=true)
|
|
379
|
-
update_memory(id="D0010",
|
|
380
|
-
update_memory(id="D0010",
|
|
390
|
+
write_memory(prefix="D", content="...", favorite=true) # set at creation
|
|
391
|
+
update_memory(id="D0010", favorite=true) # set on existing entry
|
|
392
|
+
update_memory(id="D0010", favorite=false) # clear the flag
|
|
381
393
|
```
|
|
382
394
|
|
|
383
395
|
Use favorites for reference info you need to see every session — key decisions, API endpoints, frequently consulted patterns. Use sparingly: if everything is a favorite, nothing is.
|
|
384
396
|
|
|
397
|
+
### Pinned entries
|
|
398
|
+
|
|
399
|
+
Pinned entries are "super-favorites" — they show **all** children titles in bulk reads, not just the latest one. While favorites show the newest child + `[+N more →]`, pinned entries give you the full table of contents at a glance.
|
|
400
|
+
|
|
401
|
+
```
|
|
402
|
+
write_memory(prefix="S", content="...", pinned=true) # set at creation
|
|
403
|
+
update_memory(id="S0005", pinned=true) # set on existing entry
|
|
404
|
+
```
|
|
405
|
+
|
|
406
|
+
| Display | Normal | Favorite `[♥]` | Pinned `[P]` | `expand=true` |
|
|
407
|
+
|---|---|---|---|---|
|
|
408
|
+
| Children shown | Latest only | All titles | All titles | All with full content |
|
|
409
|
+
| `[+N more →]` hint | Yes | No | No | No |
|
|
410
|
+
|
|
411
|
+
Use pinned for entries with many structured sub-entries (handbooks, reference lists, project summaries) where you always want to see the full outline.
|
|
412
|
+
|
|
413
|
+
### Hashtags
|
|
414
|
+
|
|
415
|
+
Tag entries for cross-cutting search across prefix categories:
|
|
416
|
+
|
|
417
|
+
```
|
|
418
|
+
write_memory(prefix="L", content="...", tags=["#security", "#hmem"])
|
|
419
|
+
read_memory(tag="#security") # filter bulk reads by tag
|
|
420
|
+
read_memory(id="L0042") # shows related entries (2+ shared tags)
|
|
421
|
+
```
|
|
422
|
+
|
|
423
|
+
Tags are lowercase, must start with `#`, max 10 per entry. They work on root entries and sub-nodes.
|
|
424
|
+
|
|
385
425
|
### Access-count auto-promotion (`accessCountTopN`)
|
|
386
426
|
|
|
387
427
|
The top-N most-accessed entries are automatically promoted to L2 depth in bulk reads, marked with `[★]`. This creates "organic favorites" — entries that proved important in practice rise to the surface automatically.
|
|
@@ -405,11 +445,16 @@ This means a 1-day-old entry with 5 accesses (score 3.16) outranks a 1-year-old
|
|
|
405
445
|
| **favorite flag** | Entries you know are important from day 1 — even with zero access history |
|
|
406
446
|
| **accessCountTopN** | Entries that proved important over time — emerges from actual usage |
|
|
407
447
|
|
|
408
|
-
###
|
|
448
|
+
### Bulk reads (V2 algorithm)
|
|
449
|
+
|
|
450
|
+
`read_memory()` groups entries by prefix category. Each category expands a limited number of entries (newest + most-accessed + favorites) with their L2 children; the rest show only the L1 title. Two modes:
|
|
409
451
|
|
|
410
|
-
|
|
452
|
+
- **`discover`** (default on first read): newest-heavy — good for getting an overview after session start.
|
|
453
|
+
- **`essentials`** (auto-selected after context compression): importance-heavy — more favorites + most-accessed, fewer newest.
|
|
411
454
|
|
|
412
|
-
|
|
455
|
+
A **session cache** tracks which entries were already shown. Subsequent bulk reads suppress seen entries with Fibonacci decay `[5,3,2,1,0]`, keeping output fresh without repetition. Use `reset_memory_cache` to clear the cache.
|
|
456
|
+
|
|
457
|
+
To see all children of an entry, use `read_memory(id="P0005")`. For a deep dive with full content, use `read_memory(id="P0005", expand=true)`. For a compact table of contents, use `read_memory(titles_only=true)`.
|
|
413
458
|
|
|
414
459
|
### Effective-date sorting
|
|
415
460
|
|
|
@@ -433,43 +478,31 @@ With 5 depth levels this yields: `[120, 12780, 25440, 38120, 50000]`
|
|
|
433
478
|
{ "maxCharsPerLevel": [120, 2500, 10000, 25000, 50000] }
|
|
434
479
|
```
|
|
435
480
|
|
|
436
|
-
###
|
|
437
|
-
|
|
438
|
-
Controls how deep children are inlined for the most recent entries in a default `read_memory()` call. Each tier is `{ count, depth }`: the *count* most recent entries get children inlined up to *depth*.
|
|
481
|
+
### Bulk read tuning (`bulkReadV2`)
|
|
439
482
|
|
|
440
|
-
|
|
483
|
+
Controls how many entries are expanded in each category during a bulk read:
|
|
441
484
|
|
|
442
485
|
```json
|
|
443
|
-
"
|
|
444
|
-
|
|
445
|
-
|
|
446
|
-
|
|
486
|
+
"bulkReadV2": {
|
|
487
|
+
"topNewestCount": 5, // expand the 5 newest entries per prefix
|
|
488
|
+
"topAccessCount": 3, // expand the 3 most-accessed per prefix
|
|
489
|
+
"topObsoleteCount": 3 // show up to 3 obsolete entries (by access count)
|
|
490
|
+
}
|
|
447
491
|
```
|
|
448
492
|
|
|
449
|
-
|
|
450
|
-
| Entry position | Depth inlined |
|
|
451
|
-
|---|---|
|
|
452
|
-
| 0–2 (most recent) | L1 + L2 + L3 |
|
|
453
|
-
| 3–9 | L1 + L2 |
|
|
454
|
-
| 10+ | L1 only |
|
|
455
|
-
|
|
456
|
-
This mirrors how human memory works: you remember today's events in full detail, last week's in outline, older ones only as headlines.
|
|
457
|
-
|
|
458
|
-
Set to `[]` to disable recency inlining (L1-only for all entries).
|
|
459
|
-
|
|
460
|
-
**Backward compat:** The old `"recentChildrenCount": N` key is still accepted and treated as `[{ "count": N, "depth": 2 }]`.
|
|
493
|
+
Favorites are always expanded regardless of these limits. Entries expanded by one slot (e.g. newest) don't count against another (e.g. access).
|
|
461
494
|
|
|
462
495
|
---
|
|
463
496
|
|
|
464
|
-
## TUI Viewer (hmem.py)
|
|
497
|
+
## TUI Viewer (hmem-reader.py)
|
|
465
498
|
|
|
466
499
|
A terminal-based interactive viewer for browsing `.hmem` memory files. Built with [Textual](https://textual.textualize.io/).
|
|
467
500
|
|
|
468
501
|
```bash
|
|
469
502
|
pip install textual # one-time dependency
|
|
470
|
-
python3 hmem.py # agent selection screen (scans Agents/ directory)
|
|
471
|
-
python3 hmem.py THOR # open a specific agent's memory
|
|
472
|
-
python3 hmem.py ~/path/to/file.hmem # open any .hmem file directly
|
|
503
|
+
python3 hmem-reader.py # agent selection screen (scans Agents/ directory)
|
|
504
|
+
python3 hmem-reader.py THOR # open a specific agent's memory
|
|
505
|
+
python3 hmem-reader.py ~/path/to/file.hmem # open any .hmem file directly
|
|
473
506
|
```
|
|
474
507
|
|
|
475
508
|
**Keys:**
|
|
@@ -480,7 +513,7 @@ python3 hmem.py ~/path/to/file.hmem # open any .hmem file directly
|
|
|
480
513
|
| `q` | Quit |
|
|
481
514
|
| `Escape` | Back to agent list |
|
|
482
515
|
|
|
483
|
-
The V2 view mirrors the MCP server's bulk-read algorithm — including time-weighted access scoring, per-prefix selection, and
|
|
516
|
+
The V2 view mirrors the MCP server's bulk-read algorithm — including time-weighted access scoring, per-prefix selection, active-prefix filtering, and all markers (`[♥]`, `[!]`, `[*]`, `[s]`, `[-]`) — so you can see exactly what an agent sees at session start.
|
|
484
517
|
|
|
485
518
|
---
|
|
486
519
|
|
package/dist/hmem-config.d.ts
CHANGED
|
@@ -58,12 +58,19 @@ export interface HmemConfig {
|
|
|
58
58
|
* Controls how many entries receive expanded treatment in default reads.
|
|
59
59
|
*/
|
|
60
60
|
bulkReadV2: {
|
|
61
|
-
/** Number of top-accessed entries to expand (default: 3) */
|
|
61
|
+
/** Number of top-accessed entries to expand — legacy fixed fallback (default: 3) */
|
|
62
62
|
topAccessCount: number;
|
|
63
|
-
/** Number of newest entries to expand (default: 5) */
|
|
63
|
+
/** Number of newest entries to expand — legacy fixed fallback (default: 5) */
|
|
64
64
|
topNewestCount: number;
|
|
65
65
|
/** Number of obsolete entries to keep visible (default: 3) */
|
|
66
66
|
topObsoleteCount: number;
|
|
67
|
+
/** Percentage-based selection (overrides fixed counts when set) */
|
|
68
|
+
newestPercent?: number;
|
|
69
|
+
newestMin?: number;
|
|
70
|
+
newestMax?: number;
|
|
71
|
+
accessPercent?: number;
|
|
72
|
+
accessMin?: number;
|
|
73
|
+
accessMax?: number;
|
|
67
74
|
};
|
|
68
75
|
}
|
|
69
76
|
export declare const DEFAULT_PREFIXES: Record<string, string>;
|
package/dist/hmem-config.js
CHANGED
|
@@ -41,6 +41,12 @@ export const DEFAULT_CONFIG = {
|
|
|
41
41
|
topAccessCount: 3,
|
|
42
42
|
topNewestCount: 5,
|
|
43
43
|
topObsoleteCount: 3,
|
|
44
|
+
newestPercent: 20,
|
|
45
|
+
newestMin: 5,
|
|
46
|
+
newestMax: 15,
|
|
47
|
+
accessPercent: 10,
|
|
48
|
+
accessMin: 3,
|
|
49
|
+
accessMax: 8,
|
|
44
50
|
},
|
|
45
51
|
};
|
|
46
52
|
/**
|
|
@@ -115,6 +121,19 @@ export function loadHmemConfig(projectDir) {
|
|
|
115
121
|
cfg.bulkReadV2.topNewestCount = v2.topNewestCount;
|
|
116
122
|
if (typeof v2.topObsoleteCount === "number" && v2.topObsoleteCount >= 0)
|
|
117
123
|
cfg.bulkReadV2.topObsoleteCount = v2.topObsoleteCount;
|
|
124
|
+
// Percentage-based selection
|
|
125
|
+
if (typeof v2.newestPercent === "number" && v2.newestPercent > 0)
|
|
126
|
+
cfg.bulkReadV2.newestPercent = v2.newestPercent;
|
|
127
|
+
if (typeof v2.newestMin === "number" && v2.newestMin >= 0)
|
|
128
|
+
cfg.bulkReadV2.newestMin = v2.newestMin;
|
|
129
|
+
if (typeof v2.newestMax === "number" && v2.newestMax > 0)
|
|
130
|
+
cfg.bulkReadV2.newestMax = v2.newestMax;
|
|
131
|
+
if (typeof v2.accessPercent === "number" && v2.accessPercent > 0)
|
|
132
|
+
cfg.bulkReadV2.accessPercent = v2.accessPercent;
|
|
133
|
+
if (typeof v2.accessMin === "number" && v2.accessMin >= 0)
|
|
134
|
+
cfg.bulkReadV2.accessMin = v2.accessMin;
|
|
135
|
+
if (typeof v2.accessMax === "number" && v2.accessMax > 0)
|
|
136
|
+
cfg.bulkReadV2.accessMax = v2.accessMax;
|
|
118
137
|
}
|
|
119
138
|
// Resolve char limits: explicit array > linear endpoints > default
|
|
120
139
|
if (Array.isArray(raw.maxCharsPerLevel) && raw.maxCharsPerLevel.length >= 1) {
|
package/dist/hmem-config.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"hmem-config.js","sourceRoot":"","sources":["../src/hmem-config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,SAAS,CAAC;AACzB,OAAO,IAAI,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"hmem-config.js","sourceRoot":"","sources":["../src/hmem-config.ts"],"names":[],"mappings":"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"}
|
package/dist/hmem-store.d.ts
CHANGED
|
@@ -75,6 +75,17 @@ export interface MemoryEntry {
|
|
|
75
75
|
hiddenIrrelevantLinks?: number;
|
|
76
76
|
/** If this entry was reached via obsolete chain resolution, the chain of IDs traversed. */
|
|
77
77
|
obsoleteChain?: string[];
|
|
78
|
+
/** Optional hashtags for cross-cutting search, e.g. ["#hmem", "#curation"]. */
|
|
79
|
+
tags?: string[];
|
|
80
|
+
/** Entries sharing 2+ tags with this entry (populated on ID-based reads). */
|
|
81
|
+
relatedEntries?: {
|
|
82
|
+
id: string;
|
|
83
|
+
title: string;
|
|
84
|
+
created_at: string;
|
|
85
|
+
tags: string[];
|
|
86
|
+
}[];
|
|
87
|
+
/** True if the entry is pinned (super-favorite). Pinned entries show full L2 content in bulk reads. */
|
|
88
|
+
pinned?: boolean;
|
|
78
89
|
}
|
|
79
90
|
export interface MemoryNode {
|
|
80
91
|
id: string;
|
|
@@ -89,8 +100,11 @@ export interface MemoryNode {
|
|
|
89
100
|
access_count: number;
|
|
90
101
|
last_accessed: string | null;
|
|
91
102
|
favorite?: boolean;
|
|
103
|
+
irrelevant?: boolean;
|
|
92
104
|
child_count?: number;
|
|
93
105
|
children?: MemoryNode[];
|
|
106
|
+
/** Optional hashtags, e.g. ["#hmem", "#curation"]. */
|
|
107
|
+
tags?: string[];
|
|
94
108
|
}
|
|
95
109
|
export interface ReadOptions {
|
|
96
110
|
id?: string;
|
|
@@ -125,25 +139,44 @@ export interface ReadOptions {
|
|
|
125
139
|
titlesOnly?: boolean;
|
|
126
140
|
/** Expand full tree with complete node content (for deep-dive into a project). depth controls how deep. */
|
|
127
141
|
expand?: boolean;
|
|
128
|
-
/** IDs already delivered in this session —
|
|
129
|
-
|
|
130
|
-
/**
|
|
131
|
-
|
|
132
|
-
/**
|
|
133
|
-
|
|
142
|
+
/** IDs already delivered in this session — shown as title-only in subsequent bulk reads. */
|
|
143
|
+
cachedIds?: Set<string>;
|
|
144
|
+
/** IDs within hidden phase (< 5 min) — completely excluded from output. */
|
|
145
|
+
hiddenIds?: Set<string>;
|
|
146
|
+
/** Slot reduction fraction: 1.0 = full, 0.5 = half percentage, 0.25 = quarter, ... */
|
|
147
|
+
slotFraction?: number;
|
|
134
148
|
/** Bulk read mode: 'discover' (newest-heavy, default) or 'essentials' (importance-heavy). */
|
|
135
149
|
mode?: "discover" | "essentials";
|
|
150
|
+
/** Curation mode: show ALL entries (bypass V2 selection + session cache), depth 3 children, no child V2. */
|
|
151
|
+
showAll?: boolean;
|
|
152
|
+
/** Filter by tag, e.g. "#hmem". Only entries/nodes with this tag are included. */
|
|
153
|
+
tag?: string;
|
|
136
154
|
}
|
|
137
155
|
export interface WriteResult {
|
|
138
156
|
id: string;
|
|
139
157
|
timestamp: string;
|
|
140
158
|
}
|
|
159
|
+
export interface ImportResult {
|
|
160
|
+
inserted: number;
|
|
161
|
+
merged: number;
|
|
162
|
+
nodesInserted: number;
|
|
163
|
+
nodesSkipped: number;
|
|
164
|
+
tagsImported: number;
|
|
165
|
+
remapped: boolean;
|
|
166
|
+
conflicts: number;
|
|
167
|
+
}
|
|
141
168
|
export declare class HmemStore {
|
|
142
169
|
private db;
|
|
143
170
|
private readonly dbPath;
|
|
171
|
+
getDbPath(): string;
|
|
144
172
|
private readonly cfg;
|
|
145
173
|
/** True if integrity_check found errors on open (read-only mode recommended). */
|
|
146
174
|
readonly corrupted: boolean;
|
|
175
|
+
/**
|
|
176
|
+
* Char-limit tolerance: configured limits are the "recommended" target shown in skills/errors.
|
|
177
|
+
* Actual hard reject is at limit * CHAR_LIMIT_TOLERANCE (25% buffer to avoid wasted retries).
|
|
178
|
+
*/
|
|
179
|
+
private static readonly CHAR_LIMIT_TOLERANCE;
|
|
147
180
|
constructor(hmemPath: string, config?: HmemConfig);
|
|
148
181
|
/** Throw if the database is corrupted — prevents silent data loss on write operations. */
|
|
149
182
|
private guardCorrupted;
|
|
@@ -155,7 +188,7 @@ export declare class HmemStore {
|
|
|
155
188
|
* Each indented line → its own memory_nodes row with compound ID
|
|
156
189
|
* Multiple lines at the same indent depth → siblings (new capability)
|
|
157
190
|
*/
|
|
158
|
-
write(prefix: string, content: string, links?: string[], minRole?: AgentRole, favorite?: boolean): WriteResult;
|
|
191
|
+
write(prefix: string, content: string, links?: string[], minRole?: AgentRole, favorite?: boolean, tags?: string[], pinned?: boolean): WriteResult;
|
|
159
192
|
/**
|
|
160
193
|
* Read memories with flexible querying.
|
|
161
194
|
*
|
|
@@ -165,6 +198,12 @@ export declare class HmemStore {
|
|
|
165
198
|
* For bulk queries: returns L1 summaries (depth=1 default).
|
|
166
199
|
*/
|
|
167
200
|
read(opts?: ReadOptions): MemoryEntry[];
|
|
201
|
+
/**
|
|
202
|
+
* Calculate V2 selection slot counts based on the number of relevant entries.
|
|
203
|
+
* Uses percentage-based scaling with min/max caps when configured,
|
|
204
|
+
* falls back to fixed topNewestCount/topAccessCount otherwise.
|
|
205
|
+
*/
|
|
206
|
+
private calcV2Slots;
|
|
168
207
|
/**
|
|
169
208
|
* V2 bulk-read algorithm: per-prefix expansion, smart obsolete filtering,
|
|
170
209
|
* expanded entries with all L2 children + links.
|
|
@@ -179,12 +218,27 @@ export declare class HmemStore {
|
|
|
179
218
|
* Export entire memory to Markdown for git tracking.
|
|
180
219
|
*/
|
|
181
220
|
exportMarkdown(): string;
|
|
221
|
+
/**
|
|
222
|
+
* Export memory to a new .hmem SQLite file.
|
|
223
|
+
* Creates a standalone copy that can be opened with HmemStore or hmem.py.
|
|
224
|
+
*/
|
|
225
|
+
exportPublicToHmem(outputPath: string): {
|
|
226
|
+
entries: number;
|
|
227
|
+
nodes: number;
|
|
228
|
+
tags: number;
|
|
229
|
+
};
|
|
230
|
+
/**
|
|
231
|
+
* Import entries from another .hmem file with L1 deduplication and ID remapping.
|
|
232
|
+
*/
|
|
233
|
+
importFromHmem(sourcePath: string, dryRun?: boolean): ImportResult;
|
|
234
|
+
private _doImport;
|
|
182
235
|
/**
|
|
183
236
|
* Get statistics about the memory store.
|
|
184
237
|
*/
|
|
185
238
|
stats(): {
|
|
186
239
|
total: number;
|
|
187
240
|
byPrefix: Record<string, number>;
|
|
241
|
+
totalChars: number;
|
|
188
242
|
};
|
|
189
243
|
/**
|
|
190
244
|
* Update specific fields of an existing root entry (curator use only).
|
|
@@ -201,7 +255,7 @@ export declare class HmemStore {
|
|
|
201
255
|
* For sub-nodes: updates node content only.
|
|
202
256
|
* Does NOT modify children — use appendChildren to extend the tree.
|
|
203
257
|
*/
|
|
204
|
-
updateNode(id: string, newContent: string, links?: string[], obsolete?: boolean, favorite?: boolean, curatorBypass?: boolean, irrelevant?: boolean): boolean;
|
|
258
|
+
updateNode(id: string, newContent: string, links?: string[], obsolete?: boolean, favorite?: boolean, curatorBypass?: boolean, irrelevant?: boolean, tags?: string[], pinned?: boolean): boolean;
|
|
205
259
|
/**
|
|
206
260
|
* Append new child nodes under an existing entry (root or node).
|
|
207
261
|
* Content is tab-indented relative to the parent:
|
|
@@ -224,6 +278,32 @@ export declare class HmemStore {
|
|
|
224
278
|
*/
|
|
225
279
|
getHeaders(): MemoryEntry[];
|
|
226
280
|
close(): void;
|
|
281
|
+
private static readonly TAG_REGEX;
|
|
282
|
+
private static readonly MAX_TAGS_PER_ENTRY;
|
|
283
|
+
/** Validate and normalize tags: lowercase, must match #word pattern. */
|
|
284
|
+
private validateTags;
|
|
285
|
+
/** Replace all tags on an entry/node. Pass empty array to clear. */
|
|
286
|
+
private setTags;
|
|
287
|
+
/** Get tags for a single entry/node. */
|
|
288
|
+
private fetchTags;
|
|
289
|
+
/** Bulk-fetch tags for multiple IDs at once. */
|
|
290
|
+
private fetchTagsBulk;
|
|
291
|
+
/**
|
|
292
|
+
* Find entries sharing 2+ tags with the given entry.
|
|
293
|
+
* Returns title-only results sorted by number of shared tags (descending).
|
|
294
|
+
*/
|
|
295
|
+
findRelated(entryId: string, tags: string[], limit?: number): {
|
|
296
|
+
id: string;
|
|
297
|
+
title: string;
|
|
298
|
+
created_at: string;
|
|
299
|
+
tags: string[];
|
|
300
|
+
}[];
|
|
301
|
+
/** Bulk-assign tags to entries + their children from a single fetchTagsBulk call. */
|
|
302
|
+
private assignBulkTags;
|
|
303
|
+
/** Recursively collect all node IDs from a tree of MemoryNodes. */
|
|
304
|
+
private collectNodeIds;
|
|
305
|
+
/** Get root IDs that have a specific tag (for bulk-read filtering). */
|
|
306
|
+
private getRootIdsByTag;
|
|
227
307
|
private migrate;
|
|
228
308
|
/**
|
|
229
309
|
* One-time migration: move level_2..level_5 data to memory_nodes tree.
|