agent-working-memory 0.10.0 → 0.11.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +89 -19
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +5 -1
- package/dist/adapters/common.js.map +1 -1
- package/dist/api/routes.d.ts.map +1 -1
- package/dist/api/routes.js +2 -1
- package/dist/api/routes.js.map +1 -1
- package/dist/cli/migrate.js +29 -29
- package/dist/cli.js +82 -2
- package/dist/cli.js.map +1 -1
- package/dist/coordination/circuit-breaker.js +23 -23
- package/dist/index.js +2 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +50 -3
- package/dist/mcp.js.map +1 -1
- package/dist/onboard/index.d.ts +68 -0
- package/dist/onboard/index.d.ts.map +1 -0
- package/dist/onboard/index.js +265 -0
- package/dist/onboard/index.js.map +1 -0
- package/dist/storage/pglite-schema.js +143 -143
- package/dist/storage/postgres.js +138 -138
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +27 -0
- package/dist/version.js.map +1 -0
- package/package.json +9 -1
- package/src/adapters/common.ts +5 -1
- package/src/api/index.ts +3 -3
- package/src/api/routes.ts +2 -1
- package/src/cli/migrate.ts +307 -307
- package/src/cli.ts +77 -2
- 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/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/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/index.ts +2 -1
- package/src/mcp.ts +62 -3
- package/src/onboard/index.ts +298 -0
- package/src/storage/index.ts +3 -3
- package/src/storage/pglite-schema.ts +166 -166
- package/src/storage/postgres.ts +1475 -1475
- 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/src/version.ts +26 -0
package/dist/storage/postgres.js
CHANGED
|
@@ -274,7 +274,7 @@ export class PostgresEngramStore {
|
|
|
274
274
|
// ALWAYS the pool, never this.q(): this background flush is fire-and-forget and must
|
|
275
275
|
// not route to a transaction's client (a future caller that logs an activation inside a
|
|
276
276
|
// 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)
|
|
277
|
+
await this.pool.query(`INSERT INTO activation_events (id, agent_id, timestamp, context, results_returned, top_score, latency_ms, engram_ids)
|
|
278
278
|
VALUES ${values.join(',')}`, params);
|
|
279
279
|
}
|
|
280
280
|
catch {
|
|
@@ -321,18 +321,18 @@ export class PostgresEngramStore {
|
|
|
321
321
|
await this.readyPromise;
|
|
322
322
|
const id = input.id ?? randomUUID();
|
|
323
323
|
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
|
-
) VALUES (
|
|
331
|
-
$1, $2, $3, $4, $5::vector, $6,
|
|
332
|
-
$7, $8, 0, $9, $10,
|
|
333
|
-
$11, $12, 'active', $13, FALSE,
|
|
334
|
-
$14, $15, $16, $17, $18,
|
|
335
|
-
$19, $20, $21, $22, $23
|
|
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
|
+
) VALUES (
|
|
331
|
+
$1, $2, $3, $4, $5::vector, $6,
|
|
332
|
+
$7, $8, 0, $9, $10,
|
|
333
|
+
$11, $12, 'active', $13, FALSE,
|
|
334
|
+
$14, $15, $16, $17, $18,
|
|
335
|
+
$19, $20, $21, $22, $23
|
|
336
336
|
)`, [
|
|
337
337
|
id,
|
|
338
338
|
input.agentId,
|
|
@@ -468,10 +468,10 @@ export class PostgresEngramStore {
|
|
|
468
468
|
}
|
|
469
469
|
async touchEngram(id) {
|
|
470
470
|
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)))
|
|
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)))
|
|
475
475
|
WHERE id = $2`, [new Date().toISOString(), id]);
|
|
476
476
|
}
|
|
477
477
|
async updateStage(id, stage) {
|
|
@@ -517,9 +517,9 @@ export class PostgresEngramStore {
|
|
|
517
517
|
async timeWarp(agentId, ms) {
|
|
518
518
|
await this.readyPromise;
|
|
519
519
|
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"')
|
|
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"')
|
|
523
523
|
WHERE agent_id = $4`, ['now', seconds, 'now', agentId]);
|
|
524
524
|
// node-postgres reports the affected-row count as `rowCount` (PGlite called it
|
|
525
525
|
// `affectedRows` — the one result-shape field that differs between the drivers).
|
|
@@ -545,13 +545,13 @@ export class PostgresEngramStore {
|
|
|
545
545
|
// Restrict to active + fading. Faded engrams (Paper 1: storage degradation)
|
|
546
546
|
// retain their embedding so they still participate in semantic recall, even
|
|
547
547
|
// 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
|
|
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
|
|
555
555
|
LIMIT $3`, [agentId, vectorToLiteral(vec), limit]);
|
|
556
556
|
return result.rows.map((r) => ({ engram: rowToEngram(r), distance: r.distance }));
|
|
557
557
|
}
|
|
@@ -570,11 +570,11 @@ export class PostgresEngramStore {
|
|
|
570
570
|
if (tokens.length === 0)
|
|
571
571
|
return [];
|
|
572
572
|
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
|
|
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
|
|
578
578
|
LIMIT $3`, [agentId, websearchQuery, limit]);
|
|
579
579
|
return result.rows.map((r) => ({ engram: rowToEngram(r), bm25Score: calibrateBm25(Number(r.rank)) }));
|
|
580
580
|
}
|
|
@@ -588,11 +588,11 @@ export class PostgresEngramStore {
|
|
|
588
588
|
if (tokens.length === 0)
|
|
589
589
|
return [];
|
|
590
590
|
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
|
|
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
|
|
596
596
|
LIMIT $3`, [agentIds, websearchQuery, limit]);
|
|
597
597
|
return result.rows.map((r) => ({ engram: rowToEngram(r), bm25Score: calibrateBm25(Number(r.rank)) }));
|
|
598
598
|
}
|
|
@@ -673,32 +673,32 @@ export class PostgresEngramStore {
|
|
|
673
673
|
sql += ` AND task_status = $${params.length + 1}`;
|
|
674
674
|
params.push(status);
|
|
675
675
|
}
|
|
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,
|
|
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,
|
|
684
684
|
created_at DESC`;
|
|
685
685
|
const result = await this.q(sql, params);
|
|
686
686
|
return result.rows.map(rowToEngram);
|
|
687
687
|
}
|
|
688
688
|
async getNextTask(agentId) {
|
|
689
689
|
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
|
|
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
702
|
LIMIT 1`, [agentId]);
|
|
703
703
|
return result.rows.length > 0 ? rowToEngram(result.rows[0]) : null;
|
|
704
704
|
}
|
|
@@ -712,11 +712,11 @@ export class PostgresEngramStore {
|
|
|
712
712
|
}
|
|
713
713
|
async findActiveMatchByConcept(agentId, concept, requiredTags) {
|
|
714
714
|
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
|
|
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
|
|
720
720
|
AND superseded_by IS NULL`;
|
|
721
721
|
const params = [agentId, concept];
|
|
722
722
|
if (requiredTags && requiredTags.length > 0) {
|
|
@@ -749,12 +749,12 @@ export class PostgresEngramStore {
|
|
|
749
749
|
await this.readyPromise;
|
|
750
750
|
const id = randomUUID();
|
|
751
751
|
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,
|
|
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,
|
|
758
758
|
activation_count = associations.activation_count + 1`, [id, fromId, toId, weight, confidence, type, now]);
|
|
759
759
|
const assoc = await this.getAssociation(fromId, toId);
|
|
760
760
|
if (!assoc)
|
|
@@ -776,12 +776,12 @@ export class PostgresEngramStore {
|
|
|
776
776
|
if (engramIds.length === 0)
|
|
777
777
|
return result;
|
|
778
778
|
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[])
|
|
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[])
|
|
785
785
|
GROUP BY id`, [engramIds]);
|
|
786
786
|
for (const row of r.rows) {
|
|
787
787
|
result.set(row.id, { count: Number(row.count), sumWeight: Number(row.sum_weight) });
|
|
@@ -797,7 +797,7 @@ export class PostgresEngramStore {
|
|
|
797
797
|
if (engramIds.length === 0)
|
|
798
798
|
return result;
|
|
799
799
|
await this.readyPromise;
|
|
800
|
-
const r = await this.q(`SELECT * FROM associations
|
|
800
|
+
const r = await this.q(`SELECT * FROM associations
|
|
801
801
|
WHERE from_engram_id = ANY($1::text[]) OR to_engram_id = ANY($1::text[])`, [engramIds]);
|
|
802
802
|
for (const row of r.rows) {
|
|
803
803
|
const a = rowToAssociation(row);
|
|
@@ -837,8 +837,8 @@ export class PostgresEngramStore {
|
|
|
837
837
|
}
|
|
838
838
|
async getAllAssociations(agentId) {
|
|
839
839
|
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
|
|
840
|
+
const result = await this.q(`SELECT a.* FROM associations a
|
|
841
|
+
JOIN engrams e ON a.from_engram_id = e.id
|
|
842
842
|
WHERE e.agent_id = $1`, [agentId]);
|
|
843
843
|
return result.rows.map(rowToAssociation);
|
|
844
844
|
}
|
|
@@ -847,11 +847,11 @@ export class PostgresEngramStore {
|
|
|
847
847
|
// ============================================================
|
|
848
848
|
async getEvictionCandidates(agentId, limit) {
|
|
849
849
|
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
|
|
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
|
|
855
855
|
LIMIT $2`, [agentId, limit]);
|
|
856
856
|
return result.rows.map(rowToEngram);
|
|
857
857
|
}
|
|
@@ -886,7 +886,7 @@ export class PostgresEngramStore {
|
|
|
886
886
|
}
|
|
887
887
|
async logStagingEvent(event) {
|
|
888
888
|
await this.readyPromise;
|
|
889
|
-
await this.q(`INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
|
|
889
|
+
await this.q(`INSERT INTO staging_events (engram_id, agent_id, action, resonance_score, timestamp, age_ms)
|
|
890
890
|
VALUES ($1, $2, $3, $4, $5, $6)`, [
|
|
891
891
|
event.engramId, event.agentId, event.action,
|
|
892
892
|
event.resonanceScore, event.timestamp.toISOString(), event.ageMs,
|
|
@@ -894,18 +894,18 @@ export class PostgresEngramStore {
|
|
|
894
894
|
}
|
|
895
895
|
async logRetrievalFeedback(activationEventId, engramId, useful, context) {
|
|
896
896
|
await this.readyPromise;
|
|
897
|
-
await this.q(`INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
|
|
897
|
+
await this.q(`INSERT INTO retrieval_feedback (id, activation_event_id, engram_id, useful, context, timestamp)
|
|
898
898
|
VALUES ($1, $2, $3, $4, $5, $6)`, [randomUUID(), activationEventId, engramId, useful, context, new Date().toISOString()]);
|
|
899
899
|
}
|
|
900
900
|
async getRetrievalPrecision(agentId, windowHours = 24) {
|
|
901
901
|
await this.readyPromise;
|
|
902
902
|
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
|
|
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
|
|
909
909
|
WHERE e.agent_id = $1 AND rf.timestamp > $2`, [agentId, since]);
|
|
910
910
|
const row = result.rows[0];
|
|
911
911
|
const total = Number(row?.total_count ?? 0);
|
|
@@ -914,10 +914,10 @@ export class PostgresEngramStore {
|
|
|
914
914
|
}
|
|
915
915
|
async getStagingMetrics(agentId) {
|
|
916
916
|
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
|
|
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
|
|
921
921
|
FROM staging_events WHERE agent_id = $1`, [agentId]);
|
|
922
922
|
const row = result.rows[0] ?? { promoted: 0, discarded: 0, expired: 0 };
|
|
923
923
|
return {
|
|
@@ -931,8 +931,8 @@ export class PostgresEngramStore {
|
|
|
931
931
|
// Flush any buffered activation events so stats reflect the latest writes.
|
|
932
932
|
await this.flushActivationEvents();
|
|
933
933
|
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
|
|
934
|
+
const result = await this.q(`SELECT latency_ms FROM activation_events
|
|
935
|
+
WHERE agent_id = $1 AND timestamp > $2
|
|
936
936
|
ORDER BY latency_ms ASC`, [agentId, since]);
|
|
937
937
|
if (result.rows.length === 0)
|
|
938
938
|
return { count: 0, avgLatencyMs: 0, p95LatencyMs: 0 };
|
|
@@ -957,7 +957,7 @@ export class PostgresEngramStore {
|
|
|
957
957
|
await this.readyPromise;
|
|
958
958
|
const id = randomUUID();
|
|
959
959
|
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)
|
|
960
|
+
await this.q(`INSERT INTO episodes (id, agent_id, label, embedding, engram_count, start_time, end_time, created_at)
|
|
961
961
|
VALUES ($1, $2, $3, $4::vector, 0, $5, $5, $5)`, [id, input.agentId, input.label, vectorToLiteral(input.embedding ?? null), now]);
|
|
962
962
|
const ep = await this.getEpisode(id);
|
|
963
963
|
if (!ep)
|
|
@@ -983,9 +983,9 @@ export class PostgresEngramStore {
|
|
|
983
983
|
async addEngramToEpisode(engramId, episodeId) {
|
|
984
984
|
await this.readyPromise;
|
|
985
985
|
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)
|
|
986
|
+
await this.q(`UPDATE episodes SET
|
|
987
|
+
engram_count = engram_count + 1,
|
|
988
|
+
end_time = GREATEST(end_time, $1)
|
|
989
989
|
WHERE id = $2`, [new Date().toISOString(), episodeId]);
|
|
990
990
|
}
|
|
991
991
|
async getEngramsByEpisode(episodeId) {
|
|
@@ -1024,44 +1024,44 @@ export class PostgresEngramStore {
|
|
|
1024
1024
|
async updateAutoCheckpointWrite(agentId, engramId) {
|
|
1025
1025
|
await this.readyPromise;
|
|
1026
1026
|
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,
|
|
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,
|
|
1033
1033
|
updated_at = EXCLUDED.updated_at`, [agentId, engramId, now]);
|
|
1034
1034
|
}
|
|
1035
1035
|
async updateAutoCheckpointRecall(agentId, context, engramIds) {
|
|
1036
1036
|
await this.readyPromise;
|
|
1037
1037
|
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,
|
|
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,
|
|
1045
1045
|
updated_at = EXCLUDED.updated_at`, [agentId, context, JSON.stringify(engramIds), now]);
|
|
1046
1046
|
}
|
|
1047
1047
|
async touchActivity(agentId) {
|
|
1048
1048
|
await this.readyPromise;
|
|
1049
1049
|
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,
|
|
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,
|
|
1054
1054
|
updated_at = EXCLUDED.updated_at`, [agentId, now]);
|
|
1055
1055
|
}
|
|
1056
1056
|
async saveCheckpoint(agentId, state) {
|
|
1057
1057
|
await this.readyPromise;
|
|
1058
1058
|
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,
|
|
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,
|
|
1065
1065
|
updated_at = EXCLUDED.updated_at`, [agentId, JSON.stringify(state), now]);
|
|
1066
1066
|
}
|
|
1067
1067
|
async getCheckpoint(agentId) {
|
|
@@ -1094,13 +1094,13 @@ export class PostgresEngramStore {
|
|
|
1094
1094
|
await this.q(`UPDATE conscious_state SET last_mini_consolidation_at = $1, updated_at = $1 WHERE agent_id = $2`, [now, agentId]);
|
|
1095
1095
|
}
|
|
1096
1096
|
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
|
|
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
|
|
1104
1104
|
WHERE agent_id = $2`, [now, agentId]);
|
|
1105
1105
|
}
|
|
1106
1106
|
}
|
|
@@ -1125,10 +1125,10 @@ export class PostgresEngramStore {
|
|
|
1125
1125
|
// ============================================================
|
|
1126
1126
|
async getLatestByTag(opts) {
|
|
1127
1127
|
await this.readyPromise;
|
|
1128
|
-
let sql = `SELECT * FROM engrams
|
|
1129
|
-
WHERE agent_id = $1
|
|
1130
|
-
AND retracted = $2
|
|
1131
|
-
AND stage = 'active'
|
|
1128
|
+
let sql = `SELECT * FROM engrams
|
|
1129
|
+
WHERE agent_id = $1
|
|
1130
|
+
AND retracted = $2
|
|
1131
|
+
AND stage = 'active'
|
|
1132
1132
|
AND tags LIKE $3`;
|
|
1133
1133
|
const params = [opts.agentId, opts.retracted ?? false, `%"${opts.tagKeyPrefix}%`];
|
|
1134
1134
|
if (opts.scopeTagsAll && opts.scopeTagsAll.length > 0) {
|
|
@@ -1155,10 +1155,10 @@ export class PostgresEngramStore {
|
|
|
1155
1155
|
}
|
|
1156
1156
|
async getTopBy(opts) {
|
|
1157
1157
|
await this.readyPromise;
|
|
1158
|
-
let sql = `SELECT * FROM engrams
|
|
1159
|
-
WHERE agent_id = $1
|
|
1160
|
-
AND retracted = $2
|
|
1161
|
-
AND stage = 'active'
|
|
1158
|
+
let sql = `SELECT * FROM engrams
|
|
1159
|
+
WHERE agent_id = $1
|
|
1160
|
+
AND retracted = $2
|
|
1161
|
+
AND stage = 'active'
|
|
1162
1162
|
AND tags LIKE $3`;
|
|
1163
1163
|
const params = [opts.agentId, opts.retracted ?? false, `%"${opts.sortField}%`];
|
|
1164
1164
|
if (opts.filterTagsAll && opts.filterTagsAll.length > 0) {
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAyBA,eAAO,MAAM,OAAO,EAAE,MAAyB,CAAC"}
|
package/dist/version.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { readFileSync } from 'node:fs';
|
|
2
|
+
import { fileURLToPath } from 'node:url';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
/**
|
|
5
|
+
* The running package version, read from package.json at runtime so the number
|
|
6
|
+
* reported by /health, the startup banner, and the MCP server always matches
|
|
7
|
+
* the actually-deployed build. Hand-maintained version literals drifted across
|
|
8
|
+
* releases (a 0.10.0 build was still reporting 0.8.5/0.8.8) — this removes them.
|
|
9
|
+
*/
|
|
10
|
+
function resolveVersion() {
|
|
11
|
+
const here = dirname(fileURLToPath(import.meta.url));
|
|
12
|
+
// src/version.ts (dev) and dist/version.js (build) both sit one level below
|
|
13
|
+
// the package root; the ../../ fallback covers a deeper output nesting.
|
|
14
|
+
for (const rel of ['../package.json', '../../package.json']) {
|
|
15
|
+
try {
|
|
16
|
+
const v = JSON.parse(readFileSync(join(here, rel), 'utf8')).version;
|
|
17
|
+
if (typeof v === 'string' && v)
|
|
18
|
+
return v;
|
|
19
|
+
}
|
|
20
|
+
catch {
|
|
21
|
+
/* try the next candidate path */
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
return '0.0.0';
|
|
25
|
+
}
|
|
26
|
+
export const VERSION = resolveVersion();
|
|
27
|
+
//# sourceMappingURL=version.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"version.js","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,aAAa,EAAE,MAAM,UAAU,CAAC;AACzC,OAAO,EAAE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAE1C;;;;;GAKG;AACH,SAAS,cAAc;IACrB,MAAM,IAAI,GAAG,OAAO,CAAC,aAAa,CAAC,MAAM,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC,CAAC;IACrD,4EAA4E;IAC5E,wEAAwE;IACxE,KAAK,MAAM,GAAG,IAAI,CAAC,iBAAiB,EAAE,oBAAoB,CAAC,EAAE,CAAC;QAC5D,IAAI,CAAC;YACH,MAAM,CAAC,GAAG,IAAI,CAAC,KAAK,CAAC,YAAY,CAAC,IAAI,CAAC,IAAI,EAAE,GAAG,CAAC,EAAE,MAAM,CAAC,CAAC,CAAC,OAAO,CAAC;YACpE,IAAI,OAAO,CAAC,KAAK,QAAQ,IAAI,CAAC;gBAAE,OAAO,CAAC,CAAC;QAC3C,CAAC;QAAC,MAAM,CAAC;YACP,iCAAiC;QACnC,CAAC;IACH,CAAC;IACD,OAAO,OAAO,CAAC;AACjB,CAAC;AAED,MAAM,CAAC,MAAM,OAAO,GAAW,cAAc,EAAE,CAAC"}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "agent-working-memory",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.11.0",
|
|
4
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",
|
|
@@ -58,5 +58,13 @@
|
|
|
58
58
|
"tsx": "^4.21.0",
|
|
59
59
|
"typescript": "^5.9.3",
|
|
60
60
|
"vitest": "^4.0.18"
|
|
61
|
+
},
|
|
62
|
+
"repository": {
|
|
63
|
+
"type": "git",
|
|
64
|
+
"url": "git+https://github.com/CompleteIdeas/agent-working-memory.git"
|
|
65
|
+
},
|
|
66
|
+
"homepage": "https://github.com/CompleteIdeas/agent-working-memory#readme",
|
|
67
|
+
"bugs": {
|
|
68
|
+
"url": "https://github.com/CompleteIdeas/agent-working-memory/issues"
|
|
61
69
|
}
|
|
62
70
|
}
|
package/src/adapters/common.ts
CHANGED
|
@@ -222,7 +222,11 @@ directly — but **DO NOT fall back to markdown files**. Files drift the moment
|
|
|
222
222
|
you write them; AWM stays current because every agent reads + writes the same store.
|
|
223
223
|
|
|
224
224
|
### Lifecycle (always do these, in this order)
|
|
225
|
-
1. **Session start**: call \`memory_restore\` to recover previous context.
|
|
225
|
+
1. **Session start**: call \`memory_restore\` to recover previous context. If it reports the
|
|
226
|
+
store is empty/new (or recall keeps returning nothing), **warm-start first**: recall the
|
|
227
|
+
\`onboard a new project\` skill and follow it — or call \`onboard_scan\` on the project's
|
|
228
|
+
docs/repo, refine the candidates, run \`onboard_questions\`, and save the good ones with
|
|
229
|
+
\`memory_write\` (canonical). A cold store is nearly useless until it's seeded.
|
|
226
230
|
2. **Starting a task**: call \`memory_task_begin\` (checkpoints + recalls relevant memories).
|
|
227
231
|
3. **During work**: call \`memory_recall\` BEFORE stating any fact, BEFORE searching
|
|
228
232
|
the filesystem, BEFORE making architectural decisions. Recall is ~300ms — cheaper
|
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
|
@@ -49,6 +49,7 @@ import type { TaskStatus, TaskPriority } from '../types/engram.js';
|
|
|
49
49
|
import type { ConsciousState } from '../types/checkpoint.js';
|
|
50
50
|
import { DEFAULT_AGENT_CONFIG } from '../types/agent.js';
|
|
51
51
|
import { embed, embedBatch } from '../core/embeddings.js';
|
|
52
|
+
import { VERSION } from '../version.js';
|
|
52
53
|
|
|
53
54
|
export interface MemoryDeps {
|
|
54
55
|
store: EngramStore;
|
|
@@ -952,7 +953,7 @@ export function registerRoutes(app: FastifyInstance, deps: MemoryDeps): void {
|
|
|
952
953
|
const base: Record<string, unknown> = {
|
|
953
954
|
status: 'ok',
|
|
954
955
|
timestamp: new Date().toISOString(),
|
|
955
|
-
version:
|
|
956
|
+
version: VERSION,
|
|
956
957
|
coordination: coordEnabled,
|
|
957
958
|
};
|
|
958
959
|
if (coordEnabled && typeof (deps.store as any).getDb === 'function') {
|