@oh-my-pi/pi-mnemopi 16.3.3 → 16.3.5

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/CHANGELOG.md CHANGED
@@ -2,6 +2,12 @@
2
2
 
3
3
  ## [Unreleased]
4
4
 
5
+ ## [16.3.5] - 2026-07-04
6
+
7
+ ### Fixed
8
+
9
+ - Fixed `remember(..., { embedText })` so hosts can store full transcripts while embedding, FTS-indexing, and rebuild-reembedding a marker-free projection. ([#4395](https://github.com/can1357/oh-my-pi/issues/4395))
10
+
5
11
  ## [16.2.2] - 2026-06-27
6
12
 
7
13
  ### Fixed
@@ -12,6 +12,7 @@ type StoreRememberOptions = RememberOptions & {
12
12
  extractEntities?: boolean;
13
13
  extract_entities?: boolean;
14
14
  extract_text?: string;
15
+ embed_text?: string;
15
16
  channelId?: string | null;
16
17
  channel_id?: string | null;
17
18
  };
@@ -108,6 +108,11 @@ export interface RememberOptions {
108
108
  * as user `Instruction:` memories.
109
109
  */
110
110
  extractText?: string;
111
+ /**
112
+ * Override the text passed to embeddings and FTS indexing. Stored `content`
113
+ * remains unchanged; when unset, embeddings and FTS use `content`.
114
+ */
115
+ embedText?: string;
111
116
  veracity?: Veracity;
112
117
  memoryType?: string;
113
118
  scope?: MemoryScope;
@@ -44,6 +44,8 @@ export interface RememberInput extends MemoryInput {
44
44
  readonly extract_entities?: boolean;
45
45
  readonly extractText?: string | null;
46
46
  readonly extract_text?: string | null;
47
+ readonly embedText?: string | null;
48
+ readonly embed_text?: string | null;
47
49
  readonly trustTier?: string | null;
48
50
  readonly trust_tier?: string | null;
49
51
  readonly memoryType?: string | null;
@@ -65,6 +67,12 @@ export interface RememberFacadeOptions {
65
67
  */
66
68
  readonly extractText?: string | null;
67
69
  readonly extract_text?: string | null;
70
+ /**
71
+ * Override the text passed to embeddings and FTS indexing. Stored content
72
+ * remains unchanged; when unset, embeddings and FTS use stored content.
73
+ */
74
+ readonly embedText?: string | null;
75
+ readonly embed_text?: string | null;
68
76
  readonly trustTier?: string | null;
69
77
  readonly trust_tier?: string | null;
70
78
  readonly timestamp?: string | Date | null;
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-mnemopi",
4
- "version": "16.3.3",
4
+ "version": "16.3.5",
5
5
  "description": "Local SQLite memory engine for Oh My Pi agents",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": "Can Boluk",
@@ -39,9 +39,9 @@
39
39
  "fmt": "biome format --write ."
40
40
  },
41
41
  "dependencies": {
42
- "@oh-my-pi/pi-ai": "16.3.3",
43
- "@oh-my-pi/pi-catalog": "16.3.3",
44
- "@oh-my-pi/pi-utils": "16.3.3",
42
+ "@oh-my-pi/pi-ai": "16.3.5",
43
+ "@oh-my-pi/pi-catalog": "16.3.5",
44
+ "@oh-my-pi/pi-utils": "16.3.5",
45
45
  "lru-cache": "11.5.1"
46
46
  },
47
47
  "peerDependencies": {
@@ -896,7 +896,7 @@ function eligibleWorkingRows(beam: BeamMemoryState, sessionId: string): Row[] {
896
896
  return asRows(
897
897
  beam.db
898
898
  .query(
899
- `SELECT id, content, source, timestamp, importance, metadata_json, scope, valid_until, veracity
899
+ `SELECT id, COALESCE(embed_text, content) AS content, source, timestamp, importance, metadata_json, scope, valid_until, veracity
900
900
  FROM working_memory
901
901
  WHERE COALESCE(session_id, 'default') = ? AND timestamp < ? AND consolidated_at IS NULL
902
902
  ORDER BY timestamp ASC LIMIT ?`,
@@ -507,6 +507,8 @@ function buildWhere(
507
507
 
508
508
  const MEMORY_COLUMNS =
509
509
  "id, content, source, timestamp, session_id, importance, metadata_json, veracity, memory_type, recall_count, last_recalled, valid_until, superseded_by, scope, author_id, author_type, channel_id, event_date, event_date_precision, temporal_tags";
510
+ const WORKING_MEMORY_COLUMNS =
511
+ "id, content, embed_text, source, timestamp, session_id, importance, metadata_json, veracity, memory_type, recall_count, last_recalled, valid_until, superseded_by, scope, author_id, author_type, channel_id, event_date, event_date_precision, temporal_tags";
510
512
  const EPISODIC_COLUMNS = `${MEMORY_COLUMNS}, rowid, summary_of, tier`;
511
513
 
512
514
  function ftsRows(
@@ -624,7 +626,7 @@ function fetchCandidates(
624
626
  if (idsOrRowids.length === 0) return [];
625
627
  const table = tierLabel === "working" ? "working_memory" : "episodic_memory";
626
628
  const keyColumn = tierLabel === "working" ? "id" : "rowid";
627
- const columns = tierLabel === "working" ? MEMORY_COLUMNS : EPISODIC_COLUMNS;
629
+ const columns = tierLabel === "working" ? WORKING_MEMORY_COLUMNS : EPISODIC_COLUMNS;
628
630
  const { where, params } = buildWhere(beam, "m", options);
629
631
  const rows = queryAll(
630
632
  beam,
@@ -662,7 +664,7 @@ function fallbackCandidates(
662
664
  options: RecallOptionsInternal,
663
665
  ): MemoryCandidate[] {
664
666
  const table = tierLabel === "working" ? "working_memory" : "episodic_memory";
665
- const columns = tierLabel === "working" ? MEMORY_COLUMNS : EPISODIC_COLUMNS;
667
+ const columns = tierLabel === "working" ? WORKING_MEMORY_COLUMNS : EPISODIC_COLUMNS;
666
668
  const { where, params } = buildWhere(beam, "", options);
667
669
  const rows = queryAll(beam, `SELECT ${columns} FROM ${table} WHERE ${where} ORDER BY timestamp DESC LIMIT ?`, [
668
670
  ...params,
@@ -684,10 +686,11 @@ function scoreCandidate(
684
686
  options: RecallOptionsInternal,
685
687
  ): RecallResult | null {
686
688
  const content = asString(candidate.row.content);
689
+ const searchableContent = asString(candidate.row.embed_text) || content;
687
690
  const lexical =
688
691
  queryGroups.length > 0
689
- ? lexicalGroupRelevance(queryGroups, content, normalizedQueryLower)
690
- : lexicalRelevance(queryTokens, content, normalizedQueryLower);
692
+ ? lexicalGroupRelevance(queryGroups, searchableContent, normalizedQueryLower)
693
+ : lexicalRelevance(queryTokens, searchableContent, normalizedQueryLower);
691
694
  const minRel = minimumRelevance(queryTokens);
692
695
  if (lexical < minRel && candidate.signals.dense < 0.65) return null;
693
696
  const [vecWeight, ftsWeight, importanceWeight] = weights;
@@ -732,7 +735,7 @@ function scoreCandidate(
732
735
  const tierWeight = degradationTier === 1 ? 1 : degradationTier === 2 ? 0.85 : 0.7;
733
736
  score *= tierWeight;
734
737
  }
735
- score *= veracityWeight * currentContentAdjustment(content, options.currentSensitive === true);
738
+ score *= veracityWeight * currentContentAdjustment(searchableContent, options.currentSensitive === true);
736
739
  const result: RecallResult = {
737
740
  ...candidate.row,
738
741
  id: asString(candidate.row.id),
@@ -26,6 +26,7 @@ export function initBeam(db: Database): void {
26
26
  CREATE TABLE IF NOT EXISTS working_memory (
27
27
  id TEXT PRIMARY KEY,
28
28
  content TEXT NOT NULL,
29
+ embed_text TEXT DEFAULT NULL,
29
30
  source TEXT,
30
31
  timestamp TEXT,
31
32
  session_id TEXT DEFAULT 'default',
@@ -105,6 +106,7 @@ export function initBeam(db: Database): void {
105
106
  addColumnIfMissing(db, "working_memory", "veracity", "TEXT DEFAULT 'unknown'");
106
107
  addColumnIfMissing(db, "episodic_memory", "veracity", "TEXT DEFAULT 'unknown'");
107
108
  addColumnIfMissing(db, "working_memory", "memory_type", "TEXT DEFAULT 'unknown'");
109
+ addColumnIfMissing(db, "working_memory", "embed_text", "TEXT DEFAULT NULL");
108
110
  addColumnIfMissing(db, "episodic_memory", "memory_type", "TEXT DEFAULT 'unknown'");
109
111
  addColumnIfMissing(db, "episodic_memory", "binary_vector", "BLOB");
110
112
  const consolidatedAtAdded = addColumnIfMissing(db, "working_memory", "consolidated_at", "TEXT");
@@ -150,16 +152,17 @@ export function initBeam(db: Database): void {
150
152
  INSERT INTO fts_episodes(fts_episodes, rowid, content) VALUES ('delete', old.rowid, old.content);
151
153
  INSERT INTO fts_episodes(rowid, content) VALUES (new.rowid, new.content);
152
154
  END`,
155
+ "DROP TRIGGER IF EXISTS wm_ai",
153
156
  `CREATE TRIGGER IF NOT EXISTS wm_ai AFTER INSERT ON working_memory BEGIN
154
- INSERT INTO fts_working(id, content) VALUES (new.id, new.content);
157
+ INSERT INTO fts_working(id, content) VALUES (new.id, COALESCE(new.embed_text, new.content));
155
158
  END`,
156
159
  `CREATE TRIGGER IF NOT EXISTS wm_ad AFTER DELETE ON working_memory BEGIN
157
160
  DELETE FROM fts_working WHERE id = old.id;
158
161
  END`,
159
162
  "DROP TRIGGER IF EXISTS wm_au",
160
- `CREATE TRIGGER IF NOT EXISTS wm_au AFTER UPDATE OF content ON working_memory BEGIN
163
+ `CREATE TRIGGER IF NOT EXISTS wm_au AFTER UPDATE OF content, embed_text ON working_memory BEGIN
161
164
  DELETE FROM fts_working WHERE id = old.id;
162
- INSERT INTO fts_working(id, content) VALUES (new.id, new.content);
165
+ INSERT INTO fts_working(id, content) VALUES (new.id, COALESCE(new.embed_text, new.content));
163
166
  END`,
164
167
  ]);
165
168
 
@@ -37,6 +37,7 @@ type StoreRememberOptions = RememberOptions & {
37
37
  extractEntities?: boolean;
38
38
  extract_entities?: boolean;
39
39
  extract_text?: string;
40
+ embed_text?: string;
40
41
  channelId?: string | null;
41
42
  channel_id?: string | null;
42
43
  };
@@ -89,6 +90,14 @@ function sqlBinding(value: unknown, fallback: SQLQueryBindings): SQLQueryBinding
89
90
  return isSqlBinding(value) ? value : fallback;
90
91
  }
91
92
 
93
+ function embeddingText(content: string, options: { embedText?: string; embed_text?: string }): string {
94
+ return options.embedText ?? options.embed_text ?? content;
95
+ }
96
+
97
+ function storedEmbeddingText(content: string, embedText: string): string | null {
98
+ return embedText === content ? null : embedText;
99
+ }
100
+
92
101
  function clampVeracity(value: unknown): Veracity {
93
102
  if (typeof value !== "string") return "unknown";
94
103
  const normalized = value.trim().toLowerCase();
@@ -301,7 +310,7 @@ export function reconcileEmbeddingModel(beam: BeamMemoryState): void {
301
310
  .all(active) as { model: string | null }[];
302
311
  const live = beam.db
303
312
  .query(`
304
- SELECT id AS memoryId, content FROM working_memory WHERE superseded_by IS NULL
313
+ SELECT id AS memoryId, COALESCE(embed_text, content) AS content FROM working_memory WHERE superseded_by IS NULL
305
314
  UNION ALL
306
315
  SELECT id AS memoryId, content FROM episodic_memory WHERE superseded_by IS NULL
307
316
  `)
@@ -334,7 +343,7 @@ export function reconcileEmbeddingModel(beam: BeamMemoryState): void {
334
343
  // row still missing an active-model embedding.
335
344
  const missing = beam.db
336
345
  .query(`
337
- SELECT id AS memoryId, content FROM working_memory
346
+ SELECT id AS memoryId, COALESCE(embed_text, content) AS content FROM working_memory
338
347
  WHERE superseded_by IS NULL AND id NOT IN (SELECT memory_id FROM memory_embeddings WHERE model = ?)
339
348
  UNION ALL
340
349
  SELECT id AS memoryId, content FROM episodic_memory
@@ -359,6 +368,7 @@ export function remember(beam: BeamMemoryState, content: string, options: StoreR
359
368
  const authorType = options.authorType ?? options.author_type ?? beam.authorType;
360
369
  const channelId = options.channelId ?? options.channel_id ?? beam.channelId;
361
370
  const metadata = options.metadata ?? null;
371
+ const embedText = embeddingText(content, options);
362
372
 
363
373
  const existingId = findDuplicate(beam, content);
364
374
  if (existingId !== null) {
@@ -374,6 +384,7 @@ export function remember(beam: BeamMemoryState, content: string, options: StoreR
374
384
  memory_type = COALESCE(?, memory_type),
375
385
  veracity = CASE WHEN ? != 'unknown' THEN ? ELSE veracity END,
376
386
  trust_tier = COALESCE(?, trust_tier),
387
+ embed_text = COALESCE(?, embed_text),
377
388
  consolidated_at = NULL
378
389
  WHERE id = ? AND session_id = ?
379
390
  `)
@@ -390,6 +401,7 @@ export function remember(beam: BeamMemoryState, content: string, options: StoreR
390
401
  veracity,
391
402
  veracity,
392
403
  trustTier,
404
+ storedEmbeddingText(content, embedText),
393
405
  existingId,
394
406
  beam.sessionId,
395
407
  );
@@ -400,6 +412,7 @@ export function remember(beam: BeamMemoryState, content: string, options: StoreR
400
412
  importance,
401
413
  metadata: metadata ?? undefined,
402
414
  });
415
+ if (embedText !== content) scheduleEmbedding(beam, [{ memoryId: existingId, content: embedText }]);
403
416
  invalidateCaches(beam);
404
417
  return existingId;
405
418
  }
@@ -408,13 +421,14 @@ export function remember(beam: BeamMemoryState, content: string, options: StoreR
408
421
  beam.db
409
422
  .prepare(`
410
423
  INSERT INTO working_memory
411
- (id, content, source, timestamp, session_id, importance, metadata_json, valid_until, scope,
424
+ (id, content, embed_text, source, timestamp, session_id, importance, metadata_json, valid_until, scope,
412
425
  author_id, author_type, channel_id, veracity, memory_type, trust_tier)
413
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
426
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
414
427
  `)
415
428
  .run(
416
429
  memoryId,
417
430
  content,
431
+ storedEmbeddingText(content, embedText),
418
432
  source,
419
433
  timestamp,
420
434
  beam.sessionId,
@@ -448,7 +462,7 @@ export function remember(beam: BeamMemoryState, content: string, options: StoreR
448
462
  importance,
449
463
  metadata: metadata ?? undefined,
450
464
  });
451
- scheduleEmbedding(beam, [{ memoryId, content }]);
465
+ scheduleEmbedding(beam, [{ memoryId, content: embedText }]);
452
466
  if (options.extract === true) scheduleFactExtraction(beam, memoryId, extractionSource);
453
467
  invalidateCaches(beam);
454
468
  return memoryId;
@@ -469,9 +483,9 @@ export function rememberBatch(
469
483
  transaction(beam.db, () => {
470
484
  const statement = beam.db.prepare(`
471
485
  INSERT INTO working_memory
472
- (id, content, source, timestamp, session_id, importance, metadata_json,
486
+ (id, content, embed_text, source, timestamp, session_id, importance, metadata_json,
473
487
  author_id, author_type, channel_id, memory_type, veracity, trust_tier, scope)
474
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
488
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
475
489
  `);
476
490
  for (const item of items) {
477
491
  const itemTimestamp = item.timestamp ?? timestamp;
@@ -479,6 +493,7 @@ export function rememberBatch(
479
493
  ids.push(memoryId);
480
494
  const source = item.source ?? "conversation";
481
495
  const storeItem = item as StoreRememberOptions;
496
+ const embedText = embeddingText(item.content, storeItem);
482
497
  const itemVeracity = forceVeracity
483
498
  ? defaultVeracity
484
499
  : item.veracity !== undefined
@@ -487,6 +502,7 @@ export function rememberBatch(
487
502
  statement.run(
488
503
  memoryId,
489
504
  item.content,
505
+ storedEmbeddingText(item.content, embedText),
490
506
  source,
491
507
  itemTimestamp,
492
508
  beam.sessionId,
@@ -516,7 +532,7 @@ export function rememberBatch(
516
532
  items.forEach((item, index) => {
517
533
  const id = ids[index];
518
534
  if (id === undefined) return;
519
- embeddingItems.push({ memoryId: id, content: item.content });
535
+ embeddingItems.push({ memoryId: id, content: embeddingText(item.content, item as StoreRememberOptions) });
520
536
  });
521
537
  scheduleEmbedding(beam, embeddingItems);
522
538
  items.forEach((item, index) => {
@@ -611,7 +627,7 @@ export function updateWorking(
611
627
  const assignments: string[] = [];
612
628
  const params: SQLQueryBindings[] = [];
613
629
  if (content !== null) {
614
- assignments.push("content = ?");
630
+ assignments.push("content = ?", "embed_text = NULL");
615
631
  params.push(content);
616
632
  }
617
633
  if (importance !== null) {
@@ -710,6 +726,7 @@ export function exportToDict(beam: BeamMemoryState): Record<string, unknown> {
710
726
  working_memory: db
711
727
  .prepare(`
712
728
  SELECT id, content, source, timestamp, session_id, importance,
729
+ embed_text,
713
730
  metadata_json, valid_until, superseded_by, scope,
714
731
  recall_count, last_recalled, created_at, veracity, consolidated_at,
715
732
  memory_type, author_id, author_type, channel_id, trust_tier,
@@ -777,9 +794,9 @@ export function importFromDict(beam: BeamMemoryState, data: Record<string, unkno
777
794
  INSERT INTO working_memory
778
795
  (id, content, source, timestamp, session_id, importance, metadata_json,
779
796
  valid_until, superseded_by, scope, recall_count, last_recalled, created_at,
780
- veracity, consolidated_at, memory_type, author_id, author_type, channel_id,
797
+ veracity, consolidated_at, memory_type, embed_text, author_id, author_type, channel_id,
781
798
  trust_tier, event_date, event_date_precision, temporal_tags)
782
- VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
799
+ VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
783
800
  `).run(
784
801
  id,
785
802
  sqlBinding(item.content, ""),
@@ -797,6 +814,7 @@ export function importFromDict(beam: BeamMemoryState, data: Record<string, unkno
797
814
  clampVeracity(item.veracity),
798
815
  sqlBinding(item.consolidated_at, null),
799
816
  sqlBinding(item.memory_type, "unknown"),
817
+ sqlBinding(item.embed_text, null),
800
818
  sqlBinding(item.author_id, null),
801
819
  sqlBinding(item.author_type, null),
802
820
  sqlBinding(item.channel_id, null),
@@ -129,6 +129,11 @@ export interface RememberOptions {
129
129
  * as user `Instruction:` memories.
130
130
  */
131
131
  extractText?: string;
132
+ /**
133
+ * Override the text passed to embeddings and FTS indexing. Stored `content`
134
+ * remains unchanged; when unset, embeddings and FTS use `content`.
135
+ */
136
+ embedText?: string;
132
137
  veracity?: Veracity;
133
138
  memoryType?: string;
134
139
  scope?: MemoryScope;
@@ -115,9 +115,7 @@ export function parseFacts(rawOutput: string | null | undefined): string[] {
115
115
  }
116
116
  }
117
117
  }
118
- if (out.length > 0) {
119
- return out.slice(0, 5);
120
- }
118
+ return out.slice(0, 5);
121
119
  }
122
120
  } catch {
123
121
  const matches = [...raw.matchAll(/"([^"]{10,})"/g)].map(m => m[1]).filter((v): v is string => v !== undefined);
@@ -61,6 +61,8 @@ export interface RememberInput extends MemoryInput {
61
61
  readonly extract_entities?: boolean;
62
62
  readonly extractText?: string | null;
63
63
  readonly extract_text?: string | null;
64
+ readonly embedText?: string | null;
65
+ readonly embed_text?: string | null;
64
66
  readonly trustTier?: string | null;
65
67
  readonly trust_tier?: string | null;
66
68
  readonly memoryType?: string | null;
@@ -83,6 +85,12 @@ export interface RememberFacadeOptions {
83
85
  */
84
86
  readonly extractText?: string | null;
85
87
  readonly extract_text?: string | null;
88
+ /**
89
+ * Override the text passed to embeddings and FTS indexing. Stored content
90
+ * remains unchanged; when unset, embeddings and FTS use stored content.
91
+ */
92
+ readonly embedText?: string | null;
93
+ readonly embed_text?: string | null;
86
94
  readonly trustTier?: string | null;
87
95
  readonly trust_tier?: string | null;
88
96
  readonly timestamp?: string | Date | null;
@@ -148,6 +156,7 @@ type FacadeRememberOptions = {
148
156
  extractEntities: boolean;
149
157
  extract: boolean;
150
158
  extractText: string | undefined;
159
+ embedText: string | undefined;
151
160
  trustTier: string | undefined;
152
161
  veracity: string | undefined;
153
162
  memoryType: string | undefined;
@@ -270,6 +279,7 @@ function toRememberOptions(input: string | RememberInput, options: RememberFacad
270
279
  const timestamp = normalizeDate(options.timestamp ?? memory?.timestamp);
271
280
  const extractText =
272
281
  options.extractText ?? options.extract_text ?? memory?.extractText ?? memory?.extract_text ?? null;
282
+ const embedText = options.embedText ?? options.embed_text ?? memory?.embedText ?? memory?.embed_text ?? null;
273
283
  const rememberOptions: FacadeRememberOptions = {
274
284
  source: options.source ?? memory?.source ?? "conversation",
275
285
  importance: options.importance ?? memory?.importance ?? 0.5,
@@ -284,6 +294,7 @@ function toRememberOptions(input: string | RememberInput, options: RememberFacad
284
294
  false,
285
295
  extract: options.extract ?? memory?.extract ?? false,
286
296
  extractText: extractText ?? undefined,
297
+ embedText: embedText ?? undefined,
287
298
  trustTier: options.trustTier ?? options.trust_tier ?? memory?.trustTier ?? memory?.trust_tier ?? undefined,
288
299
  veracity: options.veracity ?? memory?.veracity ?? undefined,
289
300
  memoryType: options.memoryType ?? options.memory_type ?? memory?.memoryType ?? memory?.memory_type ?? undefined,