pi-hermes-memory 0.7.4 → 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 CHANGED
@@ -357,11 +357,13 @@ Create `~/.pi/agent/hermes-memory-config.json`:
357
357
  "nudgeToolCalls": 15,
358
358
  "reviewRecentMessages": 0,
359
359
  "reviewEnabled": true,
360
+ "memoryOverflowStrategy": "auto-consolidate",
360
361
  "autoConsolidate": true,
361
362
  "correctionDetection": true,
362
363
  "failureInjectionEnabled": true,
363
364
  "failureInjectionMaxAgeDays": 7,
364
365
  "failureInjectionMaxEntries": 5,
366
+ "consolidationTimeoutMs": 60000,
365
367
  "flushOnCompact": true,
366
368
  "flushOnShutdown": true,
367
369
  "flushMinTurns": 6,
@@ -383,7 +385,9 @@ Create `~/.pi/agent/hermes-memory-config.json`:
383
385
  | `nudgeToolCalls` | `15` | Tool calls between auto-reviews (OR with turns) |
384
386
  | `reviewRecentMessages` | `0` | Recent messages included in background review (`0` = all) |
385
387
  | `reviewEnabled` | `true` | Enable/disable background learning loop |
386
- | `autoConsolidate` | `true` | Auto-merge when memory hits capacity |
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 |
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 |
387
391
  | `correctionDetection` | `true` | Detect user corrections and save immediately |
388
392
  | `correctionStrongPatterns` | unset | Optional case-insensitive regex sources replacing strong correction patterns; omitted preserves defaults, invalid entries are ignored |
389
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.4",
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
@@ -1,7 +1,7 @@
1
1
  import * as fs from "node:fs";
2
2
  import * as path from "node:path";
3
3
  import * as os from "node:os";
4
- import type { MemoryConfig } from "./types.js";
4
+ import type { MemoryConfig, MemoryOverflowStrategy } from "./types.js";
5
5
  import {
6
6
  DEFAULT_MEMORY_CHAR_LIMIT,
7
7
  DEFAULT_USER_CHAR_LIMIT,
@@ -12,10 +12,17 @@ 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";
18
19
 
20
+ const MEMORY_OVERFLOW_STRATEGIES: readonly MemoryOverflowStrategy[] = ["auto-consolidate", "reject", "fifo-evict"];
21
+
22
+ function isMemoryOverflowStrategy(value: unknown): value is MemoryOverflowStrategy {
23
+ return typeof value === "string" && MEMORY_OVERFLOW_STRATEGIES.includes(value as MemoryOverflowStrategy);
24
+ }
25
+
19
26
  const DEFAULT_CONFIG: MemoryConfig = {
20
27
  memoryMode: "policy-only",
21
28
  memoryPolicyStyle: "full",
@@ -29,11 +36,13 @@ const DEFAULT_CONFIG: MemoryConfig = {
29
36
  flushOnShutdown: true,
30
37
  flushMinTurns: DEFAULT_FLUSH_MIN_TURNS,
31
38
  flushRecentMessages: DEFAULT_FLUSH_RECENT_MESSAGES,
39
+ memoryOverflowStrategy: "auto-consolidate",
32
40
  autoConsolidate: true,
33
41
  correctionDetection: true,
34
42
  failureInjectionEnabled: true,
35
43
  failureInjectionMaxAgeDays: DEFAULT_FAILURE_INJECTION_MAX_AGE_DAYS,
36
44
  failureInjectionMaxEntries: DEFAULT_FAILURE_INJECTION_MAX_ENTRIES,
45
+ consolidationTimeoutMs: DEFAULT_CONSOLIDATION_TIMEOUT_MS,
37
46
  nudgeToolCalls: DEFAULT_NUDGE_TOOL_CALLS,
38
47
  projectsMemoryDir: DEFAULT_PROJECTS_MEMORY_DIR,
39
48
  };
@@ -58,6 +67,8 @@ export function loadConfig(configPath = DEFAULT_CONFIG_PATH): MemoryConfig {
58
67
  const isStringArray = (value: unknown): value is string[] => (
59
68
  Array.isArray(value) && value.every((item) => typeof item === "string")
60
69
  );
70
+ let hasLegacyAutoConsolidate = false;
71
+ let hasMemoryOverflowStrategy = false;
61
72
  if (parsed.memoryMode === "policy-only" || parsed.memoryMode === "legacy-inject") config.memoryMode = parsed.memoryMode;
62
73
  if (
63
74
  parsed.memoryPolicyStyle === "full" ||
@@ -75,12 +86,20 @@ export function loadConfig(configPath = DEFAULT_CONFIG_PATH): MemoryConfig {
75
86
  if (typeof parsed.flushOnShutdown === "boolean") config.flushOnShutdown = parsed.flushOnShutdown;
76
87
  if (typeof parsed.flushMinTurns === "number") config.flushMinTurns = parsed.flushMinTurns;
77
88
  if (isNonNegativeNumber(parsed.flushRecentMessages)) config.flushRecentMessages = parsed.flushRecentMessages;
78
- if (typeof parsed.autoConsolidate === "boolean") config.autoConsolidate = parsed.autoConsolidate;
89
+ if (typeof parsed.autoConsolidate === "boolean") {
90
+ config.autoConsolidate = parsed.autoConsolidate;
91
+ hasLegacyAutoConsolidate = true;
92
+ }
93
+ if (isMemoryOverflowStrategy(parsed.memoryOverflowStrategy)) {
94
+ config.memoryOverflowStrategy = parsed.memoryOverflowStrategy;
95
+ hasMemoryOverflowStrategy = true;
96
+ }
79
97
  if (typeof parsed.correctionDetection === "boolean") config.correctionDetection = parsed.correctionDetection;
80
98
  if (isStringArray(parsed.correctionStrongPatterns)) config.correctionStrongPatterns = parsed.correctionStrongPatterns;
81
99
  if (isStringArray(parsed.correctionWeakPatterns)) config.correctionWeakPatterns = parsed.correctionWeakPatterns;
82
100
  if (isStringArray(parsed.correctionNegativePatterns)) config.correctionNegativePatterns = parsed.correctionNegativePatterns;
83
101
  if (isStringArray(parsed.correctionDirectiveWords)) config.correctionDirectiveWords = parsed.correctionDirectiveWords;
102
+ if (typeof parsed.consolidationTimeoutMs === "number") config.consolidationTimeoutMs = parsed.consolidationTimeoutMs;
84
103
  if (typeof parsed.failureInjectionEnabled === "boolean") config.failureInjectionEnabled = parsed.failureInjectionEnabled;
85
104
  if (typeof parsed.failureInjectionMaxAgeDays === "number") config.failureInjectionMaxAgeDays = parsed.failureInjectionMaxAgeDays;
86
105
  if (typeof parsed.failureInjectionMaxEntries === "number") config.failureInjectionMaxEntries = parsed.failureInjectionMaxEntries;
@@ -88,6 +107,11 @@ export function loadConfig(configPath = DEFAULT_CONFIG_PATH): MemoryConfig {
88
107
  if (typeof parsed.projectCharLimit === "number") config.projectCharLimit = parsed.projectCharLimit;
89
108
  if (typeof parsed.memoryDir === "string") config.memoryDir = parsed.memoryDir;
90
109
  if (typeof parsed.projectsMemoryDir === "string") config.projectsMemoryDir = parsed.projectsMemoryDir;
110
+ if (hasMemoryOverflowStrategy) {
111
+ config.autoConsolidate = config.memoryOverflowStrategy === "auto-consolidate";
112
+ } else if (hasLegacyAutoConsolidate) {
113
+ config.memoryOverflowStrategy = config.autoConsolidate ? "auto-consolidate" : "reject";
114
+ }
91
115
  return config;
92
116
  }
93
117
  } catch {
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: 60000,
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);
@@ -24,7 +24,7 @@ import {
24
24
  MEMORY_FILE,
25
25
  USER_FILE,
26
26
  } from "../constants.js";
27
- import type { MemoryConfig, MemoryResult, MemorySnapshot, ConsolidationResult, MemoryCategory } from "../types.js";
27
+ import type { MemoryConfig, MemoryResult, MemorySnapshot, ConsolidationResult, MemoryCategory, MemoryOverflowStrategy } from "../types.js";
28
28
 
29
29
  export class MemoryStore {
30
30
  private memoryEntries: string[] = [];
@@ -77,6 +77,10 @@ export class MemoryStore {
77
77
  return entries.length ? entries.join(ENTRY_DELIMITER).length : 0;
78
78
  }
79
79
 
80
+ private memoryOverflowStrategy(): MemoryOverflowStrategy {
81
+ return this.config.memoryOverflowStrategy ?? (this.config.autoConsolidate ? "auto-consolidate" : "reject");
82
+ }
83
+
80
84
  // ─── Load from disk ───
81
85
 
82
86
  async loadFromDisk(): Promise<void> {
@@ -176,8 +180,14 @@ export class MemoryStore {
176
180
 
177
181
  const newTotal = [...entries, encoded].join(ENTRY_DELIMITER).length;
178
182
  if (newTotal > limit) {
183
+ const strategy = this.memoryOverflowStrategy();
184
+
185
+ if (strategy === "fifo-evict") {
186
+ return this.fifoEvictAndAdd(target, entries, encoded, content.length, limit);
187
+ }
188
+
179
189
  // Auto-consolidate once if configured — limit retries to prevent infinite loops
180
- if (this.config.autoConsolidate && this.consolidator && _retriesLeft > 0) {
190
+ if (strategy === "auto-consolidate" && this.consolidator && _retriesLeft > 0) {
181
191
  try {
182
192
  const result = await this.consolidator(target, signal);
183
193
  if (result.consolidated) {
@@ -190,11 +200,7 @@ export class MemoryStore {
190
200
  // Consolidation failed — fall through to error
191
201
  }
192
202
  }
193
- const current = this.charCount(target);
194
- return {
195
- success: false,
196
- error: `Memory at ${current}/${limit} chars. Adding this entry (${content.length} chars) would exceed the limit. Replace or remove existing entries first.`,
197
- };
203
+ return this.memoryFullError(target, content.length);
198
204
  }
199
205
 
200
206
  entries.push(encoded);
@@ -204,6 +210,48 @@ export class MemoryStore {
204
210
  return this.successResponse(target, "Entry added.");
205
211
  }
206
212
 
213
+ private async fifoEvictAndAdd(
214
+ target: "memory" | "user" | "failure",
215
+ entries: string[],
216
+ encoded: string,
217
+ contentLength: number,
218
+ limit: number,
219
+ ): Promise<MemoryResult> {
220
+ if (encoded.length > limit) {
221
+ return this.memoryFullError(target, contentLength);
222
+ }
223
+
224
+ const remaining = [...entries];
225
+ const evictedEntries: string[] = [];
226
+
227
+ while ([...remaining, encoded].join(ENTRY_DELIMITER).length > limit && remaining.length > 0) {
228
+ const evicted = remaining.shift()!;
229
+ evictedEntries.push(this.stripMetadata(evicted));
230
+ }
231
+
232
+ remaining.push(encoded);
233
+ this.setEntries(target, remaining);
234
+ await this.saveToDisk(target);
235
+
236
+ return {
237
+ ...this.successResponse(
238
+ target,
239
+ `Memory updated. Rotated ${evictedEntries.length} older ${evictedEntries.length === 1 ? "entry" : "entries"} to stay within the limit.`,
240
+ ),
241
+ evicted_entries: evictedEntries,
242
+ evicted_count: evictedEntries.length,
243
+ };
244
+ }
245
+
246
+ private memoryFullError(target: "memory" | "user" | "failure", contentLength: number): MemoryResult {
247
+ const current = this.charCount(target);
248
+ const limit = this.charLimit(target);
249
+ return {
250
+ success: false,
251
+ error: `Memory at ${current}/${limit} chars. Adding this entry (${contentLength} chars) would exceed the limit. Replace or remove existing entries first.`,
252
+ };
253
+ }
254
+
207
255
  async replace(target: "memory" | "user" | "failure", oldText: string, newContent: string): Promise<MemoryResult> {
208
256
  oldText = oldText.trim();
209
257
  newContent = newContent.trim();
@@ -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.
@@ -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,
@@ -29,6 +30,29 @@ function appendSyncWarning(result: MemoryResult, warning: string): MemoryResult
29
30
  } as MemoryResult;
30
31
  }
31
32
 
33
+ function formatMemoryToolText(result: MemoryResult): string {
34
+ const evictedEntries = result.evicted_entries ?? [];
35
+ if (result.success && evictedEntries.length > 0) {
36
+ const lines = [
37
+ result.message ?? `Memory updated. Rotated ${evictedEntries.length} older ${evictedEntries.length === 1 ? "entry" : "entries"} to stay within the limit.`,
38
+ "",
39
+ "Rotated active memory entries:",
40
+ "",
41
+ ];
42
+
43
+ evictedEntries.forEach((entry, index) => {
44
+ lines.push(`${index + 1}. ${entry}`);
45
+ lines.push("");
46
+ });
47
+
48
+ lines.push("If one of these entries should stay active, add it again.");
49
+ if (result.usage) lines.push(`Usage: ${result.usage}`);
50
+ return lines.join("\n").trim();
51
+ }
52
+
53
+ return JSON.stringify(result);
54
+ }
55
+
32
56
  function sqliteProjectFor(rawTarget: "memory" | "user" | "project" | "failure", projectName?: string | null): string | null | undefined {
33
57
  if (rawTarget === "project") return projectName?.trim() || null;
34
58
  if (rawTarget === "memory") return null;
@@ -136,6 +160,31 @@ async function syncRemoveFromSqlite(
136
160
  }
137
161
  }
138
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
+
139
188
  export function registerMemoryTool(
140
189
  pi: ExtensionAPI,
141
190
  store: MemoryStore,
@@ -224,6 +273,7 @@ export function registerMemoryTool(
224
273
  } else {
225
274
  result = await store_.add(target, content);
226
275
  if (result.success) {
276
+ await syncEvictionsFromSqlite(rawTarget, result.evicted_entries, dbManager, projectName);
227
277
  syncWarning = await syncAddToSqlite(rawTarget, content, undefined, undefined, dbManager, projectName);
228
278
  }
229
279
  }
@@ -305,7 +355,7 @@ export function registerMemoryTool(
305
355
  }
306
356
 
307
357
  return {
308
- content: [{ type: "text", text: JSON.stringify(result) }],
358
+ content: [{ type: "text", text: formatMemoryToolText(result) }],
309
359
  details: result,
310
360
  };
311
361
  },
package/src/types.ts CHANGED
@@ -4,6 +4,8 @@
4
4
 
5
5
  import type { TextContent } from "@mariozechner/pi-ai";
6
6
 
7
+ export type MemoryOverflowStrategy = "auto-consolidate" | "reject" | "fifo-evict";
8
+
7
9
  export interface MemoryConfig {
8
10
  /** Prompt memory mode. Default: policy-only */
9
11
  memoryMode: "policy-only" | "legacy-inject";
@@ -35,7 +37,9 @@ export interface MemoryConfig {
35
37
  memoryDir?: string;
36
38
  /** Directory for project-scoped memory (relative to ~/.pi/agent). Default: "projects-memory" */
37
39
  projectsMemoryDir?: string;
38
- /** Auto-consolidate when memory is full instead of returning error. Default: true */
40
+ /** Strategy when memory is full. Default: auto-consolidate */
41
+ memoryOverflowStrategy?: MemoryOverflowStrategy;
42
+ /** Legacy alias for memoryOverflowStrategy. Default: true */
39
43
  autoConsolidate: boolean;
40
44
  /** Detect user corrections and trigger immediate memory save. Default: true */
41
45
  correctionDetection: boolean;
@@ -55,6 +59,8 @@ export interface MemoryConfig {
55
59
  failureInjectionMaxEntries: number;
56
60
  /** Tool calls before triggering background review (in addition to turn count). Default: 15 */
57
61
  nudgeToolCalls: number;
62
+ /** Maximum time in milliseconds for auto-consolidation to complete. Default: 60000 */
63
+ consolidationTimeoutMs: number;
58
64
  /** Enable session history search via SQLite FTS5. Default: true */
59
65
  sessionSearchEnabled?: boolean;
60
66
  /** Days to retain session history. Default: 90 */
@@ -79,6 +85,8 @@ export interface MemoryResult {
79
85
  entries?: string[];
80
86
  usage?: string;
81
87
  entry_count?: number;
88
+ evicted_entries?: string[];
89
+ evicted_count?: number;
82
90
  matches?: string[];
83
91
  }
84
92