agent-working-memory 0.9.1 → 0.10.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.
@@ -493,12 +493,21 @@ export async function computeNoveltyWithMatch(
493
493
  : typeof created === 'number' ? created : Date.parse(created);
494
494
  return Number.isFinite(createdMs) && createdMs >= cutoffMs;
495
495
  };
496
+ // Novelty PENALTY: an exact-concept recent match on EITHER channel (including cross-agent workspace
497
+ // results) is a near-duplicate for novelty-scoring purposes.
496
498
  const exactConceptRecent = allBm25.some(r => checkExactConcept(r.engram))
497
499
  || (topCosine ? checkExactConcept(topCosine.engram) : false);
500
+ // REINFORCE redirect: prefer an exact same-concept match as the matched engram so a true duplicate
501
+ // REINFORCES it (R1) instead of creating a new one even when a different-concept engram out-scores it.
502
+ // CRUCIALLY, only consider AGENT-SCOPED candidates — bm25Results and the cosine channel are scoped to
503
+ // this agent, but `wsResults` are OTHER agents' engrams; redirecting to one would make the write
504
+ // pipeline reinforce/supersede a foreign agent's memory (cross-agent contamination).
505
+ const exactMatch = bm25Results.find(r => checkExactConcept(r.engram))?.engram
506
+ ?? (topCosine && checkExactConcept(topCosine.engram) ? topCosine.engram : undefined);
498
507
  const conceptPenalty = exactConceptRecent ? 0.3 : 0;
499
508
 
500
509
  const novelty = Math.max(0.05, Math.min(0.95, baseNovelty - conceptPenalty));
501
- return { novelty, matchedEngramId: combinedTop.engramId, matchScore: topScore };
510
+ return { novelty, matchedEngramId: exactMatch?.id ?? combinedTop.engramId, matchScore: topScore };
502
511
  } catch {
503
512
  return { novelty: 0.8, matchedEngramId: null, matchScore: 0 };
504
513
  }
@@ -220,7 +220,11 @@ export async function performWrite(
220
220
  let result: WriteResult | null = null;
221
221
  if (enableReinforcement && noveltyResult.matchedEngramId) {
222
222
  const matched = await store.getEngram(noveltyResult.matchedEngramId);
223
- if (matched) {
223
+ // Agent-scope guard: NEVER reinforce/supersede an engram that belongs to a DIFFERENT agent. A
224
+ // workspace/hive recall can surface another agent's same-concept engram as the match; mutating it
225
+ // (merge content, re-embed, bump confidence, or supersede) would corrupt that agent's memory. A
226
+ // cross-agent match falls through to createNewEngram so this agent gets its own copy.
227
+ if (matched && matched.agentId === input.agentId) {
224
228
  const newConcept = (input.concept ?? '').toLowerCase().trim();
225
229
  const matchedConcept = (matched.concept ?? '').toLowerCase().trim();
226
230
  const sameConcept = newConcept === matchedConcept && newConcept.length > 0;
@@ -39,7 +39,7 @@
39
39
  import { existsSync, statSync } from 'node:fs';
40
40
  import type { IEngramStore } from './store.js';
41
41
 
42
- export type StoreBackend = 'sqlite' | 'pglite';
42
+ export type StoreBackend = 'sqlite' | 'pglite' | 'postgres';
43
43
 
44
44
  /**
45
45
  * Auto-detect the backend from on-disk state. Used when `AWM_STORE_BACKEND`
@@ -71,6 +71,7 @@ export function getConfiguredBackend(): StoreBackend {
71
71
  const normalized = raw.toLowerCase();
72
72
  if (normalized === 'pglite') return 'pglite';
73
73
  if (normalized === 'sqlite') return 'sqlite';
74
+ if (normalized === 'postgres') return 'postgres';
74
75
  console.warn(`Unknown AWM_STORE_BACKEND=${raw}; falling back to sqlite`);
75
76
  return 'sqlite';
76
77
  }
@@ -80,8 +81,11 @@ export function getConfiguredBackend(): StoreBackend {
80
81
  }
81
82
 
82
83
  export function getConfiguredPath(): string {
84
+ const backend = getConfiguredBackend();
85
+ // Postgres uses a connection URL, not an on-disk path.
86
+ if (backend === 'postgres') return process.env.AWM_DATABASE_URL ?? 'postgres://localhost:5432/awm';
83
87
  if (process.env.AWM_DB_PATH) return process.env.AWM_DB_PATH;
84
- return getConfiguredBackend() === 'pglite' ? 'memory-pglite' : 'memory.db';
88
+ return backend === 'pglite' ? 'memory-pglite' : 'memory.db';
85
89
  }
86
90
 
87
91
  /**
@@ -132,7 +136,15 @@ export async function openStore(): Promise<{
132
136
  const backend = getConfiguredBackend();
133
137
  const path = getConfiguredPath();
134
138
 
135
- warnIfBackendDisagreesWithDisk(backend, path);
139
+ // The on-disk mismatch warning only applies to file/dir backends.
140
+ if (backend !== 'postgres') warnIfBackendDisagreesWithDisk(backend, path);
141
+
142
+ if (backend === 'postgres') {
143
+ const { PostgresEngramStore } = await import('./postgres.js');
144
+ const store = new PostgresEngramStore(path);
145
+ await store.ready();
146
+ return { store: store as unknown as IEngramStore, backend, path };
147
+ }
136
148
 
137
149
  if (backend === 'pglite') {
138
150
  const { PGliteEngramStore } = await import('./pglite.js');
@@ -141,6 +141,8 @@ function extractTagValue(tags: string[], prefix: string): string | null {
141
141
  return null;
142
142
  }
143
143
 
144
+ let warnedNoCoordPglite = false; // one-time warning when workspace/hive recall falls back (coord_agents absent)
145
+
144
146
  export class PGliteEngramStore {
145
147
  private db!: PGlite;
146
148
  private readyPromise: Promise<void>;
@@ -395,6 +397,13 @@ export class PGliteEngramStore {
395
397
  if (!names.includes(agentId)) names.push(agentId);
396
398
  return names;
397
399
  } catch {
400
+ // coord_agents isn't provisioned on PGlite — workspace/hive coordination is currently SQLite-only.
401
+ // Warn ONCE so this degrades VISIBLY (recall scoped to self) instead of silently.
402
+ if (!warnedNoCoordPglite) {
403
+ warnedNoCoordPglite = true;
404
+ console.warn('[awm:pglite] workspace/hive coordination is not available on the PGlite backend ' +
405
+ '(coord_agents not provisioned) — recall is scoped to THIS agent only. Hive coordination is SQLite-only for now.');
406
+ }
398
407
  return [agentId];
399
408
  }
400
409
  }