agent-working-memory 0.3.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 (143) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +311 -0
  3. package/dist/api/index.d.ts +2 -0
  4. package/dist/api/index.d.ts.map +1 -0
  5. package/dist/api/index.js +2 -0
  6. package/dist/api/index.js.map +1 -0
  7. package/dist/api/routes.d.ts +53 -0
  8. package/dist/api/routes.d.ts.map +1 -0
  9. package/dist/api/routes.js +388 -0
  10. package/dist/api/routes.js.map +1 -0
  11. package/dist/cli.d.ts +12 -0
  12. package/dist/cli.d.ts.map +1 -0
  13. package/dist/cli.js +245 -0
  14. package/dist/cli.js.map +1 -0
  15. package/dist/core/decay.d.ts +36 -0
  16. package/dist/core/decay.d.ts.map +1 -0
  17. package/dist/core/decay.js +38 -0
  18. package/dist/core/decay.js.map +1 -0
  19. package/dist/core/embeddings.d.ts +33 -0
  20. package/dist/core/embeddings.d.ts.map +1 -0
  21. package/dist/core/embeddings.js +76 -0
  22. package/dist/core/embeddings.js.map +1 -0
  23. package/dist/core/hebbian.d.ts +38 -0
  24. package/dist/core/hebbian.d.ts.map +1 -0
  25. package/dist/core/hebbian.js +74 -0
  26. package/dist/core/hebbian.js.map +1 -0
  27. package/dist/core/index.d.ts +4 -0
  28. package/dist/core/index.d.ts.map +1 -0
  29. package/dist/core/index.js +4 -0
  30. package/dist/core/index.js.map +1 -0
  31. package/dist/core/query-expander.d.ts +24 -0
  32. package/dist/core/query-expander.d.ts.map +1 -0
  33. package/dist/core/query-expander.js +58 -0
  34. package/dist/core/query-expander.js.map +1 -0
  35. package/dist/core/reranker.d.ts +25 -0
  36. package/dist/core/reranker.d.ts.map +1 -0
  37. package/dist/core/reranker.js +75 -0
  38. package/dist/core/reranker.js.map +1 -0
  39. package/dist/core/salience.d.ts +30 -0
  40. package/dist/core/salience.d.ts.map +1 -0
  41. package/dist/core/salience.js +81 -0
  42. package/dist/core/salience.js.map +1 -0
  43. package/dist/engine/activation.d.ts +38 -0
  44. package/dist/engine/activation.d.ts.map +1 -0
  45. package/dist/engine/activation.js +516 -0
  46. package/dist/engine/activation.js.map +1 -0
  47. package/dist/engine/connections.d.ts +31 -0
  48. package/dist/engine/connections.d.ts.map +1 -0
  49. package/dist/engine/connections.js +74 -0
  50. package/dist/engine/connections.js.map +1 -0
  51. package/dist/engine/consolidation-scheduler.d.ts +31 -0
  52. package/dist/engine/consolidation-scheduler.d.ts.map +1 -0
  53. package/dist/engine/consolidation-scheduler.js +115 -0
  54. package/dist/engine/consolidation-scheduler.js.map +1 -0
  55. package/dist/engine/consolidation.d.ts +62 -0
  56. package/dist/engine/consolidation.d.ts.map +1 -0
  57. package/dist/engine/consolidation.js +368 -0
  58. package/dist/engine/consolidation.js.map +1 -0
  59. package/dist/engine/eval.d.ts +22 -0
  60. package/dist/engine/eval.d.ts.map +1 -0
  61. package/dist/engine/eval.js +79 -0
  62. package/dist/engine/eval.js.map +1 -0
  63. package/dist/engine/eviction.d.ts +29 -0
  64. package/dist/engine/eviction.d.ts.map +1 -0
  65. package/dist/engine/eviction.js +86 -0
  66. package/dist/engine/eviction.js.map +1 -0
  67. package/dist/engine/index.d.ts +7 -0
  68. package/dist/engine/index.d.ts.map +1 -0
  69. package/dist/engine/index.js +7 -0
  70. package/dist/engine/index.js.map +1 -0
  71. package/dist/engine/retraction.d.ts +32 -0
  72. package/dist/engine/retraction.d.ts.map +1 -0
  73. package/dist/engine/retraction.js +77 -0
  74. package/dist/engine/retraction.js.map +1 -0
  75. package/dist/engine/staging.d.ts +33 -0
  76. package/dist/engine/staging.d.ts.map +1 -0
  77. package/dist/engine/staging.js +63 -0
  78. package/dist/engine/staging.js.map +1 -0
  79. package/dist/index.d.ts +2 -0
  80. package/dist/index.d.ts.map +1 -0
  81. package/dist/index.js +95 -0
  82. package/dist/index.js.map +1 -0
  83. package/dist/mcp.d.ts +24 -0
  84. package/dist/mcp.d.ts.map +1 -0
  85. package/dist/mcp.js +532 -0
  86. package/dist/mcp.js.map +1 -0
  87. package/dist/storage/index.d.ts +2 -0
  88. package/dist/storage/index.d.ts.map +1 -0
  89. package/dist/storage/index.js +2 -0
  90. package/dist/storage/index.js.map +1 -0
  91. package/dist/storage/sqlite.d.ts +116 -0
  92. package/dist/storage/sqlite.d.ts.map +1 -0
  93. package/dist/storage/sqlite.js +750 -0
  94. package/dist/storage/sqlite.js.map +1 -0
  95. package/dist/types/agent.d.ts +30 -0
  96. package/dist/types/agent.d.ts.map +1 -0
  97. package/dist/types/agent.js +23 -0
  98. package/dist/types/agent.js.map +1 -0
  99. package/dist/types/checkpoint.d.ts +50 -0
  100. package/dist/types/checkpoint.d.ts.map +1 -0
  101. package/dist/types/checkpoint.js +8 -0
  102. package/dist/types/checkpoint.js.map +1 -0
  103. package/dist/types/engram.d.ts +165 -0
  104. package/dist/types/engram.d.ts.map +1 -0
  105. package/dist/types/engram.js +8 -0
  106. package/dist/types/engram.js.map +1 -0
  107. package/dist/types/eval.d.ts +84 -0
  108. package/dist/types/eval.d.ts.map +1 -0
  109. package/dist/types/eval.js +11 -0
  110. package/dist/types/eval.js.map +1 -0
  111. package/dist/types/index.d.ts +5 -0
  112. package/dist/types/index.d.ts.map +1 -0
  113. package/dist/types/index.js +5 -0
  114. package/dist/types/index.js.map +1 -0
  115. package/package.json +55 -0
  116. package/src/api/index.ts +1 -0
  117. package/src/api/routes.ts +528 -0
  118. package/src/cli.ts +260 -0
  119. package/src/core/decay.ts +61 -0
  120. package/src/core/embeddings.ts +82 -0
  121. package/src/core/hebbian.ts +91 -0
  122. package/src/core/index.ts +3 -0
  123. package/src/core/query-expander.ts +64 -0
  124. package/src/core/reranker.ts +99 -0
  125. package/src/core/salience.ts +95 -0
  126. package/src/engine/activation.ts +577 -0
  127. package/src/engine/connections.ts +101 -0
  128. package/src/engine/consolidation-scheduler.ts +123 -0
  129. package/src/engine/consolidation.ts +443 -0
  130. package/src/engine/eval.ts +100 -0
  131. package/src/engine/eviction.ts +99 -0
  132. package/src/engine/index.ts +6 -0
  133. package/src/engine/retraction.ts +98 -0
  134. package/src/engine/staging.ts +72 -0
  135. package/src/index.ts +100 -0
  136. package/src/mcp.ts +635 -0
  137. package/src/storage/index.ts +1 -0
  138. package/src/storage/sqlite.ts +893 -0
  139. package/src/types/agent.ts +65 -0
  140. package/src/types/checkpoint.ts +44 -0
  141. package/src/types/engram.ts +194 -0
  142. package/src/types/eval.ts +98 -0
  143. package/src/types/index.ts +4 -0
@@ -0,0 +1,100 @@
1
+ /**
2
+ * Evaluation Engine — measures whether memory actually helps.
3
+ *
4
+ * Four dimensions (from Codex):
5
+ * 1. Retrieval quality — precision@k, latency
6
+ * 2. Connection quality — edge utility, stability
7
+ * 3. Staging accuracy — promotion precision, discard regret
8
+ * 4. Memory health — contamination tracking, confidence distribution
9
+ *
10
+ * Task impact (with/without memory) is measured externally via TaskTrial records.
11
+ */
12
+
13
+ import type { EngramStore } from '../storage/sqlite.js';
14
+ import type { EvalMetrics } from '../types/index.js';
15
+
16
+ export class EvalEngine {
17
+ private store: EngramStore;
18
+
19
+ constructor(store: EngramStore) {
20
+ this.store = store;
21
+ }
22
+
23
+ /**
24
+ * Compute aggregate metrics for an agent over a time window.
25
+ */
26
+ computeMetrics(agentId: string, windowHours: number = 24): EvalMetrics {
27
+ const window = windowHours <= 24 ? '24h' : `${Math.round(windowHours / 24)}d`;
28
+
29
+ // Retrieval quality
30
+ const precision = this.store.getRetrievalPrecision(agentId, windowHours);
31
+
32
+ // Staging accuracy
33
+ const stagingMetrics = this.store.getStagingMetrics(agentId);
34
+ const totalStaged = stagingMetrics.promoted + stagingMetrics.discarded + stagingMetrics.expired;
35
+ const promotionPrecision = totalStaged > 0 ? stagingMetrics.promoted / totalStaged : 0;
36
+
37
+ // Memory health
38
+ const activeEngrams = this.store.getEngramsByAgent(agentId, 'active');
39
+ const stagingEngrams = this.store.getEngramsByAgent(agentId, 'staging');
40
+ const retractedEngrams = this.store.getEngramsByAgent(agentId, undefined, true)
41
+ .filter(e => e.retracted);
42
+ const allAssociations = this.store.getAllAssociations(agentId);
43
+
44
+ const avgConfidence = activeEngrams.length > 0
45
+ ? activeEngrams.reduce((sum, e) => sum + e.confidence, 0) / activeEngrams.length
46
+ : 0;
47
+
48
+ // Edge utility — % of edges that have been used in activation
49
+ const usedEdges = allAssociations.filter(a => a.activationCount > 0);
50
+ const edgeUtility = allAssociations.length > 0
51
+ ? usedEdges.length / allAssociations.length
52
+ : 0;
53
+
54
+ // Edge survival — average age of edges that are still above minimum weight
55
+ const livingEdges = allAssociations.filter(a => a.weight > 0.01);
56
+ const avgSurvival = livingEdges.length > 0
57
+ ? livingEdges.reduce((sum, a) =>
58
+ sum + (Date.now() - a.createdAt.getTime()) / (1000 * 60 * 60 * 24), 0
59
+ ) / livingEdges.length
60
+ : 0;
61
+
62
+ // Activation performance stats
63
+ const activationStats = this.store.getActivationStats(agentId, windowHours);
64
+
65
+ // Consolidated count
66
+ const consolidatedCount = this.store.getConsolidatedCount(agentId);
67
+
68
+ return {
69
+ agentId,
70
+ timestamp: new Date(),
71
+ window,
72
+
73
+ activationCount: activationStats.count,
74
+ avgPrecisionAtK: precision,
75
+ avgLatencyMs: activationStats.avgLatencyMs,
76
+ p95LatencyMs: activationStats.p95LatencyMs,
77
+
78
+ totalEdges: allAssociations.length,
79
+ edgesUsedInActivation: usedEdges.length,
80
+ edgeUtilityRate: edgeUtility,
81
+ avgEdgeSurvivalDays: avgSurvival,
82
+
83
+ totalStaged: totalStaged,
84
+ promotedCount: stagingMetrics.promoted,
85
+ discardedCount: stagingMetrics.discarded,
86
+ promotionPrecision,
87
+ discardRegret: 0, // Requires tracking discarded-then-rediscovered items
88
+
89
+ activeEngramCount: activeEngrams.length,
90
+ stagingEngramCount: stagingEngrams.length,
91
+ retractedCount: retractedEngrams.length,
92
+ consolidatedCount,
93
+ avgConfidence,
94
+
95
+ staleUsageCount: 0, // Requires per-activation age/confidence tracking
96
+ retractionRate: retractedEngrams.length /
97
+ Math.max(activeEngrams.length + retractedEngrams.length, 1),
98
+ };
99
+ }
100
+ }
@@ -0,0 +1,99 @@
1
+ /**
2
+ * Eviction Engine — capacity enforcement and edge pruning.
3
+ *
4
+ * When memory budgets are exceeded:
5
+ * 1. Archive lowest-value active engrams
6
+ * 2. Delete expired staging engrams
7
+ * 3. Prune weakest edges when per-engram cap exceeded
8
+ * 4. Decay unused association weights over time
9
+ */
10
+
11
+ import type { EngramStore } from '../storage/sqlite.js';
12
+ import type { AgentConfig } from '../types/agent.js';
13
+ import { decayAssociation } from '../core/hebbian.js';
14
+
15
+ export class EvictionEngine {
16
+ private store: EngramStore;
17
+
18
+ constructor(store: EngramStore) {
19
+ this.store = store;
20
+ }
21
+
22
+ /**
23
+ * Check capacity budgets and evict if needed.
24
+ * Returns count of evicted engrams.
25
+ */
26
+ enforceCapacity(agentId: string, config: AgentConfig): { evicted: number; edgesPruned: number } {
27
+ let evicted = 0;
28
+ let edgesPruned = 0;
29
+
30
+ // Active engram budget
31
+ const activeCount = this.store.getActiveCount(agentId);
32
+ if (activeCount > config.maxActiveEngrams) {
33
+ const excess = activeCount - config.maxActiveEngrams;
34
+ const candidates = this.store.getEvictionCandidates(agentId, excess);
35
+ for (const engram of candidates) {
36
+ this.store.updateStage(engram.id, 'archived');
37
+ evicted++;
38
+ }
39
+ }
40
+
41
+ // Staging budget
42
+ const stagingCount = this.store.getStagingCount(agentId);
43
+ if (stagingCount > config.maxStagingEngrams) {
44
+ const expired = this.store.getExpiredStaging();
45
+ for (const engram of expired) {
46
+ this.store.deleteEngram(engram.id);
47
+ evicted++;
48
+ }
49
+ }
50
+
51
+ // Edge pruning — cap per engram
52
+ const engrams = this.store.getEngramsByAgent(agentId, 'active');
53
+ for (const engram of engrams) {
54
+ const edgeCount = this.store.countAssociationsFor(engram.id);
55
+ if (edgeCount > config.maxEdgesPerEngram) {
56
+ // Remove weakest edges until under cap
57
+ let toRemove = edgeCount - config.maxEdgesPerEngram;
58
+ while (toRemove > 0) {
59
+ const weakest = this.store.getWeakestAssociation(engram.id);
60
+ if (weakest) {
61
+ this.store.deleteAssociation(weakest.id);
62
+ edgesPruned++;
63
+ }
64
+ toRemove--;
65
+ }
66
+ }
67
+ }
68
+
69
+ return { evicted, edgesPruned };
70
+ }
71
+
72
+ /**
73
+ * Decay all association weights based on time since last activation.
74
+ * Run periodically (e.g., daily).
75
+ */
76
+ decayEdges(agentId: string, halfLifeDays: number = 7): number {
77
+ const associations = this.store.getAllAssociations(agentId);
78
+ let decayed = 0;
79
+
80
+ for (const assoc of associations) {
81
+ const daysSince = (Date.now() - assoc.lastActivated.getTime()) / (1000 * 60 * 60 * 24);
82
+ if (daysSince < 0.5) continue; // Skip recently activated
83
+
84
+ const newWeight = decayAssociation(assoc.weight, daysSince, halfLifeDays);
85
+ if (newWeight < 0.01) {
86
+ // Below minimum useful weight — prune
87
+ this.store.deleteAssociation(assoc.id);
88
+ decayed++;
89
+ } else if (Math.abs(newWeight - assoc.weight) > 0.001) {
90
+ this.store.upsertAssociation(
91
+ assoc.fromEngramId, assoc.toEngramId, newWeight, assoc.type, assoc.confidence
92
+ );
93
+ decayed++;
94
+ }
95
+ }
96
+
97
+ return decayed;
98
+ }
99
+ }
@@ -0,0 +1,6 @@
1
+ export * from './activation.js';
2
+ export * from './staging.js';
3
+ export * from './connections.js';
4
+ export * from './eviction.js';
5
+ export * from './retraction.js';
6
+ export * from './eval.js';
@@ -0,0 +1,98 @@
1
+ /**
2
+ * Retraction Engine — negative memory / invalidation.
3
+ *
4
+ * Codex critique: "You need explicit anti-salience for wrong info.
5
+ * Otherwise wrong memories persist and compound mistakes."
6
+ *
7
+ * When an agent discovers a memory is wrong:
8
+ * 1. The original engram is marked retracted (not deleted — audit trail)
9
+ * 2. An invalidation association is created
10
+ * 3. Optionally, a counter-engram with correct info is created
11
+ * 4. Confidence of associated engrams is reduced (contamination check)
12
+ */
13
+
14
+ import type { EngramStore } from '../storage/sqlite.js';
15
+ import type { Retraction } from '../types/index.js';
16
+
17
+ export class RetractionEngine {
18
+ private store: EngramStore;
19
+
20
+ constructor(store: EngramStore) {
21
+ this.store = store;
22
+ }
23
+
24
+ /**
25
+ * Retract a memory — mark it invalid and optionally create a correction.
26
+ */
27
+ retract(retraction: Retraction): { retractedId: string; correctionId: string | null; associatesAffected: number } {
28
+ const target = this.store.getEngram(retraction.targetEngramId);
29
+ if (!target) {
30
+ throw new Error(`Engram ${retraction.targetEngramId} not found`);
31
+ }
32
+
33
+ // Mark the original as retracted
34
+ this.store.retractEngram(target.id, null);
35
+
36
+ let correctionId: string | null = null;
37
+
38
+ // Create counter-engram if correction content provided
39
+ if (retraction.counterContent) {
40
+ const correction = this.store.createEngram({
41
+ agentId: retraction.agentId,
42
+ concept: `correction:${target.concept}`,
43
+ content: retraction.counterContent,
44
+ tags: [...target.tags, 'correction', 'retraction'],
45
+ salience: Math.max(target.salience, 0.6), // Corrections are at least moderately salient
46
+ confidence: 0.7,
47
+ reasonCodes: ['retraction_correction', `invalidates:${target.id}`],
48
+ });
49
+
50
+ correctionId = correction.id;
51
+
52
+ // Create invalidation link
53
+ this.store.upsertAssociation(
54
+ correction.id, target.id, 1.0, 'invalidation', 1.0
55
+ );
56
+
57
+ // Update retracted_by to point to correction
58
+ this.store.retractEngram(target.id, correction.id);
59
+ }
60
+
61
+ // Reduce confidence of closely associated engrams (contamination spread)
62
+ const associatesAffected = this.propagateConfidenceReduction(target.id, 0.1, 1);
63
+
64
+ return { retractedId: target.id, correctionId, associatesAffected };
65
+ }
66
+
67
+ /**
68
+ * Reduce confidence of engrams associated with a retracted engram.
69
+ * Shallow propagation (depth 1) to avoid over-penalizing.
70
+ */
71
+ private propagateConfidenceReduction(
72
+ engramId: string,
73
+ penalty: number,
74
+ maxDepth: number,
75
+ currentDepth: number = 0
76
+ ): number {
77
+ if (currentDepth >= maxDepth) return 0;
78
+
79
+ let affected = 0;
80
+ const associations = this.store.getAssociationsFor(engramId);
81
+ for (const assoc of associations) {
82
+ if (assoc.type === 'invalidation') continue; // Don't penalize corrections
83
+
84
+ const neighborId = assoc.fromEngramId === engramId
85
+ ? assoc.toEngramId
86
+ : assoc.fromEngramId;
87
+ const neighbor = this.store.getEngram(neighborId);
88
+ if (!neighbor || neighbor.retracted) continue;
89
+
90
+ // Scale penalty by association weight
91
+ const scaledPenalty = penalty * assoc.weight;
92
+ const newConfidence = Math.max(0.1, neighbor.confidence - scaledPenalty);
93
+ this.store.updateConfidence(neighborId, newConfidence);
94
+ affected++;
95
+ }
96
+ return affected;
97
+ }
98
+ }
@@ -0,0 +1,72 @@
1
+ /**
2
+ * Staging Buffer — weak signal handler.
3
+ *
4
+ * Observations that don't meet the salience threshold for active memory
5
+ * go to staging. The staging buffer periodically:
6
+ * 1. Checks staged engrams against active memory for resonance
7
+ * 2. Promotes resonant engrams to active
8
+ * 3. Discards expired engrams that never resonated
9
+ *
10
+ * Modeled on hippocampal consolidation — provisional encoding
11
+ * that only persists if reactivated.
12
+ */
13
+
14
+ import type { EngramStore } from '../storage/sqlite.js';
15
+ import type { ActivationEngine } from './activation.js';
16
+
17
+ export class StagingBuffer {
18
+ private store: EngramStore;
19
+ private engine: ActivationEngine;
20
+ private checkInterval: ReturnType<typeof setInterval> | null = null;
21
+
22
+ constructor(store: EngramStore, engine: ActivationEngine) {
23
+ this.store = store;
24
+ this.engine = engine;
25
+ }
26
+
27
+ /**
28
+ * Start the periodic staging check.
29
+ */
30
+ start(intervalMs: number = 60_000): void {
31
+ this.checkInterval = setInterval(() => this.sweep(), intervalMs);
32
+ }
33
+
34
+ stop(): void {
35
+ if (this.checkInterval) {
36
+ clearInterval(this.checkInterval);
37
+ this.checkInterval = null;
38
+ }
39
+ }
40
+
41
+ /**
42
+ * Sweep staged engrams: promote or discard.
43
+ */
44
+ async sweep(): Promise<{ promoted: string[]; discarded: string[] }> {
45
+ const promoted: string[] = [];
46
+ const discarded: string[] = [];
47
+
48
+ const expired = this.store.getExpiredStaging();
49
+ for (const engram of expired) {
50
+ // Check if this engram resonates with active memory
51
+ const results = await this.engine.activate({
52
+ agentId: engram.agentId,
53
+ context: `${engram.concept} ${engram.content}`,
54
+ limit: 3,
55
+ minScore: 0.3,
56
+ internal: true,
57
+ });
58
+
59
+ if (results.length > 0) {
60
+ // Resonance found — promote to active
61
+ this.store.updateStage(engram.id, 'active');
62
+ promoted.push(engram.id);
63
+ } else {
64
+ // No resonance — discard
65
+ this.store.deleteEngram(engram.id);
66
+ discarded.push(engram.id);
67
+ }
68
+ }
69
+
70
+ return { promoted, discarded };
71
+ }
72
+ }
package/src/index.ts ADDED
@@ -0,0 +1,100 @@
1
+ import { readFileSync } from 'node:fs';
2
+ import { resolve } from 'node:path';
3
+ import Fastify from 'fastify';
4
+
5
+ // Load .env file if present (no external dependency)
6
+ try {
7
+ const envPath = resolve(process.cwd(), '.env');
8
+ const envContent = readFileSync(envPath, 'utf-8');
9
+ for (const line of envContent.split('\n')) {
10
+ const trimmed = line.trim();
11
+ if (!trimmed || trimmed.startsWith('#')) continue;
12
+ const eqIdx = trimmed.indexOf('=');
13
+ if (eqIdx === -1) continue;
14
+ const key = trimmed.slice(0, eqIdx).trim();
15
+ const val = trimmed.slice(eqIdx + 1).trim().replace(/^["']|["']$/g, '');
16
+ if (!process.env[key]) process.env[key] = val; // Don't override existing env
17
+ }
18
+ } catch { /* No .env file — that's fine */ }
19
+ import { EngramStore } from './storage/sqlite.js';
20
+ import { ActivationEngine } from './engine/activation.js';
21
+ import { ConnectionEngine } from './engine/connections.js';
22
+ import { StagingBuffer } from './engine/staging.js';
23
+ import { EvictionEngine } from './engine/eviction.js';
24
+ import { RetractionEngine } from './engine/retraction.js';
25
+ import { EvalEngine } from './engine/eval.js';
26
+ import { ConsolidationEngine } from './engine/consolidation.js';
27
+ import { ConsolidationScheduler } from './engine/consolidation-scheduler.js';
28
+ import { registerRoutes } from './api/routes.js';
29
+ import { DEFAULT_AGENT_CONFIG } from './types/agent.js';
30
+ import { getEmbedder } from './core/embeddings.js';
31
+ import { getReranker } from './core/reranker.js';
32
+ import { getExpander } from './core/query-expander.js';
33
+
34
+ const PORT = parseInt(process.env.AWM_PORT ?? '8400', 10);
35
+ const DB_PATH = process.env.AWM_DB_PATH ?? 'memory.db';
36
+ const API_KEY = process.env.AWM_API_KEY ?? null;
37
+
38
+ async function main() {
39
+ // Storage
40
+ const store = new EngramStore(DB_PATH);
41
+
42
+ // Engines
43
+ const activationEngine = new ActivationEngine(store);
44
+ const connectionEngine = new ConnectionEngine(store, activationEngine);
45
+ const stagingBuffer = new StagingBuffer(store, activationEngine);
46
+ const evictionEngine = new EvictionEngine(store);
47
+ const retractionEngine = new RetractionEngine(store);
48
+ const evalEngine = new EvalEngine(store);
49
+ const consolidationEngine = new ConsolidationEngine(store);
50
+ const consolidationScheduler = new ConsolidationScheduler(store, consolidationEngine);
51
+
52
+ // API
53
+ const app = Fastify({ logger: true });
54
+
55
+ // Bearer token auth — only enforced when AWM_API_KEY is set
56
+ if (API_KEY) {
57
+ app.addHook('onRequest', async (req, reply) => {
58
+ if (req.url === '/health') return; // Health check is always public
59
+ const bearer = req.headers.authorization;
60
+ const xApiKey = req.headers['x-api-key'] as string | undefined;
61
+ if (bearer === `Bearer ${API_KEY}` || xApiKey === API_KEY) return;
62
+ reply.code(401).send({ error: 'Unauthorized' });
63
+ });
64
+ console.log('API key auth enabled (AWM_API_KEY set)');
65
+ }
66
+
67
+ registerRoutes(app, {
68
+ store, activationEngine, connectionEngine,
69
+ evictionEngine, retractionEngine, evalEngine,
70
+ consolidationEngine, consolidationScheduler,
71
+ });
72
+
73
+ // Background tasks
74
+ stagingBuffer.start(DEFAULT_AGENT_CONFIG.stagingTtlMs);
75
+ consolidationScheduler.start();
76
+
77
+ // Pre-load ML models (downloads on first run: embeddings ~22MB, reranker ~22MB, expander ~80MB)
78
+ getEmbedder().catch(err => console.warn('Embedding model unavailable:', err.message));
79
+ getReranker().catch(err => console.warn('Reranker model unavailable:', err.message));
80
+ getExpander().catch(err => console.warn('Query expander model unavailable:', err.message));
81
+
82
+ // Start server
83
+ await app.listen({ port: PORT, host: '0.0.0.0' });
84
+ console.log(`AgentWorkingMemory v0.3.0 listening on port ${PORT}`);
85
+
86
+ // Graceful shutdown
87
+ const shutdown = () => {
88
+ consolidationScheduler.stop();
89
+ stagingBuffer.stop();
90
+ store.close();
91
+ process.exit(0);
92
+ };
93
+ process.on('SIGINT', shutdown);
94
+ process.on('SIGTERM', shutdown);
95
+ }
96
+
97
+ main().catch(err => {
98
+ console.error('Failed to start:', err);
99
+ process.exit(1);
100
+ });