@oh-my-pi/pi-mnemopi 18.8.4 → 18.8.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
+ ## [18.8.5] - 2026-10-08
6
+
7
+ ### Fixed
8
+
9
+ - Fixed new memories gaining graph links (`related_to`, `references`, `ctx`) to memories that were already invalidated, superseded, or expired; proactive linking and consolidation now link only to memories recall can still return, so the graph stops growing toward retired memories ([#14427](https://github.com/can1357/oh-my-pi/pull/14427) by [@tickernelz](https://github.com/tickernelz)).
10
+
5
11
  ## [18.5.1] - 2026-10-03
6
12
 
7
13
  ### Changed
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "type": "module",
3
3
  "name": "@oh-my-pi/pi-mnemopi",
4
- "version": "18.8.4",
4
+ "version": "18.8.5",
5
5
  "description": "Local SQLite memory engine for omp agents",
6
6
  "homepage": "https://omp.sh",
7
7
  "author": {
@@ -42,10 +42,10 @@
42
42
  "fmt": "oxfmt --no-error-on-unmatched-pattern 'src/**/*.{ts,tsx}' '{test,bench,examples,scripts}/**/*.ts' '*.ts'"
43
43
  },
44
44
  "dependencies": {
45
- "@oh-my-pi/pi-ai": "18.8.4",
46
- "@oh-my-pi/pi-catalog": "18.8.4",
47
- "@oh-my-pi/pi-natives": "18.8.4",
48
- "@oh-my-pi/pi-utils": "18.8.4"
45
+ "@oh-my-pi/pi-ai": "18.8.5",
46
+ "@oh-my-pi/pi-catalog": "18.8.5",
47
+ "@oh-my-pi/pi-natives": "18.8.5",
48
+ "@oh-my-pi/pi-utils": "18.8.5"
49
49
  },
50
50
  "peerDependencies": {
51
51
  "fastembed": "3.0.0",
@@ -98,14 +98,27 @@ interface EdgeRow {
98
98
  readonly timestamp: string | null;
99
99
  }
100
100
 
101
+ interface MemoryLivenessRow {
102
+ readonly id: string;
103
+ readonly live: number;
104
+ }
105
+
101
106
  const EXTRACT_FACTS_MAX_CONTENT_LEN = 4096;
102
107
  const MAX_FACTS_PER_MEMORY = 5;
103
108
  const DEFAULT_LINK_THRESHOLD = 0.35;
109
+ const LIVE_MEMORY_SQL = "superseded_by IS NULL AND (valid_until IS NULL OR valid_until > ?)";
104
110
 
105
111
  function nowIso(): string {
106
112
  return new Date().toISOString();
107
113
  }
108
114
 
115
+ function partitionByLiveness(rows: readonly MemoryLivenessRow[], live: Set<string>, retired: Set<string>): void {
116
+ for (const row of rows) {
117
+ if (row.live === 1) live.add(row.id);
118
+ else retired.add(row.id);
119
+ }
120
+ }
121
+
109
122
  function unique(values: Iterable<string>, limit = Number.MAX_SAFE_INTEGER): string[] {
110
123
  const seen = new Set<string>();
111
124
  const out: string[] = [];
@@ -518,7 +531,7 @@ export class EpisodicGraph {
518
531
  if (linkExisting) {
519
532
  const sourceTokens = contentTokenSet(content);
520
533
  for (const otherId of previousMemoryIds) {
521
- const otherContent = this.memoryContent(otherId);
534
+ const otherContent = this.memoryContent(otherId, timestamp);
522
535
  const lexicalScore = Math.round(jaccard(sourceTokens, contentTokenSet(otherContent)) * 1000) / 1000;
523
536
  let wroteCtxEdge = false;
524
537
  if (lexicalScore >= minLinkScore) {
@@ -646,33 +659,40 @@ export class EpisodicGraph {
646
659
  }
647
660
 
648
661
  private knownMemoryIds(exclude: string): string[] {
662
+ const now = nowIso();
649
663
  const ids = new Set<string>();
664
+ const live = new Set<string>();
665
+ const retired = new Set<string>();
650
666
  const gistRows = this.db
651
667
  .query("SELECT DISTINCT memory_id FROM gists WHERE memory_id IS NOT NULL AND memory_id != ?")
652
668
  .all(exclude) as { memory_id: string }[];
653
669
  for (const row of gistRows) ids.add(row.memory_id);
654
670
  try {
655
- const workingRows = this.db.query("SELECT id FROM working_memory WHERE id != ?").all(exclude) as {
656
- id: string;
657
- }[];
658
- for (const row of workingRows) ids.add(row.id);
671
+ const workingRows = this.db
672
+ .query(`SELECT id, ${LIVE_MEMORY_SQL} AS live FROM working_memory WHERE id != ?`)
673
+ .all(now, exclude) as MemoryLivenessRow[];
674
+ partitionByLiveness(workingRows, live, retired);
659
675
  } catch {
660
676
  // Standalone graph stores do not have Beam memory tables.
661
677
  }
662
678
  try {
663
- const episodicRows = this.db.query("SELECT id FROM episodic_memory WHERE id != ?").all(exclude) as {
664
- id: string;
665
- }[];
666
- for (const row of episodicRows) ids.add(row.id);
679
+ const episodicRows = this.db
680
+ .query(`SELECT id, ${LIVE_MEMORY_SQL} AS live FROM episodic_memory WHERE id != ?`)
681
+ .all(now, exclude) as MemoryLivenessRow[];
682
+ partitionByLiveness(episodicRows, live, retired);
667
683
  } catch {
668
684
  // Standalone graph stores do not have Beam memory tables.
669
685
  }
686
+ for (const id of live) ids.add(id);
687
+ for (const id of retired) if (!live.has(id)) ids.delete(id);
670
688
  return [...ids];
671
689
  }
672
690
 
673
- private memoryContent(memoryId: string): string {
691
+ private memoryContent(memoryId: string, now: string): string {
674
692
  try {
675
- const working = this.db.query("SELECT content FROM working_memory WHERE id = ?").get(memoryId) as {
693
+ const working = this.db
694
+ .query(`SELECT content FROM working_memory WHERE id = ? AND ${LIVE_MEMORY_SQL}`)
695
+ .get(memoryId, now) as {
676
696
  content: string;
677
697
  } | null;
678
698
  if (working !== null) return working.content;
@@ -680,7 +700,9 @@ export class EpisodicGraph {
680
700
  // Standalone EpisodicGraph users may not have Beam tables.
681
701
  }
682
702
  try {
683
- const episodic = this.db.query("SELECT content FROM episodic_memory WHERE id = ?").get(memoryId) as {
703
+ const episodic = this.db
704
+ .query(`SELECT content FROM episodic_memory WHERE id = ? AND ${LIVE_MEMORY_SQL}`)
705
+ .get(memoryId, now) as {
684
706
  content: string;
685
707
  } | null;
686
708
  if (episodic !== null) return episodic.content;