agent-working-memory 0.8.5 → 0.8.7
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 +4 -2
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +108 -8
- package/dist/adapters/common.js.map +1 -1
- package/dist/api/routes.js +1 -1
- package/dist/cli/migrate.js +29 -29
- package/dist/cli.js +1 -1
- package/dist/coordination/circuit-breaker.js +23 -23
- package/dist/core/lite-compress.d.ts +26 -0
- package/dist/core/lite-compress.d.ts.map +1 -0
- package/dist/core/lite-compress.js +105 -0
- package/dist/core/lite-compress.js.map +1 -0
- package/dist/mcp.d.ts +5 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +58 -4
- package/dist/mcp.js.map +1 -1
- package/dist/storage/pglite-schema.js +143 -143
- package/dist/storage/pglite.js +138 -138
- package/package.json +4 -3
- package/src/adapters/common.ts +108 -8
- package/src/api/index.ts +3 -3
- package/src/api/routes.ts +1 -1
- package/src/cli/migrate.ts +307 -307
- package/src/cli.ts +1 -1
- package/src/coordination/circuit-breaker.ts +83 -83
- package/src/coordination/failure-modes.ts +50 -50
- package/src/core/decay.ts +63 -63
- package/src/core/embeddings.ts +110 -110
- package/src/core/index.ts +5 -5
- package/src/core/lite-compress.ts +129 -0
- package/src/core/logger.ts +36 -36
- package/src/core/ml-worker-entry.ts +194 -194
- package/src/core/ml-worker.ts +281 -281
- package/src/core/query-expander.ts +122 -122
- package/src/core/reranker.ts +119 -119
- package/src/engine/confidence.ts +120 -120
- package/src/engine/connections.ts +162 -162
- package/src/engine/consolidation-scheduler.ts +242 -242
- 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 -366
- package/src/engine/staging.ts +74 -74
- package/src/mcp.ts +70 -4
- package/src/storage/factory.ts +147 -147
- package/src/storage/index.ts +3 -3
- package/src/storage/pglite-schema.ts +166 -166
- package/src/storage/pglite.ts +1363 -1363
- package/src/storage/store.ts +80 -80
- package/src/types/agent.ts +67 -67
- package/src/types/checkpoint.ts +46 -46
- package/src/types/eval.ts +100 -100
- package/src/types/index.ts +6 -6
package/dist/storage/pglite.js
CHANGED
|
@@ -184,7 +184,7 @@ export class PGliteEngramStore {
|
|
|
184
184
|
params.push(e.id, e.agentId, e.timestamp.toISOString(), e.context, e.resultsReturned, e.topScore, e.latencyMs, JSON.stringify(e.engramIds));
|
|
185
185
|
}
|
|
186
186
|
try {
|
|
187
|
-
await this.db.query(`INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
|
|
187
|
+
await this.db.query(`INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
|
|
188
188
|
VALUES ${values.join(',')}`, params);
|
|
189
189
|
}
|
|
190
190
|
catch {
|
|
@@ -222,18 +222,18 @@ export class PGliteEngramStore {
|
|
|
222
222
|
await this.readyPromise;
|
|
223
223
|
const id = input.id ?? randomUUID();
|
|
224
224
|
const now = new Date().toISOString();
|
|
225
|
-
await this.db.query(`INSERT INTO engrams (
|
|
226
|
-
id, agent_id, concept, content, embedding, embedding_model,
|
|
227
|
-
confidence, salience, access_count, last_accessed, created_at,
|
|
228
|
-
salience_features, reason_codes, stage, ttl, retracted,
|
|
229
|
-
tags, memory_type, memory_class, supersedes, episode_id,
|
|
230
|
-
task_status, task_priority, blocked_by, sequence, references_json
|
|
231
|
-
) VALUES (
|
|
232
|
-
$1, $2, $3, $4, $5::vector, $6,
|
|
233
|
-
$7, $8, 0, $9, $10,
|
|
234
|
-
$11, $12, 'active', $13, FALSE,
|
|
235
|
-
$14, $15, $16, $17, $18,
|
|
236
|
-
$19, $20, $21, $22, $23
|
|
225
|
+
await this.db.query(`INSERT INTO engrams (
|
|
226
|
+
id, agent_id, concept, content, embedding, embedding_model,
|
|
227
|
+
confidence, salience, access_count, last_accessed, created_at,
|
|
228
|
+
salience_features, reason_codes, stage, ttl, retracted,
|
|
229
|
+
tags, memory_type, memory_class, supersedes, episode_id,
|
|
230
|
+
task_status, task_priority, blocked_by, sequence, references_json
|
|
231
|
+
) VALUES (
|
|
232
|
+
$1, $2, $3, $4, $5::vector, $6,
|
|
233
|
+
$7, $8, 0, $9, $10,
|
|
234
|
+
$11, $12, 'active', $13, FALSE,
|
|
235
|
+
$14, $15, $16, $17, $18,
|
|
236
|
+
$19, $20, $21, $22, $23
|
|
237
237
|
)`, [
|
|
238
238
|
id,
|
|
239
239
|
input.agentId,
|
|
@@ -362,10 +362,10 @@ export class PGliteEngramStore {
|
|
|
362
362
|
}
|
|
363
363
|
async touchEngram(id) {
|
|
364
364
|
await this.readyPromise;
|
|
365
|
-
await this.db.query(`UPDATE engrams
|
|
366
|
-
SET access_count = access_count + 1,
|
|
367
|
-
last_accessed = $1,
|
|
368
|
-
confidence = LEAST(0.85, confidence + 0.02 / (1.0 + sqrt(access_count::float)))
|
|
365
|
+
await this.db.query(`UPDATE engrams
|
|
366
|
+
SET access_count = access_count + 1,
|
|
367
|
+
last_accessed = $1,
|
|
368
|
+
confidence = LEAST(0.85, confidence + 0.02 / (1.0 + sqrt(access_count::float)))
|
|
369
369
|
WHERE id = $2`, [new Date().toISOString(), id]);
|
|
370
370
|
}
|
|
371
371
|
async updateStage(id, stage) {
|
|
@@ -411,9 +411,9 @@ export class PGliteEngramStore {
|
|
|
411
411
|
async timeWarp(agentId, ms) {
|
|
412
412
|
await this.readyPromise;
|
|
413
413
|
const seconds = Math.round(ms / 1000);
|
|
414
|
-
const r1 = await this.db.query(`UPDATE engrams SET
|
|
415
|
-
created_at = to_char(($1::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
|
|
416
|
-
last_accessed = to_char(($3::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
|
|
414
|
+
const r1 = await this.db.query(`UPDATE engrams SET
|
|
415
|
+
created_at = to_char(($1::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
|
|
416
|
+
last_accessed = to_char(($3::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
|
|
417
417
|
WHERE agent_id = $4`, ['now', seconds, 'now', agentId]);
|
|
418
418
|
// Simpler: just update with relative arithmetic on stored ISO strings.
|
|
419
419
|
// PGlite doesn't support all date ops cleanly; fall through to JS-side calculation.
|
|
@@ -439,13 +439,13 @@ export class PGliteEngramStore {
|
|
|
439
439
|
// Restrict to active + fading. Faded engrams (Paper 1: storage degradation)
|
|
440
440
|
// retain their embedding so they still participate in semantic recall, even
|
|
441
441
|
// though their content has been trimmed. Excludes staging/consolidated/archived.
|
|
442
|
-
const result = await this.db.query(`SELECT *, (embedding <=> $2::vector) AS distance
|
|
443
|
-
FROM engrams
|
|
444
|
-
WHERE agent_id = $1
|
|
445
|
-
AND embedding IS NOT NULL
|
|
446
|
-
AND retracted = FALSE
|
|
447
|
-
AND stage IN ('active', 'fading')
|
|
448
|
-
ORDER BY distance ASC
|
|
442
|
+
const result = await this.db.query(`SELECT *, (embedding <=> $2::vector) AS distance
|
|
443
|
+
FROM engrams
|
|
444
|
+
WHERE agent_id = $1
|
|
445
|
+
AND embedding IS NOT NULL
|
|
446
|
+
AND retracted = FALSE
|
|
447
|
+
AND stage IN ('active', 'fading')
|
|
448
|
+
ORDER BY distance ASC
|
|
449
449
|
LIMIT $3`, [agentId, vectorToLiteral(vec), limit]);
|
|
450
450
|
return result.rows.map((r) => ({ engram: rowToEngram(r), distance: r.distance }));
|
|
451
451
|
}
|
|
@@ -464,11 +464,11 @@ export class PGliteEngramStore {
|
|
|
464
464
|
if (tokens.length === 0)
|
|
465
465
|
return [];
|
|
466
466
|
const websearchQuery = tokens.join(' OR ');
|
|
467
|
-
const result = await this.db.query(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
|
|
468
|
-
FROM engrams
|
|
469
|
-
WHERE agent_id = $1 AND retracted = FALSE
|
|
470
|
-
AND fts @@ websearch_to_tsquery('english', $2)
|
|
471
|
-
ORDER BY rank DESC
|
|
467
|
+
const result = await this.db.query(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
|
|
468
|
+
FROM engrams
|
|
469
|
+
WHERE agent_id = $1 AND retracted = FALSE
|
|
470
|
+
AND fts @@ websearch_to_tsquery('english', $2)
|
|
471
|
+
ORDER BY rank DESC
|
|
472
472
|
LIMIT $3`, [agentId, websearchQuery, limit]);
|
|
473
473
|
return result.rows.map((r) => ({ engram: rowToEngram(r), bm25Score: calibrateBm25(Number(r.rank)) }));
|
|
474
474
|
}
|
|
@@ -482,11 +482,11 @@ export class PGliteEngramStore {
|
|
|
482
482
|
if (tokens.length === 0)
|
|
483
483
|
return [];
|
|
484
484
|
const websearchQuery = tokens.join(' OR ');
|
|
485
|
-
const result = await this.db.query(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
|
|
486
|
-
FROM engrams
|
|
487
|
-
WHERE agent_id = ANY($1::text[]) AND retracted = FALSE
|
|
488
|
-
AND fts @@ websearch_to_tsquery('english', $2)
|
|
489
|
-
ORDER BY rank DESC
|
|
485
|
+
const result = await this.db.query(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
|
|
486
|
+
FROM engrams
|
|
487
|
+
WHERE agent_id = ANY($1::text[]) AND retracted = FALSE
|
|
488
|
+
AND fts @@ websearch_to_tsquery('english', $2)
|
|
489
|
+
ORDER BY rank DESC
|
|
490
490
|
LIMIT $3`, [agentIds, websearchQuery, limit]);
|
|
491
491
|
return result.rows.map((r) => ({ engram: rowToEngram(r), bm25Score: calibrateBm25(Number(r.rank)) }));
|
|
492
492
|
}
|
|
@@ -567,32 +567,32 @@ export class PGliteEngramStore {
|
|
|
567
567
|
sql += ` AND task_status = $${params.length + 1}`;
|
|
568
568
|
params.push(status);
|
|
569
569
|
}
|
|
570
|
-
sql += ` ORDER BY
|
|
571
|
-
CASE task_priority
|
|
572
|
-
WHEN 'urgent' THEN 0
|
|
573
|
-
WHEN 'high' THEN 1
|
|
574
|
-
WHEN 'medium' THEN 2
|
|
575
|
-
WHEN 'low' THEN 3
|
|
576
|
-
ELSE 4
|
|
577
|
-
END,
|
|
570
|
+
sql += ` ORDER BY
|
|
571
|
+
CASE task_priority
|
|
572
|
+
WHEN 'urgent' THEN 0
|
|
573
|
+
WHEN 'high' THEN 1
|
|
574
|
+
WHEN 'medium' THEN 2
|
|
575
|
+
WHEN 'low' THEN 3
|
|
576
|
+
ELSE 4
|
|
577
|
+
END,
|
|
578
578
|
created_at DESC`;
|
|
579
579
|
const result = await this.db.query(sql, params);
|
|
580
580
|
return result.rows.map(rowToEngram);
|
|
581
581
|
}
|
|
582
582
|
async getNextTask(agentId) {
|
|
583
583
|
await this.readyPromise;
|
|
584
|
-
const result = await this.db.query(`SELECT * FROM engrams
|
|
585
|
-
WHERE agent_id = $1 AND task_status IN ('open', 'in_progress') AND retracted = FALSE
|
|
586
|
-
ORDER BY
|
|
587
|
-
CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
|
|
588
|
-
CASE task_priority
|
|
589
|
-
WHEN 'urgent' THEN 0
|
|
590
|
-
WHEN 'high' THEN 1
|
|
591
|
-
WHEN 'medium' THEN 2
|
|
592
|
-
WHEN 'low' THEN 3
|
|
593
|
-
ELSE 4
|
|
594
|
-
END,
|
|
595
|
-
created_at ASC
|
|
584
|
+
const result = await this.db.query(`SELECT * FROM engrams
|
|
585
|
+
WHERE agent_id = $1 AND task_status IN ('open', 'in_progress') AND retracted = FALSE
|
|
586
|
+
ORDER BY
|
|
587
|
+
CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
|
|
588
|
+
CASE task_priority
|
|
589
|
+
WHEN 'urgent' THEN 0
|
|
590
|
+
WHEN 'high' THEN 1
|
|
591
|
+
WHEN 'medium' THEN 2
|
|
592
|
+
WHEN 'low' THEN 3
|
|
593
|
+
ELSE 4
|
|
594
|
+
END,
|
|
595
|
+
created_at ASC
|
|
596
596
|
LIMIT 1`, [agentId]);
|
|
597
597
|
return result.rows.length > 0 ? rowToEngram(result.rows[0]) : null;
|
|
598
598
|
}
|
|
@@ -606,11 +606,11 @@ export class PGliteEngramStore {
|
|
|
606
606
|
}
|
|
607
607
|
async findActiveMatchByConcept(agentId, concept, requiredTags) {
|
|
608
608
|
await this.readyPromise;
|
|
609
|
-
let sql = `SELECT * FROM engrams
|
|
610
|
-
WHERE agent_id = $1
|
|
611
|
-
AND LOWER(TRIM(concept)) = LOWER(TRIM($2))
|
|
612
|
-
AND stage = 'active'
|
|
613
|
-
AND retracted = FALSE
|
|
609
|
+
let sql = `SELECT * FROM engrams
|
|
610
|
+
WHERE agent_id = $1
|
|
611
|
+
AND LOWER(TRIM(concept)) = LOWER(TRIM($2))
|
|
612
|
+
AND stage = 'active'
|
|
613
|
+
AND retracted = FALSE
|
|
614
614
|
AND superseded_by IS NULL`;
|
|
615
615
|
const params = [agentId, concept];
|
|
616
616
|
if (requiredTags && requiredTags.length > 0) {
|
|
@@ -643,12 +643,12 @@ export class PGliteEngramStore {
|
|
|
643
643
|
await this.readyPromise;
|
|
644
644
|
const id = randomUUID();
|
|
645
645
|
const now = new Date().toISOString();
|
|
646
|
-
await this.db.query(`INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
|
|
647
|
-
VALUES ($1, $2, $3, $4, $5, $6, 0, $7, $7)
|
|
648
|
-
ON CONFLICT (from_engram_id, to_engram_id) DO UPDATE SET
|
|
649
|
-
weight = EXCLUDED.weight,
|
|
650
|
-
confidence = EXCLUDED.confidence,
|
|
651
|
-
last_activated = EXCLUDED.last_activated,
|
|
646
|
+
await this.db.query(`INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
|
|
647
|
+
VALUES ($1, $2, $3, $4, $5, $6, 0, $7, $7)
|
|
648
|
+
ON CONFLICT (from_engram_id, to_engram_id) DO UPDATE SET
|
|
649
|
+
weight = EXCLUDED.weight,
|
|
650
|
+
confidence = EXCLUDED.confidence,
|
|
651
|
+
last_activated = EXCLUDED.last_activated,
|
|
652
652
|
activation_count = associations.activation_count + 1`, [id, fromId, toId, weight, confidence, type, now]);
|
|
653
653
|
const assoc = await this.getAssociation(fromId, toId);
|
|
654
654
|
if (!assoc)
|
|
@@ -670,12 +670,12 @@ export class PGliteEngramStore {
|
|
|
670
670
|
if (engramIds.length === 0)
|
|
671
671
|
return result;
|
|
672
672
|
await this.readyPromise;
|
|
673
|
-
const r = await this.db.query(`SELECT id, SUM(cnt) AS count, SUM(sw) AS sum_weight FROM (
|
|
674
|
-
SELECT from_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE from_engram_id = ANY($1::text[])
|
|
675
|
-
UNION ALL
|
|
676
|
-
SELECT to_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE to_engram_id = ANY($1::text[])
|
|
677
|
-
) t
|
|
678
|
-
WHERE id = ANY($1::text[])
|
|
673
|
+
const r = await this.db.query(`SELECT id, SUM(cnt) AS count, SUM(sw) AS sum_weight FROM (
|
|
674
|
+
SELECT from_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE from_engram_id = ANY($1::text[])
|
|
675
|
+
UNION ALL
|
|
676
|
+
SELECT to_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE to_engram_id = ANY($1::text[])
|
|
677
|
+
) t
|
|
678
|
+
WHERE id = ANY($1::text[])
|
|
679
679
|
GROUP BY id`, [engramIds]);
|
|
680
680
|
for (const row of r.rows) {
|
|
681
681
|
result.set(row.id, { count: Number(row.count), sumWeight: Number(row.sum_weight) });
|
|
@@ -691,7 +691,7 @@ export class PGliteEngramStore {
|
|
|
691
691
|
if (engramIds.length === 0)
|
|
692
692
|
return result;
|
|
693
693
|
await this.readyPromise;
|
|
694
|
-
const r = await this.db.query(`SELECT * FROM associations
|
|
694
|
+
const r = await this.db.query(`SELECT * FROM associations
|
|
695
695
|
WHERE from_engram_id = ANY($1::text[]) OR to_engram_id = ANY($1::text[])`, [engramIds]);
|
|
696
696
|
for (const row of r.rows) {
|
|
697
697
|
const a = rowToAssociation(row);
|
|
@@ -731,8 +731,8 @@ export class PGliteEngramStore {
|
|
|
731
731
|
}
|
|
732
732
|
async getAllAssociations(agentId) {
|
|
733
733
|
await this.readyPromise;
|
|
734
|
-
const result = await this.db.query(`SELECT a.* FROM associations a
|
|
735
|
-
JOIN engrams e ON a.from_engram_id = e.id
|
|
734
|
+
const result = await this.db.query(`SELECT a.* FROM associations a
|
|
735
|
+
JOIN engrams e ON a.from_engram_id = e.id
|
|
736
736
|
WHERE e.agent_id = $1`, [agentId]);
|
|
737
737
|
return result.rows.map(rowToAssociation);
|
|
738
738
|
}
|
|
@@ -741,11 +741,11 @@ export class PGliteEngramStore {
|
|
|
741
741
|
// ============================================================
|
|
742
742
|
async getEvictionCandidates(agentId, limit) {
|
|
743
743
|
await this.readyPromise;
|
|
744
|
-
const result = await this.db.query(`SELECT * FROM engrams
|
|
745
|
-
WHERE agent_id = $1 AND stage = 'active' AND retracted = FALSE
|
|
746
|
-
ORDER BY (salience * 0.3 + confidence * 0.3
|
|
747
|
-
+ (access_count::float / (access_count + 5)) * 0.2
|
|
748
|
-
+ (1.0 / (1.0 + EXTRACT(EPOCH FROM (now() - last_accessed::timestamptz)) / 86400.0)) * 0.2) ASC
|
|
744
|
+
const result = await this.db.query(`SELECT * FROM engrams
|
|
745
|
+
WHERE agent_id = $1 AND stage = 'active' AND retracted = FALSE
|
|
746
|
+
ORDER BY (salience * 0.3 + confidence * 0.3
|
|
747
|
+
+ (access_count::float / (access_count + 5)) * 0.2
|
|
748
|
+
+ (1.0 / (1.0 + EXTRACT(EPOCH FROM (now() - last_accessed::timestamptz)) / 86400.0)) * 0.2) ASC
|
|
749
749
|
LIMIT $2`, [agentId, limit]);
|
|
750
750
|
return result.rows.map(rowToEngram);
|
|
751
751
|
}
|
|
@@ -780,7 +780,7 @@ export class PGliteEngramStore {
|
|
|
780
780
|
}
|
|
781
781
|
async logStagingEvent(event) {
|
|
782
782
|
await this.readyPromise;
|
|
783
|
-
await this.db.query(`INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
|
|
783
|
+
await this.db.query(`INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
|
|
784
784
|
VALUES ($1, $2, $3, $4, $5, $6)`, [
|
|
785
785
|
event.engramId, event.agentId, event.action,
|
|
786
786
|
event.resonanceScore, event.timestamp.toISOString(), event.ageMs,
|
|
@@ -788,18 +788,18 @@ export class PGliteEngramStore {
|
|
|
788
788
|
}
|
|
789
789
|
async logRetrievalFeedback(activationEventId, engramId, useful, context) {
|
|
790
790
|
await this.readyPromise;
|
|
791
|
-
await this.db.query(`INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
|
|
791
|
+
await this.db.query(`INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
|
|
792
792
|
VALUES ($1, $2, $3, $4, $5, $6)`, [randomUUID(), activationEventId, engramId, useful, context, new Date().toISOString()]);
|
|
793
793
|
}
|
|
794
794
|
async getRetrievalPrecision(agentId, windowHours = 24) {
|
|
795
795
|
await this.readyPromise;
|
|
796
796
|
const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
|
|
797
|
-
const result = await this.db.query(`SELECT
|
|
798
|
-
COUNT(CASE WHEN useful = TRUE THEN 1 END) AS useful_count,
|
|
799
|
-
COUNT(*) AS total_count
|
|
800
|
-
FROM retrieval_feedback rf
|
|
801
|
-
LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
|
|
802
|
-
JOIN engrams e ON rf.engram_id = e.id
|
|
797
|
+
const result = await this.db.query(`SELECT
|
|
798
|
+
COUNT(CASE WHEN useful = TRUE THEN 1 END) AS useful_count,
|
|
799
|
+
COUNT(*) AS total_count
|
|
800
|
+
FROM retrieval_feedback rf
|
|
801
|
+
LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
|
|
802
|
+
JOIN engrams e ON rf.engram_id = e.id
|
|
803
803
|
WHERE e.agent_id = $1 AND rf.timestamp > $2`, [agentId, since]);
|
|
804
804
|
const row = result.rows[0];
|
|
805
805
|
const total = Number(row?.total_count ?? 0);
|
|
@@ -808,10 +808,10 @@ export class PGliteEngramStore {
|
|
|
808
808
|
}
|
|
809
809
|
async getStagingMetrics(agentId) {
|
|
810
810
|
await this.readyPromise;
|
|
811
|
-
const result = await this.db.query(`SELECT
|
|
812
|
-
COUNT(CASE WHEN action = 'promoted' THEN 1 END) AS promoted,
|
|
813
|
-
COUNT(CASE WHEN action = 'discarded' THEN 1 END) AS discarded,
|
|
814
|
-
COUNT(CASE WHEN action = 'expired' THEN 1 END) AS expired
|
|
811
|
+
const result = await this.db.query(`SELECT
|
|
812
|
+
COUNT(CASE WHEN action = 'promoted' THEN 1 END) AS promoted,
|
|
813
|
+
COUNT(CASE WHEN action = 'discarded' THEN 1 END) AS discarded,
|
|
814
|
+
COUNT(CASE WHEN action = 'expired' THEN 1 END) AS expired
|
|
815
815
|
FROM staging_events WHERE agent_id = $1`, [agentId]);
|
|
816
816
|
const row = result.rows[0] ?? { promoted: 0, discarded: 0, expired: 0 };
|
|
817
817
|
return {
|
|
@@ -825,8 +825,8 @@ export class PGliteEngramStore {
|
|
|
825
825
|
// Flush any buffered activation events so stats reflect the latest writes.
|
|
826
826
|
await this.flushActivationEvents();
|
|
827
827
|
const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
|
|
828
|
-
const result = await this.db.query(`SELECT latency_ms FROM activation_events
|
|
829
|
-
WHERE agent_id = $1 AND timestamp > $2
|
|
828
|
+
const result = await this.db.query(`SELECT latency_ms FROM activation_events
|
|
829
|
+
WHERE agent_id = $1 AND timestamp > $2
|
|
830
830
|
ORDER BY latency_ms ASC`, [agentId, since]);
|
|
831
831
|
if (result.rows.length === 0)
|
|
832
832
|
return { count: 0, avgLatencyMs: 0, p95LatencyMs: 0 };
|
|
@@ -851,7 +851,7 @@ export class PGliteEngramStore {
|
|
|
851
851
|
await this.readyPromise;
|
|
852
852
|
const id = randomUUID();
|
|
853
853
|
const now = new Date().toISOString();
|
|
854
|
-
await this.db.query(`INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
|
|
854
|
+
await this.db.query(`INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
|
|
855
855
|
VALUES ($1, $2, $3, $4::vector, 0, $5, $5, $5)`, [id, input.agentId, input.label, vectorToLiteral(input.embedding ?? null), now]);
|
|
856
856
|
const ep = await this.getEpisode(id);
|
|
857
857
|
if (!ep)
|
|
@@ -877,9 +877,9 @@ export class PGliteEngramStore {
|
|
|
877
877
|
async addEngramToEpisode(engramId, episodeId) {
|
|
878
878
|
await this.readyPromise;
|
|
879
879
|
await this.db.query(`UPDATE engrams SET episode_id = $1 WHERE id = $2`, [episodeId, engramId]);
|
|
880
|
-
await this.db.query(`UPDATE episodes SET
|
|
881
|
-
engram_count = engram_count + 1,
|
|
882
|
-
end_time = GREATEST(end_time, $1)
|
|
880
|
+
await this.db.query(`UPDATE episodes SET
|
|
881
|
+
engram_count = engram_count + 1,
|
|
882
|
+
end_time = GREATEST(end_time, $1)
|
|
883
883
|
WHERE id = $2`, [new Date().toISOString(), episodeId]);
|
|
884
884
|
}
|
|
885
885
|
async getEngramsByEpisode(episodeId) {
|
|
@@ -918,44 +918,44 @@ export class PGliteEngramStore {
|
|
|
918
918
|
async updateAutoCheckpointWrite(agentId, engramId) {
|
|
919
919
|
await this.readyPromise;
|
|
920
920
|
const now = new Date().toISOString();
|
|
921
|
-
await this.db.query(`INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
|
|
922
|
-
VALUES ($1, $2, $3, 1, $3)
|
|
923
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
924
|
-
last_write_id = EXCLUDED.last_write_id,
|
|
925
|
-
last_activity_at = EXCLUDED.last_activity_at,
|
|
926
|
-
write_count_since_consolidation = conscious_state.write_count_since_consolidation + 1,
|
|
921
|
+
await this.db.query(`INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
|
|
922
|
+
VALUES ($1, $2, $3, 1, $3)
|
|
923
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
924
|
+
last_write_id = EXCLUDED.last_write_id,
|
|
925
|
+
last_activity_at = EXCLUDED.last_activity_at,
|
|
926
|
+
write_count_since_consolidation = conscious_state.write_count_since_consolidation + 1,
|
|
927
927
|
updated_at = EXCLUDED.updated_at`, [agentId, engramId, now]);
|
|
928
928
|
}
|
|
929
929
|
async updateAutoCheckpointRecall(agentId, context, engramIds) {
|
|
930
930
|
await this.readyPromise;
|
|
931
931
|
const now = new Date().toISOString();
|
|
932
|
-
await this.db.query(`INSERT INTO conscious_state (agent_id, last_recall_context, last_recall_ids, last_activity_at, recall_count_since_consolidation, updated_at)
|
|
933
|
-
VALUES ($1, $2, $3, $4, 1, $4)
|
|
934
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
935
|
-
last_recall_context = EXCLUDED.last_recall_context,
|
|
936
|
-
last_recall_ids = EXCLUDED.last_recall_ids,
|
|
937
|
-
last_activity_at = EXCLUDED.last_activity_at,
|
|
938
|
-
recall_count_since_consolidation = conscious_state.recall_count_since_consolidation + 1,
|
|
932
|
+
await this.db.query(`INSERT INTO conscious_state (agent_id, last_recall_context, last_recall_ids, last_activity_at, recall_count_since_consolidation, updated_at)
|
|
933
|
+
VALUES ($1, $2, $3, $4, 1, $4)
|
|
934
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
935
|
+
last_recall_context = EXCLUDED.last_recall_context,
|
|
936
|
+
last_recall_ids = EXCLUDED.last_recall_ids,
|
|
937
|
+
last_activity_at = EXCLUDED.last_activity_at,
|
|
938
|
+
recall_count_since_consolidation = conscious_state.recall_count_since_consolidation + 1,
|
|
939
939
|
updated_at = EXCLUDED.updated_at`, [agentId, context, JSON.stringify(engramIds), now]);
|
|
940
940
|
}
|
|
941
941
|
async touchActivity(agentId) {
|
|
942
942
|
await this.readyPromise;
|
|
943
943
|
const now = new Date().toISOString();
|
|
944
|
-
await this.db.query(`INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
|
|
945
|
-
VALUES ($1, $2, $2)
|
|
946
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
947
|
-
last_activity_at = EXCLUDED.last_activity_at,
|
|
944
|
+
await this.db.query(`INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
|
|
945
|
+
VALUES ($1, $2, $2)
|
|
946
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
947
|
+
last_activity_at = EXCLUDED.last_activity_at,
|
|
948
948
|
updated_at = EXCLUDED.updated_at`, [agentId, now]);
|
|
949
949
|
}
|
|
950
950
|
async saveCheckpoint(agentId, state) {
|
|
951
951
|
await this.readyPromise;
|
|
952
952
|
const now = new Date().toISOString();
|
|
953
|
-
await this.db.query(`INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
|
|
954
|
-
VALUES ($1, $2, $3, $3, $3)
|
|
955
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
956
|
-
execution_state = EXCLUDED.execution_state,
|
|
957
|
-
checkpoint_at = EXCLUDED.checkpoint_at,
|
|
958
|
-
last_activity_at = EXCLUDED.last_activity_at,
|
|
953
|
+
await this.db.query(`INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
|
|
954
|
+
VALUES ($1, $2, $3, $3, $3)
|
|
955
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
956
|
+
execution_state = EXCLUDED.execution_state,
|
|
957
|
+
checkpoint_at = EXCLUDED.checkpoint_at,
|
|
958
|
+
last_activity_at = EXCLUDED.last_activity_at,
|
|
959
959
|
updated_at = EXCLUDED.updated_at`, [agentId, JSON.stringify(state), now]);
|
|
960
960
|
}
|
|
961
961
|
async getCheckpoint(agentId) {
|
|
@@ -988,13 +988,13 @@ export class PGliteEngramStore {
|
|
|
988
988
|
await this.db.query(`UPDATE conscious_state SET last_mini_consolidation_at = $1, updated_at = $1 WHERE agent_id = $2`, [now, agentId]);
|
|
989
989
|
}
|
|
990
990
|
else {
|
|
991
|
-
await this.db.query(`UPDATE conscious_state SET
|
|
992
|
-
last_consolidation_at = $1,
|
|
993
|
-
last_mini_consolidation_at = $1,
|
|
994
|
-
write_count_since_consolidation = 0,
|
|
995
|
-
recall_count_since_consolidation = 0,
|
|
996
|
-
consolidation_cycle_count = consolidation_cycle_count + 1,
|
|
997
|
-
updated_at = $1
|
|
991
|
+
await this.db.query(`UPDATE conscious_state SET
|
|
992
|
+
last_consolidation_at = $1,
|
|
993
|
+
last_mini_consolidation_at = $1,
|
|
994
|
+
write_count_since_consolidation = 0,
|
|
995
|
+
recall_count_since_consolidation = 0,
|
|
996
|
+
consolidation_cycle_count = consolidation_cycle_count + 1,
|
|
997
|
+
updated_at = $1
|
|
998
998
|
WHERE agent_id = $2`, [now, agentId]);
|
|
999
999
|
}
|
|
1000
1000
|
}
|
|
@@ -1019,10 +1019,10 @@ export class PGliteEngramStore {
|
|
|
1019
1019
|
// ============================================================
|
|
1020
1020
|
async getLatestByTag(opts) {
|
|
1021
1021
|
await this.readyPromise;
|
|
1022
|
-
let sql = `SELECT * FROM engrams
|
|
1023
|
-
WHERE agent_id = $1
|
|
1024
|
-
AND retracted = $2
|
|
1025
|
-
AND stage = 'active'
|
|
1022
|
+
let sql = `SELECT * FROM engrams
|
|
1023
|
+
WHERE agent_id = $1
|
|
1024
|
+
AND retracted = $2
|
|
1025
|
+
AND stage = 'active'
|
|
1026
1026
|
AND tags LIKE $3`;
|
|
1027
1027
|
const params = [opts.agentId, opts.retracted ?? false, `%"${opts.tagKeyPrefix}%`];
|
|
1028
1028
|
if (opts.scopeTagsAll && opts.scopeTagsAll.length > 0) {
|
|
@@ -1049,10 +1049,10 @@ export class PGliteEngramStore {
|
|
|
1049
1049
|
}
|
|
1050
1050
|
async getTopBy(opts) {
|
|
1051
1051
|
await this.readyPromise;
|
|
1052
|
-
let sql = `SELECT * FROM engrams
|
|
1053
|
-
WHERE agent_id = $1
|
|
1054
|
-
AND retracted = $2
|
|
1055
|
-
AND stage = 'active'
|
|
1052
|
+
let sql = `SELECT * FROM engrams
|
|
1053
|
+
WHERE agent_id = $1
|
|
1054
|
+
AND retracted = $2
|
|
1055
|
+
AND stage = 'active'
|
|
1056
1056
|
AND tags LIKE $3`;
|
|
1057
1057
|
const params = [opts.agentId, opts.retracted ?? false, `%"${opts.sortField}%`];
|
|
1058
1058
|
if (opts.filterTagsAll && opts.filterTagsAll.length > 0) {
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-working-memory",
|
|
3
|
-
"version": "0.8.
|
|
4
|
-
"description": "Cognitive memory layer for AI agents
|
|
3
|
+
"version": "0.8.7",
|
|
4
|
+
"description": "Cognitive memory layer for AI agents — activation-based retrieval, salience filtering, associative connections",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "dist/index.js",
|
|
7
7
|
"bin": {
|
|
@@ -31,7 +31,7 @@
|
|
|
31
31
|
"test:all": "vitest run && tsx tests/mcp-smoke.ts"
|
|
32
32
|
},
|
|
33
33
|
"engines": {
|
|
34
|
-
"node": ">=
|
|
34
|
+
"node": ">=22.0.0"
|
|
35
35
|
},
|
|
36
36
|
"license": "Apache-2.0",
|
|
37
37
|
"files": [
|
|
@@ -45,6 +45,7 @@
|
|
|
45
45
|
"@electric-sql/pglite": "^0.4.6",
|
|
46
46
|
"@huggingface/transformers": "^3.8.1",
|
|
47
47
|
"@modelcontextprotocol/sdk": "^1.27.1",
|
|
48
|
+
"@toon-format/toon": "^2.3.0",
|
|
48
49
|
"better-sqlite3": "^12.6.2",
|
|
49
50
|
"fastify": "^5.8.2",
|
|
50
51
|
"zod": "^4.3.6"
|
package/src/adapters/common.ts
CHANGED
|
@@ -352,6 +352,33 @@ When it isn't:
|
|
|
352
352
|
return nothing, the memory probably isn't there — read the code instead of
|
|
353
353
|
burning more recalls.
|
|
354
354
|
|
|
355
|
+
### Recall tuning (0.8.x — opt-in parameters for higher-quality recall)
|
|
356
|
+
Default \`memory_recall\` is tuned for the common case. The 0.8.x recall pipeline
|
|
357
|
+
exposes four opt-in parameters that change the cost/quality tradeoff. Use them
|
|
358
|
+
when the default doesn't match what you actually need.
|
|
359
|
+
|
|
360
|
+
- **\`granularity: 'compact'\`** — every result carries a 200-char \`summary\`
|
|
361
|
+
field with a query-aware snippet (the densest window of query terms in the
|
|
362
|
+
content). Use this when you expect to scan 5+ results to find one — saves
|
|
363
|
+
~70% of recall output tokens. The full content stays available in
|
|
364
|
+
\`engram.content\` if you want to drill into a specific result.
|
|
365
|
+
- **\`granularity: 'auto'\`** — confidence-adaptive. If the top result is a clear
|
|
366
|
+
winner, it gets a longer summary while the rest are compact. If confidence
|
|
367
|
+
is uniform across results, everything is compact. Use when you don't know
|
|
368
|
+
in advance whether one result will dominate.
|
|
369
|
+
- **\`require_confidence: 0.10 | 0.25 | 0.40\`** — opt-in abstention. AWM
|
|
370
|
+
returns \`[]\` instead of low-confidence noise. Use when you're about to ACT
|
|
371
|
+
on the recalled fact (grounding a decision, citing the memory verbatim,
|
|
372
|
+
contradicting a prior assumption). Thresholds: \`0.10\` strict — only abstain
|
|
373
|
+
on garbage; \`0.25\` balanced; \`0.40\` aggressive — prefer "I don't know"
|
|
374
|
+
over "best of bad." When abstention fires (empty result), treat it as a
|
|
375
|
+
signal — either the memory genuinely isn't there (read the code) or your
|
|
376
|
+
query missed (reformulate). Don't retry without the threshold.
|
|
377
|
+
- **\`workspace: "<name>"\`** — hive-mode recall across all agents in the
|
|
378
|
+
workspace. Use when other agents may have written canonical knowledge you
|
|
379
|
+
need. Default is agent-scoped (your own memories only). Can also be set
|
|
380
|
+
globally via the \`AWM_WORKSPACE\` env var.
|
|
381
|
+
|
|
355
382
|
### Keep memory fresh
|
|
356
383
|
- After recalling a memory, if you observe the real state is different → call
|
|
357
384
|
\`memory_supersede\` immediately with the corrected version.
|
|
@@ -362,6 +389,24 @@ When it isn't:
|
|
|
362
389
|
drifts out of date. The system relies on you to keep it current. This is the #1
|
|
363
390
|
failure mode.**
|
|
364
391
|
|
|
392
|
+
### Content fade — write-and-forget is safe (0.8.x)
|
|
393
|
+
Un-recalled engrams gradually fade their content while preserving cue pathways
|
|
394
|
+
(concept + tags + embedding stay intact). This is Paper 1 — storage
|
|
395
|
+
degradation. Practical implications:
|
|
396
|
+
|
|
397
|
+
- **Don't manually purge memories** to "save space." The system already
|
|
398
|
+
compresses unused content. Old memories stay findable via cue match even
|
|
399
|
+
when their body has decayed.
|
|
400
|
+
- **Don't over-pin with \`memory_class: canonical\`** to fight fade. Canonical
|
|
401
|
+
only changes salience gating at write time, not fade behavior. Fade
|
|
402
|
+
affects un-recalled engrams of any class.
|
|
403
|
+
- **Recall keeps content alive.** Every recall touches the engram and resets
|
|
404
|
+
its fade clock. Frequently-recalled memories stay full-fidelity automatically.
|
|
405
|
+
- **Supersede is the right tool for stale facts.** When you observe a memory
|
|
406
|
+
is outdated, call \`memory_supersede\` — the new version inherits the old
|
|
407
|
+
one's coherent associations (counter-narrative replacement, 0.8.x) so cue
|
|
408
|
+
pathways carry forward to the replacement.
|
|
409
|
+
|
|
365
410
|
### Example — good vs bad memory_write
|
|
366
411
|
|
|
367
412
|
**BAD** (no prefix tags, vague concept, can't be recalled by future queries):
|
|
@@ -392,19 +437,74 @@ memory_write(
|
|
|
392
437
|
- AWM is shared across all agents in real time. When any agent writes or supersedes a
|
|
393
438
|
memory, every other agent can recall it immediately.
|
|
394
439
|
|
|
440
|
+
### Output compression (token efficiency, output-only)
|
|
441
|
+
When a tool returns a LARGE STRUCTURED result you need to keep in context — a JSON
|
|
442
|
+
array of records, query rows, a log dump, an API response — pass it through
|
|
443
|
+
\`compress_output\` first. It re-encodes the data as TOON (a compact, lossless,
|
|
444
|
+
schema-aware tabular form of JSON), cutting ~50-65% of the tokens at no
|
|
445
|
+
comprehension cost. This is output-only: it never changes the data or your memories.
|
|
446
|
+
- Use it on big STRUCTURED outputs, not on prose. Prose is returned unchanged —
|
|
447
|
+
for trimming memory prose, use recall \`granularity: 'compact'\` instead.
|
|
448
|
+
- It returns a \`ref\`; call \`retrieve_original(ref)\` if you later need the exact
|
|
449
|
+
verbatim source (e.g. to hand it to another tool unchanged).
|
|
450
|
+
- Don't bother for small outputs — it only compresses when the saving is worthwhile
|
|
451
|
+
and falls back to plain JSON if TOON wouldn't reproduce the data exactly.
|
|
452
|
+
|
|
453
|
+
### Backend (SQLite vs PGlite, 0.8.x)
|
|
454
|
+
AWM ships two storage backends. The installer picks SQLite by default; both
|
|
455
|
+
are functionally equivalent for cognitive workloads, but differ in operational
|
|
456
|
+
guarantees:
|
|
457
|
+
|
|
458
|
+
- **SQLite** (default) — embedded, **multi-process safe** via WAL mode. Best
|
|
459
|
+
for single-machine setups and MCP scenarios where multiple Claude Code
|
|
460
|
+
sessions may open the same database concurrently.
|
|
461
|
+
- **PGlite** — embedded Postgres (WASM) with pgvector. **Single-process only**
|
|
462
|
+
— two MCP processes against the same \`memory-pglite/\` directory will
|
|
463
|
+
abort the second. Pick via \`AWM_STORE_BACKEND=pglite\` and
|
|
464
|
+
\`AWM_DB_PATH=path/to/memory-pglite\`.
|
|
465
|
+
- **Auto-detect** — if \`AWM_DB_PATH\` points to a directory that already
|
|
466
|
+
exists, AWM detects PGlite; a file → SQLite. No explicit
|
|
467
|
+
\`AWM_STORE_BACKEND\` needed when an existing DB is present.
|
|
468
|
+
|
|
469
|
+
For the comparison table (recall quality parity, BM25 vs \`ts_rank_cd\`,
|
|
470
|
+
multi-process guarantees), see \`docs/pglite-feature-parity.md\`.
|
|
471
|
+
|
|
395
472
|
### Diagnostics / escape hatches (env vars, only if you know why)
|
|
396
|
-
The 0.7.6→0.7.14 work cut recall latency from 11s to ~300ms.
|
|
397
|
-
|
|
398
|
-
|
|
473
|
+
The 0.7.6→0.7.14 work cut recall latency from 11s to ~300ms. The 0.8.x work
|
|
474
|
+
added the write-path rewrite (per-write 300+ ms → under 10ms) and PGlite
|
|
475
|
+
parity tuning. Each optimization is gated by an env-var so it can be disabled
|
|
476
|
+
for A/B testing if a regression appears in your workload:
|
|
399
477
|
|
|
400
|
-
|
|
478
|
+
Recall pipeline (0.7.x):
|
|
479
|
+
- \`AWM_DISABLE_POOL_FILTER=1\` — disables the candidate pool reduction
|
|
401
480
|
pre-filter in recall. Reverts to scoring all active candidates.
|
|
402
|
-
- \`AWM_DISABLE_SLIM_CACHE=1\`
|
|
481
|
+
- \`AWM_DISABLE_SLIM_CACHE=1\` — disables the in-memory slim cache.
|
|
403
482
|
Reverts to per-recall SQL fetch + Buffer→Float32Array conversion.
|
|
404
|
-
- \`AWM_DISABLE_RERANK_SKIP=1\`
|
|
483
|
+
- \`AWM_DISABLE_RERANK_SKIP=1\` — disables the cross-encoder skip on
|
|
405
484
|
clear-winner queries. Forces every recall through the reranker.
|
|
406
|
-
- \`AWM_DISABLE_EXPANSION_CACHE=1\`
|
|
407
|
-
|
|
485
|
+
- \`AWM_DISABLE_EXPANSION_CACHE=1\` — disables the query expansion skip
|
|
486
|
+
heuristic + LRU cache. Forces every recall through flan-t5-small.
|
|
487
|
+
|
|
488
|
+
Write pipeline + lifecycle (0.8.x):
|
|
489
|
+
- \`AWM_REINFORCE_MAX_CONTENT_LEN=1500\` — max chars an engram's content
|
|
490
|
+
can grow to via merge-on-reinforce (drop-oldest on overflow). Higher =
|
|
491
|
+
preserves more reinforced detail; lower = leaner recall output.
|
|
492
|
+
- \`AWM_REINFORCE_MERGE_CONTENT=0\` — disable content merge on reinforce.
|
|
493
|
+
Reverts to pre-0.8.5 behavior (discard new content, only bump confidence).
|
|
494
|
+
- \`AWM_NOVELTY_EMBED=0\` — disable the cosine channel in novelty
|
|
495
|
+
computation. BM25-only fallback. Reverts to pre-0.8.5 novelty.
|
|
496
|
+
- \`AWM_GRANULARITY_COMPACT_LEN=200\` — char budget for query-aware snippet
|
|
497
|
+
in \`granularity: 'compact'\` mode.
|
|
498
|
+
- \`AWM_GRANULARITY_FULL_LEN=1000\` — char budget for the top result in
|
|
499
|
+
\`granularity: 'auto'\` mode when there's a clear winner.
|
|
500
|
+
|
|
501
|
+
PGlite backend (0.8.x):
|
|
502
|
+
- \`AWM_PGLITE_BM25_M=1\` — multiplier on PGlite \`ts_rank_cd\` to calibrate
|
|
503
|
+
against SQLite FTS5 BM25 distribution. M=1 (default) is passthrough;
|
|
504
|
+
higher M boosts PGlite scores at the cost of recall-ranking precision
|
|
505
|
+
(see CHANGELOG 0.8.5 follow-up).
|
|
506
|
+
- \`AWM_IVFFLAT_PROBES=5\` — pgvector ivfflat probes per query. Higher =
|
|
507
|
+
more accurate, slower.
|
|
408
508
|
|
|
409
509
|
In production, leave these all unset. Use only when diagnosing a suspected
|
|
410
510
|
recall-quality regression.
|
package/src/api/index.ts
CHANGED
|
@@ -1,3 +1,3 @@
|
|
|
1
|
-
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
-
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
-
export * from './routes.js';
|
|
1
|
+
// Copyright 2026 Robert Winter / Complete Ideas
|
|
2
|
+
// SPDX-License-Identifier: Apache-2.0
|
|
3
|
+
export * from './routes.js';
|
package/src/api/routes.ts
CHANGED
|
@@ -952,7 +952,7 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
952
952
|
const base: Record<string, unknown> = {
|
|
953
953
|
status: 'ok',
|
|
954
954
|
timestamp: new Date().toISOString(),
|
|
955
|
-
version: '0.8.
|
|
955
|
+
version: '0.8.7',
|
|
956
956
|
coordination: coordEnabled,
|
|
957
957
|
};
|
|
958
958
|
if (coordEnabled && typeof (deps.store as any).getDb === 'function') {
|