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.
- package/AGENTS.md +36 -0
- package/README.md +23 -12
- package/dist/adapters/common.d.ts +1 -1
- package/dist/adapters/common.d.ts.map +1 -1
- package/dist/adapters/common.js +33 -20
- package/dist/adapters/common.js.map +1 -1
- package/dist/core/embeddings.d.ts +1 -1
- package/dist/core/embeddings.js +1 -1
- package/dist/core/entity-extract.js +1 -1
- package/dist/core/query-expander.d.ts +4 -2
- package/dist/core/query-expander.d.ts.map +1 -1
- package/dist/core/query-expander.js +4 -2
- package/dist/core/query-expander.js.map +1 -1
- package/dist/core/recall-config.d.ts +1 -1
- package/dist/core/recall-config.d.ts.map +1 -1
- package/dist/core/recall-config.js +6 -1
- package/dist/core/recall-config.js.map +1 -1
- package/dist/core/recall-telemetry.d.ts +15 -0
- package/dist/core/recall-telemetry.d.ts.map +1 -0
- package/dist/core/recall-telemetry.js +108 -0
- package/dist/core/recall-telemetry.js.map +1 -0
- package/dist/core/rerank-window.d.ts +5 -4
- package/dist/core/rerank-window.d.ts.map +1 -1
- package/dist/core/rerank-window.js +5 -4
- package/dist/core/rerank-window.js.map +1 -1
- package/dist/core/reranker.d.ts +2 -1
- package/dist/core/reranker.d.ts.map +1 -1
- package/dist/core/reranker.js +23 -3
- package/dist/core/reranker.js.map +1 -1
- package/dist/core/salience.js +1 -1
- package/dist/core/salience.js.map +1 -1
- package/dist/core/token-budget.d.ts +2 -2
- package/dist/core/token-budget.js +2 -2
- package/dist/core/whoami.d.ts +1 -1
- package/dist/engine/activation.d.ts +30 -13
- package/dist/engine/activation.d.ts.map +1 -1
- package/dist/engine/activation.js +159 -50
- package/dist/engine/activation.js.map +1 -1
- package/dist/index.js +1 -1
- package/dist/index.js.map +1 -1
- package/dist/mcp.js +98 -98
- package/dist/mcp.js.map +1 -1
- package/dist/storage/pglite.d.ts +28 -0
- package/dist/storage/pglite.d.ts.map +1 -1
- package/dist/storage/pglite.js +55 -0
- package/dist/storage/pglite.js.map +1 -1
- package/dist/storage/postgres.d.ts +28 -0
- package/dist/storage/postgres.d.ts.map +1 -1
- package/dist/storage/postgres.js +55 -0
- package/dist/storage/postgres.js.map +1 -1
- package/dist/storage/sqlite.d.ts +28 -0
- package/dist/storage/sqlite.d.ts.map +1 -1
- package/dist/storage/sqlite.js +56 -0
- package/dist/storage/sqlite.js.map +1 -1
- package/llms.txt +437 -0
- package/package.json +7 -1
- package/plugin/.claude-plugin/plugin.json +1 -1
- package/plugin/README.md +1 -1
- package/plugin/bin/awm-mcp-launcher.cjs +2 -2
- package/plugin/skills/awm-memory/SKILL.md +33 -20
- package/src/adapters/common.ts +33 -20
- package/src/core/embeddings.ts +1 -1
- package/src/core/entity-extract.ts +1 -1
- package/src/core/query-expander.ts +4 -2
- package/src/core/recall-config.ts +6 -1
- package/src/core/recall-telemetry.ts +131 -0
- package/src/core/rerank-window.ts +159 -158
- package/src/core/reranker.ts +24 -3
- package/src/core/salience.ts +1 -1
- package/src/core/token-budget.ts +160 -160
- package/src/core/whoami.ts +120 -120
- package/src/engine/activation.ts +159 -51
- package/src/index.ts +1 -1
- package/src/mcp.ts +1475 -1475
- package/src/storage/pglite.ts +49 -0
- package/src/storage/postgres.ts +49 -0
- package/src/storage/sqlite.ts +51 -0
package/src/storage/pglite.ts
CHANGED
|
@@ -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 {
|
package/src/storage/postgres.ts
CHANGED
|
@@ -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 {
|
package/src/storage/sqlite.ts
CHANGED
|
@@ -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
|
*/
|