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.
- package/LICENSE +21 -0
- package/README.md +311 -0
- package/dist/api/index.d.ts +2 -0
- package/dist/api/index.d.ts.map +1 -0
- package/dist/api/index.js +2 -0
- package/dist/api/index.js.map +1 -0
- package/dist/api/routes.d.ts +53 -0
- package/dist/api/routes.d.ts.map +1 -0
- package/dist/api/routes.js +388 -0
- package/dist/api/routes.js.map +1 -0
- package/dist/cli.d.ts +12 -0
- package/dist/cli.d.ts.map +1 -0
- package/dist/cli.js +245 -0
- package/dist/cli.js.map +1 -0
- package/dist/core/decay.d.ts +36 -0
- package/dist/core/decay.d.ts.map +1 -0
- package/dist/core/decay.js +38 -0
- package/dist/core/decay.js.map +1 -0
- package/dist/core/embeddings.d.ts +33 -0
- package/dist/core/embeddings.d.ts.map +1 -0
- package/dist/core/embeddings.js +76 -0
- package/dist/core/embeddings.js.map +1 -0
- package/dist/core/hebbian.d.ts +38 -0
- package/dist/core/hebbian.d.ts.map +1 -0
- package/dist/core/hebbian.js +74 -0
- package/dist/core/hebbian.js.map +1 -0
- package/dist/core/index.d.ts +4 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +4 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/query-expander.d.ts +24 -0
- package/dist/core/query-expander.d.ts.map +1 -0
- package/dist/core/query-expander.js +58 -0
- package/dist/core/query-expander.js.map +1 -0
- package/dist/core/reranker.d.ts +25 -0
- package/dist/core/reranker.d.ts.map +1 -0
- package/dist/core/reranker.js +75 -0
- package/dist/core/reranker.js.map +1 -0
- package/dist/core/salience.d.ts +30 -0
- package/dist/core/salience.d.ts.map +1 -0
- package/dist/core/salience.js +81 -0
- package/dist/core/salience.js.map +1 -0
- package/dist/engine/activation.d.ts +38 -0
- package/dist/engine/activation.d.ts.map +1 -0
- package/dist/engine/activation.js +516 -0
- package/dist/engine/activation.js.map +1 -0
- package/dist/engine/connections.d.ts +31 -0
- package/dist/engine/connections.d.ts.map +1 -0
- package/dist/engine/connections.js +74 -0
- package/dist/engine/connections.js.map +1 -0
- package/dist/engine/consolidation-scheduler.d.ts +31 -0
- package/dist/engine/consolidation-scheduler.d.ts.map +1 -0
- package/dist/engine/consolidation-scheduler.js +115 -0
- package/dist/engine/consolidation-scheduler.js.map +1 -0
- package/dist/engine/consolidation.d.ts +62 -0
- package/dist/engine/consolidation.d.ts.map +1 -0
- package/dist/engine/consolidation.js +368 -0
- package/dist/engine/consolidation.js.map +1 -0
- package/dist/engine/eval.d.ts +22 -0
- package/dist/engine/eval.d.ts.map +1 -0
- package/dist/engine/eval.js +79 -0
- package/dist/engine/eval.js.map +1 -0
- package/dist/engine/eviction.d.ts +29 -0
- package/dist/engine/eviction.d.ts.map +1 -0
- package/dist/engine/eviction.js +86 -0
- package/dist/engine/eviction.js.map +1 -0
- package/dist/engine/index.d.ts +7 -0
- package/dist/engine/index.d.ts.map +1 -0
- package/dist/engine/index.js +7 -0
- package/dist/engine/index.js.map +1 -0
- package/dist/engine/retraction.d.ts +32 -0
- package/dist/engine/retraction.d.ts.map +1 -0
- package/dist/engine/retraction.js +77 -0
- package/dist/engine/retraction.js.map +1 -0
- package/dist/engine/staging.d.ts +33 -0
- package/dist/engine/staging.d.ts.map +1 -0
- package/dist/engine/staging.js +63 -0
- package/dist/engine/staging.js.map +1 -0
- package/dist/index.d.ts +2 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +95 -0
- package/dist/index.js.map +1 -0
- package/dist/mcp.d.ts +24 -0
- package/dist/mcp.d.ts.map +1 -0
- package/dist/mcp.js +532 -0
- package/dist/mcp.js.map +1 -0
- package/dist/storage/index.d.ts +2 -0
- package/dist/storage/index.d.ts.map +1 -0
- package/dist/storage/index.js +2 -0
- package/dist/storage/index.js.map +1 -0
- package/dist/storage/sqlite.d.ts +116 -0
- package/dist/storage/sqlite.d.ts.map +1 -0
- package/dist/storage/sqlite.js +750 -0
- package/dist/storage/sqlite.js.map +1 -0
- package/dist/types/agent.d.ts +30 -0
- package/dist/types/agent.d.ts.map +1 -0
- package/dist/types/agent.js +23 -0
- package/dist/types/agent.js.map +1 -0
- package/dist/types/checkpoint.d.ts +50 -0
- package/dist/types/checkpoint.d.ts.map +1 -0
- package/dist/types/checkpoint.js +8 -0
- package/dist/types/checkpoint.js.map +1 -0
- package/dist/types/engram.d.ts +165 -0
- package/dist/types/engram.d.ts.map +1 -0
- package/dist/types/engram.js +8 -0
- package/dist/types/engram.js.map +1 -0
- package/dist/types/eval.d.ts +84 -0
- package/dist/types/eval.d.ts.map +1 -0
- package/dist/types/eval.js +11 -0
- package/dist/types/eval.js.map +1 -0
- package/dist/types/index.d.ts +5 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +5 -0
- package/dist/types/index.js.map +1 -0
- package/package.json +55 -0
- package/src/api/index.ts +1 -0
- package/src/api/routes.ts +528 -0
- package/src/cli.ts +260 -0
- package/src/core/decay.ts +61 -0
- package/src/core/embeddings.ts +82 -0
- package/src/core/hebbian.ts +91 -0
- package/src/core/index.ts +3 -0
- package/src/core/query-expander.ts +64 -0
- package/src/core/reranker.ts +99 -0
- package/src/core/salience.ts +95 -0
- package/src/engine/activation.ts +577 -0
- package/src/engine/connections.ts +101 -0
- package/src/engine/consolidation-scheduler.ts +123 -0
- package/src/engine/consolidation.ts +443 -0
- package/src/engine/eval.ts +100 -0
- package/src/engine/eviction.ts +99 -0
- package/src/engine/index.ts +6 -0
- package/src/engine/retraction.ts +98 -0
- package/src/engine/staging.ts +72 -0
- package/src/index.ts +100 -0
- package/src/mcp.ts +635 -0
- package/src/storage/index.ts +1 -0
- package/src/storage/sqlite.ts +893 -0
- package/src/types/agent.ts +65 -0
- package/src/types/checkpoint.ts +44 -0
- package/src/types/engram.ts +194 -0
- package/src/types/eval.ts +98 -0
- 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,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
|
+
});
|