@agentmemory/agentmemory 0.9.4 → 0.9.5

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/dist/index.mjs CHANGED
@@ -895,22 +895,38 @@ let imageEmbeddingProvider = null;
895
895
  function createImageEmbeddingProvider() {
896
896
  if (process.env["AGENTMEMORY_IMAGE_EMBEDDINGS"] !== "true") return null;
897
897
  if (imageEmbeddingProvider) return imageEmbeddingProvider;
898
- imageEmbeddingProvider = new ClipEmbeddingProvider();
898
+ imageEmbeddingProvider = withDimensionGuard(new ClipEmbeddingProvider());
899
899
  return imageEmbeddingProvider;
900
900
  }
901
901
  function createEmbeddingProvider() {
902
902
  const detected = detectEmbeddingProvider();
903
903
  if (!detected) return null;
904
904
  switch (detected) {
905
- case "gemini": return new GeminiEmbeddingProvider(getEnvVar("GEMINI_API_KEY"));
906
- case "openai": return new OpenAIEmbeddingProvider(getEnvVar("OPENAI_API_KEY"));
907
- case "voyage": return new VoyageEmbeddingProvider(getEnvVar("VOYAGE_API_KEY"));
908
- case "cohere": return new CohereEmbeddingProvider(getEnvVar("COHERE_API_KEY"));
909
- case "openrouter": return new OpenRouterEmbeddingProvider(getEnvVar("OPENROUTER_API_KEY"));
910
- case "local": return new LocalEmbeddingProvider();
905
+ case "gemini": return withDimensionGuard(new GeminiEmbeddingProvider(getEnvVar("GEMINI_API_KEY")));
906
+ case "openai": return withDimensionGuard(new OpenAIEmbeddingProvider(getEnvVar("OPENAI_API_KEY")));
907
+ case "voyage": return withDimensionGuard(new VoyageEmbeddingProvider(getEnvVar("VOYAGE_API_KEY")));
908
+ case "cohere": return withDimensionGuard(new CohereEmbeddingProvider(getEnvVar("COHERE_API_KEY")));
909
+ case "openrouter": return withDimensionGuard(new OpenRouterEmbeddingProvider(getEnvVar("OPENROUTER_API_KEY")));
910
+ case "local": return withDimensionGuard(new LocalEmbeddingProvider());
911
911
  default: return null;
912
912
  }
913
913
  }
914
+ function withDimensionGuard(provider) {
915
+ const expected = provider.dimensions;
916
+ const check = (v, where) => {
917
+ if (v.length !== expected) throw new Error(`Embedding dimension mismatch in ${provider.name}.${where}: expected ${expected}, got ${v.length}`);
918
+ return v;
919
+ };
920
+ const wrapped = Object.create(provider);
921
+ wrapped.embed = async (t) => check(await provider.embed(t), "embed");
922
+ wrapped.embedBatch = async (ts) => {
923
+ const out = await provider.embedBatch(ts);
924
+ out.forEach((v, i) => check(v, `embedBatch[${i}]`));
925
+ return out;
926
+ };
927
+ if (provider.embedImage) wrapped.embedImage = async (s) => check(await provider.embedImage(s), "embedImage");
928
+ return wrapped;
929
+ }
914
930
 
915
931
  //#endregion
916
932
  //#region src/providers/index.ts
@@ -1006,6 +1022,75 @@ var StateKV = class {
1006
1022
  }
1007
1023
  };
1008
1024
 
1025
+ //#endregion
1026
+ //#region src/state/schema.ts
1027
+ const KV = {
1028
+ sessions: "mem:sessions",
1029
+ observations: (sessionId) => `mem:obs:${sessionId}`,
1030
+ memories: "mem:memories",
1031
+ summaries: "mem:summaries",
1032
+ config: "mem:config",
1033
+ metrics: "mem:metrics",
1034
+ health: "mem:health",
1035
+ embeddings: (obsId) => `mem:emb:${obsId}`,
1036
+ bm25Index: "mem:index:bm25",
1037
+ relations: "mem:relations",
1038
+ profiles: "mem:profiles",
1039
+ claudeBridge: "mem:claude-bridge",
1040
+ graphNodes: "mem:graph:nodes",
1041
+ graphEdges: "mem:graph:edges",
1042
+ semantic: "mem:semantic",
1043
+ procedural: "mem:procedural",
1044
+ teamShared: (teamId) => `mem:team:${teamId}:shared`,
1045
+ teamUsers: (teamId, userId) => `mem:team:${teamId}:users:${userId}`,
1046
+ teamProfile: (teamId) => `mem:team:${teamId}:profile`,
1047
+ audit: "mem:audit",
1048
+ actions: "mem:actions",
1049
+ actionEdges: "mem:action-edges",
1050
+ leases: "mem:leases",
1051
+ routines: "mem:routines",
1052
+ routineRuns: "mem:routine-runs",
1053
+ signals: "mem:signals",
1054
+ checkpoints: "mem:checkpoints",
1055
+ mesh: "mem:mesh",
1056
+ sketches: "mem:sketches",
1057
+ facets: "mem:facets",
1058
+ sentinels: "mem:sentinels",
1059
+ crystals: "mem:crystals",
1060
+ lessons: "mem:lessons",
1061
+ insights: "mem:insights",
1062
+ graphEdgeHistory: "mem:graph:edge-history",
1063
+ enrichedChunks: (sessionId) => `mem:enriched:${sessionId}`,
1064
+ latentEmbeddings: (obsId) => `mem:latent:${obsId}`,
1065
+ retentionScores: "mem:retention",
1066
+ accessLog: "mem:access",
1067
+ imageRefs: "mem:image-refs",
1068
+ imageEmbeddings: "mem:image-embeddings",
1069
+ slots: "mem:slots",
1070
+ globalSlots: "mem:slots:global",
1071
+ state: "mem:state"
1072
+ };
1073
+ const STREAM = {
1074
+ name: "mem-live",
1075
+ group: (sessionId) => sessionId,
1076
+ viewerGroup: "viewer"
1077
+ };
1078
+ function generateId(prefix) {
1079
+ return `${prefix}_${Date.now().toString(36)}_${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
1080
+ }
1081
+ function fingerprintId(prefix, content) {
1082
+ return `${prefix}_${createHash("sha256").update(content).digest("hex").slice(0, 16)}`;
1083
+ }
1084
+ function jaccardSimilarity(a, b) {
1085
+ const setA = new Set(a.split(/\s+/).filter((t) => t.length > 2));
1086
+ const setB = new Set(b.split(/\s+/).filter((t) => t.length > 2));
1087
+ if (setA.size === 0 && setB.size === 0) return 1;
1088
+ if (setA.size === 0 || setB.size === 0) return 0;
1089
+ let intersection = 0;
1090
+ for (const word of setA) if (setB.has(word)) intersection++;
1091
+ return intersection / (setA.size + setB.size - intersection);
1092
+ }
1093
+
1009
1094
  //#endregion
1010
1095
  //#region src/state/vector-index.ts
1011
1096
  function float32ToBase64(arr) {
@@ -1069,6 +1154,22 @@ var VectorIndex = class VectorIndex {
1069
1154
  get size() {
1070
1155
  return this.vectors.size;
1071
1156
  }
1157
+ validateDimensions(expected) {
1158
+ const mismatches = [];
1159
+ const seenDimensions = /* @__PURE__ */ new Set();
1160
+ for (const [obsId, entry] of this.vectors) {
1161
+ const dim = entry.embedding.length;
1162
+ seenDimensions.add(dim);
1163
+ if (dim !== expected) mismatches.push({
1164
+ obsId,
1165
+ dim
1166
+ });
1167
+ }
1168
+ return {
1169
+ mismatches,
1170
+ seenDimensions
1171
+ };
1172
+ }
1072
1173
  clear() {
1073
1174
  this.vectors.clear();
1074
1175
  }
@@ -1112,75 +1213,6 @@ var VectorIndex = class VectorIndex {
1112
1213
  }
1113
1214
  };
1114
1215
 
1115
- //#endregion
1116
- //#region src/state/schema.ts
1117
- const KV = {
1118
- sessions: "mem:sessions",
1119
- observations: (sessionId) => `mem:obs:${sessionId}`,
1120
- memories: "mem:memories",
1121
- summaries: "mem:summaries",
1122
- config: "mem:config",
1123
- metrics: "mem:metrics",
1124
- health: "mem:health",
1125
- embeddings: (obsId) => `mem:emb:${obsId}`,
1126
- bm25Index: "mem:index:bm25",
1127
- relations: "mem:relations",
1128
- profiles: "mem:profiles",
1129
- claudeBridge: "mem:claude-bridge",
1130
- graphNodes: "mem:graph:nodes",
1131
- graphEdges: "mem:graph:edges",
1132
- semantic: "mem:semantic",
1133
- procedural: "mem:procedural",
1134
- teamShared: (teamId) => `mem:team:${teamId}:shared`,
1135
- teamUsers: (teamId, userId) => `mem:team:${teamId}:users:${userId}`,
1136
- teamProfile: (teamId) => `mem:team:${teamId}:profile`,
1137
- audit: "mem:audit",
1138
- actions: "mem:actions",
1139
- actionEdges: "mem:action-edges",
1140
- leases: "mem:leases",
1141
- routines: "mem:routines",
1142
- routineRuns: "mem:routine-runs",
1143
- signals: "mem:signals",
1144
- checkpoints: "mem:checkpoints",
1145
- mesh: "mem:mesh",
1146
- sketches: "mem:sketches",
1147
- facets: "mem:facets",
1148
- sentinels: "mem:sentinels",
1149
- crystals: "mem:crystals",
1150
- lessons: "mem:lessons",
1151
- insights: "mem:insights",
1152
- graphEdgeHistory: "mem:graph:edge-history",
1153
- enrichedChunks: (sessionId) => `mem:enriched:${sessionId}`,
1154
- latentEmbeddings: (obsId) => `mem:latent:${obsId}`,
1155
- retentionScores: "mem:retention",
1156
- accessLog: "mem:access",
1157
- imageRefs: "mem:image-refs",
1158
- imageEmbeddings: "mem:image-embeddings",
1159
- slots: "mem:slots",
1160
- globalSlots: "mem:slots:global",
1161
- state: "mem:state"
1162
- };
1163
- const STREAM = {
1164
- name: "mem-live",
1165
- group: (sessionId) => sessionId,
1166
- viewerGroup: "viewer"
1167
- };
1168
- function generateId(prefix) {
1169
- return `${prefix}_${Date.now().toString(36)}_${crypto.randomUUID().replace(/-/g, "").slice(0, 12)}`;
1170
- }
1171
- function fingerprintId(prefix, content) {
1172
- return `${prefix}_${createHash("sha256").update(content).digest("hex").slice(0, 16)}`;
1173
- }
1174
- function jaccardSimilarity(a, b) {
1175
- const setA = new Set(a.split(/\s+/).filter((t) => t.length > 2));
1176
- const setB = new Set(b.split(/\s+/).filter((t) => t.length > 2));
1177
- if (setA.size === 0 && setB.size === 0) return 1;
1178
- if (setA.size === 0 || setB.size === 0) return 0;
1179
- let intersection = 0;
1180
- for (const word of setA) if (setB.has(word)) intersection++;
1181
- return intersection / (setA.size + setB.size - intersection);
1182
- }
1183
-
1184
1216
  //#endregion
1185
1217
  //#region src/functions/graph-retrieval.ts
1186
1218
  function buildGraphContext(path) {
@@ -2052,6 +2084,9 @@ var SearchIndex = class SearchIndex {
2052
2084
  }
2053
2085
  this.sortedTerms = null;
2054
2086
  }
2087
+ has(id) {
2088
+ return this.entries.has(id);
2089
+ }
2055
2090
  search(query, limit = 20) {
2056
2091
  const rawTerms = this.tokenize(query.toLowerCase());
2057
2092
  if (rawTerms.length === 0) return [];
@@ -2472,6 +2507,20 @@ async function deleteAccessLog(kv, memoryId) {
2472
2507
 
2473
2508
  //#endregion
2474
2509
  //#region src/functions/search.ts
2510
+ function memoryAsIndexable$1(memory) {
2511
+ return {
2512
+ id: memory.id,
2513
+ sessionId: memory.sessionIds[0] ?? "memory",
2514
+ timestamp: memory.createdAt,
2515
+ type: "decision",
2516
+ title: memory.title,
2517
+ facts: [memory.content],
2518
+ narrative: memory.content,
2519
+ concepts: memory.concepts,
2520
+ files: memory.files,
2521
+ importance: memory.strength
2522
+ };
2523
+ }
2475
2524
  let index = null;
2476
2525
  function getSearchIndex() {
2477
2526
  if (!index) index = new SearchIndex();
@@ -2480,9 +2529,20 @@ function getSearchIndex() {
2480
2529
  async function rebuildIndex(kv) {
2481
2530
  const idx = getSearchIndex();
2482
2531
  idx.clear();
2483
- const sessions = await kv.list(KV.sessions);
2484
- if (!sessions.length) return 0;
2485
2532
  let count = 0;
2533
+ try {
2534
+ const memories = await kv.list(KV.memories);
2535
+ for (const memory of memories) {
2536
+ if (memory.isLatest === false) continue;
2537
+ if (!memory.title || !memory.content) continue;
2538
+ idx.add(memoryAsIndexable$1(memory));
2539
+ count++;
2540
+ }
2541
+ } catch (err) {
2542
+ logger.warn("rebuildIndex: failed to load memories", { error: err instanceof Error ? err.message : String(err) });
2543
+ }
2544
+ const sessions = await kv.list(KV.sessions);
2545
+ if (!sessions.length) return count;
2486
2546
  const obsPerSession = [];
2487
2547
  const failedSessions = [];
2488
2548
  for (let batch = 0; batch < sessions.length; batch += 10) {
@@ -4788,6 +4848,20 @@ function registerPatternsFunction(sdk, kv) {
4788
4848
 
4789
4849
  //#endregion
4790
4850
  //#region src/functions/remember.ts
4851
+ function memoryAsIndexable(memory) {
4852
+ return {
4853
+ id: memory.id,
4854
+ sessionId: memory.sessionIds[0] ?? "memory",
4855
+ timestamp: memory.createdAt,
4856
+ type: "decision",
4857
+ title: memory.title,
4858
+ facts: [memory.content],
4859
+ narrative: memory.content,
4860
+ concepts: memory.concepts,
4861
+ files: memory.files,
4862
+ importance: memory.strength
4863
+ };
4864
+ }
4791
4865
  function registerRememberFunction(sdk, kv) {
4792
4866
  sdk.registerFunction("mem::remember", async (data) => {
4793
4867
  if (!data.content || typeof data.content !== "string" || !data.content.trim()) return {
@@ -4853,6 +4927,14 @@ function registerRememberFunction(sdk, kv) {
4853
4927
  await kv.set(KV.memories, supersededMemory.id, supersededMemory);
4854
4928
  }
4855
4929
  await kv.set(KV.memories, memory.id, memory);
4930
+ try {
4931
+ getSearchIndex().add(memoryAsIndexable(memory));
4932
+ } catch (err) {
4933
+ logger.warn("Failed to index saved memory into BM25", {
4934
+ memId: memory.id,
4935
+ error: err instanceof Error ? err.message : String(err)
4936
+ });
4937
+ }
4856
4938
  if (supersededId) await sdk.trigger({
4857
4939
  function_id: "mem::cascade-update",
4858
4940
  payload: { supersededMemoryId: supersededId },
@@ -5650,7 +5732,7 @@ function registerAutoForgetFunction(sdk, kv) {
5650
5732
 
5651
5733
  //#endregion
5652
5734
  //#region src/version.ts
5653
- const VERSION = "0.9.4";
5735
+ const VERSION = "0.9.5";
5654
5736
 
5655
5737
  //#endregion
5656
5738
  //#region src/functions/export-import.ts
@@ -5772,7 +5854,8 @@ function registerExportImportFunction(sdk, kv) {
5772
5854
  "0.9.1",
5773
5855
  "0.9.2",
5774
5856
  "0.9.3",
5775
- "0.9.4"
5857
+ "0.9.4",
5858
+ "0.9.5"
5776
5859
  ]).has(importData.version)) return {
5777
5860
  success: false,
5778
5861
  error: `Unsupported export version: ${importData.version}`
@@ -19327,6 +19410,7 @@ async function main() {
19327
19410
  console.log(`[agentmemory] Streams: ws://localhost:${config.streamsPort}`);
19328
19411
  const sdk = registerWorker(config.engineUrl, {
19329
19412
  workerName: "agentmemory",
19413
+ invocationTimeoutMs: 18e4,
19330
19414
  otel: {
19331
19415
  serviceName: OTEL_CONFIG.serviceName,
19332
19416
  serviceVersion: OTEL_CONFIG.serviceVersion,
@@ -19435,8 +19519,20 @@ async function main() {
19435
19519
  console.log(`[agentmemory] Loaded persisted BM25 index (${bm25Index.size} docs)`);
19436
19520
  }
19437
19521
  if (loaded?.vector && vectorIndex && loaded.vector.size > 0) {
19438
- vectorIndex.restoreFrom(loaded.vector);
19439
- console.log(`[agentmemory] Loaded persisted vector index (${vectorIndex.size} vectors)`);
19522
+ const activeDim = embeddingProvider?.dimensions ?? 0;
19523
+ const { mismatches, seenDimensions } = activeDim > 0 ? loaded.vector.validateDimensions(activeDim) : {
19524
+ mismatches: [],
19525
+ seenDimensions: /* @__PURE__ */ new Set()
19526
+ };
19527
+ if (mismatches.length > 0) {
19528
+ const sample = mismatches.slice(0, 5).map((m) => `${m.obsId} (dim=${m.dim})`).join(", ");
19529
+ const distinct = Array.from(seenDimensions).sort((a, b) => a - b).join(", ");
19530
+ if (process.env["AGENTMEMORY_DROP_STALE_INDEX"] === "true") console.warn(`[agentmemory] Persisted vector index has ${mismatches.length} of ${loaded.vector.size} vectors with the wrong dimension. Active provider (${embeddingProvider?.name}) declares ${activeDim}; dimensions seen on disk: ${distinct}. AGENTMEMORY_DROP_STALE_INDEX=true is set — discarding the persisted vectors. Live observations will rebuild the index over time.`);
19531
+ else throw new Error(`[agentmemory] Refusing to start: persisted vector index has ${mismatches.length} of ${loaded.vector.size} vectors with the wrong dimension. Active provider (${embeddingProvider?.name}) declares ${activeDim}; dimensions seen on disk: ${distinct}. First mismatched obsIds: ${sample}. Loading would silently corrupt search (cross-dimension cosine returns 0). Choose one:\n - Re-embed the existing index against the new provider, then start.\n - Set AGENTMEMORY_DROP_STALE_INDEX=true to discard the persisted vectors and rebuild from live observations.\n - Switch the embedding provider back to the one that wrote the index.`);
19532
+ } else {
19533
+ vectorIndex.restoreFrom(loaded.vector);
19534
+ console.log(`[agentmemory] Loaded persisted vector index (${vectorIndex.size} vectors)`);
19535
+ }
19440
19536
  }
19441
19537
  if (bm25Index.size === 0) {
19442
19538
  const indexCount = await rebuildIndex(kv).catch((err) => {
@@ -19444,9 +19540,36 @@ async function main() {
19444
19540
  return 0;
19445
19541
  });
19446
19542
  if (indexCount > 0) {
19447
- console.log(`[agentmemory] Search index rebuilt: ${indexCount} observations`);
19543
+ console.log(`[agentmemory] Search index rebuilt: ${indexCount} entries`);
19544
+ indexPersistence.scheduleSave();
19545
+ }
19546
+ } else try {
19547
+ const memories = await kv.list(KV.memories);
19548
+ let backfilled = 0;
19549
+ for (const memory of memories) {
19550
+ if (memory.isLatest === false) continue;
19551
+ if (!memory.title || !memory.content) continue;
19552
+ if (bm25Index.has(memory.id)) continue;
19553
+ bm25Index.add({
19554
+ id: memory.id,
19555
+ sessionId: memory.sessionIds[0] ?? "memory",
19556
+ timestamp: memory.createdAt,
19557
+ type: "decision",
19558
+ title: memory.title,
19559
+ facts: [memory.content],
19560
+ narrative: memory.content,
19561
+ concepts: memory.concepts,
19562
+ files: memory.files,
19563
+ importance: memory.strength
19564
+ });
19565
+ backfilled++;
19566
+ }
19567
+ if (backfilled > 0) {
19568
+ console.log(`[agentmemory] Backfilled ${backfilled} memories into BM25 (legacy gap before #257)`);
19448
19569
  indexPersistence.scheduleSave();
19449
19570
  }
19571
+ } catch (err) {
19572
+ console.warn(`[agentmemory] Failed to backfill memories into BM25:`, err);
19450
19573
  }
19451
19574
  console.log(`[agentmemory] Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
19452
19575
  console.log(`[agentmemory] Endpoints: 107 REST + ${getAllTools().length} MCP tools + 6 MCP resources + 3 MCP prompts`);