agent-working-memory 0.5.5 → 0.6.0

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.
Files changed (82) hide show
  1. package/README.md +428 -399
  2. package/dist/api/routes.d.ts.map +1 -1
  3. package/dist/api/routes.js +60 -5
  4. package/dist/api/routes.js.map +1 -1
  5. package/dist/cli.js +468 -68
  6. package/dist/cli.js.map +1 -1
  7. package/dist/coordination/index.d.ts +11 -0
  8. package/dist/coordination/index.d.ts.map +1 -0
  9. package/dist/coordination/index.js +39 -0
  10. package/dist/coordination/index.js.map +1 -0
  11. package/dist/coordination/mcp-tools.d.ts +8 -0
  12. package/dist/coordination/mcp-tools.d.ts.map +1 -0
  13. package/dist/coordination/mcp-tools.js +221 -0
  14. package/dist/coordination/mcp-tools.js.map +1 -0
  15. package/dist/coordination/routes.d.ts +9 -0
  16. package/dist/coordination/routes.d.ts.map +1 -0
  17. package/dist/coordination/routes.js +573 -0
  18. package/dist/coordination/routes.js.map +1 -0
  19. package/dist/coordination/schema.d.ts +12 -0
  20. package/dist/coordination/schema.d.ts.map +1 -0
  21. package/dist/coordination/schema.js +125 -0
  22. package/dist/coordination/schema.js.map +1 -0
  23. package/dist/coordination/schemas.d.ts +227 -0
  24. package/dist/coordination/schemas.d.ts.map +1 -0
  25. package/dist/coordination/schemas.js +125 -0
  26. package/dist/coordination/schemas.js.map +1 -0
  27. package/dist/coordination/stale.d.ts +27 -0
  28. package/dist/coordination/stale.d.ts.map +1 -0
  29. package/dist/coordination/stale.js +58 -0
  30. package/dist/coordination/stale.js.map +1 -0
  31. package/dist/engine/activation.d.ts.map +1 -1
  32. package/dist/engine/activation.js +119 -23
  33. package/dist/engine/activation.js.map +1 -1
  34. package/dist/engine/consolidation.d.ts.map +1 -1
  35. package/dist/engine/consolidation.js +27 -6
  36. package/dist/engine/consolidation.js.map +1 -1
  37. package/dist/index.js +100 -4
  38. package/dist/index.js.map +1 -1
  39. package/dist/mcp.js +149 -80
  40. package/dist/mcp.js.map +1 -1
  41. package/dist/storage/sqlite.d.ts +21 -0
  42. package/dist/storage/sqlite.d.ts.map +1 -1
  43. package/dist/storage/sqlite.js +331 -282
  44. package/dist/storage/sqlite.js.map +1 -1
  45. package/dist/types/engram.d.ts +24 -0
  46. package/dist/types/engram.d.ts.map +1 -1
  47. package/dist/types/engram.js.map +1 -1
  48. package/package.json +57 -55
  49. package/src/api/index.ts +3 -3
  50. package/src/api/routes.ts +600 -536
  51. package/src/cli.ts +850 -397
  52. package/src/coordination/index.ts +47 -0
  53. package/src/coordination/mcp-tools.ts +318 -0
  54. package/src/coordination/routes.ts +846 -0
  55. package/src/coordination/schema.ts +120 -0
  56. package/src/coordination/schemas.ts +155 -0
  57. package/src/coordination/stale.ts +97 -0
  58. package/src/core/decay.ts +63 -63
  59. package/src/core/embeddings.ts +88 -88
  60. package/src/core/hebbian.ts +93 -93
  61. package/src/core/index.ts +5 -5
  62. package/src/core/logger.ts +36 -36
  63. package/src/core/query-expander.ts +66 -66
  64. package/src/core/reranker.ts +101 -101
  65. package/src/engine/activation.ts +758 -656
  66. package/src/engine/connections.ts +103 -103
  67. package/src/engine/consolidation-scheduler.ts +125 -125
  68. package/src/engine/consolidation.ts +29 -6
  69. package/src/engine/eval.ts +102 -102
  70. package/src/engine/eviction.ts +101 -101
  71. package/src/engine/index.ts +8 -8
  72. package/src/engine/retraction.ts +100 -100
  73. package/src/engine/staging.ts +74 -74
  74. package/src/index.ts +208 -121
  75. package/src/mcp.ts +1093 -1013
  76. package/src/storage/index.ts +3 -3
  77. package/src/storage/sqlite.ts +1017 -963
  78. package/src/types/agent.ts +67 -67
  79. package/src/types/checkpoint.ts +46 -46
  80. package/src/types/engram.ts +245 -217
  81. package/src/types/eval.ts +100 -100
  82. package/src/types/index.ts +6 -6
@@ -1,103 +1,103 @@
1
- // Copyright 2026 Robert Winter / Complete Ideas
2
- // SPDX-License-Identifier: Apache-2.0
3
- /**
4
- * Connection Engine — discovers links between memories.
5
- *
6
- * Runs asynchronously. When a new engram is written, the connection
7
- * engine checks it against existing memories and forms association
8
- * edges where resonance exceeds a threshold.
9
- *
10
- * Connection memories are first-class engrams — they can themselves
11
- * activate and form higher-order connections, producing emergent
12
- * associative structure over time.
13
- */
14
-
15
- import type { EngramStore } from '../storage/sqlite.js';
16
- import type { ActivationEngine } from './activation.js';
17
- import type { Engram } from '../types/index.js';
18
-
19
- export class ConnectionEngine {
20
- private store: EngramStore;
21
- private engine: ActivationEngine;
22
- private threshold: number;
23
- private queue: string[] = [];
24
- private processing = false;
25
-
26
- constructor(
27
- store: EngramStore,
28
- engine: ActivationEngine,
29
- threshold: number = 0.7
30
- ) {
31
- this.store = store;
32
- this.engine = engine;
33
- this.threshold = threshold;
34
- }
35
-
36
- /**
37
- * Queue a newly written engram for connection discovery.
38
- */
39
- enqueue(engramId: string): void {
40
- this.queue.push(engramId);
41
- if (!this.processing) {
42
- this.processQueue();
43
- }
44
- }
45
-
46
- private async processQueue(): Promise<void> {
47
- this.processing = true;
48
-
49
- while (this.queue.length > 0) {
50
- const engramId = this.queue.shift()!;
51
- const engram = this.store.getEngram(engramId);
52
- if (!engram || engram.stage !== 'active') continue;
53
-
54
- try {
55
- await this.findConnections(engram);
56
- } catch {
57
- // Connection discovery is best-effort — don't crash the server
58
- }
59
- }
60
-
61
- this.processing = false;
62
- }
63
-
64
- /**
65
- * Find and create connections for a given engram.
66
- */
67
- private async findConnections(engram: Engram): Promise<void> {
68
- const results = await this.engine.activate({
69
- agentId: engram.agentId,
70
- context: `${engram.concept} ${engram.content}`,
71
- limit: 5,
72
- minScore: this.threshold,
73
- internal: true,
74
- });
75
-
76
- // Filter out self and already-connected engrams
77
- const existing = this.store.getAssociationsFor(engram.id);
78
- const existingIds = new Set(existing.map(a =>
79
- a.fromEngramId === engram.id ? a.toEngramId : a.fromEngramId
80
- ));
81
-
82
- for (const result of results) {
83
- if (result.engram.id === engram.id) continue;
84
- if (existingIds.has(result.engram.id)) continue;
85
-
86
- // Create a connection association
87
- this.store.upsertAssociation(
88
- engram.id,
89
- result.engram.id,
90
- result.score,
91
- 'connection'
92
- );
93
-
94
- // Bidirectional
95
- this.store.upsertAssociation(
96
- result.engram.id,
97
- engram.id,
98
- result.score,
99
- 'connection'
100
- );
101
- }
102
- }
103
- }
1
+ // Copyright 2026 Robert Winter / Complete Ideas
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Connection Engine — discovers links between memories.
5
+ *
6
+ * Runs asynchronously. When a new engram is written, the connection
7
+ * engine checks it against existing memories and forms association
8
+ * edges where resonance exceeds a threshold.
9
+ *
10
+ * Connection memories are first-class engrams — they can themselves
11
+ * activate and form higher-order connections, producing emergent
12
+ * associative structure over time.
13
+ */
14
+
15
+ import type { EngramStore } from '../storage/sqlite.js';
16
+ import type { ActivationEngine } from './activation.js';
17
+ import type { Engram } from '../types/index.js';
18
+
19
+ export class ConnectionEngine {
20
+ private store: EngramStore;
21
+ private engine: ActivationEngine;
22
+ private threshold: number;
23
+ private queue: string[] = [];
24
+ private processing = false;
25
+
26
+ constructor(
27
+ store: EngramStore,
28
+ engine: ActivationEngine,
29
+ threshold: number = 0.7
30
+ ) {
31
+ this.store = store;
32
+ this.engine = engine;
33
+ this.threshold = threshold;
34
+ }
35
+
36
+ /**
37
+ * Queue a newly written engram for connection discovery.
38
+ */
39
+ enqueue(engramId: string): void {
40
+ this.queue.push(engramId);
41
+ if (!this.processing) {
42
+ this.processQueue();
43
+ }
44
+ }
45
+
46
+ private async processQueue(): Promise<void> {
47
+ this.processing = true;
48
+
49
+ while (this.queue.length > 0) {
50
+ const engramId = this.queue.shift()!;
51
+ const engram = this.store.getEngram(engramId);
52
+ if (!engram || engram.stage !== 'active') continue;
53
+
54
+ try {
55
+ await this.findConnections(engram);
56
+ } catch {
57
+ // Connection discovery is best-effort — don't crash the server
58
+ }
59
+ }
60
+
61
+ this.processing = false;
62
+ }
63
+
64
+ /**
65
+ * Find and create connections for a given engram.
66
+ */
67
+ private async findConnections(engram: Engram): Promise<void> {
68
+ const results = await this.engine.activate({
69
+ agentId: engram.agentId,
70
+ context: `${engram.concept} ${engram.content}`,
71
+ limit: 5,
72
+ minScore: this.threshold,
73
+ internal: true,
74
+ });
75
+
76
+ // Filter out self and already-connected engrams
77
+ const existing = this.store.getAssociationsFor(engram.id);
78
+ const existingIds = new Set(existing.map(a =>
79
+ a.fromEngramId === engram.id ? a.toEngramId : a.fromEngramId
80
+ ));
81
+
82
+ for (const result of results) {
83
+ if (result.engram.id === engram.id) continue;
84
+ if (existingIds.has(result.engram.id)) continue;
85
+
86
+ // Create a connection association
87
+ this.store.upsertAssociation(
88
+ engram.id,
89
+ result.engram.id,
90
+ result.score,
91
+ 'connection'
92
+ );
93
+
94
+ // Bidirectional
95
+ this.store.upsertAssociation(
96
+ result.engram.id,
97
+ engram.id,
98
+ result.score,
99
+ 'connection'
100
+ );
101
+ }
102
+ }
103
+ }
@@ -1,125 +1,125 @@
1
- // Copyright 2026 Robert Winter / Complete Ideas
2
- // SPDX-License-Identifier: Apache-2.0
3
- /**
4
- * Consolidation Scheduler — automatically triggers sleep cycles.
5
- *
6
- * Four triggers:
7
- * 1. Idle — agent inactive >10min → full consolidation
8
- * 2. Volume — 50+ writes since last consolidation → full consolidation
9
- * 3. Time — 30min since last consolidation → full consolidation
10
- * 4. Adaptive — retrieval precision <0.4 → full consolidation
11
- *
12
- * Also provides mini-consolidation for restore (fire-and-forget, lightweight).
13
- * Checks every 30 seconds across all active agents.
14
- */
15
-
16
- import type { EngramStore } from '../storage/sqlite.js';
17
- import type { ConsolidationEngine } from './consolidation.js';
18
-
19
- const TICK_INTERVAL_MS = 30_000; // Check every 30s
20
- const IDLE_THRESHOLD_MS = 10 * 60_000; // 10 minutes
21
- const VOLUME_THRESHOLD = 50; // 50 writes
22
- const TIME_THRESHOLD_MS = 30 * 60_000; // 30 minutes
23
- const PRECISION_THRESHOLD = 0.4; // Below this triggers consolidation
24
-
25
- export class ConsolidationScheduler {
26
- private timer: ReturnType<typeof setInterval> | null = null;
27
- private running = false;
28
-
29
- constructor(
30
- private store: EngramStore,
31
- private consolidationEngine: ConsolidationEngine,
32
- ) {}
33
-
34
- start(): void {
35
- if (this.timer) return;
36
- this.timer = setInterval(() => this.tick(), TICK_INTERVAL_MS);
37
- console.log('ConsolidationScheduler started (30s tick)');
38
- }
39
-
40
- stop(): void {
41
- if (this.timer) {
42
- clearInterval(this.timer);
43
- this.timer = null;
44
- }
45
- console.log('ConsolidationScheduler stopped');
46
- }
47
-
48
- /**
49
- * Mini-consolidation — lightweight, called from restore path.
50
- * Only runs replay + strengthen (phases 1-2), skips heavy phases.
51
- */
52
- async runMiniConsolidation(agentId: string): Promise<void> {
53
- if (this.running) return;
54
- this.running = true;
55
- try {
56
- console.log(`[scheduler] mini-consolidation for ${agentId}`);
57
- await this.consolidationEngine.consolidate(agentId);
58
- this.store.markConsolidation(agentId, true);
59
- } catch (err) {
60
- console.error(`[scheduler] mini-consolidation failed for ${agentId}:`, err);
61
- } finally {
62
- this.running = false;
63
- }
64
- }
65
-
66
- private tick(): void {
67
- if (this.running) return;
68
-
69
- const agents = this.store.getActiveAgents();
70
- const now = Date.now();
71
-
72
- for (const agent of agents) {
73
- const idleMs = now - agent.lastActivityAt.getTime();
74
- const sinceConsolidation = agent.lastConsolidationAt
75
- ? now - agent.lastConsolidationAt.getTime()
76
- : Infinity;
77
-
78
- let trigger: string | null = null;
79
-
80
- // 1. Idle trigger — agent stopped writing/recalling >10min ago
81
- if (idleMs > IDLE_THRESHOLD_MS && sinceConsolidation > IDLE_THRESHOLD_MS) {
82
- trigger = `idle (${Math.round(idleMs / 60_000)}min)`;
83
- }
84
-
85
- // 2. Volume trigger — many writes accumulated
86
- if (!trigger && agent.writeCount >= VOLUME_THRESHOLD) {
87
- trigger = `volume (${agent.writeCount} writes)`;
88
- }
89
-
90
- // 3. Time trigger — been too long since last consolidation
91
- if (!trigger && sinceConsolidation > TIME_THRESHOLD_MS) {
92
- trigger = `time (${Math.round(sinceConsolidation / 60_000)}min)`;
93
- }
94
-
95
- // 4. Adaptive trigger — precision is low
96
- if (!trigger) {
97
- try {
98
- const precision = this.store.getRetrievalPrecision(agent.agentId, 1);
99
- if (precision > 0 && precision < PRECISION_THRESHOLD) {
100
- trigger = `adaptive (precision ${(precision * 100).toFixed(0)}%)`;
101
- }
102
- } catch { /* precision check is non-fatal */ }
103
- }
104
-
105
- if (trigger) {
106
- this.runFullConsolidation(agent.agentId, trigger);
107
- return; // One consolidation per tick to avoid overload
108
- }
109
- }
110
- }
111
-
112
- private async runFullConsolidation(agentId: string, reason: string): Promise<void> {
113
- this.running = true;
114
- try {
115
- console.log(`[scheduler] full consolidation for ${agentId} — trigger: ${reason}`);
116
- const result = await this.consolidationEngine.consolidate(agentId);
117
- this.store.markConsolidation(agentId, false);
118
- console.log(`[scheduler] consolidation done: ${result.edgesStrengthened} strengthened, ${result.memoriesForgotten} forgotten`);
119
- } catch (err) {
120
- console.error(`[scheduler] consolidation failed for ${agentId}:`, err);
121
- } finally {
122
- this.running = false;
123
- }
124
- }
125
- }
1
+ // Copyright 2026 Robert Winter / Complete Ideas
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Consolidation Scheduler — automatically triggers sleep cycles.
5
+ *
6
+ * Four triggers:
7
+ * 1. Idle — agent inactive >10min → full consolidation
8
+ * 2. Volume — 50+ writes since last consolidation → full consolidation
9
+ * 3. Time — 30min since last consolidation → full consolidation
10
+ * 4. Adaptive — retrieval precision <0.4 → full consolidation
11
+ *
12
+ * Also provides mini-consolidation for restore (fire-and-forget, lightweight).
13
+ * Checks every 30 seconds across all active agents.
14
+ */
15
+
16
+ import type { EngramStore } from '../storage/sqlite.js';
17
+ import type { ConsolidationEngine } from './consolidation.js';
18
+
19
+ const TICK_INTERVAL_MS = 30_000; // Check every 30s
20
+ const IDLE_THRESHOLD_MS = 10 * 60_000; // 10 minutes
21
+ const VOLUME_THRESHOLD = 50; // 50 writes
22
+ const TIME_THRESHOLD_MS = 30 * 60_000; // 30 minutes
23
+ const PRECISION_THRESHOLD = 0.4; // Below this triggers consolidation
24
+
25
+ export class ConsolidationScheduler {
26
+ private timer: ReturnType<typeof setInterval> | null = null;
27
+ private running = false;
28
+
29
+ constructor(
30
+ private store: EngramStore,
31
+ private consolidationEngine: ConsolidationEngine,
32
+ ) {}
33
+
34
+ start(): void {
35
+ if (this.timer) return;
36
+ this.timer = setInterval(() => this.tick(), TICK_INTERVAL_MS);
37
+ console.log('ConsolidationScheduler started (30s tick)');
38
+ }
39
+
40
+ stop(): void {
41
+ if (this.timer) {
42
+ clearInterval(this.timer);
43
+ this.timer = null;
44
+ }
45
+ console.log('ConsolidationScheduler stopped');
46
+ }
47
+
48
+ /**
49
+ * Mini-consolidation — lightweight, called from restore path.
50
+ * Only runs replay + strengthen (phases 1-2), skips heavy phases.
51
+ */
52
+ async runMiniConsolidation(agentId: string): Promise<void> {
53
+ if (this.running) return;
54
+ this.running = true;
55
+ try {
56
+ console.log(`[scheduler] mini-consolidation for ${agentId}`);
57
+ await this.consolidationEngine.consolidate(agentId);
58
+ this.store.markConsolidation(agentId, true);
59
+ } catch (err) {
60
+ console.error(`[scheduler] mini-consolidation failed for ${agentId}:`, err);
61
+ } finally {
62
+ this.running = false;
63
+ }
64
+ }
65
+
66
+ private tick(): void {
67
+ if (this.running) return;
68
+
69
+ const agents = this.store.getActiveAgents();
70
+ const now = Date.now();
71
+
72
+ for (const agent of agents) {
73
+ const idleMs = now - agent.lastActivityAt.getTime();
74
+ const sinceConsolidation = agent.lastConsolidationAt
75
+ ? now - agent.lastConsolidationAt.getTime()
76
+ : Infinity;
77
+
78
+ let trigger: string | null = null;
79
+
80
+ // 1. Idle trigger — agent stopped writing/recalling >10min ago
81
+ if (idleMs > IDLE_THRESHOLD_MS && sinceConsolidation > IDLE_THRESHOLD_MS) {
82
+ trigger = `idle (${Math.round(idleMs / 60_000)}min)`;
83
+ }
84
+
85
+ // 2. Volume trigger — many writes accumulated
86
+ if (!trigger && agent.writeCount >= VOLUME_THRESHOLD) {
87
+ trigger = `volume (${agent.writeCount} writes)`;
88
+ }
89
+
90
+ // 3. Time trigger — been too long since last consolidation
91
+ if (!trigger && sinceConsolidation > TIME_THRESHOLD_MS) {
92
+ trigger = `time (${Math.round(sinceConsolidation / 60_000)}min)`;
93
+ }
94
+
95
+ // 4. Adaptive trigger — precision is low
96
+ if (!trigger) {
97
+ try {
98
+ const precision = this.store.getRetrievalPrecision(agent.agentId, 1);
99
+ if (precision > 0 && precision < PRECISION_THRESHOLD) {
100
+ trigger = `adaptive (precision ${(precision * 100).toFixed(0)}%)`;
101
+ }
102
+ } catch { /* precision check is non-fatal */ }
103
+ }
104
+
105
+ if (trigger) {
106
+ this.runFullConsolidation(agent.agentId, trigger);
107
+ return; // One consolidation per tick to avoid overload
108
+ }
109
+ }
110
+ }
111
+
112
+ private async runFullConsolidation(agentId: string, reason: string): Promise<void> {
113
+ this.running = true;
114
+ try {
115
+ console.log(`[scheduler] full consolidation for ${agentId} — trigger: ${reason}`);
116
+ const result = await this.consolidationEngine.consolidate(agentId);
117
+ this.store.markConsolidation(agentId, false);
118
+ console.log(`[scheduler] consolidation done: ${result.edgesStrengthened} strengthened, ${result.memoriesForgotten} forgotten`);
119
+ } catch (err) {
120
+ console.error(`[scheduler] consolidation failed for ${agentId}:`, err);
121
+ } finally {
122
+ this.running = false;
123
+ }
124
+ }
125
+ }
@@ -68,11 +68,15 @@ const FORGET_ARCHIVE_DAYS = 30;
68
68
  /** Age at which archived, never-retrieved, unconnected memories get deleted (days) */
69
69
  const FORGET_DELETE_DAYS = 90;
70
70
 
71
- /** Cosine similarity above which two low-confidence memories are considered redundant */
72
- const REDUNDANCY_THRESHOLD = 0.85;
71
+ /** Cosine similarity above which two low-confidence memories are considered redundant.
72
+ * MiniLM-L6 paraphrases typically score 0.75-0.88 cosine; 0.85 misses most of them.
73
+ * 0.75 catches paraphrases while keeping precision above 0.60 for unrelated facts. */
74
+ const REDUNDANCY_THRESHOLD = 0.75;
73
75
 
74
- /** Max redundant memories to prune per cycle (gradual, not sudden) */
75
- const MAX_REDUNDANCY_PRUNE_PER_CYCLE = 10;
76
+ /** Max redundant memories to prune per cycle (gradual, not sudden).
77
+ * Raised from 10 to 25 — the eval harness runs multiple cycles anyway,
78
+ * but faster convergence reduces consolidation time for larger pools. */
79
+ const MAX_REDUNDANCY_PRUNE_PER_CYCLE = 25;
76
80
 
77
81
  /** Max confidence drift per consolidation cycle (prevents runaway) */
78
82
  const CONFIDENCE_DRIFT_CAP = 0.03;
@@ -404,9 +408,28 @@ export class ConsolidationEngine {
404
408
 
405
409
  const sim = cosineSimilarity(sortedLow[i].embedding!, sortedLow[j].embedding!);
406
410
  if (sim >= REDUNDANCY_THRESHOLD) {
411
+ const survivorId = sortedLow[i].id;
412
+ const prunedId = sortedLow[j].id;
413
+
414
+ // Transfer associations from pruned memory to survivor
415
+ const prunedEdges = this.store.getAssociationsFor(prunedId);
416
+ for (const edge of prunedEdges) {
417
+ const peerId = edge.fromEngramId === prunedId ? edge.toEngramId : edge.fromEngramId;
418
+ if (peerId === survivorId) continue; // Skip self-loops
419
+ this.store.upsertAssociation(survivorId, peerId, edge.weight, edge.type, edge.confidence);
420
+ }
421
+
422
+ // Merge tags from pruned to survivor
423
+ const survivor = sortedLow[i];
424
+ const prunedMem = sortedLow[j];
425
+ const mergedTags = [...new Set([...survivor.tags, ...prunedMem.tags])];
426
+ if (mergedTags.length > survivor.tags.length) {
427
+ this.store.updateTags(survivorId, mergedTags);
428
+ }
429
+
407
430
  // Archive the lower-quality duplicate
408
- this.store.updateStage(sortedLow[j].id, 'archived');
409
- pruned.add(sortedLow[j].id);
431
+ this.store.updateStage(prunedId, 'archived');
432
+ pruned.add(prunedId);
410
433
  redundancyCount++;
411
434
  }
412
435
  }