agent-working-memory 0.7.16 → 0.8.5
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/README.md +156 -15
- package/dist/adapters/claude-code.d.ts.map +1 -1
- package/dist/adapters/claude-code.js +2 -16
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/codex.d.ts.map +1 -1
- package/dist/adapters/codex.js +2 -11
- package/dist/adapters/codex.js.map +1 -1
- package/dist/adapters/common.d.ts +18 -0
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +254 -141
- package/dist/adapters/common.js.map +1 -1
- package/dist/adapters/cursor.d.ts.map +1 -1
- package/dist/adapters/cursor.js +2 -15
- package/dist/adapters/cursor.js.map +1 -1
- package/dist/adapters/http.d.ts.map +1 -1
- package/dist/adapters/http.js +28 -34
- package/dist/adapters/http.js.map +1 -1
- package/dist/api/routes.d.ts +1 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +280 -113
- package/dist/api/routes.js.map +1 -1
- package/dist/cli/migrate.d.ts +42 -0
- package/dist/cli/migrate.d.ts.map +1 -0
- package/dist/cli/migrate.js +268 -0
- package/dist/cli/migrate.js.map +1 -0
- package/dist/cli.js +45 -1
- package/dist/cli.js.map +1 -1
- package/dist/coordination/circuit-breaker.d.ts +24 -0
- package/dist/coordination/circuit-breaker.d.ts.map +1 -0
- package/dist/coordination/circuit-breaker.js +70 -0
- package/dist/coordination/circuit-breaker.js.map +1 -0
- package/dist/coordination/failure-modes.d.ts +21 -0
- package/dist/coordination/failure-modes.d.ts.map +1 -0
- package/dist/coordination/failure-modes.js +49 -0
- package/dist/coordination/failure-modes.js.map +1 -0
- package/dist/coordination/peer-decisions.js +7 -7
- package/dist/coordination/routes.d.ts.map +1 -1
- package/dist/coordination/routes.js +44 -1
- package/dist/coordination/routes.js.map +1 -1
- package/dist/coordination/schema.d.ts.map +1 -1
- package/dist/coordination/schema.js +18 -0
- package/dist/coordination/schema.js.map +1 -1
- package/dist/coordination/schemas.d.ts +9 -9
- package/dist/coordination/stale.d.ts +9 -1
- package/dist/coordination/stale.d.ts.map +1 -1
- package/dist/coordination/stale.js +53 -9
- package/dist/coordination/stale.js.map +1 -1
- package/dist/core/embeddings.d.ts +16 -10
- package/dist/core/embeddings.d.ts.map +1 -1
- package/dist/core/embeddings.js +54 -38
- package/dist/core/embeddings.js.map +1 -1
- package/dist/core/ml-worker-entry.d.ts +17 -0
- package/dist/core/ml-worker-entry.d.ts.map +1 -0
- package/dist/core/ml-worker-entry.js +193 -0
- package/dist/core/ml-worker-entry.js.map +1 -0
- package/dist/core/ml-worker.d.ts +59 -0
- package/dist/core/ml-worker.d.ts.map +1 -0
- package/dist/core/ml-worker.js +253 -0
- package/dist/core/ml-worker.js.map +1 -0
- package/dist/core/query-expander.d.ts +13 -15
- package/dist/core/query-expander.d.ts.map +1 -1
- package/dist/core/query-expander.js +48 -54
- package/dist/core/query-expander.js.map +1 -1
- package/dist/core/reranker.d.ts +9 -19
- package/dist/core/reranker.d.ts.map +1 -1
- package/dist/core/reranker.js +33 -35
- package/dist/core/reranker.js.map +1 -1
- package/dist/core/salience.d.ts +32 -5
- package/dist/core/salience.d.ts.map +1 -1
- package/dist/core/salience.js +201 -28
- package/dist/core/salience.js.map +1 -1
- package/dist/core/write-pipeline.d.ts +128 -0
- package/dist/core/write-pipeline.d.ts.map +1 -0
- package/dist/core/write-pipeline.js +405 -0
- package/dist/core/write-pipeline.js.map +1 -0
- package/dist/engine/activation.d.ts +2 -2
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +238 -115
- package/dist/engine/activation.js.map +1 -1
- package/dist/engine/confidence.d.ts +62 -0
- package/dist/engine/confidence.d.ts.map +1 -0
- package/dist/engine/confidence.js +100 -0
- package/dist/engine/confidence.js.map +1 -0
- package/dist/engine/connections.d.ts +47 -8
- package/dist/engine/connections.d.ts.map +1 -1
- package/dist/engine/connections.js +83 -22
- package/dist/engine/connections.js.map +1 -1
- package/dist/engine/consolidation-scheduler.d.ts +46 -10
- package/dist/engine/consolidation-scheduler.d.ts.map +1 -1
- package/dist/engine/consolidation-scheduler.js +170 -50
- package/dist/engine/consolidation-scheduler.js.map +1 -1
- package/dist/engine/consolidation.d.ts +16 -2
- package/dist/engine/consolidation.d.ts.map +1 -1
- package/dist/engine/consolidation.js +143 -44
- package/dist/engine/consolidation.js.map +1 -1
- package/dist/engine/eval.d.ts +2 -2
- package/dist/engine/eval.d.ts.map +1 -1
- package/dist/engine/eval.js +9 -9
- package/dist/engine/eval.js.map +1 -1
- package/dist/engine/eviction.d.ts +4 -4
- package/dist/engine/eviction.d.ts.map +1 -1
- package/dist/engine/eviction.js +15 -15
- package/dist/engine/eviction.js.map +1 -1
- package/dist/engine/retraction.d.ts +93 -5
- package/dist/engine/retraction.d.ts.map +1 -1
- package/dist/engine/retraction.js +238 -19
- package/dist/engine/retraction.js.map +1 -1
- package/dist/engine/staging.d.ts +1 -1
- package/dist/engine/staging.d.ts.map +1 -1
- package/dist/engine/staging.js +3 -3
- package/dist/engine/staging.js.map +1 -1
- package/dist/hooks/sidecar.d.ts +1 -1
- package/dist/hooks/sidecar.d.ts.map +1 -1
- package/dist/hooks/sidecar.js +4 -4
- package/dist/hooks/sidecar.js.map +1 -1
- package/dist/index.js +48 -31
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +110 -155
- package/dist/mcp.js.map +1 -1
- package/dist/storage/factory.d.ts +55 -0
- package/dist/storage/factory.d.ts.map +1 -0
- package/dist/storage/factory.js +135 -0
- package/dist/storage/factory.js.map +1 -0
- package/dist/storage/pglite-schema.d.ts +20 -0
- package/dist/storage/pglite-schema.d.ts.map +1 -0
- package/dist/storage/pglite-schema.js +165 -0
- package/dist/storage/pglite-schema.js.map +1 -0
- package/dist/storage/pglite.d.ts +187 -0
- package/dist/storage/pglite.d.ts.map +1 -0
- package/dist/storage/pglite.js +1114 -0
- package/dist/storage/pglite.js.map +1 -0
- package/dist/storage/sqlite.d.ts +117 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +357 -8
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/storage/store.d.ts +63 -0
- package/dist/storage/store.d.ts.map +1 -0
- package/dist/storage/store.js +27 -0
- package/dist/storage/store.js.map +1 -0
- package/dist/types/engram.d.ts +92 -2
- package/dist/types/engram.d.ts.map +1 -1
- package/dist/types/engram.js.map +1 -1
- package/package.json +3 -1
- package/src/adapters/claude-code.ts +218 -234
- package/src/adapters/codex.ts +252 -262
- package/src/adapters/common.ts +411 -284
- package/src/adapters/cursor.ts +135 -150
- package/src/adapters/http.ts +93 -100
- package/src/adapters/index.ts +31 -31
- package/src/adapters/types.ts +75 -75
- package/src/api/index.ts +3 -3
- package/src/api/routes.ts +367 -120
- package/src/cli/migrate.ts +307 -0
- package/src/cli.ts +37 -1
- package/src/coordination/circuit-breaker.ts +83 -0
- package/src/coordination/events.ts +90 -90
- package/src/coordination/failure-modes.ts +50 -0
- package/src/coordination/peer-decisions.ts +105 -105
- package/src/coordination/plugin-loader.ts +60 -60
- package/src/coordination/plugin.ts +44 -44
- package/src/coordination/routes.ts +56 -1
- package/src/coordination/schema.ts +13 -0
- package/src/coordination/stale.ts +80 -11
- package/src/coordination/types.ts +311 -311
- package/src/coordination/write-mutex.ts +69 -69
- package/src/core/auto-tagger.ts +168 -168
- package/src/core/decay.ts +63 -63
- package/src/core/embeddings.ts +110 -93
- package/src/core/index.ts +5 -5
- package/src/core/logger.ts +36 -36
- package/src/core/ml-worker-entry.ts +194 -0
- package/src/core/ml-worker.ts +281 -0
- package/src/core/query-expander.ts +122 -128
- package/src/core/reranker.ts +119 -125
- package/src/core/salience.ts +200 -33
- package/src/core/write-pipeline.ts +521 -0
- package/src/engine/activation.ts +235 -109
- package/src/engine/confidence.ts +120 -0
- package/src/engine/connections.ts +162 -103
- package/src/engine/consolidation-scheduler.ts +242 -125
- package/src/engine/consolidation.ts +138 -45
- package/src/engine/eval.ts +102 -102
- package/src/engine/eviction.ts +101 -101
- package/src/engine/index.ts +8 -8
- package/src/engine/retraction.ts +366 -116
- package/src/engine/staging.ts +74 -74
- package/src/hooks/sidecar.ts +5 -5
- package/src/index.ts +42 -28
- package/src/mcp.ts +105 -152
- package/src/storage/factory.ts +147 -0
- package/src/storage/index.ts +3 -3
- package/src/storage/pglite-schema.ts +166 -0
- package/src/storage/pglite.ts +1363 -0
- package/src/storage/sqlite.ts +402 -7
- package/src/storage/store.ts +80 -0
- package/src/types/agent.ts +67 -67
- package/src/types/checkpoint.ts +46 -46
- package/src/types/engram.ts +101 -3
- package/src/types/eval.ts +100 -100
- package/src/types/index.ts +6 -6
|
@@ -23,7 +23,8 @@
|
|
|
23
23
|
import { cosineSimilarity } from '../core/embeddings.js';
|
|
24
24
|
import { strengthenAssociation, decayAssociation } from '../core/hebbian.js';
|
|
25
25
|
import type { Engram } from '../types/index.js';
|
|
26
|
-
import type { EngramStore } from '../storage/
|
|
26
|
+
import type { IEngramStore as EngramStore } from '../storage/store.js';
|
|
27
|
+
import type { ConnectionEngine } from './connections.js';
|
|
27
28
|
|
|
28
29
|
/** Cosine similarity for initial candidate detection (single-link entry gate) */
|
|
29
30
|
const SIMILARITY_THRESHOLD = 0.65;
|
|
@@ -81,6 +82,36 @@ const MAX_REDUNDANCY_PRUNE_PER_CYCLE = 25;
|
|
|
81
82
|
/** Max confidence drift per consolidation cycle (prevents runaway) */
|
|
82
83
|
const CONFIDENCE_DRIFT_CAP = 0.03;
|
|
83
84
|
|
|
85
|
+
/**
|
|
86
|
+
* Content fade — Paper 1 (PLOS Comp Biology, storage degradation).
|
|
87
|
+
* Memories whose content has not been recalled in a long time get coarsened:
|
|
88
|
+
* the content string is trimmed to FADE_KEEP_CHARS with a "[faded]" marker,
|
|
89
|
+
* while concept, tags, and embedding are preserved.
|
|
90
|
+
*
|
|
91
|
+
* Rationale: human memory degrades the detailed surface but retains the gist
|
|
92
|
+
* and the cue-association pathway. The faded engram still surfaces by tag/concept
|
|
93
|
+
* BM25 and by vector similarity (embedding preserved), but the full content body
|
|
94
|
+
* is gone — which is the correct behavior for a memory that has not been useful
|
|
95
|
+
* enough to rehearse in a long time.
|
|
96
|
+
*
|
|
97
|
+
* Fade comes BEFORE archive in the lifecycle (active → fading → archived → deleted),
|
|
98
|
+
* applied only to engrams that have been accessed at least once. Engrams that have
|
|
99
|
+
* never been accessed go straight to archive via the standard forgetting path —
|
|
100
|
+
* they have no surface worth preserving.
|
|
101
|
+
*
|
|
102
|
+
* Env: AWM_FADE_DAYS_SINCE_ACCESS (default 45),
|
|
103
|
+
* AWM_FADE_KEEP_CHARS (default 150),
|
|
104
|
+
* AWM_FADE_MIN_CONTENT_LEN (default 250),
|
|
105
|
+
* AWM_FADE_MAX_PER_CYCLE (default 25).
|
|
106
|
+
*/
|
|
107
|
+
const FADE_DAYS_SINCE_ACCESS = Number(process.env.AWM_FADE_DAYS_SINCE_ACCESS ?? 45);
|
|
108
|
+
const FADE_KEEP_CHARS = Number(process.env.AWM_FADE_KEEP_CHARS ?? 150);
|
|
109
|
+
const FADE_MIN_CONTENT_LEN = Number(process.env.AWM_FADE_MIN_CONTENT_LEN ?? 250);
|
|
110
|
+
const FADE_MAX_PER_CYCLE = Number(process.env.AWM_FADE_MAX_PER_CYCLE ?? 25);
|
|
111
|
+
const FADE_PROTECTED_ACCESS_COUNT = 10;
|
|
112
|
+
const FADE_MARKER = '… [faded]';
|
|
113
|
+
const FADE_PROTECTED_CLASSES = new Set(['canonical', 'structural']);
|
|
114
|
+
|
|
84
115
|
/** Days without recall before confidence starts drifting down */
|
|
85
116
|
const CONFIDENCE_NEGLECT_DAYS = 30;
|
|
86
117
|
|
|
@@ -94,6 +125,7 @@ export interface ConsolidationResult {
|
|
|
94
125
|
edgesNormalized: number;
|
|
95
126
|
memoriesForgotten: number;
|
|
96
127
|
memoriesArchived: number;
|
|
128
|
+
memoriesFaded: number;
|
|
97
129
|
redundancyPruned: number;
|
|
98
130
|
confidenceAdjusted: number;
|
|
99
131
|
stagingPromoted: number;
|
|
@@ -114,24 +146,45 @@ const SYNTH_STOPWORDS = new Set(['the', 'is', 'a', 'an', 'and', 'or', 'of', 'to'
|
|
|
114
146
|
|
|
115
147
|
export class ConsolidationEngine {
|
|
116
148
|
private store: EngramStore;
|
|
149
|
+
private connectionEngine: ConnectionEngine | null;
|
|
117
150
|
|
|
118
|
-
|
|
151
|
+
/**
|
|
152
|
+
* Optional `connectionEngine` is drained at the start of each
|
|
153
|
+
* consolidation pass (v0.8.2). Connection discovery is no longer
|
|
154
|
+
* triggered per write — the work batches into the existing sleep cycle
|
|
155
|
+
* so writes return in ~50 ms instead of being pegged by an inline
|
|
156
|
+
* embed + rerank cycle (~200-500 ms/write under load).
|
|
157
|
+
*/
|
|
158
|
+
constructor(store: EngramStore, connectionEngine?: ConnectionEngine) {
|
|
119
159
|
this.store = store;
|
|
160
|
+
this.connectionEngine = connectionEngine ?? null;
|
|
120
161
|
}
|
|
121
162
|
|
|
122
163
|
/**
|
|
123
164
|
* Run a full sleep cycle for an agent.
|
|
124
165
|
*
|
|
166
|
+
* Phase 0: Connection drain — discover associations for engrams enqueued
|
|
167
|
+
* by writes since the last consolidation (v0.8.2)
|
|
125
168
|
* Phase 1: Replay — find clusters of semantically similar memories
|
|
126
169
|
* Phase 2: Strengthen — reinforce edges within clusters (access-weighted)
|
|
127
170
|
* Phase 3: Bridge — create cross-cluster shortcuts
|
|
128
171
|
* Phase 4: Decay — weaken unused edges, prune dead ones
|
|
129
172
|
* Phase 5: Homeostasis — normalize outgoing edge weights per node
|
|
173
|
+
* Phase 5.5: Content fade — coarsen un-recalled engrams (Paper 1)
|
|
130
174
|
* Phase 6: Forget — archive/delete memories never retrieved (age-gated)
|
|
175
|
+
* Phase 6.5: Redundancy prune
|
|
131
176
|
* Phase 6.7: Confidence drift — adjust confidence based on structural signals
|
|
132
177
|
* Phase 7: Sweep — check staging buffer for resonance
|
|
133
178
|
*/
|
|
134
179
|
async consolidate(agentId: string): Promise<ConsolidationResult> {
|
|
180
|
+
// --- Phase 0: Connection drain ---
|
|
181
|
+
// Discover associations for engrams enqueued by writes since the last
|
|
182
|
+
// consolidation. Per-write inline discovery was removed in v0.8.2
|
|
183
|
+
// because each findConnections() call runs a full activation cycle
|
|
184
|
+
// (embed + rerank, ~200-500 ms event-loop block per write).
|
|
185
|
+
if (this.connectionEngine) {
|
|
186
|
+
try { await this.connectionEngine.processQueue(); } catch { /* best-effort */ }
|
|
187
|
+
}
|
|
135
188
|
const result: ConsolidationResult = {
|
|
136
189
|
clustersFound: 0,
|
|
137
190
|
edgesStrengthened: 0,
|
|
@@ -142,6 +195,7 @@ export class ConsolidationEngine {
|
|
|
142
195
|
edgesNormalized: 0,
|
|
143
196
|
memoriesForgotten: 0,
|
|
144
197
|
memoriesArchived: 0,
|
|
198
|
+
memoriesFaded: 0,
|
|
145
199
|
redundancyPruned: 0,
|
|
146
200
|
confidenceAdjusted: 0,
|
|
147
201
|
stagingPromoted: 0,
|
|
@@ -152,7 +206,7 @@ export class ConsolidationEngine {
|
|
|
152
206
|
|
|
153
207
|
// --- Phase 1: Replay ---
|
|
154
208
|
// Get all active engrams, backfill missing embeddings
|
|
155
|
-
const allActive = this.store.getEngramsByAgent(agentId, 'active');
|
|
209
|
+
const allActive = await this.store.getEngramsByAgent(agentId, 'active');
|
|
156
210
|
const needsEmbedding = allActive.filter(e => !e.embedding || e.embedding.length === 0);
|
|
157
211
|
if (needsEmbedding.length > 0) {
|
|
158
212
|
try {
|
|
@@ -165,7 +219,7 @@ export class ConsolidationEngine {
|
|
|
165
219
|
const texts = batch.map(e => `${e.concept} ${e.content}`);
|
|
166
220
|
const vecs = await embedBatch(texts);
|
|
167
221
|
for (let j = 0; j < batch.length; j++) {
|
|
168
|
-
this.store.updateEmbedding(batch[j].id, vecs[j], modelId);
|
|
222
|
+
await this.store.updateEmbedding(batch[j].id, vecs[j], modelId);
|
|
169
223
|
batch[j].embedding = vecs[j];
|
|
170
224
|
}
|
|
171
225
|
} catch { /* batch failed, skip — non-fatal */ }
|
|
@@ -178,10 +232,10 @@ export class ConsolidationEngine {
|
|
|
178
232
|
const engrams = allActive.filter(e => e.embedding && e.embedding.length > 0);
|
|
179
233
|
|
|
180
234
|
result.engramsProcessed = engrams.length;
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
//
|
|
184
|
-
const clusters = this.findClusters(engrams);
|
|
235
|
+
// Clustering requires at least two engrams to form pairs. Per-engram phases
|
|
236
|
+
// (fade, forget, confidence drift, staging sweep) still run for singletons —
|
|
237
|
+
// they don't need a graph.
|
|
238
|
+
const clusters = engrams.length >= 2 ? this.findClusters(engrams) : [];
|
|
185
239
|
result.clustersFound = clusters.length;
|
|
186
240
|
|
|
187
241
|
// --- Phase 2: Strengthen (access-weighted) ---
|
|
@@ -200,17 +254,17 @@ export class ConsolidationEngine {
|
|
|
200
254
|
0.3 + 0.7 * Math.log1p(a.accessCount + b.accessCount) / Math.log1p(20),
|
|
201
255
|
);
|
|
202
256
|
|
|
203
|
-
const existing = this.store.getAssociation(a.id, b.id);
|
|
257
|
+
const existing = await this.store.getAssociation(a.id, b.id);
|
|
204
258
|
if (existing) {
|
|
205
259
|
const newWeight = strengthenAssociation(
|
|
206
260
|
existing.weight, CONSOLIDATION_SIGNAL * accessFactor, 0.25,
|
|
207
261
|
);
|
|
208
|
-
this.store.upsertAssociation(
|
|
262
|
+
await this.store.upsertAssociation(
|
|
209
263
|
a.id, b.id, newWeight, existing.type, existing.confidence,
|
|
210
264
|
);
|
|
211
265
|
result.edgesStrengthened++;
|
|
212
266
|
} else if (newEdges < MAX_NEW_EDGES_PER_CYCLE) {
|
|
213
|
-
this.store.upsertAssociation(
|
|
267
|
+
await this.store.upsertAssociation(
|
|
214
268
|
a.id, b.id, INITIAL_EDGE_WEIGHT * accessFactor, 'connection',
|
|
215
269
|
);
|
|
216
270
|
newEdges++;
|
|
@@ -283,7 +337,7 @@ export class ConsolidationEngine {
|
|
|
283
337
|
`Discussed: ${keyTerms.slice(8).join(', ')}.`,
|
|
284
338
|
].join(' ');
|
|
285
339
|
|
|
286
|
-
const synthEngram = this.store.createEngram({
|
|
340
|
+
const synthEngram = await this.store.createEngram({
|
|
287
341
|
agentId,
|
|
288
342
|
concept: `session: ${tag} (${keyTerms.slice(0, 3).join(', ')})`,
|
|
289
343
|
content: synthContent,
|
|
@@ -296,12 +350,12 @@ export class ConsolidationEngine {
|
|
|
296
350
|
// Set embedding to group centroid
|
|
297
351
|
const centroid = this.computeCentroid(group);
|
|
298
352
|
if (centroid.length > 0) {
|
|
299
|
-
this.store.updateEmbedding(synthEngram.id, centroid);
|
|
353
|
+
await this.store.updateEmbedding(synthEngram.id, centroid);
|
|
300
354
|
}
|
|
301
355
|
|
|
302
356
|
// Link to sources
|
|
303
357
|
for (const source of group.slice(0, 10)) { // Cap links to prevent explosion
|
|
304
|
-
this.store.upsertAssociation(synthEngram.id, source.id, 0.4, 'causal');
|
|
358
|
+
await this.store.upsertAssociation(synthEngram.id, source.id, 0.4, 'causal');
|
|
305
359
|
}
|
|
306
360
|
|
|
307
361
|
synthCount++;
|
|
@@ -344,7 +398,7 @@ export class ConsolidationEngine {
|
|
|
344
398
|
`Common themes: ${keyTerms.join(', ')}.`,
|
|
345
399
|
].join(' ');
|
|
346
400
|
|
|
347
|
-
const synthEngram = this.store.createEngram({
|
|
401
|
+
const synthEngram = await this.store.createEngram({
|
|
348
402
|
agentId,
|
|
349
403
|
concept: `pattern: ${keyTerms.slice(0, 3).join(', ')}`,
|
|
350
404
|
content: synthContent,
|
|
@@ -356,11 +410,11 @@ export class ConsolidationEngine {
|
|
|
356
410
|
|
|
357
411
|
const centroid = this.computeCentroid(cluster);
|
|
358
412
|
if (centroid.length > 0) {
|
|
359
|
-
this.store.updateEmbedding(synthEngram.id, centroid);
|
|
413
|
+
await this.store.updateEmbedding(synthEngram.id, centroid);
|
|
360
414
|
}
|
|
361
415
|
|
|
362
416
|
for (const source of cluster.slice(0, 8)) {
|
|
363
|
-
this.store.upsertAssociation(synthEngram.id, source.id, 0.3, 'bridge');
|
|
417
|
+
await this.store.upsertAssociation(synthEngram.id, source.id, 0.3, 'bridge');
|
|
364
418
|
}
|
|
365
419
|
|
|
366
420
|
synthCount++;
|
|
@@ -386,10 +440,10 @@ export class ConsolidationEngine {
|
|
|
386
440
|
}
|
|
387
441
|
}
|
|
388
442
|
if (bestA && bestB && bestSim > MIN_BRIDGE_SIM) {
|
|
389
|
-
const existing = this.store.getAssociation(bestA.id, bestB.id);
|
|
443
|
+
const existing = await this.store.getAssociation(bestA.id, bestB.id);
|
|
390
444
|
if (!existing) {
|
|
391
|
-
this.store.upsertAssociation(bestA.id, bestB.id, bestSim, 'bridge');
|
|
392
|
-
this.store.upsertAssociation(bestB.id, bestA.id, bestSim, 'bridge');
|
|
445
|
+
await this.store.upsertAssociation(bestA.id, bestB.id, bestSim, 'bridge');
|
|
446
|
+
await this.store.upsertAssociation(bestB.id, bestA.id, bestSim, 'bridge');
|
|
393
447
|
bridges++;
|
|
394
448
|
result.bridgesCreated++;
|
|
395
449
|
}
|
|
@@ -404,7 +458,7 @@ export class ConsolidationEngine {
|
|
|
404
458
|
// practiced memories are more resistant to forgetting in the brain.
|
|
405
459
|
// Base half-life: 7 days. High-confidence (0.8+) gets up to 30 days.
|
|
406
460
|
const engramMap = new Map(engrams.map(e => [e.id, e]));
|
|
407
|
-
const associations = this.store.getAllAssociations(agentId);
|
|
461
|
+
const associations = await this.store.getAllAssociations(agentId);
|
|
408
462
|
for (const assoc of associations) {
|
|
409
463
|
const daysSince =
|
|
410
464
|
(Date.now() - assoc.lastActivated.getTime()) / (1000 * 60 * 60 * 24);
|
|
@@ -424,10 +478,10 @@ export class ConsolidationEngine {
|
|
|
424
478
|
|
|
425
479
|
const newWeight = decayAssociation(assoc.weight, daysSince, halfLifeDays);
|
|
426
480
|
if (newWeight < PRUNE_THRESHOLD) {
|
|
427
|
-
this.store.deleteAssociation(assoc.id);
|
|
481
|
+
await this.store.deleteAssociation(assoc.id);
|
|
428
482
|
result.edgesPruned++;
|
|
429
483
|
} else if (Math.abs(newWeight - assoc.weight) > 0.001) {
|
|
430
|
-
this.store.upsertAssociation(
|
|
484
|
+
await this.store.upsertAssociation(
|
|
431
485
|
assoc.fromEngramId, assoc.toEngramId,
|
|
432
486
|
newWeight, assoc.type, assoc.confidence,
|
|
433
487
|
);
|
|
@@ -440,17 +494,17 @@ export class ConsolidationEngine {
|
|
|
440
494
|
// Nodes with many strong edges get scaled down so relative weights stay meaningful.
|
|
441
495
|
const engramIds = new Set(engrams.map(e => e.id));
|
|
442
496
|
for (const id of engramIds) {
|
|
443
|
-
const outgoing = this.store.getOutgoingAssociations(id);
|
|
497
|
+
const outgoing = await this.store.getOutgoingAssociations(id);
|
|
444
498
|
const totalWeight = outgoing.reduce((sum, a) => sum + a.weight, 0);
|
|
445
499
|
if (totalWeight > HOMEOSTASIS_TARGET) {
|
|
446
500
|
const scale = HOMEOSTASIS_TARGET / totalWeight;
|
|
447
501
|
for (const edge of outgoing) {
|
|
448
502
|
const newWeight = edge.weight * scale;
|
|
449
503
|
if (newWeight < PRUNE_THRESHOLD) {
|
|
450
|
-
this.store.deleteAssociation(edge.id);
|
|
504
|
+
await this.store.deleteAssociation(edge.id);
|
|
451
505
|
result.edgesPruned++;
|
|
452
506
|
} else {
|
|
453
|
-
this.store.upsertAssociation(
|
|
507
|
+
await this.store.upsertAssociation(
|
|
454
508
|
edge.fromEngramId, edge.toEngramId,
|
|
455
509
|
newWeight, edge.type, edge.confidence,
|
|
456
510
|
);
|
|
@@ -460,6 +514,37 @@ export class ConsolidationEngine {
|
|
|
460
514
|
}
|
|
461
515
|
}
|
|
462
516
|
|
|
517
|
+
// --- Phase 5.5: Content fade (Paper 1: storage degradation) ---
|
|
518
|
+
// Coarsens engrams that have been accessed before but have gone stale.
|
|
519
|
+
// Trims content surface area while preserving concept, tags, and embedding so
|
|
520
|
+
// the engram still participates in BM25 + vector recall — just with less
|
|
521
|
+
// body to score against. This models how human memory loses surface detail
|
|
522
|
+
// while retaining cue-association pathways.
|
|
523
|
+
//
|
|
524
|
+
// Fade applies only to accessed engrams (the never-accessed ones go straight
|
|
525
|
+
// to archive via Phase 6). Heavily-used (accessCount >= FADE_PROTECTED_ACCESS_COUNT),
|
|
526
|
+
// canonical, structural, and short-content engrams are excluded.
|
|
527
|
+
let fadeCount = 0;
|
|
528
|
+
for (const engram of engrams) {
|
|
529
|
+
if (fadeCount >= FADE_MAX_PER_CYCLE) break;
|
|
530
|
+
if (engram.accessCount === 0) continue;
|
|
531
|
+
if (engram.accessCount >= FADE_PROTECTED_ACCESS_COUNT) continue;
|
|
532
|
+
if (FADE_PROTECTED_CLASSES.has(engram.memoryClass)) continue;
|
|
533
|
+
if (engram.retracted) continue;
|
|
534
|
+
if (engram.content.length < FADE_MIN_CONTENT_LEN) continue;
|
|
535
|
+
if (engram.content.endsWith(FADE_MARKER)) continue; // Already faded
|
|
536
|
+
const daysSinceAccess = (Date.now() - engram.lastAccessed.getTime()) / (1000 * 60 * 60 * 24);
|
|
537
|
+
if (daysSinceAccess < FADE_DAYS_SINCE_ACCESS) continue;
|
|
538
|
+
|
|
539
|
+
const trimmed = engram.content.slice(0, FADE_KEEP_CHARS).trimEnd() + FADE_MARKER;
|
|
540
|
+
await this.store.updateContent(engram.id, trimmed);
|
|
541
|
+
await this.store.updateStage(engram.id, 'fading');
|
|
542
|
+
engram.content = trimmed;
|
|
543
|
+
engram.stage = 'fading';
|
|
544
|
+
result.memoriesFaded++;
|
|
545
|
+
fadeCount++;
|
|
546
|
+
}
|
|
547
|
+
|
|
463
548
|
// --- Phase 6: Forgetting (age-gated) ---
|
|
464
549
|
// Models how human memory actually works:
|
|
465
550
|
// - New memories get a grace period (too new to judge)
|
|
@@ -474,19 +559,27 @@ export class ConsolidationEngine {
|
|
|
474
559
|
// Compute edge count percentile for relative protection threshold.
|
|
475
560
|
// With avg 12 edges/node, an absolute threshold of 3 protects everything.
|
|
476
561
|
// Use 25th percentile so "weakly connected" is relative to actual graph density.
|
|
477
|
-
const edgeCounts = engrams.map(e => this.store.countAssociationsFor(e.id));
|
|
562
|
+
const edgeCounts = await Promise.all(engrams.map(async e => await this.store.countAssociationsFor(e.id)));
|
|
478
563
|
edgeCounts.sort((a, b) => a - b);
|
|
479
564
|
const percentileIdx = Math.floor(edgeCounts.length * EDGE_PROTECTION_PERCENTILE);
|
|
480
565
|
const baseEdgeThreshold = edgeCounts.length > 0 ? edgeCounts[percentileIdx] : 3;
|
|
481
566
|
|
|
482
567
|
// Get consolidation cycle count for cycle-based archiving
|
|
483
|
-
const cycleCount = this.store.getConsolidationCycleCount(agentId);
|
|
568
|
+
const cycleCount = await this.store.getConsolidationCycleCount(agentId);
|
|
484
569
|
|
|
485
570
|
for (const engram of engrams) {
|
|
571
|
+
// Skip engrams that just transitioned to 'fading' in Phase 5.5 this cycle.
|
|
572
|
+
// Fade is intended as an intermediate stage between active and archived —
|
|
573
|
+
// the engram should live in 'fading' for at least one more recall window
|
|
574
|
+
// before being eligible for archival. Without this guard, a same-cycle
|
|
575
|
+
// fade-then-archive sequence collapses the active pool to zero (observed
|
|
576
|
+
// in the stress test scale phase around cycle 90).
|
|
577
|
+
if (engram.stage !== 'active') continue;
|
|
578
|
+
|
|
486
579
|
const ageDays = (Date.now() - engram.createdAt.getTime()) / (1000 * 60 * 60 * 24);
|
|
487
580
|
if (ageDays < FORGET_GRACE_DAYS) continue; // Grace period — too new to judge
|
|
488
581
|
|
|
489
|
-
const edgeCount = this.store.countAssociationsFor(engram.id);
|
|
582
|
+
const edgeCount = await this.store.countAssociationsFor(engram.id);
|
|
490
583
|
|
|
491
584
|
// Use relative threshold (percentile-based) instead of absolute.
|
|
492
585
|
// High-confidence memories need fewer edges to survive.
|
|
@@ -499,7 +592,7 @@ export class ConsolidationEngine {
|
|
|
499
592
|
// Cycle-based archive: 0-access memories archived after N cycles
|
|
500
593
|
// regardless of age. Handles small pools where time thresholds are too generous.
|
|
501
594
|
if (engram.accessCount === 0 && cycleCount >= FORGET_CYCLE_THRESHOLD) {
|
|
502
|
-
this.store.updateStage(engram.id, 'archived');
|
|
595
|
+
await this.store.updateStage(engram.id, 'archived');
|
|
503
596
|
result.memoriesArchived++;
|
|
504
597
|
continue;
|
|
505
598
|
}
|
|
@@ -522,24 +615,24 @@ export class ConsolidationEngine {
|
|
|
522
615
|
|
|
523
616
|
if (engram.accessCount === 0 && ageDays > FORGET_ARCHIVE_DAYS) {
|
|
524
617
|
// Never retrieved, old, weakly connected → archive
|
|
525
|
-
this.store.updateStage(engram.id, 'archived');
|
|
618
|
+
await this.store.updateStage(engram.id, 'archived');
|
|
526
619
|
result.memoriesArchived++;
|
|
527
620
|
} else if (engram.accessCount > 0 && daysSinceAccess > effectiveArchiveDays) {
|
|
528
621
|
// Accessed before but not recently enough given its strength — archive
|
|
529
|
-
this.store.updateStage(engram.id, 'archived');
|
|
622
|
+
await this.store.updateStage(engram.id, 'archived');
|
|
530
623
|
result.memoriesArchived++;
|
|
531
624
|
}
|
|
532
625
|
}
|
|
533
626
|
|
|
534
627
|
// Check archived memories for deletion — only truly orphaned ancient ones
|
|
535
|
-
const archived = this.store.getEngramsByAgent(agentId, 'archived');
|
|
628
|
+
const archived = await this.store.getEngramsByAgent(agentId, 'archived');
|
|
536
629
|
for (const engram of archived) {
|
|
537
630
|
const ageDays = (Date.now() - engram.createdAt.getTime()) / (1000 * 60 * 60 * 24);
|
|
538
|
-
const edgeCount = this.store.countAssociationsFor(engram.id);
|
|
631
|
+
const edgeCount = await this.store.countAssociationsFor(engram.id);
|
|
539
632
|
|
|
540
633
|
if (engram.accessCount === 0 && ageDays > FORGET_DELETE_DAYS && edgeCount === 0) {
|
|
541
634
|
// Very old, never accessed, completely isolated → truly forgotten
|
|
542
|
-
this.store.deleteEngram(engram.id);
|
|
635
|
+
await this.store.deleteEngram(engram.id);
|
|
543
636
|
result.memoriesForgotten++;
|
|
544
637
|
}
|
|
545
638
|
// Otherwise: stay archived — still searchable, just not in active recall
|
|
@@ -577,11 +670,11 @@ export class ConsolidationEngine {
|
|
|
577
670
|
const prunedId = sortedLow[j].id;
|
|
578
671
|
|
|
579
672
|
// Transfer associations from pruned memory to survivor
|
|
580
|
-
const prunedEdges = this.store.getAssociationsFor(prunedId);
|
|
673
|
+
const prunedEdges = await this.store.getAssociationsFor(prunedId);
|
|
581
674
|
for (const edge of prunedEdges) {
|
|
582
675
|
const peerId = edge.fromEngramId === prunedId ? edge.toEngramId : edge.fromEngramId;
|
|
583
676
|
if (peerId === survivorId) continue; // Skip self-loops
|
|
584
|
-
this.store.upsertAssociation(survivorId, peerId, edge.weight, edge.type, edge.confidence);
|
|
677
|
+
await this.store.upsertAssociation(survivorId, peerId, edge.weight, edge.type, edge.confidence);
|
|
585
678
|
}
|
|
586
679
|
|
|
587
680
|
// Merge tags from pruned to survivor
|
|
@@ -589,11 +682,11 @@ export class ConsolidationEngine {
|
|
|
589
682
|
const prunedMem = sortedLow[j];
|
|
590
683
|
const mergedTags = [...new Set([...survivor.tags, ...prunedMem.tags])];
|
|
591
684
|
if (mergedTags.length > survivor.tags.length) {
|
|
592
|
-
this.store.updateTags(survivorId, mergedTags);
|
|
685
|
+
await this.store.updateTags(survivorId, mergedTags);
|
|
593
686
|
}
|
|
594
687
|
|
|
595
688
|
// Archive the lower-quality duplicate
|
|
596
|
-
this.store.updateStage(prunedId, 'archived');
|
|
689
|
+
await this.store.updateStage(prunedId, 'archived');
|
|
597
690
|
pruned.add(prunedId);
|
|
598
691
|
redundancyCount++;
|
|
599
692
|
}
|
|
@@ -623,7 +716,7 @@ export class ConsolidationEngine {
|
|
|
623
716
|
|
|
624
717
|
for (const engram of engrams) {
|
|
625
718
|
let drift = 0;
|
|
626
|
-
const edgeCount = this.store.countAssociationsFor(engram.id);
|
|
719
|
+
const edgeCount = await this.store.countAssociationsFor(engram.id);
|
|
627
720
|
const daysSinceAccess = (Date.now() - engram.lastAccessed.getTime()) / (1000 * 60 * 60 * 24);
|
|
628
721
|
|
|
629
722
|
// Signal 1: Cluster membership → small boost
|
|
@@ -646,14 +739,14 @@ export class ConsolidationEngine {
|
|
|
646
739
|
drift = Math.max(-CONFIDENCE_DRIFT_CAP, Math.min(CONFIDENCE_DRIFT_CAP, drift));
|
|
647
740
|
const newConf = Math.max(0.15, Math.min(0.85, engram.confidence + drift));
|
|
648
741
|
if (Math.abs(newConf - engram.confidence) > 0.001) {
|
|
649
|
-
this.store.updateConfidence(engram.id, newConf);
|
|
742
|
+
await this.store.updateConfidence(engram.id, newConf);
|
|
650
743
|
result.confidenceAdjusted++;
|
|
651
744
|
}
|
|
652
745
|
}
|
|
653
746
|
}
|
|
654
747
|
|
|
655
748
|
// --- Phase 7: Sweep staging ---
|
|
656
|
-
const staging = this.store.getEngramsByAgent(agentId, 'staging')
|
|
749
|
+
const staging = (await this.store.getEngramsByAgent(agentId, 'staging'))
|
|
657
750
|
.filter(e => e.embedding && e.embedding.length > 0);
|
|
658
751
|
|
|
659
752
|
for (const staged of staging) {
|
|
@@ -669,12 +762,12 @@ export class ConsolidationEngine {
|
|
|
669
762
|
|
|
670
763
|
if (maxSim >= 0.6) {
|
|
671
764
|
// Resonates — promote to active with low confidence (barely made it)
|
|
672
|
-
this.store.updateStage(staged.id, 'active');
|
|
673
|
-
this.store.updateConfidence(staged.id, 0.40);
|
|
765
|
+
await this.store.updateStage(staged.id, 'active');
|
|
766
|
+
await this.store.updateConfidence(staged.id, 0.40);
|
|
674
767
|
result.stagingPromoted++;
|
|
675
768
|
} else if (ageMs > 24 * 60 * 60 * 1000) {
|
|
676
769
|
// Over 24h and no resonance — discard
|
|
677
|
-
this.store.deleteEngram(staged.id);
|
|
770
|
+
await this.store.deleteEngram(staged.id);
|
|
678
771
|
result.stagingDiscarded++;
|
|
679
772
|
}
|
|
680
773
|
// Otherwise: leave in staging, maybe next cycle
|
package/src/engine/eval.ts
CHANGED
|
@@ -1,102 +1,102 @@
|
|
|
1
|
-
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
/**
|
|
4
|
-
* Evaluation Engine — measures whether memory actually helps.
|
|
5
|
-
*
|
|
6
|
-
* Four dimensions (from Codex):
|
|
7
|
-
* 1. Retrieval quality — precision@k, latency
|
|
8
|
-
* 2. Connection quality — edge utility, stability
|
|
9
|
-
* 3. Staging accuracy — promotion precision, discard regret
|
|
10
|
-
* 4. Memory health — contamination tracking, confidence distribution
|
|
11
|
-
*
|
|
12
|
-
* Task impact (with/without memory) is measured externally via TaskTrial records.
|
|
13
|
-
*/
|
|
14
|
-
|
|
15
|
-
import type { EngramStore } from '../storage/
|
|
16
|
-
import type { EvalMetrics } from '../types/index.js';
|
|
17
|
-
|
|
18
|
-
export class EvalEngine {
|
|
19
|
-
private store: EngramStore;
|
|
20
|
-
|
|
21
|
-
constructor(store: EngramStore) {
|
|
22
|
-
this.store = store;
|
|
23
|
-
}
|
|
24
|
-
|
|
25
|
-
/**
|
|
26
|
-
* Compute aggregate metrics for an agent over a time window.
|
|
27
|
-
*/
|
|
28
|
-
computeMetrics(agentId: string, windowHours: number = 24): EvalMetrics {
|
|
29
|
-
const window = windowHours <= 24 ? '24h' : `${Math.round(windowHours / 24)}d`;
|
|
30
|
-
|
|
31
|
-
// Retrieval quality
|
|
32
|
-
const precision = this.store.getRetrievalPrecision(agentId, windowHours);
|
|
33
|
-
|
|
34
|
-
// Staging accuracy
|
|
35
|
-
const stagingMetrics = this.store.getStagingMetrics(agentId);
|
|
36
|
-
const totalStaged = stagingMetrics.promoted + stagingMetrics.discarded + stagingMetrics.expired;
|
|
37
|
-
const promotionPrecision = totalStaged > 0 ? stagingMetrics.promoted / totalStaged : 0;
|
|
38
|
-
|
|
39
|
-
// Memory health
|
|
40
|
-
const activeEngrams = this.store.getEngramsByAgent(agentId, 'active');
|
|
41
|
-
const stagingEngrams = this.store.getEngramsByAgent(agentId, 'staging');
|
|
42
|
-
const retractedEngrams = this.store.getEngramsByAgent(agentId, undefined, true)
|
|
43
|
-
.filter(e => e.retracted);
|
|
44
|
-
const allAssociations = this.store.getAllAssociations(agentId);
|
|
45
|
-
|
|
46
|
-
const avgConfidence = activeEngrams.length > 0
|
|
47
|
-
? activeEngrams.reduce((sum, e) => sum + e.confidence, 0) / activeEngrams.length
|
|
48
|
-
: 0;
|
|
49
|
-
|
|
50
|
-
// Edge utility — % of edges that have been used in activation
|
|
51
|
-
const usedEdges = allAssociations.filter(a => a.activationCount > 0);
|
|
52
|
-
const edgeUtility = allAssociations.length > 0
|
|
53
|
-
? usedEdges.length / allAssociations.length
|
|
54
|
-
: 0;
|
|
55
|
-
|
|
56
|
-
// Edge survival — average age of edges that are still above minimum weight
|
|
57
|
-
const livingEdges = allAssociations.filter(a => a.weight > 0.01);
|
|
58
|
-
const avgSurvival = livingEdges.length > 0
|
|
59
|
-
? livingEdges.reduce((sum, a) =>
|
|
60
|
-
sum + (Date.now() - a.createdAt.getTime()) / (1000 * 60 * 60 * 24), 0
|
|
61
|
-
) / livingEdges.length
|
|
62
|
-
: 0;
|
|
63
|
-
|
|
64
|
-
// Activation performance stats
|
|
65
|
-
const activationStats = this.store.getActivationStats(agentId, windowHours);
|
|
66
|
-
|
|
67
|
-
// Consolidated count
|
|
68
|
-
const consolidatedCount = this.store.getConsolidatedCount(agentId);
|
|
69
|
-
|
|
70
|
-
return {
|
|
71
|
-
agentId,
|
|
72
|
-
timestamp: new Date(),
|
|
73
|
-
window,
|
|
74
|
-
|
|
75
|
-
activationCount: activationStats.count,
|
|
76
|
-
avgPrecisionAtK: precision,
|
|
77
|
-
avgLatencyMs: activationStats.avgLatencyMs,
|
|
78
|
-
p95LatencyMs: activationStats.p95LatencyMs,
|
|
79
|
-
|
|
80
|
-
totalEdges: allAssociations.length,
|
|
81
|
-
edgesUsedInActivation: usedEdges.length,
|
|
82
|
-
edgeUtilityRate: edgeUtility,
|
|
83
|
-
avgEdgeSurvivalDays: avgSurvival,
|
|
84
|
-
|
|
85
|
-
totalStaged: totalStaged,
|
|
86
|
-
promotedCount: stagingMetrics.promoted,
|
|
87
|
-
discardedCount: stagingMetrics.discarded,
|
|
88
|
-
promotionPrecision,
|
|
89
|
-
discardRegret: 0, // Requires tracking discarded-then-rediscovered items
|
|
90
|
-
|
|
91
|
-
activeEngramCount: activeEngrams.length,
|
|
92
|
-
stagingEngramCount: stagingEngrams.length,
|
|
93
|
-
retractedCount: retractedEngrams.length,
|
|
94
|
-
consolidatedCount,
|
|
95
|
-
avgConfidence,
|
|
96
|
-
|
|
97
|
-
staleUsageCount: 0, // Requires per-activation age/confidence tracking
|
|
98
|
-
retractionRate: retractedEngrams.length /
|
|
99
|
-
Math.max(activeEngrams.length + retractedEngrams.length, 1),
|
|
100
|
-
};
|
|
101
|
-
}
|
|
102
|
-
}
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
/**
|
|
4
|
+
* Evaluation Engine — measures whether memory actually helps.
|
|
5
|
+
*
|
|
6
|
+
* Four dimensions (from Codex):
|
|
7
|
+
* 1. Retrieval quality — precision@k, latency
|
|
8
|
+
* 2. Connection quality — edge utility, stability
|
|
9
|
+
* 3. Staging accuracy — promotion precision, discard regret
|
|
10
|
+
* 4. Memory health — contamination tracking, confidence distribution
|
|
11
|
+
*
|
|
12
|
+
* Task impact (with/without memory) is measured externally via TaskTrial records.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import type { IEngramStore as EngramStore } from '../storage/store.js';
|
|
16
|
+
import type { EvalMetrics } from '../types/index.js';
|
|
17
|
+
|
|
18
|
+
export class EvalEngine {
|
|
19
|
+
private store: EngramStore;
|
|
20
|
+
|
|
21
|
+
constructor(store: EngramStore) {
|
|
22
|
+
this.store = store;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
/**
|
|
26
|
+
* Compute aggregate metrics for an agent over a time window.
|
|
27
|
+
*/
|
|
28
|
+
async computeMetrics(agentId: string, windowHours: number = 24): Promise<EvalMetrics> {
|
|
29
|
+
const window = windowHours <= 24 ? '24h' : `${Math.round(windowHours / 24)}d`;
|
|
30
|
+
|
|
31
|
+
// Retrieval quality
|
|
32
|
+
const precision = await this.store.getRetrievalPrecision(agentId, windowHours);
|
|
33
|
+
|
|
34
|
+
// Staging accuracy
|
|
35
|
+
const stagingMetrics = await this.store.getStagingMetrics(agentId);
|
|
36
|
+
const totalStaged = stagingMetrics.promoted + stagingMetrics.discarded + stagingMetrics.expired;
|
|
37
|
+
const promotionPrecision = totalStaged > 0 ? stagingMetrics.promoted / totalStaged : 0;
|
|
38
|
+
|
|
39
|
+
// Memory health
|
|
40
|
+
const activeEngrams = await this.store.getEngramsByAgent(agentId, 'active');
|
|
41
|
+
const stagingEngrams = await this.store.getEngramsByAgent(agentId, 'staging');
|
|
42
|
+
const retractedEngrams = (await this.store.getEngramsByAgent(agentId, undefined, true))
|
|
43
|
+
.filter(e => e.retracted);
|
|
44
|
+
const allAssociations = await this.store.getAllAssociations(agentId);
|
|
45
|
+
|
|
46
|
+
const avgConfidence = activeEngrams.length > 0
|
|
47
|
+
? activeEngrams.reduce((sum, e) => sum + e.confidence, 0) / activeEngrams.length
|
|
48
|
+
: 0;
|
|
49
|
+
|
|
50
|
+
// Edge utility — % of edges that have been used in activation
|
|
51
|
+
const usedEdges = allAssociations.filter(a => a.activationCount > 0);
|
|
52
|
+
const edgeUtility = allAssociations.length > 0
|
|
53
|
+
? usedEdges.length / allAssociations.length
|
|
54
|
+
: 0;
|
|
55
|
+
|
|
56
|
+
// Edge survival — average age of edges that are still above minimum weight
|
|
57
|
+
const livingEdges = allAssociations.filter(a => a.weight > 0.01);
|
|
58
|
+
const avgSurvival = livingEdges.length > 0
|
|
59
|
+
? livingEdges.reduce((sum, a) =>
|
|
60
|
+
sum + (Date.now() - a.createdAt.getTime()) / (1000 * 60 * 60 * 24), 0
|
|
61
|
+
) / livingEdges.length
|
|
62
|
+
: 0;
|
|
63
|
+
|
|
64
|
+
// Activation performance stats
|
|
65
|
+
const activationStats = await this.store.getActivationStats(agentId, windowHours);
|
|
66
|
+
|
|
67
|
+
// Consolidated count
|
|
68
|
+
const consolidatedCount = await this.store.getConsolidatedCount(agentId);
|
|
69
|
+
|
|
70
|
+
return {
|
|
71
|
+
agentId,
|
|
72
|
+
timestamp: new Date(),
|
|
73
|
+
window,
|
|
74
|
+
|
|
75
|
+
activationCount: activationStats.count,
|
|
76
|
+
avgPrecisionAtK: precision,
|
|
77
|
+
avgLatencyMs: activationStats.avgLatencyMs,
|
|
78
|
+
p95LatencyMs: activationStats.p95LatencyMs,
|
|
79
|
+
|
|
80
|
+
totalEdges: allAssociations.length,
|
|
81
|
+
edgesUsedInActivation: usedEdges.length,
|
|
82
|
+
edgeUtilityRate: edgeUtility,
|
|
83
|
+
avgEdgeSurvivalDays: avgSurvival,
|
|
84
|
+
|
|
85
|
+
totalStaged: totalStaged,
|
|
86
|
+
promotedCount: stagingMetrics.promoted,
|
|
87
|
+
discardedCount: stagingMetrics.discarded,
|
|
88
|
+
promotionPrecision,
|
|
89
|
+
discardRegret: 0, // Requires tracking discarded-then-rediscovered items
|
|
90
|
+
|
|
91
|
+
activeEngramCount: activeEngrams.length,
|
|
92
|
+
stagingEngramCount: stagingEngrams.length,
|
|
93
|
+
retractedCount: retractedEngrams.length,
|
|
94
|
+
consolidatedCount,
|
|
95
|
+
avgConfidence,
|
|
96
|
+
|
|
97
|
+
staleUsageCount: 0, // Requires per-activation age/confidence tracking
|
|
98
|
+
retractionRate: retractedEngrams.length /
|
|
99
|
+
Math.max(activeEngrams.length + retractedEngrams.length, 1),
|
|
100
|
+
};
|
|
101
|
+
}
|
|
102
|
+
}
|