agent-working-memory 0.15.9 → 0.16.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 (77) hide show
  1. package/AGENTS.md +36 -0
  2. package/README.md +23 -12
  3. package/dist/adapters/common.d.ts +1 -1
  4. package/dist/adapters/common.d.ts.map +1 -1
  5. package/dist/adapters/common.js +33 -20
  6. package/dist/adapters/common.js.map +1 -1
  7. package/dist/core/embeddings.d.ts +1 -1
  8. package/dist/core/embeddings.js +1 -1
  9. package/dist/core/entity-extract.js +1 -1
  10. package/dist/core/query-expander.d.ts +4 -2
  11. package/dist/core/query-expander.d.ts.map +1 -1
  12. package/dist/core/query-expander.js +4 -2
  13. package/dist/core/query-expander.js.map +1 -1
  14. package/dist/core/recall-config.d.ts +1 -1
  15. package/dist/core/recall-config.d.ts.map +1 -1
  16. package/dist/core/recall-config.js +6 -1
  17. package/dist/core/recall-config.js.map +1 -1
  18. package/dist/core/recall-telemetry.d.ts +15 -0
  19. package/dist/core/recall-telemetry.d.ts.map +1 -0
  20. package/dist/core/recall-telemetry.js +108 -0
  21. package/dist/core/recall-telemetry.js.map +1 -0
  22. package/dist/core/rerank-window.d.ts +5 -4
  23. package/dist/core/rerank-window.d.ts.map +1 -1
  24. package/dist/core/rerank-window.js +5 -4
  25. package/dist/core/rerank-window.js.map +1 -1
  26. package/dist/core/reranker.d.ts +2 -1
  27. package/dist/core/reranker.d.ts.map +1 -1
  28. package/dist/core/reranker.js +23 -3
  29. package/dist/core/reranker.js.map +1 -1
  30. package/dist/core/salience.js +1 -1
  31. package/dist/core/salience.js.map +1 -1
  32. package/dist/core/token-budget.d.ts +2 -2
  33. package/dist/core/token-budget.js +2 -2
  34. package/dist/core/whoami.d.ts +1 -1
  35. package/dist/engine/activation.d.ts +30 -13
  36. package/dist/engine/activation.d.ts.map +1 -1
  37. package/dist/engine/activation.js +159 -50
  38. package/dist/engine/activation.js.map +1 -1
  39. package/dist/index.js +1 -1
  40. package/dist/index.js.map +1 -1
  41. package/dist/mcp.js +98 -98
  42. package/dist/mcp.js.map +1 -1
  43. package/dist/storage/pglite.d.ts +28 -0
  44. package/dist/storage/pglite.d.ts.map +1 -1
  45. package/dist/storage/pglite.js +55 -0
  46. package/dist/storage/pglite.js.map +1 -1
  47. package/dist/storage/postgres.d.ts +28 -0
  48. package/dist/storage/postgres.d.ts.map +1 -1
  49. package/dist/storage/postgres.js +55 -0
  50. package/dist/storage/postgres.js.map +1 -1
  51. package/dist/storage/sqlite.d.ts +28 -0
  52. package/dist/storage/sqlite.d.ts.map +1 -1
  53. package/dist/storage/sqlite.js +56 -0
  54. package/dist/storage/sqlite.js.map +1 -1
  55. package/llms.txt +437 -0
  56. package/package.json +7 -1
  57. package/plugin/.claude-plugin/plugin.json +1 -1
  58. package/plugin/README.md +1 -1
  59. package/plugin/bin/awm-mcp-launcher.cjs +2 -2
  60. package/plugin/skills/awm-memory/SKILL.md +33 -20
  61. package/src/adapters/common.ts +33 -20
  62. package/src/core/embeddings.ts +1 -1
  63. package/src/core/entity-extract.ts +1 -1
  64. package/src/core/query-expander.ts +4 -2
  65. package/src/core/recall-config.ts +6 -1
  66. package/src/core/recall-telemetry.ts +131 -0
  67. package/src/core/rerank-window.ts +159 -158
  68. package/src/core/reranker.ts +24 -3
  69. package/src/core/salience.ts +1 -1
  70. package/src/core/token-budget.ts +160 -160
  71. package/src/core/whoami.ts +120 -120
  72. package/src/engine/activation.ts +159 -51
  73. package/src/index.ts +1 -1
  74. package/src/mcp.ts +1475 -1475
  75. package/src/storage/pglite.ts +49 -0
  76. package/src/storage/postgres.ts +49 -0
  77. package/src/storage/sqlite.ts +51 -0
@@ -401,6 +401,55 @@ export class PGliteEngramStore {
401
401
  return result.rows.map(rowToEngram);
402
402
  }
403
403
 
404
+ /**
405
+ * Tags of the most-accessed engrams, bounded IN SQL.
406
+ *
407
+ * Exists because the recall path's pronoun branch used to call
408
+ * `getEngramsByAgents(agentIds, 'active')` — hydrating EVERY active engram
409
+ * into JS, embeddings and all — then sort by accessCount in memory, keep the
410
+ * top ten, and extract five tag words. Measured 2026-10-08 on an
411
+ * 11k-retrievable store: 245ms mean / 280ms median whenever it ran, which was
412
+ * 2.1% of real prompts (the words that trigger it include "it", "this",
413
+ * "that" and "there") against 0.2% of benchmark probes — so it cost real
414
+ * users ~62% of a recall and the benchmark never saw it. See
415
+ * docs/recall-latency.md.
416
+ *
417
+ * ONE QUERY PER AGENT, deliberately. `agent_id IN (...)` makes SQLite abandon
418
+ * the ordered index walk and sort the whole partition in a temp B-tree:
419
+ * measured on the 11k-retrievable store, the IN form costs 86ms against
420
+ * **0.03ms** for the equality form, because only the latter can walk
421
+ * `idx_engrams_access` and stop at LIMIT. Agent-scoped recall passes a single
422
+ * agent and hits the fast path either way; workspace-scoped recall would have
423
+ * paid the 86ms on every pronoun query. The global top N is always contained
424
+ * in the union of the per-agent top Ns, so merging in JS is exact, not an
425
+ * approximation.
426
+ *
427
+ * Ordering is `access_count DESC, id`. The `id` tiebreak is deliberate: the
428
+ * old JS sort broke ties on SQLite's scan order, which is arbitrary, so this
429
+ * is both cheaper and more deterministic.
430
+ */
431
+ async getTopAccessedTags(agentIds: string[], topN: number, stage: EngramStage = 'active'): Promise<string[][]> {
432
+ if (agentIds.length === 0 || topN <= 0) return [];
433
+ await this.readyPromise;
434
+ const rows: { tags: any; accessCount: number; id: string }[] = [];
435
+ for (const agentId of agentIds) {
436
+ const result = await this.db.query<any>(`
437
+ SELECT tags, access_count AS "accessCount", id FROM engrams
438
+ WHERE agent_id = $1 AND stage = $2 AND retracted = FALSE
439
+ ORDER BY access_count DESC, id
440
+ LIMIT $3
441
+ `, [agentId, stage, topN]);
442
+ rows.push(...result.rows);
443
+ }
444
+ if (agentIds.length > 1) {
445
+ rows.sort((a, b) => (b.accessCount - a.accessCount) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
446
+ }
447
+ return rows.slice(0, topN).map((r) => {
448
+ if (Array.isArray(r.tags)) return r.tags as string[];
449
+ try { return JSON.parse(r.tags) as string[]; } catch { return []; }
450
+ });
451
+ }
452
+
404
453
  async getWorkspaceAgentIds(agentId: string, workspace: string): Promise<string[]> {
405
454
  await this.readyPromise;
406
455
  try {
@@ -500,6 +500,55 @@ export class PostgresEngramStore {
500
500
  return result.rows.map(rowToEngram);
501
501
  }
502
502
 
503
+ /**
504
+ * Tags of the most-accessed engrams, bounded IN SQL.
505
+ *
506
+ * Exists because the recall path's pronoun branch used to call
507
+ * `getEngramsByAgents(agentIds, 'active')` — hydrating EVERY active engram
508
+ * into JS, embeddings and all — then sort by accessCount in memory, keep the
509
+ * top ten, and extract five tag words. Measured 2026-10-08 on an
510
+ * 11k-retrievable store: 245ms mean / 280ms median whenever it ran, which was
511
+ * 2.1% of real prompts (the words that trigger it include "it", "this",
512
+ * "that" and "there") against 0.2% of benchmark probes — so it cost real
513
+ * users ~62% of a recall and the benchmark never saw it. See
514
+ * docs/recall-latency.md.
515
+ *
516
+ * ONE QUERY PER AGENT, deliberately. `agent_id IN (...)` makes SQLite abandon
517
+ * the ordered index walk and sort the whole partition in a temp B-tree:
518
+ * measured on the 11k-retrievable store, the IN form costs 86ms against
519
+ * **0.03ms** for the equality form, because only the latter can walk
520
+ * `idx_engrams_access` and stop at LIMIT. Agent-scoped recall passes a single
521
+ * agent and hits the fast path either way; workspace-scoped recall would have
522
+ * paid the 86ms on every pronoun query. The global top N is always contained
523
+ * in the union of the per-agent top Ns, so merging in JS is exact, not an
524
+ * approximation.
525
+ *
526
+ * Ordering is `access_count DESC, id`. The `id` tiebreak is deliberate: the
527
+ * old JS sort broke ties on SQLite's scan order, which is arbitrary, so this
528
+ * is both cheaper and more deterministic.
529
+ */
530
+ async getTopAccessedTags(agentIds: string[], topN: number, stage: EngramStage = 'active'): Promise<string[][]> {
531
+ if (agentIds.length === 0 || topN <= 0) return [];
532
+ await this.readyPromise;
533
+ const rows: { tags: any; accessCount: number; id: string }[] = [];
534
+ for (const agentId of agentIds) {
535
+ const result = await this.q<any>(`
536
+ SELECT tags, access_count AS "accessCount", id FROM engrams
537
+ WHERE agent_id = $1 AND stage = $2 AND retracted = FALSE
538
+ ORDER BY access_count DESC, id
539
+ LIMIT $3
540
+ `, [agentId, stage, topN]);
541
+ rows.push(...result.rows);
542
+ }
543
+ if (agentIds.length > 1) {
544
+ rows.sort((a, b) => (b.accessCount - a.accessCount) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
545
+ }
546
+ return rows.slice(0, topN).map((r) => {
547
+ if (Array.isArray(r.tags)) return r.tags as string[];
548
+ try { return JSON.parse(r.tags) as string[]; } catch { return []; }
549
+ });
550
+ }
551
+
503
552
  async getWorkspaceAgentIds(agentId: string, workspace: string): Promise<string[]> {
504
553
  await this.readyPromise;
505
554
  try {
@@ -215,6 +215,10 @@ export class EngramStore {
215
215
  CREATE INDEX IF NOT EXISTS idx_engrams_stage ON engrams(agent_id, stage);
216
216
  CREATE INDEX IF NOT EXISTS idx_engrams_concept ON engrams(concept);
217
217
  CREATE INDEX IF NOT EXISTS idx_engrams_retracted ON engrams(agent_id, retracted);
218
+ -- Serves getTopAccessedTags: the recall path's pronoun branch needs the ten
219
+ -- most-accessed engrams, and without this it filters then sorts the whole
220
+ -- agent partition on every query containing "it", "this" or "that".
221
+ CREATE INDEX IF NOT EXISTS idx_engrams_access ON engrams(agent_id, stage, access_count DESC);
218
222
 
219
223
  CREATE TABLE IF NOT EXISTS associations (
220
224
  id TEXT PRIMARY KEY,
@@ -626,6 +630,53 @@ export class EngramStore {
626
630
  return (this.db.prepare(query).all(...params) as any[]).map(r => this.rowToEngram(r));
627
631
  }
628
632
 
633
+ /**
634
+ * Tags of the most-accessed engrams, bounded IN SQL.
635
+ *
636
+ * Exists because the recall path's pronoun branch used to call
637
+ * `getEngramsByAgents(agentIds, 'active')` — hydrating EVERY active engram
638
+ * into JS, embeddings and all — then sort by accessCount in memory, keep the
639
+ * top ten, and extract five tag words. Measured 2026-10-08 on an
640
+ * 11k-retrievable store: 245ms mean / 280ms median whenever it ran, which was
641
+ * 2.1% of real prompts (the words that trigger it include "it", "this",
642
+ * "that" and "there") against 0.2% of benchmark probes — so it cost real
643
+ * users ~62% of a recall and the benchmark never saw it. See
644
+ * docs/recall-latency.md.
645
+ *
646
+ * ONE QUERY PER AGENT, deliberately. `agent_id IN (...)` makes SQLite abandon
647
+ * the ordered index walk and sort the whole partition in a temp B-tree:
648
+ * measured on the 11k-retrievable store, the IN form costs 86ms against
649
+ * **0.03ms** for the equality form, because only the latter can walk
650
+ * `idx_engrams_access` and stop at LIMIT. Agent-scoped recall passes a single
651
+ * agent and hits the fast path either way; workspace-scoped recall would have
652
+ * paid the 86ms on every pronoun query. The global top N is always contained
653
+ * in the union of the per-agent top Ns, so merging in JS is exact, not an
654
+ * approximation.
655
+ *
656
+ * Ordering is `access_count DESC, id`. The `id` tiebreak is deliberate: the
657
+ * old JS sort broke ties on SQLite's scan order, which is arbitrary, so this
658
+ * is both cheaper and more deterministic.
659
+ */
660
+ getTopAccessedTags(agentIds: string[], topN: number, stage: EngramStage = 'active'): string[][] {
661
+ if (agentIds.length === 0 || topN <= 0) return [];
662
+ const stmt = this.db.prepare(`
663
+ SELECT tags, access_count AS accessCount, id FROM engrams
664
+ WHERE agent_id = ? AND stage = ? AND retracted = 0
665
+ ORDER BY access_count DESC, id
666
+ LIMIT ?
667
+ `);
668
+ const rows: { tags: string; accessCount: number; id: string }[] = [];
669
+ for (const agentId of agentIds) {
670
+ rows.push(...(stmt.all(agentId, stage, topN) as { tags: string; accessCount: number; id: string }[]));
671
+ }
672
+ if (agentIds.length > 1) {
673
+ rows.sort((a, b) => (b.accessCount - a.accessCount) || (a.id < b.id ? -1 : a.id > b.id ? 1 : 0));
674
+ }
675
+ return rows.slice(0, topN).map(r => {
676
+ try { return JSON.parse(r.tags) as string[]; } catch { return []; }
677
+ });
678
+ }
679
+
629
680
  /**
630
681
  * BM25 search across multiple agents (workspace-scoped).
631
682
  */