agent-working-memory 0.4.3 → 0.5.2

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.
@@ -12,7 +12,7 @@ import { randomUUID } from 'node:crypto';
12
12
  import type {
13
13
  Engram, EngramCreate, EngramStage, Association, AssociationType,
14
14
  SearchQuery, SalienceFeatures, ActivationEvent, StagingEvent,
15
- RetrievalFeedbackEvent, Episode, TaskStatus, TaskPriority,
15
+ RetrievalFeedbackEvent, Episode, TaskStatus, TaskPriority, MemoryClass,
16
16
  ConsciousState, AutoCheckpoint, CheckpointRow,
17
17
  } from '../types/index.js';
18
18
 
@@ -172,6 +172,17 @@ export class EngramStore {
172
172
  this.db.exec('CREATE INDEX IF NOT EXISTS idx_engrams_task ON engrams(agent_id, task_status)');
173
173
  }
174
174
 
175
+ // Migration: add memory_class and supersession columns if missing
176
+ try {
177
+ this.db.prepare('SELECT memory_class FROM engrams LIMIT 0').get();
178
+ } catch {
179
+ this.db.exec(`
180
+ ALTER TABLE engrams ADD COLUMN memory_class TEXT NOT NULL DEFAULT 'working';
181
+ ALTER TABLE engrams ADD COLUMN superseded_by TEXT;
182
+ ALTER TABLE engrams ADD COLUMN supersedes TEXT;
183
+ `);
184
+ }
185
+
175
186
  // Migration: add conscious_state table for checkpointing
176
187
  this.db.exec(`
177
188
  CREATE TABLE IF NOT EXISTS conscious_state (
@@ -186,9 +197,15 @@ export class EngramStore {
186
197
  checkpoint_at TEXT,
187
198
  last_consolidation_at TEXT,
188
199
  last_mini_consolidation_at TEXT,
200
+ consolidation_cycle_count INTEGER NOT NULL DEFAULT 0,
189
201
  updated_at TEXT NOT NULL DEFAULT (datetime('now'))
190
202
  )
191
203
  `);
204
+
205
+ // Migration: add consolidation_cycle_count if missing (existing DBs)
206
+ try {
207
+ this.db.exec(`ALTER TABLE conscious_state ADD COLUMN consolidation_cycle_count INTEGER NOT NULL DEFAULT 0`);
208
+ } catch { /* column already exists */ }
192
209
  }
193
210
 
194
211
  // --- Engram CRUD ---
@@ -203,8 +220,8 @@ export class EngramStore {
203
220
  this.db.prepare(`
204
221
  INSERT INTO engrams (id, agent_id, concept, content, embedding, confidence, salience,
205
222
  access_count, last_accessed, created_at, salience_features, reason_codes, stage, tags, episode_id,
206
- ttl, task_status, task_priority, blocked_by)
207
- VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?)
223
+ ttl, memory_class, supersedes, task_status, task_priority, blocked_by)
224
+ VALUES (?, ?, ?, ?, ?, ?, ?, 0, ?, ?, ?, ?, 'active', ?, ?, ?, ?, ?, ?, ?, ?)
208
225
  `).run(
209
226
  id, input.agentId, input.concept, input.content, embeddingBlob,
210
227
  input.confidence ?? 0.5,
@@ -215,6 +232,8 @@ export class EngramStore {
215
232
  JSON.stringify(input.tags ?? []),
216
233
  input.episodeId ?? null,
217
234
  input.ttl ?? null,
235
+ input.memoryClass ?? 'working',
236
+ input.supersedes ?? null,
218
237
  input.taskStatus ?? null,
219
238
  input.taskPriority ?? null,
220
239
  input.blockedBy ?? null,
@@ -455,6 +474,29 @@ export class EngramStore {
455
474
  return row ? this.rowToEngram(row) : null;
456
475
  }
457
476
 
477
+ // --- Supersession ---
478
+
479
+ /**
480
+ * Mark an engram as superseded by another.
481
+ * The old memory stays in the DB (historical) but gets down-ranked in recall.
482
+ */
483
+ supersedeEngram(oldId: string, newId: string): void {
484
+ this.db.prepare('UPDATE engrams SET superseded_by = ? WHERE id = ?').run(newId, oldId);
485
+ this.db.prepare('UPDATE engrams SET supersedes = ? WHERE id = ?').run(oldId, newId);
486
+ }
487
+
488
+ /**
489
+ * Check if an engram has been superseded.
490
+ */
491
+ isSuperseded(id: string): boolean {
492
+ const row = this.db.prepare('SELECT superseded_by FROM engrams WHERE id = ?').get(id) as any;
493
+ return row?.superseded_by != null;
494
+ }
495
+
496
+ updateMemoryClass(id: string, memoryClass: MemoryClass): void {
497
+ this.db.prepare('UPDATE engrams SET memory_class = ? WHERE id = ?').run(memoryClass, id);
498
+ }
499
+
458
500
  // --- Associations ---
459
501
 
460
502
  upsertAssociation(
@@ -488,6 +530,13 @@ export class EngramStore {
488
530
  return (rows as any[]).map(r => this.rowToAssociation(r));
489
531
  }
490
532
 
533
+ getOutgoingAssociations(engramId: string): Association[] {
534
+ const rows = this.db.prepare(
535
+ 'SELECT * FROM associations WHERE from_engram_id = ?'
536
+ ).all(engramId);
537
+ return (rows as any[]).map(r => this.rowToAssociation(r));
538
+ }
539
+
491
540
  countAssociationsFor(engramId: string): number {
492
541
  const row = this.db.prepare(
493
542
  'SELECT COUNT(*) as count FROM associations WHERE from_engram_id = ?'
@@ -667,6 +716,9 @@ export class EngramStore {
667
716
  retractedAt: row.retracted_at ? new Date(row.retracted_at) : null,
668
717
  tags: JSON.parse(row.tags),
669
718
  episodeId: row.episode_id ?? null,
719
+ memoryClass: (row.memory_class ?? 'working') as MemoryClass,
720
+ supersededBy: row.superseded_by ?? null,
721
+ supersedes: row.supersedes ?? null,
670
722
  taskStatus: row.task_status ?? null,
671
723
  taskPriority: row.task_priority ?? null,
672
724
  blockedBy: row.blocked_by ?? null,
@@ -880,6 +932,7 @@ export class EngramStore {
880
932
  last_mini_consolidation_at = ?,
881
933
  write_count_since_consolidation = 0,
882
934
  recall_count_since_consolidation = 0,
935
+ consolidation_cycle_count = consolidation_cycle_count + 1,
883
936
  updated_at = ?
884
937
  WHERE agent_id = ?
885
938
  `).run(now, now, now, agentId);
@@ -897,6 +950,13 @@ export class EngramStore {
897
950
  }));
898
951
  }
899
952
 
953
+ getConsolidationCycleCount(agentId: string): number {
954
+ const row = this.db.prepare(
955
+ 'SELECT consolidation_cycle_count FROM conscious_state WHERE agent_id = ?',
956
+ ).get(agentId) as { consolidation_cycle_count: number } | undefined;
957
+ return row?.consolidation_cycle_count ?? 0;
958
+ }
959
+
900
960
  close(): void {
901
961
  this.db.close();
902
962
  }
@@ -40,6 +40,13 @@ export interface Engram {
40
40
  // Episode grouping
41
41
  episodeId: string | null;
42
42
 
43
+ // Memory class
44
+ memoryClass: MemoryClass;
45
+
46
+ // Supersession — "this replaces that" (not retraction — original wasn't wrong, just outdated)
47
+ supersededBy: string | null; // ID of the engram that replaced this one
48
+ supersedes: string | null; // ID of the engram this one replaces
49
+
43
50
  // Task management (null = not a task)
44
51
  taskStatus: TaskStatus | null;
45
52
  taskPriority: TaskPriority | null;
@@ -51,6 +58,18 @@ export type EngramStage = 'staging' | 'active' | 'consolidated' | 'archived';
51
58
  export type TaskStatus = 'open' | 'in_progress' | 'blocked' | 'done';
52
59
  export type TaskPriority = 'urgent' | 'high' | 'medium' | 'low';
53
60
 
61
+ /**
62
+ * Memory class — controls salience floor and recall priority.
63
+ *
64
+ * canonical: Source-of-truth facts (current state, decisions, architecture).
65
+ * Never goes to staging. Minimum salience 0.7.
66
+ * working: Normal observations, learnings, context (default).
67
+ * Standard salience rules apply.
68
+ * ephemeral: Temporary context (debugging traces, session-specific notes).
69
+ * Stronger time decay, lower recall priority.
70
+ */
71
+ export type MemoryClass = 'canonical' | 'working' | 'ephemeral';
72
+
54
73
  /**
55
74
  * Raw feature scores that produced the salience score.
56
75
  * Persisted for auditability and tuning.
@@ -75,6 +94,8 @@ export interface EngramCreate {
75
94
  reasonCodes?: string[];
76
95
  episodeId?: string;
77
96
  ttl?: number;
97
+ memoryClass?: MemoryClass;
98
+ supersedes?: string;
78
99
  taskStatus?: TaskStatus;
79
100
  taskPriority?: TaskPriority;
80
101
  blockedBy?: string;