pi-hermes-memory 0.7.5 → 0.7.6
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 +2 -0
- package/package.json +1 -1
- package/src/config.ts +3 -0
- package/src/constants.ts +1 -0
- package/src/handlers/auto-consolidate.ts +4 -2
- package/src/index.ts +2 -2
- package/src/store/sqlite-memory-store.ts +42 -4
- package/src/tools/memory-tool.ts +27 -0
- package/src/types.ts +2 -0
package/README.md
CHANGED
|
@@ -363,6 +363,7 @@ Create `~/.pi/agent/hermes-memory-config.json`:
|
|
|
363
363
|
"failureInjectionEnabled": true,
|
|
364
364
|
"failureInjectionMaxAgeDays": 7,
|
|
365
365
|
"failureInjectionMaxEntries": 5,
|
|
366
|
+
"consolidationTimeoutMs": 60000,
|
|
366
367
|
"flushOnCompact": true,
|
|
367
368
|
"flushOnShutdown": true,
|
|
368
369
|
"flushMinTurns": 6,
|
|
@@ -386,6 +387,7 @@ Create `~/.pi/agent/hermes-memory-config.json`:
|
|
|
386
387
|
| `reviewEnabled` | `true` | Enable/disable background learning loop |
|
|
387
388
|
| `memoryOverflowStrategy` | `auto-consolidate` | Behavior when MEMORY.md, USER.md, or project-scoped memory reaches its character limit: `auto-consolidate` runs the existing consolidation flow; `reject` returns an error; `fifo-evict` rotates older entries in file order until the new entry fits |
|
|
388
389
|
| `autoConsolidate` | `true` | Legacy alias for `memoryOverflowStrategy` when `memoryOverflowStrategy` is not set (`true` = `auto-consolidate`, `false` = `reject`) |
|
|
390
|
+
| `consolidationTimeoutMs` | `60000` | Maximum time in milliseconds for auto-consolidation to complete |
|
|
389
391
|
| `correctionDetection` | `true` | Detect user corrections and save immediately |
|
|
390
392
|
| `correctionStrongPatterns` | unset | Optional case-insensitive regex sources replacing strong correction patterns; omitted preserves defaults, invalid entries are ignored |
|
|
391
393
|
| `correctionWeakPatterns` | unset | Optional case-insensitive regex sources replacing weak correction patterns; omitted preserves defaults, invalid entries are ignored |
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "pi-hermes-memory",
|
|
3
|
-
"version": "0.7.
|
|
3
|
+
"version": "0.7.6",
|
|
4
4
|
"description": "🧠 Persistent memory + 🔍 session search + 🛡️ secret scanning for Pi. Token-aware policy-only memory by default, SQLite FTS5 search, auto-consolidation, procedural skills. 368 tests. Ported from Hermes agent.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.ts",
|
package/src/config.ts
CHANGED
|
@@ -12,6 +12,7 @@ import {
|
|
|
12
12
|
DEFAULT_NUDGE_TOOL_CALLS,
|
|
13
13
|
DEFAULT_REVIEW_RECENT_MESSAGES,
|
|
14
14
|
DEFAULT_FLUSH_RECENT_MESSAGES,
|
|
15
|
+
DEFAULT_CONSOLIDATION_TIMEOUT_MS,
|
|
15
16
|
DEFAULT_FAILURE_INJECTION_MAX_AGE_DAYS,
|
|
16
17
|
DEFAULT_FAILURE_INJECTION_MAX_ENTRIES,
|
|
17
18
|
} from "./constants.js";
|
|
@@ -41,6 +42,7 @@ const DEFAULT_CONFIG: MemoryConfig = {
|
|
|
41
42
|
failureInjectionEnabled: true,
|
|
42
43
|
failureInjectionMaxAgeDays: DEFAULT_FAILURE_INJECTION_MAX_AGE_DAYS,
|
|
43
44
|
failureInjectionMaxEntries: DEFAULT_FAILURE_INJECTION_MAX_ENTRIES,
|
|
45
|
+
consolidationTimeoutMs: DEFAULT_CONSOLIDATION_TIMEOUT_MS,
|
|
44
46
|
nudgeToolCalls: DEFAULT_NUDGE_TOOL_CALLS,
|
|
45
47
|
projectsMemoryDir: DEFAULT_PROJECTS_MEMORY_DIR,
|
|
46
48
|
};
|
|
@@ -97,6 +99,7 @@ export function loadConfig(configPath = DEFAULT_CONFIG_PATH): MemoryConfig {
|
|
|
97
99
|
if (isStringArray(parsed.correctionWeakPatterns)) config.correctionWeakPatterns = parsed.correctionWeakPatterns;
|
|
98
100
|
if (isStringArray(parsed.correctionNegativePatterns)) config.correctionNegativePatterns = parsed.correctionNegativePatterns;
|
|
99
101
|
if (isStringArray(parsed.correctionDirectiveWords)) config.correctionDirectiveWords = parsed.correctionDirectiveWords;
|
|
102
|
+
if (typeof parsed.consolidationTimeoutMs === "number") config.consolidationTimeoutMs = parsed.consolidationTimeoutMs;
|
|
100
103
|
if (typeof parsed.failureInjectionEnabled === "boolean") config.failureInjectionEnabled = parsed.failureInjectionEnabled;
|
|
101
104
|
if (typeof parsed.failureInjectionMaxAgeDays === "number") config.failureInjectionMaxAgeDays = parsed.failureInjectionMaxAgeDays;
|
|
102
105
|
if (typeof parsed.failureInjectionMaxEntries === "number") config.failureInjectionMaxEntries = parsed.failureInjectionMaxEntries;
|
package/src/constants.ts
CHANGED
|
@@ -23,6 +23,7 @@ export const DEFAULT_NUDGE_TOOL_CALLS = 15;
|
|
|
23
23
|
export const DEFAULT_REVIEW_RECENT_MESSAGES = 0;
|
|
24
24
|
export const DEFAULT_FLUSH_RECENT_MESSAGES = 0;
|
|
25
25
|
export const DEFAULT_SKILL_TRIGGER_TOOL_CALLS = 8;
|
|
26
|
+
export const DEFAULT_CONSOLIDATION_TIMEOUT_MS = 60000;
|
|
26
27
|
export const DEFAULT_FAILURE_INJECTION_MAX_AGE_DAYS = 7;
|
|
27
28
|
export const DEFAULT_FAILURE_INJECTION_MAX_ENTRIES = 5;
|
|
28
29
|
|
|
@@ -17,6 +17,7 @@ export async function triggerConsolidation(
|
|
|
17
17
|
store: MemoryStore,
|
|
18
18
|
target: "memory" | "user" | "failure",
|
|
19
19
|
signal?: AbortSignal,
|
|
20
|
+
timeoutMs: number = 60000,
|
|
20
21
|
): Promise<ConsolidationResult> {
|
|
21
22
|
const entries =
|
|
22
23
|
target === "memory" ? store.getMemoryEntries() : store.getUserEntries();
|
|
@@ -34,7 +35,7 @@ export async function triggerConsolidation(
|
|
|
34
35
|
try {
|
|
35
36
|
const result = await pi.exec("pi", ["-p", "--no-session", prompt], {
|
|
36
37
|
signal,
|
|
37
|
-
timeout:
|
|
38
|
+
timeout: timeoutMs,
|
|
38
39
|
});
|
|
39
40
|
|
|
40
41
|
if (result.code === 0) {
|
|
@@ -58,6 +59,7 @@ export async function triggerConsolidation(
|
|
|
58
59
|
export function registerConsolidateCommand(
|
|
59
60
|
pi: ExtensionAPI,
|
|
60
61
|
store: MemoryStore,
|
|
62
|
+
timeoutMs: number = 60000,
|
|
61
63
|
): void {
|
|
62
64
|
pi.registerCommand("memory-consolidate", {
|
|
63
65
|
description: "Manually trigger memory consolidation to free up space",
|
|
@@ -75,7 +77,7 @@ export function registerConsolidateCommand(
|
|
|
75
77
|
continue;
|
|
76
78
|
}
|
|
77
79
|
|
|
78
|
-
const result = await triggerConsolidation(pi, store, target, ctx.signal);
|
|
80
|
+
const result = await triggerConsolidation(pi, store, target, ctx.signal, timeoutMs);
|
|
79
81
|
|
|
80
82
|
if (result.consolidated) {
|
|
81
83
|
await store.loadFromDisk();
|
package/src/index.ts
CHANGED
|
@@ -111,9 +111,9 @@ export default function (pi: ExtensionAPI) {
|
|
|
111
111
|
|
|
112
112
|
// ── 7. Setup auto-consolidation (inject consolidator into store) ──
|
|
113
113
|
store.setConsolidator(async (target, signal) => {
|
|
114
|
-
return triggerConsolidation(pi, store, target, signal);
|
|
114
|
+
return triggerConsolidation(pi, store, target, signal, config.consolidationTimeoutMs);
|
|
115
115
|
});
|
|
116
|
-
registerConsolidateCommand(pi, store);
|
|
116
|
+
registerConsolidateCommand(pi, store, config.consolidationTimeoutMs);
|
|
117
117
|
|
|
118
118
|
// ── 8. Setup correction detection ──
|
|
119
119
|
setupCorrectionDetector(pi, store, projectStore, config, dbManager, projectName);
|
|
@@ -67,6 +67,11 @@ export interface SqliteMemoryRemoveResult {
|
|
|
67
67
|
removed: number;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
export interface SqliteMemoryRemoveOptions {
|
|
71
|
+
target: 'memory' | 'user' | 'failure';
|
|
72
|
+
project?: string | null;
|
|
73
|
+
}
|
|
74
|
+
|
|
70
75
|
export interface ParsedMarkdownMemoryEntry extends SqliteMemorySyncInput {}
|
|
71
76
|
|
|
72
77
|
function today(): string {
|
|
@@ -481,10 +486,7 @@ export function replaceSyncedMemories(
|
|
|
481
486
|
export function removeSyncedMemories(
|
|
482
487
|
dbManager: DatabaseManager,
|
|
483
488
|
oldText: string,
|
|
484
|
-
options:
|
|
485
|
-
target: 'memory' | 'user' | 'failure';
|
|
486
|
-
project?: string | null;
|
|
487
|
-
},
|
|
489
|
+
options: SqliteMemoryRemoveOptions,
|
|
488
490
|
): SqliteMemoryRemoveResult {
|
|
489
491
|
const db = dbManager.getDb();
|
|
490
492
|
const params: unknown[] = [];
|
|
@@ -512,6 +514,42 @@ export function removeSyncedMemories(
|
|
|
512
514
|
};
|
|
513
515
|
}
|
|
514
516
|
|
|
517
|
+
/**
|
|
518
|
+
* Exact removal for Markdown entries whose full content is known.
|
|
519
|
+
* Used for FIFO eviction cleanup, where substring matching could remove
|
|
520
|
+
* unrelated SQLite mirror rows that merely contain the evicted text.
|
|
521
|
+
*/
|
|
522
|
+
export function removeExactSyncedMemories(
|
|
523
|
+
dbManager: DatabaseManager,
|
|
524
|
+
content: string,
|
|
525
|
+
options: SqliteMemoryRemoveOptions,
|
|
526
|
+
): SqliteMemoryRemoveResult {
|
|
527
|
+
const db = dbManager.getDb();
|
|
528
|
+
const params: unknown[] = [];
|
|
529
|
+
const conditions = buildScopeConditions(params, options.target, options.project ?? undefined);
|
|
530
|
+
conditions.push('content = ?');
|
|
531
|
+
params.push(content.trim());
|
|
532
|
+
|
|
533
|
+
const matchingIds = db.prepare(`
|
|
534
|
+
SELECT id
|
|
535
|
+
FROM memories
|
|
536
|
+
WHERE ${conditions.join(' AND ')}
|
|
537
|
+
`).all(...params) as Array<{ id: number }>;
|
|
538
|
+
|
|
539
|
+
if (matchingIds.length === 0) {
|
|
540
|
+
return { matched: 0, removed: 0 };
|
|
541
|
+
}
|
|
542
|
+
|
|
543
|
+
const deleteParams = matchingIds.map((row) => row.id);
|
|
544
|
+
const placeholders = deleteParams.map(() => '?').join(', ');
|
|
545
|
+
const result = db.prepare(`DELETE FROM memories WHERE id IN (${placeholders})`).run(...deleteParams);
|
|
546
|
+
|
|
547
|
+
return {
|
|
548
|
+
matched: matchingIds.length,
|
|
549
|
+
removed: result.changes,
|
|
550
|
+
};
|
|
551
|
+
}
|
|
552
|
+
|
|
515
553
|
/**
|
|
516
554
|
* Escape a string for FTS5 query syntax.
|
|
517
555
|
* Wraps the query in double quotes to treat it as a literal phrase.
|
package/src/tools/memory-tool.ts
CHANGED
|
@@ -11,6 +11,7 @@ import { MemoryStore } from "../store/memory-store.js";
|
|
|
11
11
|
import { DatabaseManager } from "../store/db.js";
|
|
12
12
|
import {
|
|
13
13
|
formatFailureMemoryContent,
|
|
14
|
+
removeExactSyncedMemories,
|
|
14
15
|
removeSyncedMemories,
|
|
15
16
|
replaceSyncedMemories,
|
|
16
17
|
syncMemoryEntry,
|
|
@@ -159,6 +160,31 @@ async function syncRemoveFromSqlite(
|
|
|
159
160
|
}
|
|
160
161
|
}
|
|
161
162
|
|
|
163
|
+
async function syncEvictionsFromSqlite(
|
|
164
|
+
rawTarget: "memory" | "user" | "project" | "failure",
|
|
165
|
+
evictedEntries: string[] | undefined,
|
|
166
|
+
dbManager: DatabaseManager | null,
|
|
167
|
+
projectName?: string | null,
|
|
168
|
+
): Promise<void> {
|
|
169
|
+
if (!dbManager) return;
|
|
170
|
+
if (!evictedEntries || evictedEntries.length === 0) return;
|
|
171
|
+
|
|
172
|
+
const sqliteTarget = sqliteTargetFor(rawTarget);
|
|
173
|
+
const sqliteProject = sqliteProjectFor(rawTarget, projectName);
|
|
174
|
+
|
|
175
|
+
for (const entry of evictedEntries) {
|
|
176
|
+
try {
|
|
177
|
+
removeExactSyncedMemories(dbManager, entry, {
|
|
178
|
+
target: sqliteTarget,
|
|
179
|
+
project: sqliteProject,
|
|
180
|
+
});
|
|
181
|
+
} catch {
|
|
182
|
+
// FIFO already updated the Markdown source of truth. SQLite is only a
|
|
183
|
+
// best-effort search mirror, so eviction cleanup must not fail the write.
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
162
188
|
export function registerMemoryTool(
|
|
163
189
|
pi: ExtensionAPI,
|
|
164
190
|
store: MemoryStore,
|
|
@@ -247,6 +273,7 @@ export function registerMemoryTool(
|
|
|
247
273
|
} else {
|
|
248
274
|
result = await store_.add(target, content);
|
|
249
275
|
if (result.success) {
|
|
276
|
+
await syncEvictionsFromSqlite(rawTarget, result.evicted_entries, dbManager, projectName);
|
|
250
277
|
syncWarning = await syncAddToSqlite(rawTarget, content, undefined, undefined, dbManager, projectName);
|
|
251
278
|
}
|
|
252
279
|
}
|
package/src/types.ts
CHANGED
|
@@ -59,6 +59,8 @@ export interface MemoryConfig {
|
|
|
59
59
|
failureInjectionMaxEntries: number;
|
|
60
60
|
/** Tool calls before triggering background review (in addition to turn count). Default: 15 */
|
|
61
61
|
nudgeToolCalls: number;
|
|
62
|
+
/** Maximum time in milliseconds for auto-consolidation to complete. Default: 60000 */
|
|
63
|
+
consolidationTimeoutMs: number;
|
|
62
64
|
/** Enable session history search via SQLite FTS5. Default: true */
|
|
63
65
|
sessionSearchEnabled?: boolean;
|
|
64
66
|
/** Days to retain session history. Default: 90 */
|