agent-working-memory 0.5.4 → 0.5.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.
Files changed (71) hide show
  1. package/README.md +87 -46
  2. package/dist/api/routes.d.ts.map +1 -1
  3. package/dist/api/routes.js +21 -5
  4. package/dist/api/routes.js.map +1 -1
  5. package/dist/cli.js +67 -67
  6. package/dist/coordination/index.d.ts +11 -0
  7. package/dist/coordination/index.d.ts.map +1 -0
  8. package/dist/coordination/index.js +39 -0
  9. package/dist/coordination/index.js.map +1 -0
  10. package/dist/coordination/mcp-tools.d.ts +8 -0
  11. package/dist/coordination/mcp-tools.d.ts.map +1 -0
  12. package/dist/coordination/mcp-tools.js +216 -0
  13. package/dist/coordination/mcp-tools.js.map +1 -0
  14. package/dist/coordination/routes.d.ts +9 -0
  15. package/dist/coordination/routes.d.ts.map +1 -0
  16. package/dist/coordination/routes.js +434 -0
  17. package/dist/coordination/routes.js.map +1 -0
  18. package/dist/coordination/schema.d.ts +12 -0
  19. package/dist/coordination/schema.d.ts.map +1 -0
  20. package/dist/coordination/schema.js +91 -0
  21. package/dist/coordination/schema.js.map +1 -0
  22. package/dist/coordination/schemas.d.ts +208 -0
  23. package/dist/coordination/schemas.d.ts.map +1 -0
  24. package/dist/coordination/schemas.js +109 -0
  25. package/dist/coordination/schemas.js.map +1 -0
  26. package/dist/coordination/stale.d.ts +25 -0
  27. package/dist/coordination/stale.d.ts.map +1 -0
  28. package/dist/coordination/stale.js +53 -0
  29. package/dist/coordination/stale.js.map +1 -0
  30. package/dist/index.js +21 -3
  31. package/dist/index.js.map +1 -1
  32. package/dist/mcp.js +90 -79
  33. package/dist/mcp.js.map +1 -1
  34. package/dist/storage/sqlite.d.ts +3 -0
  35. package/dist/storage/sqlite.d.ts.map +1 -1
  36. package/dist/storage/sqlite.js +285 -281
  37. package/dist/storage/sqlite.js.map +1 -1
  38. package/package.json +55 -55
  39. package/src/api/index.ts +3 -3
  40. package/src/api/routes.ts +551 -536
  41. package/src/cli.ts +397 -397
  42. package/src/coordination/index.ts +47 -0
  43. package/src/coordination/mcp-tools.ts +313 -0
  44. package/src/coordination/routes.ts +656 -0
  45. package/src/coordination/schema.ts +94 -0
  46. package/src/coordination/schemas.ts +136 -0
  47. package/src/coordination/stale.ts +89 -0
  48. package/src/core/decay.ts +63 -63
  49. package/src/core/embeddings.ts +88 -88
  50. package/src/core/hebbian.ts +93 -93
  51. package/src/core/index.ts +5 -5
  52. package/src/core/logger.ts +36 -36
  53. package/src/core/query-expander.ts +66 -66
  54. package/src/core/reranker.ts +101 -101
  55. package/src/engine/activation.ts +656 -656
  56. package/src/engine/connections.ts +103 -103
  57. package/src/engine/consolidation-scheduler.ts +125 -125
  58. package/src/engine/eval.ts +102 -102
  59. package/src/engine/eviction.ts +101 -101
  60. package/src/engine/index.ts +8 -8
  61. package/src/engine/retraction.ts +100 -100
  62. package/src/engine/staging.ts +74 -74
  63. package/src/index.ts +137 -121
  64. package/src/mcp.ts +1024 -1013
  65. package/src/storage/index.ts +3 -3
  66. package/src/storage/sqlite.ts +968 -963
  67. package/src/types/agent.ts +67 -67
  68. package/src/types/checkpoint.ts +46 -46
  69. package/src/types/engram.ts +217 -217
  70. package/src/types/eval.ts +100 -100
  71. package/src/types/index.ts +6 -6
@@ -1,101 +1,101 @@
1
- // Copyright 2026 Robert Winter / Complete Ideas
2
- // SPDX-License-Identifier: Apache-2.0
3
- /**
4
- * Eviction Engine — capacity enforcement and edge pruning.
5
- *
6
- * When memory budgets are exceeded:
7
- * 1. Archive lowest-value active engrams
8
- * 2. Delete expired staging engrams
9
- * 3. Prune weakest edges when per-engram cap exceeded
10
- * 4. Decay unused association weights over time
11
- */
12
-
13
- import type { EngramStore } from '../storage/sqlite.js';
14
- import type { AgentConfig } from '../types/agent.js';
15
- import { decayAssociation } from '../core/hebbian.js';
16
-
17
- export class EvictionEngine {
18
- private store: EngramStore;
19
-
20
- constructor(store: EngramStore) {
21
- this.store = store;
22
- }
23
-
24
- /**
25
- * Check capacity budgets and evict if needed.
26
- * Returns count of evicted engrams.
27
- */
28
- enforceCapacity(agentId: string, config: AgentConfig): { evicted: number; edgesPruned: number } {
29
- let evicted = 0;
30
- let edgesPruned = 0;
31
-
32
- // Active engram budget
33
- const activeCount = this.store.getActiveCount(agentId);
34
- if (activeCount > config.maxActiveEngrams) {
35
- const excess = activeCount - config.maxActiveEngrams;
36
- const candidates = this.store.getEvictionCandidates(agentId, excess);
37
- for (const engram of candidates) {
38
- this.store.updateStage(engram.id, 'archived');
39
- evicted++;
40
- }
41
- }
42
-
43
- // Staging budget
44
- const stagingCount = this.store.getStagingCount(agentId);
45
- if (stagingCount > config.maxStagingEngrams) {
46
- const expired = this.store.getExpiredStaging();
47
- for (const engram of expired) {
48
- this.store.deleteEngram(engram.id);
49
- evicted++;
50
- }
51
- }
52
-
53
- // Edge pruning — cap per engram
54
- const engrams = this.store.getEngramsByAgent(agentId, 'active');
55
- for (const engram of engrams) {
56
- const edgeCount = this.store.countAssociationsFor(engram.id);
57
- if (edgeCount > config.maxEdgesPerEngram) {
58
- // Remove weakest edges until under cap
59
- let toRemove = edgeCount - config.maxEdgesPerEngram;
60
- while (toRemove > 0) {
61
- const weakest = this.store.getWeakestAssociation(engram.id);
62
- if (weakest) {
63
- this.store.deleteAssociation(weakest.id);
64
- edgesPruned++;
65
- }
66
- toRemove--;
67
- }
68
- }
69
- }
70
-
71
- return { evicted, edgesPruned };
72
- }
73
-
74
- /**
75
- * Decay all association weights based on time since last activation.
76
- * Run periodically (e.g., daily).
77
- */
78
- decayEdges(agentId: string, halfLifeDays: number = 7): number {
79
- const associations = this.store.getAllAssociations(agentId);
80
- let decayed = 0;
81
-
82
- for (const assoc of associations) {
83
- const daysSince = (Date.now() - assoc.lastActivated.getTime()) / (1000 * 60 * 60 * 24);
84
- if (daysSince < 0.5) continue; // Skip recently activated
85
-
86
- const newWeight = decayAssociation(assoc.weight, daysSince, halfLifeDays);
87
- if (newWeight < 0.01) {
88
- // Below minimum useful weight — prune
89
- this.store.deleteAssociation(assoc.id);
90
- decayed++;
91
- } else if (Math.abs(newWeight - assoc.weight) > 0.001) {
92
- this.store.upsertAssociation(
93
- assoc.fromEngramId, assoc.toEngramId, newWeight, assoc.type, assoc.confidence
94
- );
95
- decayed++;
96
- }
97
- }
98
-
99
- return decayed;
100
- }
101
- }
1
+ // Copyright 2026 Robert Winter / Complete Ideas
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Eviction Engine — capacity enforcement and edge pruning.
5
+ *
6
+ * When memory budgets are exceeded:
7
+ * 1. Archive lowest-value active engrams
8
+ * 2. Delete expired staging engrams
9
+ * 3. Prune weakest edges when per-engram cap exceeded
10
+ * 4. Decay unused association weights over time
11
+ */
12
+
13
+ import type { EngramStore } from '../storage/sqlite.js';
14
+ import type { AgentConfig } from '../types/agent.js';
15
+ import { decayAssociation } from '../core/hebbian.js';
16
+
17
+ export class EvictionEngine {
18
+ private store: EngramStore;
19
+
20
+ constructor(store: EngramStore) {
21
+ this.store = store;
22
+ }
23
+
24
+ /**
25
+ * Check capacity budgets and evict if needed.
26
+ * Returns count of evicted engrams.
27
+ */
28
+ enforceCapacity(agentId: string, config: AgentConfig): { evicted: number; edgesPruned: number } {
29
+ let evicted = 0;
30
+ let edgesPruned = 0;
31
+
32
+ // Active engram budget
33
+ const activeCount = this.store.getActiveCount(agentId);
34
+ if (activeCount > config.maxActiveEngrams) {
35
+ const excess = activeCount - config.maxActiveEngrams;
36
+ const candidates = this.store.getEvictionCandidates(agentId, excess);
37
+ for (const engram of candidates) {
38
+ this.store.updateStage(engram.id, 'archived');
39
+ evicted++;
40
+ }
41
+ }
42
+
43
+ // Staging budget
44
+ const stagingCount = this.store.getStagingCount(agentId);
45
+ if (stagingCount > config.maxStagingEngrams) {
46
+ const expired = this.store.getExpiredStaging();
47
+ for (const engram of expired) {
48
+ this.store.deleteEngram(engram.id);
49
+ evicted++;
50
+ }
51
+ }
52
+
53
+ // Edge pruning — cap per engram
54
+ const engrams = this.store.getEngramsByAgent(agentId, 'active');
55
+ for (const engram of engrams) {
56
+ const edgeCount = this.store.countAssociationsFor(engram.id);
57
+ if (edgeCount > config.maxEdgesPerEngram) {
58
+ // Remove weakest edges until under cap
59
+ let toRemove = edgeCount - config.maxEdgesPerEngram;
60
+ while (toRemove > 0) {
61
+ const weakest = this.store.getWeakestAssociation(engram.id);
62
+ if (weakest) {
63
+ this.store.deleteAssociation(weakest.id);
64
+ edgesPruned++;
65
+ }
66
+ toRemove--;
67
+ }
68
+ }
69
+ }
70
+
71
+ return { evicted, edgesPruned };
72
+ }
73
+
74
+ /**
75
+ * Decay all association weights based on time since last activation.
76
+ * Run periodically (e.g., daily).
77
+ */
78
+ decayEdges(agentId: string, halfLifeDays: number = 7): number {
79
+ const associations = this.store.getAllAssociations(agentId);
80
+ let decayed = 0;
81
+
82
+ for (const assoc of associations) {
83
+ const daysSince = (Date.now() - assoc.lastActivated.getTime()) / (1000 * 60 * 60 * 24);
84
+ if (daysSince < 0.5) continue; // Skip recently activated
85
+
86
+ const newWeight = decayAssociation(assoc.weight, daysSince, halfLifeDays);
87
+ if (newWeight < 0.01) {
88
+ // Below minimum useful weight — prune
89
+ this.store.deleteAssociation(assoc.id);
90
+ decayed++;
91
+ } else if (Math.abs(newWeight - assoc.weight) > 0.001) {
92
+ this.store.upsertAssociation(
93
+ assoc.fromEngramId, assoc.toEngramId, newWeight, assoc.type, assoc.confidence
94
+ );
95
+ decayed++;
96
+ }
97
+ }
98
+
99
+ return decayed;
100
+ }
101
+ }
@@ -1,8 +1,8 @@
1
- // Copyright 2026 Robert Winter / Complete Ideas
2
- // SPDX-License-Identifier: Apache-2.0
3
- export * from './activation.js';
4
- export * from './staging.js';
5
- export * from './connections.js';
6
- export * from './eviction.js';
7
- export * from './retraction.js';
8
- export * from './eval.js';
1
+ // Copyright 2026 Robert Winter / Complete Ideas
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ export * from './activation.js';
4
+ export * from './staging.js';
5
+ export * from './connections.js';
6
+ export * from './eviction.js';
7
+ export * from './retraction.js';
8
+ export * from './eval.js';
@@ -1,100 +1,100 @@
1
- // Copyright 2026 Robert Winter / Complete Ideas
2
- // SPDX-License-Identifier: Apache-2.0
3
- /**
4
- * Retraction Engine — negative memory / invalidation.
5
- *
6
- * Codex critique: "You need explicit anti-salience for wrong info.
7
- * Otherwise wrong memories persist and compound mistakes."
8
- *
9
- * When an agent discovers a memory is wrong:
10
- * 1. The original engram is marked retracted (not deleted — audit trail)
11
- * 2. An invalidation association is created
12
- * 3. Optionally, a counter-engram with correct info is created
13
- * 4. Confidence of associated engrams is reduced (contamination check)
14
- */
15
-
16
- import type { EngramStore } from '../storage/sqlite.js';
17
- import type { Retraction } from '../types/index.js';
18
-
19
- export class RetractionEngine {
20
- private store: EngramStore;
21
-
22
- constructor(store: EngramStore) {
23
- this.store = store;
24
- }
25
-
26
- /**
27
- * Retract a memory — mark it invalid and optionally create a correction.
28
- */
29
- retract(retraction: Retraction): { retractedId: string; correctionId: string | null; associatesAffected: number } {
30
- const target = this.store.getEngram(retraction.targetEngramId);
31
- if (!target) {
32
- throw new Error(`Engram ${retraction.targetEngramId} not found`);
33
- }
34
-
35
- // Mark the original as retracted
36
- this.store.retractEngram(target.id, null);
37
-
38
- let correctionId: string | null = null;
39
-
40
- // Create counter-engram if correction content provided
41
- if (retraction.counterContent) {
42
- const correction = this.store.createEngram({
43
- agentId: retraction.agentId,
44
- concept: `correction:${target.concept}`,
45
- content: retraction.counterContent,
46
- tags: [...target.tags, 'correction', 'retraction'],
47
- salience: Math.max(target.salience, 0.6), // Corrections are at least moderately salient
48
- confidence: 0.7,
49
- reasonCodes: ['retraction_correction', `invalidates:${target.id}`],
50
- });
51
-
52
- correctionId = correction.id;
53
-
54
- // Create invalidation link
55
- this.store.upsertAssociation(
56
- correction.id, target.id, 1.0, 'invalidation', 1.0
57
- );
58
-
59
- // Update retracted_by to point to correction
60
- this.store.retractEngram(target.id, correction.id);
61
- }
62
-
63
- // Reduce confidence of closely associated engrams (contamination spread)
64
- const associatesAffected = this.propagateConfidenceReduction(target.id, 0.1, 1);
65
-
66
- return { retractedId: target.id, correctionId, associatesAffected };
67
- }
68
-
69
- /**
70
- * Reduce confidence of engrams associated with a retracted engram.
71
- * Shallow propagation (depth 1) to avoid over-penalizing.
72
- */
73
- private propagateConfidenceReduction(
74
- engramId: string,
75
- penalty: number,
76
- maxDepth: number,
77
- currentDepth: number = 0
78
- ): number {
79
- if (currentDepth >= maxDepth) return 0;
80
-
81
- let affected = 0;
82
- const associations = this.store.getAssociationsFor(engramId);
83
- for (const assoc of associations) {
84
- if (assoc.type === 'invalidation') continue; // Don't penalize corrections
85
-
86
- const neighborId = assoc.fromEngramId === engramId
87
- ? assoc.toEngramId
88
- : assoc.fromEngramId;
89
- const neighbor = this.store.getEngram(neighborId);
90
- if (!neighbor || neighbor.retracted) continue;
91
-
92
- // Scale penalty by association weight
93
- const scaledPenalty = penalty * assoc.weight;
94
- const newConfidence = Math.max(0.1, neighbor.confidence - scaledPenalty);
95
- this.store.updateConfidence(neighborId, newConfidence);
96
- affected++;
97
- }
98
- return affected;
99
- }
100
- }
1
+ // Copyright 2026 Robert Winter / Complete Ideas
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Retraction Engine — negative memory / invalidation.
5
+ *
6
+ * Codex critique: "You need explicit anti-salience for wrong info.
7
+ * Otherwise wrong memories persist and compound mistakes."
8
+ *
9
+ * When an agent discovers a memory is wrong:
10
+ * 1. The original engram is marked retracted (not deleted — audit trail)
11
+ * 2. An invalidation association is created
12
+ * 3. Optionally, a counter-engram with correct info is created
13
+ * 4. Confidence of associated engrams is reduced (contamination check)
14
+ */
15
+
16
+ import type { EngramStore } from '../storage/sqlite.js';
17
+ import type { Retraction } from '../types/index.js';
18
+
19
+ export class RetractionEngine {
20
+ private store: EngramStore;
21
+
22
+ constructor(store: EngramStore) {
23
+ this.store = store;
24
+ }
25
+
26
+ /**
27
+ * Retract a memory — mark it invalid and optionally create a correction.
28
+ */
29
+ retract(retraction: Retraction): { retractedId: string; correctionId: string | null; associatesAffected: number } {
30
+ const target = this.store.getEngram(retraction.targetEngramId);
31
+ if (!target) {
32
+ throw new Error(`Engram ${retraction.targetEngramId} not found`);
33
+ }
34
+
35
+ // Mark the original as retracted
36
+ this.store.retractEngram(target.id, null);
37
+
38
+ let correctionId: string | null = null;
39
+
40
+ // Create counter-engram if correction content provided
41
+ if (retraction.counterContent) {
42
+ const correction = this.store.createEngram({
43
+ agentId: retraction.agentId,
44
+ concept: `correction:${target.concept}`,
45
+ content: retraction.counterContent,
46
+ tags: [...target.tags, 'correction', 'retraction'],
47
+ salience: Math.max(target.salience, 0.6), // Corrections are at least moderately salient
48
+ confidence: 0.7,
49
+ reasonCodes: ['retraction_correction', `invalidates:${target.id}`],
50
+ });
51
+
52
+ correctionId = correction.id;
53
+
54
+ // Create invalidation link
55
+ this.store.upsertAssociation(
56
+ correction.id, target.id, 1.0, 'invalidation', 1.0
57
+ );
58
+
59
+ // Update retracted_by to point to correction
60
+ this.store.retractEngram(target.id, correction.id);
61
+ }
62
+
63
+ // Reduce confidence of closely associated engrams (contamination spread)
64
+ const associatesAffected = this.propagateConfidenceReduction(target.id, 0.1, 1);
65
+
66
+ return { retractedId: target.id, correctionId, associatesAffected };
67
+ }
68
+
69
+ /**
70
+ * Reduce confidence of engrams associated with a retracted engram.
71
+ * Shallow propagation (depth 1) to avoid over-penalizing.
72
+ */
73
+ private propagateConfidenceReduction(
74
+ engramId: string,
75
+ penalty: number,
76
+ maxDepth: number,
77
+ currentDepth: number = 0
78
+ ): number {
79
+ if (currentDepth >= maxDepth) return 0;
80
+
81
+ let affected = 0;
82
+ const associations = this.store.getAssociationsFor(engramId);
83
+ for (const assoc of associations) {
84
+ if (assoc.type === 'invalidation') continue; // Don't penalize corrections
85
+
86
+ const neighborId = assoc.fromEngramId === engramId
87
+ ? assoc.toEngramId
88
+ : assoc.fromEngramId;
89
+ const neighbor = this.store.getEngram(neighborId);
90
+ if (!neighbor || neighbor.retracted) continue;
91
+
92
+ // Scale penalty by association weight
93
+ const scaledPenalty = penalty * assoc.weight;
94
+ const newConfidence = Math.max(0.1, neighbor.confidence - scaledPenalty);
95
+ this.store.updateConfidence(neighborId, newConfidence);
96
+ affected++;
97
+ }
98
+ return affected;
99
+ }
100
+ }
@@ -1,74 +1,74 @@
1
- // Copyright 2026 Robert Winter / Complete Ideas
2
- // SPDX-License-Identifier: Apache-2.0
3
- /**
4
- * Staging Buffer — weak signal handler.
5
- *
6
- * Observations that don't meet the salience threshold for active memory
7
- * go to staging. The staging buffer periodically:
8
- * 1. Checks staged engrams against active memory for resonance
9
- * 2. Promotes resonant engrams to active
10
- * 3. Discards expired engrams that never resonated
11
- *
12
- * Modeled on hippocampal consolidation — provisional encoding
13
- * that only persists if reactivated.
14
- */
15
-
16
- import type { EngramStore } from '../storage/sqlite.js';
17
- import type { ActivationEngine } from './activation.js';
18
-
19
- export class StagingBuffer {
20
- private store: EngramStore;
21
- private engine: ActivationEngine;
22
- private checkInterval: ReturnType<typeof setInterval> | null = null;
23
-
24
- constructor(store: EngramStore, engine: ActivationEngine) {
25
- this.store = store;
26
- this.engine = engine;
27
- }
28
-
29
- /**
30
- * Start the periodic staging check.
31
- */
32
- start(intervalMs: number = 60_000): void {
33
- this.checkInterval = setInterval(() => this.sweep(), intervalMs);
34
- }
35
-
36
- stop(): void {
37
- if (this.checkInterval) {
38
- clearInterval(this.checkInterval);
39
- this.checkInterval = null;
40
- }
41
- }
42
-
43
- /**
44
- * Sweep staged engrams: promote or discard.
45
- */
46
- async sweep(): Promise<{ promoted: string[]; discarded: string[] }> {
47
- const promoted: string[] = [];
48
- const discarded: string[] = [];
49
-
50
- const expired = this.store.getExpiredStaging();
51
- for (const engram of expired) {
52
- // Check if this engram resonates with active memory
53
- const results = await this.engine.activate({
54
- agentId: engram.agentId,
55
- context: `${engram.concept} ${engram.content}`,
56
- limit: 3,
57
- minScore: 0.3,
58
- internal: true,
59
- });
60
-
61
- if (results.length > 0) {
62
- // Resonance found — promote to active
63
- this.store.updateStage(engram.id, 'active');
64
- promoted.push(engram.id);
65
- } else {
66
- // No resonance — discard
67
- this.store.deleteEngram(engram.id);
68
- discarded.push(engram.id);
69
- }
70
- }
71
-
72
- return { promoted, discarded };
73
- }
74
- }
1
+ // Copyright 2026 Robert Winter / Complete Ideas
2
+ // SPDX-License-Identifier: Apache-2.0
3
+ /**
4
+ * Staging Buffer — weak signal handler.
5
+ *
6
+ * Observations that don't meet the salience threshold for active memory
7
+ * go to staging. The staging buffer periodically:
8
+ * 1. Checks staged engrams against active memory for resonance
9
+ * 2. Promotes resonant engrams to active
10
+ * 3. Discards expired engrams that never resonated
11
+ *
12
+ * Modeled on hippocampal consolidation — provisional encoding
13
+ * that only persists if reactivated.
14
+ */
15
+
16
+ import type { EngramStore } from '../storage/sqlite.js';
17
+ import type { ActivationEngine } from './activation.js';
18
+
19
+ export class StagingBuffer {
20
+ private store: EngramStore;
21
+ private engine: ActivationEngine;
22
+ private checkInterval: ReturnType<typeof setInterval> | null = null;
23
+
24
+ constructor(store: EngramStore, engine: ActivationEngine) {
25
+ this.store = store;
26
+ this.engine = engine;
27
+ }
28
+
29
+ /**
30
+ * Start the periodic staging check.
31
+ */
32
+ start(intervalMs: number = 60_000): void {
33
+ this.checkInterval = setInterval(() => this.sweep(), intervalMs);
34
+ }
35
+
36
+ stop(): void {
37
+ if (this.checkInterval) {
38
+ clearInterval(this.checkInterval);
39
+ this.checkInterval = null;
40
+ }
41
+ }
42
+
43
+ /**
44
+ * Sweep staged engrams: promote or discard.
45
+ */
46
+ async sweep(): Promise<{ promoted: string[]; discarded: string[] }> {
47
+ const promoted: string[] = [];
48
+ const discarded: string[] = [];
49
+
50
+ const expired = this.store.getExpiredStaging();
51
+ for (const engram of expired) {
52
+ // Check if this engram resonates with active memory
53
+ const results = await this.engine.activate({
54
+ agentId: engram.agentId,
55
+ context: `${engram.concept} ${engram.content}`,
56
+ limit: 3,
57
+ minScore: 0.3,
58
+ internal: true,
59
+ });
60
+
61
+ if (results.length > 0) {
62
+ // Resonance found — promote to active
63
+ this.store.updateStage(engram.id, 'active');
64
+ promoted.push(engram.id);
65
+ } else {
66
+ // No resonance — discard
67
+ this.store.deleteEngram(engram.id);
68
+ discarded.push(engram.id);
69
+ }
70
+ }
71
+
72
+ return { promoted, discarded };
73
+ }
74
+ }