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.
Files changed (60) hide show
  1. package/README.md +89 -19
  2. package/dist/adapters/common.d.ts.map +1 -1
  3. package/dist/adapters/common.js +5 -1
  4. package/dist/adapters/common.js.map +1 -1
  5. package/dist/api/routes.d.ts.map +1 -1
  6. package/dist/api/routes.js +2 -1
  7. package/dist/api/routes.js.map +1 -1
  8. package/dist/cli/migrate.js +29 -29
  9. package/dist/cli.js +82 -2
  10. package/dist/cli.js.map +1 -1
  11. package/dist/coordination/circuit-breaker.js +23 -23
  12. package/dist/index.js +2 -1
  13. package/dist/index.js.map +1 -1
  14. package/dist/mcp.js +50 -3
  15. package/dist/mcp.js.map +1 -1
  16. package/dist/onboard/index.d.ts +68 -0
  17. package/dist/onboard/index.d.ts.map +1 -0
  18. package/dist/onboard/index.js +265 -0
  19. package/dist/onboard/index.js.map +1 -0
  20. package/dist/storage/pglite-schema.js +143 -143
  21. package/dist/storage/postgres.js +138 -138
  22. package/dist/version.d.ts +2 -0
  23. package/dist/version.d.ts.map +1 -0
  24. package/dist/version.js +27 -0
  25. package/dist/version.js.map +1 -0
  26. package/package.json +9 -1
  27. package/src/adapters/common.ts +5 -1
  28. package/src/api/index.ts +3 -3
  29. package/src/api/routes.ts +2 -1
  30. package/src/cli/migrate.ts +307 -307
  31. package/src/cli.ts +77 -2
  32. package/src/coordination/circuit-breaker.ts +83 -83
  33. package/src/coordination/failure-modes.ts +50 -50
  34. package/src/core/decay.ts +63 -63
  35. package/src/core/embeddings.ts +110 -110
  36. package/src/core/index.ts +5 -5
  37. package/src/core/logger.ts +36 -36
  38. package/src/core/ml-worker-entry.ts +194 -194
  39. package/src/core/ml-worker.ts +281 -281
  40. package/src/core/query-expander.ts +122 -122
  41. package/src/core/reranker.ts +119 -119
  42. package/src/engine/confidence.ts +120 -120
  43. package/src/engine/consolidation-scheduler.ts +242 -242
  44. package/src/engine/eval.ts +102 -102
  45. package/src/engine/eviction.ts +101 -101
  46. package/src/engine/index.ts +8 -8
  47. package/src/engine/retraction.ts +366 -366
  48. package/src/engine/staging.ts +74 -74
  49. package/src/index.ts +2 -1
  50. package/src/mcp.ts +62 -3
  51. package/src/onboard/index.ts +298 -0
  52. package/src/storage/index.ts +3 -3
  53. package/src/storage/pglite-schema.ts +166 -166
  54. package/src/storage/postgres.ts +1475 -1475
  55. package/src/storage/store.ts +80 -80
  56. package/src/types/agent.ts +67 -67
  57. package/src/types/checkpoint.ts +46 -46
  58. package/src/types/eval.ts +100 -100
  59. package/src/types/index.ts +6 -6
  60. package/src/version.ts +26 -0
@@ -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,2 @@
1
+ export declare const VERSION: string;
2
+ //# sourceMappingURL=version.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"version.d.ts","sourceRoot":"","sources":["../src/version.ts"],"names":[],"mappings":"AAyBA,eAAO,MAAM,OAAO,EAAE,MAAyB,CAAC"}
@@ -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.10.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
  }
@@ -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: '0.8.8',
956
+ version: VERSION,
956
957
  coordination: coordEnabled,
957
958
  };
958
959
  if (coordEnabled && typeof (deps.store as any).getDb === 'function') {