agent-working-memory 0.11.0 → 0.12.3
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 +71 -297
- package/dist/adapters/claude-code.d.ts.map +1 -1
- package/dist/adapters/claude-code.js +63 -3
- package/dist/adapters/claude-code.js.map +1 -1
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +358 -306
- package/dist/adapters/common.js.map +1 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +29 -7
- package/dist/api/routes.js.map +1 -1
- package/dist/coordination/routes.d.ts.map +1 -1
- package/dist/coordination/routes.js +174 -170
- package/dist/coordination/routes.js.map +1 -1
- package/dist/core/embeddings.d.ts.map +1 -1
- package/dist/core/embeddings.js +4 -1
- package/dist/core/embeddings.js.map +1 -1
- package/dist/core/entity-extract.d.ts +3 -0
- package/dist/core/entity-extract.d.ts.map +1 -0
- package/dist/core/entity-extract.js +47 -0
- package/dist/core/entity-extract.js.map +1 -0
- package/dist/core/format-recall.d.ts +16 -0
- package/dist/core/format-recall.d.ts.map +1 -0
- package/dist/core/format-recall.js +24 -0
- package/dist/core/format-recall.js.map +1 -0
- package/dist/core/query-expander.js +1 -1
- package/dist/core/query-expander.js.map +1 -1
- package/dist/core/reranker.js +1 -1
- package/dist/core/reranker.js.map +1 -1
- package/dist/core/salience.d.ts.map +1 -1
- package/dist/core/salience.js +14 -2
- package/dist/core/salience.js.map +1 -1
- package/dist/core/whoami.d.ts +24 -0
- package/dist/core/whoami.d.ts.map +1 -0
- package/dist/core/whoami.js +66 -0
- package/dist/core/whoami.js.map +1 -0
- package/dist/core/write-pipeline.d.ts +9 -0
- package/dist/core/write-pipeline.d.ts.map +1 -1
- package/dist/core/write-pipeline.js +109 -68
- package/dist/core/write-pipeline.js.map +1 -1
- package/dist/core/write-telemetry.d.ts +33 -0
- package/dist/core/write-telemetry.d.ts.map +1 -0
- package/dist/core/write-telemetry.js +110 -0
- package/dist/core/write-telemetry.js.map +1 -0
- package/dist/engine/activation.d.ts +22 -12
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +133 -17
- package/dist/engine/activation.js.map +1 -1
- package/dist/engine/consolidation-scheduler.d.ts +1 -1
- package/dist/engine/consolidation-scheduler.js +1 -1
- package/dist/engine/consolidation.d.ts +1 -0
- package/dist/engine/consolidation.d.ts.map +1 -1
- package/dist/engine/consolidation.js +18 -0
- package/dist/engine/consolidation.js.map +1 -1
- package/dist/engine/eval.d.ts.map +1 -1
- package/dist/engine/eval.js +5 -1
- package/dist/engine/eval.js.map +1 -1
- package/dist/hooks/sidecar.d.ts +26 -0
- package/dist/hooks/sidecar.d.ts.map +1 -1
- package/dist/hooks/sidecar.js +30 -0
- package/dist/hooks/sidecar.js.map +1 -1
- package/dist/index.js +20 -2
- package/dist/index.js.map +1 -1
- package/dist/mcp.d.ts +2 -1
- package/dist/mcp.d.ts.map +1 -1
- package/dist/mcp.js +222 -108
- package/dist/mcp.js.map +1 -1
- package/dist/recipes/index.d.ts +57 -0
- package/dist/recipes/index.d.ts.map +1 -0
- package/dist/recipes/index.js +81 -0
- package/dist/recipes/index.js.map +1 -0
- package/dist/storage/pglite-schema.d.ts.map +1 -1
- package/dist/storage/pglite-schema.js +27 -0
- package/dist/storage/pglite-schema.js.map +1 -1
- package/dist/storage/pglite.d.ts +5 -0
- package/dist/storage/pglite.d.ts.map +1 -1
- package/dist/storage/pglite.js +180 -138
- package/dist/storage/pglite.js.map +1 -1
- package/dist/storage/postgres.d.ts +5 -0
- package/dist/storage/postgres.d.ts.map +1 -1
- package/dist/storage/postgres.js +180 -138
- package/dist/storage/postgres.js.map +1 -1
- package/dist/storage/sqlite.d.ts +9 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +394 -326
- package/dist/storage/sqlite.js.map +1 -1
- package/dist/types/engram.d.ts +14 -0
- package/dist/types/engram.d.ts.map +1 -1
- package/dist/types/engram.js.map +1 -1
- package/package.json +1 -1
- package/src/adapters/claude-code.ts +66 -3
- package/src/adapters/common.ts +567 -515
- package/src/api/routes.ts +999 -971
- package/src/coordination/routes.ts +2155 -2150
- package/src/core/embeddings.ts +4 -1
- package/src/core/entity-extract.ts +47 -0
- package/src/core/format-recall.ts +25 -0
- package/src/core/query-expander.ts +1 -1
- package/src/core/reranker.ts +1 -1
- package/src/core/salience.ts +529 -514
- package/src/core/whoami.ts +92 -0
- package/src/core/write-pipeline.ts +60 -8
- package/src/core/write-telemetry.ts +131 -0
- package/src/engine/activation.ts +1468 -1369
- package/src/engine/consolidation-scheduler.ts +1 -1
- package/src/engine/consolidation.ts +887 -869
- package/src/engine/eval.ts +6 -1
- package/src/hooks/sidecar.ts +55 -0
- package/src/index.ts +248 -227
- package/src/mcp.ts +1387 -1270
- package/src/recipes/index.ts +125 -0
- package/src/storage/pglite-schema.ts +27 -0
- package/src/storage/pglite.ts +1420 -1372
- package/src/storage/postgres.ts +1523 -1475
- package/src/storage/sqlite.ts +1936 -1861
- package/src/types/engram.ts +22 -0
package/dist/storage/postgres.js
CHANGED
|
@@ -115,6 +115,11 @@ function rowToEngram(row) {
|
|
|
115
115
|
blockedBy: row.blocked_by ?? null,
|
|
116
116
|
sequence: row.sequence == null ? null : Number(row.sequence),
|
|
117
117
|
references: row.references_json ? JSON.parse(row.references_json) : null,
|
|
118
|
+
originClass: row.origin_class ?? null,
|
|
119
|
+
writerSession: row.writer_session ?? null,
|
|
120
|
+
recipeId: row.recipe_id ?? null,
|
|
121
|
+
validFrom: row.valid_from ?? null,
|
|
122
|
+
validTo: row.valid_to ?? null,
|
|
118
123
|
};
|
|
119
124
|
}
|
|
120
125
|
function rowToAssociation(row) {
|
|
@@ -274,7 +279,7 @@ export class PostgresEngramStore {
|
|
|
274
279
|
// ALWAYS the pool, never this.q(): this background flush is fire-and-forget and must
|
|
275
280
|
// not route to a transaction's client (a future caller that logs an activation inside a
|
|
276
281
|
// withTransaction would otherwise collide with the tx's in-flight query on one pg client).
|
|
277
|
-
await this.pool.query(`INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
|
|
282
|
+
await this.pool.query(`INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
|
|
278
283
|
VALUES ${values.join(',')}`, params);
|
|
279
284
|
}
|
|
280
285
|
catch {
|
|
@@ -321,18 +326,20 @@ export class PostgresEngramStore {
|
|
|
321
326
|
await this.readyPromise;
|
|
322
327
|
const id = input.id ?? randomUUID();
|
|
323
328
|
const now = new Date().toISOString();
|
|
324
|
-
await this.q(`INSERT INTO engrams (
|
|
325
|
-
id, agent_id, concept, content, embedding, embedding_model,
|
|
326
|
-
confidence, salience, access_count, last_accessed, created_at,
|
|
327
|
-
salience_features, reason_codes, stage, ttl, retracted,
|
|
328
|
-
tags, memory_type, memory_class, supersedes, episode_id,
|
|
329
|
-
task_status, task_priority, blocked_by, sequence, references_json
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
$
|
|
333
|
-
$
|
|
334
|
-
$
|
|
335
|
-
$
|
|
329
|
+
await this.q(`INSERT INTO engrams (
|
|
330
|
+
id, agent_id, concept, content, embedding, embedding_model,
|
|
331
|
+
confidence, salience, access_count, last_accessed, created_at,
|
|
332
|
+
salience_features, reason_codes, stage, ttl, retracted,
|
|
333
|
+
tags, memory_type, memory_class, supersedes, episode_id,
|
|
334
|
+
task_status, task_priority, blocked_by, sequence, references_json,
|
|
335
|
+
origin_class, writer_session, recipe_id, valid_from, valid_to
|
|
336
|
+
) VALUES (
|
|
337
|
+
$1, $2, $3, $4, $5::vector, $6,
|
|
338
|
+
$7, $8, 0, $9, $10,
|
|
339
|
+
$11, $12, 'active', $13, FALSE,
|
|
340
|
+
$14, $15, $16, $17, $18,
|
|
341
|
+
$19, $20, $21, $22, $23,
|
|
342
|
+
$24, $25, $26, $27, $28
|
|
336
343
|
)`, [
|
|
337
344
|
id,
|
|
338
345
|
input.agentId,
|
|
@@ -357,6 +364,11 @@ export class PostgresEngramStore {
|
|
|
357
364
|
input.sequence ?? null,
|
|
358
365
|
input.references && input.references.length > 0
|
|
359
366
|
? JSON.stringify(input.references) : null,
|
|
367
|
+
input.originClass ?? null,
|
|
368
|
+
input.writerSession ?? null,
|
|
369
|
+
input.recipeId ?? null,
|
|
370
|
+
input.validFrom ?? null,
|
|
371
|
+
input.validTo ?? null,
|
|
360
372
|
]);
|
|
361
373
|
const row = await this.getEngram(id);
|
|
362
374
|
if (!row)
|
|
@@ -468,10 +480,10 @@ export class PostgresEngramStore {
|
|
|
468
480
|
}
|
|
469
481
|
async touchEngram(id) {
|
|
470
482
|
await this.readyPromise;
|
|
471
|
-
await this.q(`UPDATE engrams
|
|
472
|
-
SET access_count = access_count + 1,
|
|
473
|
-
last_accessed = $1,
|
|
474
|
-
confidence = LEAST(0.85, confidence + 0.02 / (1.0 + sqrt(access_count::float)))
|
|
483
|
+
await this.q(`UPDATE engrams
|
|
484
|
+
SET access_count = access_count + 1,
|
|
485
|
+
last_accessed = $1,
|
|
486
|
+
confidence = LEAST(0.85, confidence + 0.02 / (1.0 + sqrt(access_count::float)))
|
|
475
487
|
WHERE id = $2`, [new Date().toISOString(), id]);
|
|
476
488
|
}
|
|
477
489
|
async updateStage(id, stage) {
|
|
@@ -517,9 +529,9 @@ export class PostgresEngramStore {
|
|
|
517
529
|
async timeWarp(agentId, ms) {
|
|
518
530
|
await this.readyPromise;
|
|
519
531
|
const seconds = Math.round(ms / 1000);
|
|
520
|
-
const r1 = await this.q(`UPDATE engrams SET
|
|
521
|
-
created_at = to_char(($1::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
|
|
522
|
-
last_accessed = to_char(($3::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
|
|
532
|
+
const r1 = await this.q(`UPDATE engrams SET
|
|
533
|
+
created_at = to_char(($1::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"'),
|
|
534
|
+
last_accessed = to_char(($3::timestamptz - interval '1 second' * $2), 'YYYY-MM-DD"T"HH24:MI:SS.MS"Z"')
|
|
523
535
|
WHERE agent_id = $4`, ['now', seconds, 'now', agentId]);
|
|
524
536
|
// node-postgres reports the affected-row count as `rowCount` (PGlite called it
|
|
525
537
|
// `affectedRows` — the one result-shape field that differs between the drivers).
|
|
@@ -545,13 +557,13 @@ export class PostgresEngramStore {
|
|
|
545
557
|
// Restrict to active + fading. Faded engrams (Paper 1: storage degradation)
|
|
546
558
|
// retain their embedding so they still participate in semantic recall, even
|
|
547
559
|
// though their content has been trimmed. Excludes staging/consolidated/archived.
|
|
548
|
-
const result = await this.q(`SELECT *, (embedding <=> $2::vector) AS distance
|
|
549
|
-
FROM engrams
|
|
550
|
-
WHERE agent_id = $1
|
|
551
|
-
AND embedding IS NOT NULL
|
|
552
|
-
AND retracted = FALSE
|
|
553
|
-
AND stage IN ('active', 'fading')
|
|
554
|
-
ORDER BY distance ASC
|
|
560
|
+
const result = await this.q(`SELECT *, (embedding <=> $2::vector) AS distance
|
|
561
|
+
FROM engrams
|
|
562
|
+
WHERE agent_id = $1
|
|
563
|
+
AND embedding IS NOT NULL
|
|
564
|
+
AND retracted = FALSE
|
|
565
|
+
AND stage IN ('active', 'fading')
|
|
566
|
+
ORDER BY distance ASC
|
|
555
567
|
LIMIT $3`, [agentId, vectorToLiteral(vec), limit]);
|
|
556
568
|
return result.rows.map((r) => ({ engram: rowToEngram(r), distance: r.distance }));
|
|
557
569
|
}
|
|
@@ -570,11 +582,11 @@ export class PostgresEngramStore {
|
|
|
570
582
|
if (tokens.length === 0)
|
|
571
583
|
return [];
|
|
572
584
|
const websearchQuery = tokens.join(' OR ');
|
|
573
|
-
const result = await this.q(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
|
|
574
|
-
FROM engrams
|
|
575
|
-
WHERE agent_id = $1 AND retracted = FALSE
|
|
576
|
-
AND fts @@ websearch_to_tsquery('english', $2)
|
|
577
|
-
ORDER BY rank DESC
|
|
585
|
+
const result = await this.q(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
|
|
586
|
+
FROM engrams
|
|
587
|
+
WHERE agent_id = $1 AND retracted = FALSE
|
|
588
|
+
AND fts @@ websearch_to_tsquery('english', $2)
|
|
589
|
+
ORDER BY rank DESC
|
|
578
590
|
LIMIT $3`, [agentId, websearchQuery, limit]);
|
|
579
591
|
return result.rows.map((r) => ({ engram: rowToEngram(r), bm25Score: calibrateBm25(Number(r.rank)) }));
|
|
580
592
|
}
|
|
@@ -588,11 +600,11 @@ export class PostgresEngramStore {
|
|
|
588
600
|
if (tokens.length === 0)
|
|
589
601
|
return [];
|
|
590
602
|
const websearchQuery = tokens.join(' OR ');
|
|
591
|
-
const result = await this.q(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
|
|
592
|
-
FROM engrams
|
|
593
|
-
WHERE agent_id = ANY($1::text[]) AND retracted = FALSE
|
|
594
|
-
AND fts @@ websearch_to_tsquery('english', $2)
|
|
595
|
-
ORDER BY rank DESC
|
|
603
|
+
const result = await this.q(`SELECT *, ts_rank_cd(fts, websearch_to_tsquery('english', $2)) AS rank
|
|
604
|
+
FROM engrams
|
|
605
|
+
WHERE agent_id = ANY($1::text[]) AND retracted = FALSE
|
|
606
|
+
AND fts @@ websearch_to_tsquery('english', $2)
|
|
607
|
+
ORDER BY rank DESC
|
|
596
608
|
LIMIT $3`, [agentIds, websearchQuery, limit]);
|
|
597
609
|
return result.rows.map((r) => ({ engram: rowToEngram(r), bm25Score: calibrateBm25(Number(r.rank)) }));
|
|
598
610
|
}
|
|
@@ -673,32 +685,32 @@ export class PostgresEngramStore {
|
|
|
673
685
|
sql += ` AND task_status = $${params.length + 1}`;
|
|
674
686
|
params.push(status);
|
|
675
687
|
}
|
|
676
|
-
sql += ` ORDER BY
|
|
677
|
-
CASE task_priority
|
|
678
|
-
WHEN 'urgent' THEN 0
|
|
679
|
-
WHEN 'high' THEN 1
|
|
680
|
-
WHEN 'medium' THEN 2
|
|
681
|
-
WHEN 'low' THEN 3
|
|
682
|
-
ELSE 4
|
|
683
|
-
END,
|
|
688
|
+
sql += ` ORDER BY
|
|
689
|
+
CASE task_priority
|
|
690
|
+
WHEN 'urgent' THEN 0
|
|
691
|
+
WHEN 'high' THEN 1
|
|
692
|
+
WHEN 'medium' THEN 2
|
|
693
|
+
WHEN 'low' THEN 3
|
|
694
|
+
ELSE 4
|
|
695
|
+
END,
|
|
684
696
|
created_at DESC`;
|
|
685
697
|
const result = await this.q(sql, params);
|
|
686
698
|
return result.rows.map(rowToEngram);
|
|
687
699
|
}
|
|
688
700
|
async getNextTask(agentId) {
|
|
689
701
|
await this.readyPromise;
|
|
690
|
-
const result = await this.q(`SELECT * FROM engrams
|
|
691
|
-
WHERE agent_id = $1 AND task_status IN ('open', 'in_progress') AND retracted = FALSE
|
|
692
|
-
ORDER BY
|
|
693
|
-
CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
|
|
694
|
-
CASE task_priority
|
|
695
|
-
WHEN 'urgent' THEN 0
|
|
696
|
-
WHEN 'high' THEN 1
|
|
697
|
-
WHEN 'medium' THEN 2
|
|
698
|
-
WHEN 'low' THEN 3
|
|
699
|
-
ELSE 4
|
|
700
|
-
END,
|
|
701
|
-
created_at ASC
|
|
702
|
+
const result = await this.q(`SELECT * FROM engrams
|
|
703
|
+
WHERE agent_id = $1 AND task_status IN ('open', 'in_progress') AND retracted = FALSE
|
|
704
|
+
ORDER BY
|
|
705
|
+
CASE task_status WHEN 'in_progress' THEN 0 ELSE 1 END,
|
|
706
|
+
CASE task_priority
|
|
707
|
+
WHEN 'urgent' THEN 0
|
|
708
|
+
WHEN 'high' THEN 1
|
|
709
|
+
WHEN 'medium' THEN 2
|
|
710
|
+
WHEN 'low' THEN 3
|
|
711
|
+
ELSE 4
|
|
712
|
+
END,
|
|
713
|
+
created_at ASC
|
|
702
714
|
LIMIT 1`, [agentId]);
|
|
703
715
|
return result.rows.length > 0 ? rowToEngram(result.rows[0]) : null;
|
|
704
716
|
}
|
|
@@ -712,11 +724,11 @@ export class PostgresEngramStore {
|
|
|
712
724
|
}
|
|
713
725
|
async findActiveMatchByConcept(agentId, concept, requiredTags) {
|
|
714
726
|
await this.readyPromise;
|
|
715
|
-
let sql = `SELECT * FROM engrams
|
|
716
|
-
WHERE agent_id = $1
|
|
717
|
-
AND LOWER(TRIM(concept)) = LOWER(TRIM($2))
|
|
718
|
-
AND stage = 'active'
|
|
719
|
-
AND retracted = FALSE
|
|
727
|
+
let sql = `SELECT * FROM engrams
|
|
728
|
+
WHERE agent_id = $1
|
|
729
|
+
AND LOWER(TRIM(concept)) = LOWER(TRIM($2))
|
|
730
|
+
AND stage = 'active'
|
|
731
|
+
AND retracted = FALSE
|
|
720
732
|
AND superseded_by IS NULL`;
|
|
721
733
|
const params = [agentId, concept];
|
|
722
734
|
if (requiredTags && requiredTags.length > 0) {
|
|
@@ -749,12 +761,12 @@ export class PostgresEngramStore {
|
|
|
749
761
|
await this.readyPromise;
|
|
750
762
|
const id = randomUUID();
|
|
751
763
|
const now = new Date().toISOString();
|
|
752
|
-
await this.q(`INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
|
|
753
|
-
VALUES ($1, $2, $3, $4, $5, $6, 0, $7, $7)
|
|
754
|
-
ON CONFLICT (from_engram_id, to_engram_id) DO UPDATE SET
|
|
755
|
-
weight = EXCLUDED.weight,
|
|
756
|
-
confidence = EXCLUDED.confidence,
|
|
757
|
-
last_activated = EXCLUDED.last_activated,
|
|
764
|
+
await this.q(`INSERT INTO associations (id, from_engram_id, to_engram_id, weight, confidence, type, activation_count, created_at, last_activated)
|
|
765
|
+
VALUES ($1, $2, $3, $4, $5, $6, 0, $7, $7)
|
|
766
|
+
ON CONFLICT (from_engram_id, to_engram_id) DO UPDATE SET
|
|
767
|
+
weight = EXCLUDED.weight,
|
|
768
|
+
confidence = EXCLUDED.confidence,
|
|
769
|
+
last_activated = EXCLUDED.last_activated,
|
|
758
770
|
activation_count = associations.activation_count + 1`, [id, fromId, toId, weight, confidence, type, now]);
|
|
759
771
|
const assoc = await this.getAssociation(fromId, toId);
|
|
760
772
|
if (!assoc)
|
|
@@ -776,12 +788,12 @@ export class PostgresEngramStore {
|
|
|
776
788
|
if (engramIds.length === 0)
|
|
777
789
|
return result;
|
|
778
790
|
await this.readyPromise;
|
|
779
|
-
const r = await this.q(`SELECT id, SUM(cnt) AS count, SUM(sw) AS sum_weight FROM (
|
|
780
|
-
SELECT from_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE from_engram_id = ANY($1::text[])
|
|
781
|
-
UNION ALL
|
|
782
|
-
SELECT to_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE to_engram_id = ANY($1::text[])
|
|
783
|
-
) t
|
|
784
|
-
WHERE id = ANY($1::text[])
|
|
791
|
+
const r = await this.q(`SELECT id, SUM(cnt) AS count, SUM(sw) AS sum_weight FROM (
|
|
792
|
+
SELECT from_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE from_engram_id = ANY($1::text[])
|
|
793
|
+
UNION ALL
|
|
794
|
+
SELECT to_engram_id AS id, 1 AS cnt, weight AS sw FROM associations WHERE to_engram_id = ANY($1::text[])
|
|
795
|
+
) t
|
|
796
|
+
WHERE id = ANY($1::text[])
|
|
785
797
|
GROUP BY id`, [engramIds]);
|
|
786
798
|
for (const row of r.rows) {
|
|
787
799
|
result.set(row.id, { count: Number(row.count), sumWeight: Number(row.sum_weight) });
|
|
@@ -797,7 +809,7 @@ export class PostgresEngramStore {
|
|
|
797
809
|
if (engramIds.length === 0)
|
|
798
810
|
return result;
|
|
799
811
|
await this.readyPromise;
|
|
800
|
-
const r = await this.q(`SELECT * FROM associations
|
|
812
|
+
const r = await this.q(`SELECT * FROM associations
|
|
801
813
|
WHERE from_engram_id = ANY($1::text[]) OR to_engram_id = ANY($1::text[])`, [engramIds]);
|
|
802
814
|
for (const row of r.rows) {
|
|
803
815
|
const a = rowToAssociation(row);
|
|
@@ -837,8 +849,8 @@ export class PostgresEngramStore {
|
|
|
837
849
|
}
|
|
838
850
|
async getAllAssociations(agentId) {
|
|
839
851
|
await this.readyPromise;
|
|
840
|
-
const result = await this.q(`SELECT a.* FROM associations a
|
|
841
|
-
JOIN engrams e ON a.from_engram_id = e.id
|
|
852
|
+
const result = await this.q(`SELECT a.* FROM associations a
|
|
853
|
+
JOIN engrams e ON a.from_engram_id = e.id
|
|
842
854
|
WHERE e.agent_id = $1`, [agentId]);
|
|
843
855
|
return result.rows.map(rowToAssociation);
|
|
844
856
|
}
|
|
@@ -847,11 +859,11 @@ export class PostgresEngramStore {
|
|
|
847
859
|
// ============================================================
|
|
848
860
|
async getEvictionCandidates(agentId, limit) {
|
|
849
861
|
await this.readyPromise;
|
|
850
|
-
const result = await this.q(`SELECT * FROM engrams
|
|
851
|
-
WHERE agent_id = $1 AND stage = 'active' AND retracted = FALSE
|
|
852
|
-
ORDER BY (salience * 0.3 + confidence * 0.3
|
|
853
|
-
+ (access_count::float / (access_count + 5)) * 0.2
|
|
854
|
-
+ (1.0 / (1.0 + EXTRACT(EPOCH FROM (now() - last_accessed::timestamptz)) / 86400.0)) * 0.2) ASC
|
|
862
|
+
const result = await this.q(`SELECT * FROM engrams
|
|
863
|
+
WHERE agent_id = $1 AND stage = 'active' AND retracted = FALSE
|
|
864
|
+
ORDER BY (salience * 0.3 + confidence * 0.3
|
|
865
|
+
+ (access_count::float / (access_count + 5)) * 0.2
|
|
866
|
+
+ (1.0 / (1.0 + EXTRACT(EPOCH FROM (now() - last_accessed::timestamptz)) / 86400.0)) * 0.2) ASC
|
|
855
867
|
LIMIT $2`, [agentId, limit]);
|
|
856
868
|
return result.rows.map(rowToEngram);
|
|
857
869
|
}
|
|
@@ -886,7 +898,7 @@ export class PostgresEngramStore {
|
|
|
886
898
|
}
|
|
887
899
|
async logStagingEvent(event) {
|
|
888
900
|
await this.readyPromise;
|
|
889
|
-
await this.q(`INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
|
|
901
|
+
await this.q(`INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
|
|
890
902
|
VALUES ($1, $2, $3, $4, $5, $6)`, [
|
|
891
903
|
event.engramId, event.agentId, event.action,
|
|
892
904
|
event.resonanceScore, event.timestamp.toISOString(), event.ageMs,
|
|
@@ -894,18 +906,18 @@ export class PostgresEngramStore {
|
|
|
894
906
|
}
|
|
895
907
|
async logRetrievalFeedback(activationEventId, engramId, useful, context) {
|
|
896
908
|
await this.readyPromise;
|
|
897
|
-
await this.q(`INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
|
|
909
|
+
await this.q(`INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
|
|
898
910
|
VALUES ($1, $2, $3, $4, $5, $6)`, [randomUUID(), activationEventId, engramId, useful, context, new Date().toISOString()]);
|
|
899
911
|
}
|
|
900
912
|
async getRetrievalPrecision(agentId, windowHours = 24) {
|
|
901
913
|
await this.readyPromise;
|
|
902
914
|
const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
|
|
903
|
-
const result = await this.q(`SELECT
|
|
904
|
-
COUNT(CASE WHEN useful = TRUE THEN 1 END) AS useful_count,
|
|
905
|
-
COUNT(*) AS total_count
|
|
906
|
-
FROM retrieval_feedback rf
|
|
907
|
-
LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
|
|
908
|
-
JOIN engrams e ON rf.engram_id = e.id
|
|
915
|
+
const result = await this.q(`SELECT
|
|
916
|
+
COUNT(CASE WHEN useful = TRUE THEN 1 END) AS useful_count,
|
|
917
|
+
COUNT(*) AS total_count
|
|
918
|
+
FROM retrieval_feedback rf
|
|
919
|
+
LEFT JOIN activation_events ae ON rf.activation_event_id = ae.id
|
|
920
|
+
JOIN engrams e ON rf.engram_id = e.id
|
|
909
921
|
WHERE e.agent_id = $1 AND rf.timestamp > $2`, [agentId, since]);
|
|
910
922
|
const row = result.rows[0];
|
|
911
923
|
const total = Number(row?.total_count ?? 0);
|
|
@@ -914,10 +926,10 @@ export class PostgresEngramStore {
|
|
|
914
926
|
}
|
|
915
927
|
async getStagingMetrics(agentId) {
|
|
916
928
|
await this.readyPromise;
|
|
917
|
-
const result = await this.q(`SELECT
|
|
918
|
-
COUNT(CASE WHEN action = 'promoted' THEN 1 END) AS promoted,
|
|
919
|
-
COUNT(CASE WHEN action = 'discarded' THEN 1 END) AS discarded,
|
|
920
|
-
COUNT(CASE WHEN action = 'expired' THEN 1 END) AS expired
|
|
929
|
+
const result = await this.q(`SELECT
|
|
930
|
+
COUNT(CASE WHEN action = 'promoted' THEN 1 END) AS promoted,
|
|
931
|
+
COUNT(CASE WHEN action = 'discarded' THEN 1 END) AS discarded,
|
|
932
|
+
COUNT(CASE WHEN action = 'expired' THEN 1 END) AS expired
|
|
921
933
|
FROM staging_events WHERE agent_id = $1`, [agentId]);
|
|
922
934
|
const row = result.rows[0] ?? { promoted: 0, discarded: 0, expired: 0 };
|
|
923
935
|
return {
|
|
@@ -931,8 +943,8 @@ export class PostgresEngramStore {
|
|
|
931
943
|
// Flush any buffered activation events so stats reflect the latest writes.
|
|
932
944
|
await this.flushActivationEvents();
|
|
933
945
|
const since = new Date(Date.now() - windowHours * 3600_000).toISOString();
|
|
934
|
-
const result = await this.q(`SELECT latency_ms FROM activation_events
|
|
935
|
-
WHERE agent_id = $1 AND timestamp > $2
|
|
946
|
+
const result = await this.q(`SELECT latency_ms FROM activation_events
|
|
947
|
+
WHERE agent_id = $1 AND timestamp > $2
|
|
936
948
|
ORDER BY latency_ms ASC`, [agentId, since]);
|
|
937
949
|
if (result.rows.length === 0)
|
|
938
950
|
return { count: 0, avgLatencyMs: 0, p95LatencyMs: 0 };
|
|
@@ -957,7 +969,7 @@ export class PostgresEngramStore {
|
|
|
957
969
|
await this.readyPromise;
|
|
958
970
|
const id = randomUUID();
|
|
959
971
|
const now = new Date().toISOString();
|
|
960
|
-
await this.q(`INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
|
|
972
|
+
await this.q(`INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
|
|
961
973
|
VALUES ($1, $2, $3, $4::vector, 0, $5, $5, $5)`, [id, input.agentId, input.label, vectorToLiteral(input.embedding ?? null), now]);
|
|
962
974
|
const ep = await this.getEpisode(id);
|
|
963
975
|
if (!ep)
|
|
@@ -983,9 +995,9 @@ export class PostgresEngramStore {
|
|
|
983
995
|
async addEngramToEpisode(engramId, episodeId) {
|
|
984
996
|
await this.readyPromise;
|
|
985
997
|
await this.q(`UPDATE engrams SET episode_id = $1 WHERE id = $2`, [episodeId, engramId]);
|
|
986
|
-
await this.q(`UPDATE episodes SET
|
|
987
|
-
engram_count = engram_count + 1,
|
|
988
|
-
end_time = GREATEST(end_time, $1)
|
|
998
|
+
await this.q(`UPDATE episodes SET
|
|
999
|
+
engram_count = engram_count + 1,
|
|
1000
|
+
end_time = GREATEST(end_time, $1)
|
|
989
1001
|
WHERE id = $2`, [new Date().toISOString(), episodeId]);
|
|
990
1002
|
}
|
|
991
1003
|
async getEngramsByEpisode(episodeId) {
|
|
@@ -1024,46 +1036,76 @@ export class PostgresEngramStore {
|
|
|
1024
1036
|
async updateAutoCheckpointWrite(agentId, engramId) {
|
|
1025
1037
|
await this.readyPromise;
|
|
1026
1038
|
const now = new Date().toISOString();
|
|
1027
|
-
await this.q(`INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
|
|
1028
|
-
VALUES ($1, $2, $3, 1, $3)
|
|
1029
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1030
|
-
last_write_id = EXCLUDED.last_write_id,
|
|
1031
|
-
last_activity_at = EXCLUDED.last_activity_at,
|
|
1032
|
-
write_count_since_consolidation = conscious_state.write_count_since_consolidation + 1,
|
|
1039
|
+
await this.q(`INSERT INTO conscious_state (agent_id, last_write_id, last_activity_at, write_count_since_consolidation, updated_at)
|
|
1040
|
+
VALUES ($1, $2, $3, 1, $3)
|
|
1041
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1042
|
+
last_write_id = EXCLUDED.last_write_id,
|
|
1043
|
+
last_activity_at = EXCLUDED.last_activity_at,
|
|
1044
|
+
write_count_since_consolidation = conscious_state.write_count_since_consolidation + 1,
|
|
1033
1045
|
updated_at = EXCLUDED.updated_at`, [agentId, engramId, now]);
|
|
1034
1046
|
}
|
|
1035
1047
|
async updateAutoCheckpointRecall(agentId, context, engramIds) {
|
|
1036
1048
|
await this.readyPromise;
|
|
1037
1049
|
const now = new Date().toISOString();
|
|
1038
|
-
await this.q(`INSERT INTO conscious_state (agent_id, last_recall_context, last_recall_ids, last_activity_at, recall_count_since_consolidation, updated_at)
|
|
1039
|
-
VALUES ($1, $2, $3, $4, 1, $4)
|
|
1040
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1041
|
-
last_recall_context = EXCLUDED.last_recall_context,
|
|
1042
|
-
last_recall_ids = EXCLUDED.last_recall_ids,
|
|
1043
|
-
last_activity_at = EXCLUDED.last_activity_at,
|
|
1044
|
-
recall_count_since_consolidation = conscious_state.recall_count_since_consolidation + 1,
|
|
1050
|
+
await this.q(`INSERT INTO conscious_state (agent_id, last_recall_context, last_recall_ids, last_activity_at, recall_count_since_consolidation, updated_at)
|
|
1051
|
+
VALUES ($1, $2, $3, $4, 1, $4)
|
|
1052
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1053
|
+
last_recall_context = EXCLUDED.last_recall_context,
|
|
1054
|
+
last_recall_ids = EXCLUDED.last_recall_ids,
|
|
1055
|
+
last_activity_at = EXCLUDED.last_activity_at,
|
|
1056
|
+
recall_count_since_consolidation = conscious_state.recall_count_since_consolidation + 1,
|
|
1045
1057
|
updated_at = EXCLUDED.updated_at`, [agentId, context, JSON.stringify(engramIds), now]);
|
|
1046
1058
|
}
|
|
1047
1059
|
async touchActivity(agentId) {
|
|
1048
1060
|
await this.readyPromise;
|
|
1049
1061
|
const now = new Date().toISOString();
|
|
1050
|
-
await this.q(`INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
|
|
1051
|
-
VALUES ($1, $2, $2)
|
|
1052
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1053
|
-
last_activity_at = EXCLUDED.last_activity_at,
|
|
1062
|
+
await this.q(`INSERT INTO conscious_state (agent_id, last_activity_at, updated_at)
|
|
1063
|
+
VALUES ($1, $2, $2)
|
|
1064
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1065
|
+
last_activity_at = EXCLUDED.last_activity_at,
|
|
1054
1066
|
updated_at = EXCLUDED.updated_at`, [agentId, now]);
|
|
1055
1067
|
}
|
|
1056
1068
|
async saveCheckpoint(agentId, state) {
|
|
1057
1069
|
await this.readyPromise;
|
|
1058
1070
|
const now = new Date().toISOString();
|
|
1059
|
-
await this.q(`INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
|
|
1060
|
-
VALUES ($1, $2, $3, $3, $3)
|
|
1061
|
-
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1062
|
-
execution_state = EXCLUDED.execution_state,
|
|
1063
|
-
checkpoint_at = EXCLUDED.checkpoint_at,
|
|
1064
|
-
last_activity_at = EXCLUDED.last_activity_at,
|
|
1071
|
+
await this.q(`INSERT INTO conscious_state (agent_id, execution_state, checkpoint_at, last_activity_at, updated_at)
|
|
1072
|
+
VALUES ($1, $2, $3, $3, $3)
|
|
1073
|
+
ON CONFLICT(agent_id) DO UPDATE SET
|
|
1074
|
+
execution_state = EXCLUDED.execution_state,
|
|
1075
|
+
checkpoint_at = EXCLUDED.checkpoint_at,
|
|
1076
|
+
last_activity_at = EXCLUDED.last_activity_at,
|
|
1065
1077
|
updated_at = EXCLUDED.updated_at`, [agentId, JSON.stringify(state), now]);
|
|
1066
1078
|
}
|
|
1079
|
+
/** Distinct agent ids present in this store (D3 whoami; includes all stages). */
|
|
1080
|
+
async listAgentIds() {
|
|
1081
|
+
const res = await this.pool.query('SELECT DISTINCT agent_id FROM engrams ORDER BY agent_id');
|
|
1082
|
+
return res.rows.map(r => r.agent_id);
|
|
1083
|
+
}
|
|
1084
|
+
async recordEntityMentions(engramId, agentId, entities) {
|
|
1085
|
+
if (!entities || entities.length === 0)
|
|
1086
|
+
return;
|
|
1087
|
+
for (const entity of entities) {
|
|
1088
|
+
await this.pool.query('INSERT INTO entity_mentions (entity, engram_id, agent_id) VALUES ($1, $2, $3) ON CONFLICT DO NOTHING', [entity, engramId, agentId]);
|
|
1089
|
+
}
|
|
1090
|
+
}
|
|
1091
|
+
async searchEntities(term, limit = 8) {
|
|
1092
|
+
const t = term.toLowerCase().replace(/[%_]/g, '');
|
|
1093
|
+
if (!t)
|
|
1094
|
+
return [];
|
|
1095
|
+
const res = await this.pool.query(`SELECT DISTINCT entity FROM (
|
|
1096
|
+
SELECT entity FROM entity_mentions WHERE entity LIKE $1
|
|
1097
|
+
UNION SELECT entity FROM entity_aliases WHERE alias LIKE $1
|
|
1098
|
+
) u LIMIT $2`, [`%${t}%`, limit]);
|
|
1099
|
+
return res.rows.map(r => r.entity);
|
|
1100
|
+
}
|
|
1101
|
+
async getEngramIdsByEntity(entity, agentId) {
|
|
1102
|
+
const alias = await this.pool.query('SELECT entity FROM entity_aliases WHERE alias = $1', [entity.toLowerCase()]);
|
|
1103
|
+
const resolved = alias.rows[0]?.entity ?? entity.toLowerCase();
|
|
1104
|
+
const res = agentId
|
|
1105
|
+
? await this.pool.query('SELECT engram_id FROM entity_mentions WHERE entity = $1 AND agent_id = $2', [resolved, agentId])
|
|
1106
|
+
: await this.pool.query('SELECT engram_id FROM entity_mentions WHERE entity = $1', [resolved]);
|
|
1107
|
+
return res.rows.map(r => r.engram_id);
|
|
1108
|
+
}
|
|
1067
1109
|
async getCheckpoint(agentId) {
|
|
1068
1110
|
await this.readyPromise;
|
|
1069
1111
|
const result = await this.q(`SELECT * FROM conscious_state WHERE agent_id = $1`, [agentId]);
|
|
@@ -1094,13 +1136,13 @@ export class PostgresEngramStore {
|
|
|
1094
1136
|
await this.q(`UPDATE conscious_state SET last_mini_consolidation_at = $1, updated_at = $1 WHERE agent_id = $2`, [now, agentId]);
|
|
1095
1137
|
}
|
|
1096
1138
|
else {
|
|
1097
|
-
await this.q(`UPDATE conscious_state SET
|
|
1098
|
-
last_consolidation_at = $1,
|
|
1099
|
-
last_mini_consolidation_at = $1,
|
|
1100
|
-
write_count_since_consolidation = 0,
|
|
1101
|
-
recall_count_since_consolidation = 0,
|
|
1102
|
-
consolidation_cycle_count = consolidation_cycle_count + 1,
|
|
1103
|
-
updated_at = $1
|
|
1139
|
+
await this.q(`UPDATE conscious_state SET
|
|
1140
|
+
last_consolidation_at = $1,
|
|
1141
|
+
last_mini_consolidation_at = $1,
|
|
1142
|
+
write_count_since_consolidation = 0,
|
|
1143
|
+
recall_count_since_consolidation = 0,
|
|
1144
|
+
consolidation_cycle_count = consolidation_cycle_count + 1,
|
|
1145
|
+
updated_at = $1
|
|
1104
1146
|
WHERE agent_id = $2`, [now, agentId]);
|
|
1105
1147
|
}
|
|
1106
1148
|
}
|
|
@@ -1125,10 +1167,10 @@ export class PostgresEngramStore {
|
|
|
1125
1167
|
// ============================================================
|
|
1126
1168
|
async getLatestByTag(opts) {
|
|
1127
1169
|
await this.readyPromise;
|
|
1128
|
-
let sql = `SELECT * FROM engrams
|
|
1129
|
-
WHERE agent_id = $1
|
|
1130
|
-
AND retracted = $2
|
|
1131
|
-
AND stage = 'active'
|
|
1170
|
+
let sql = `SELECT * FROM engrams
|
|
1171
|
+
WHERE agent_id = $1
|
|
1172
|
+
AND retracted = $2
|
|
1173
|
+
AND stage = 'active'
|
|
1132
1174
|
AND tags LIKE $3`;
|
|
1133
1175
|
const params = [opts.agentId, opts.retracted ?? false, `%"${opts.tagKeyPrefix}%`];
|
|
1134
1176
|
if (opts.scopeTagsAll && opts.scopeTagsAll.length > 0) {
|
|
@@ -1155,10 +1197,10 @@ export class PostgresEngramStore {
|
|
|
1155
1197
|
}
|
|
1156
1198
|
async getTopBy(opts) {
|
|
1157
1199
|
await this.readyPromise;
|
|
1158
|
-
let sql = `SELECT * FROM engrams
|
|
1159
|
-
WHERE agent_id = $1
|
|
1160
|
-
AND retracted = $2
|
|
1161
|
-
AND stage = 'active'
|
|
1200
|
+
let sql = `SELECT * FROM engrams
|
|
1201
|
+
WHERE agent_id = $1
|
|
1202
|
+
AND retracted = $2
|
|
1203
|
+
AND stage = 'active'
|
|
1162
1204
|
AND tags LIKE $3`;
|
|
1163
1205
|
const params = [opts.agentId, opts.retracted ?? false, `%"${opts.sortField}%`];
|
|
1164
1206
|
if (opts.filterTagsAll && opts.filterTagsAll.length > 0) {
|