@agentmemory/agentmemory 0.9.4 → 0.9.6

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 (38) hide show
  1. package/README.md +108 -50
  2. package/dist/cli.mjs +60 -20
  3. package/dist/cli.mjs.map +1 -1
  4. package/dist/docker-compose.yml +10 -1
  5. package/dist/hooks/session-end.mjs +4 -4
  6. package/dist/hooks/session-end.mjs.map +1 -1
  7. package/dist/hooks/session-start.mjs +23 -10
  8. package/dist/hooks/session-start.mjs.map +1 -1
  9. package/dist/hooks/stop.mjs +1 -1
  10. package/dist/hooks/stop.mjs.map +1 -1
  11. package/dist/hooks/subagent-start.mjs +17 -18
  12. package/dist/hooks/subagent-start.mjs.map +1 -1
  13. package/dist/image-refs-CZVd2z6E.mjs +3 -0
  14. package/dist/{image-store-Cn9eD-7D.mjs → image-store-CF4gfkLr.mjs} +1 -1
  15. package/dist/index.mjs +205 -82
  16. package/dist/index.mjs.map +1 -1
  17. package/dist/{src-uDy2jLO-.mjs → src-C7vGxttN.mjs} +147 -24
  18. package/dist/src-C7vGxttN.mjs.map +1 -0
  19. package/dist/{standalone-CqqEcfNR.mjs → standalone-DnSJzyXL.mjs} +36 -4
  20. package/dist/{standalone-CqqEcfNR.mjs.map → standalone-DnSJzyXL.mjs.map} +1 -1
  21. package/dist/standalone.d.mts.map +1 -1
  22. package/dist/standalone.mjs +35 -3
  23. package/dist/standalone.mjs.map +1 -1
  24. package/dist/{tools-registry-Co8VIL4t.mjs → tools-registry-CKMeHaPN.mjs} +2 -2
  25. package/dist/{tools-registry-Co8VIL4t.mjs.map → tools-registry-CKMeHaPN.mjs.map} +1 -1
  26. package/docker-compose.yml +10 -1
  27. package/package.json +1 -1
  28. package/plugin/.claude-plugin/plugin.json +1 -1
  29. package/plugin/scripts/session-end.mjs +4 -4
  30. package/plugin/scripts/session-end.mjs.map +1 -1
  31. package/plugin/scripts/session-start.mjs +23 -10
  32. package/plugin/scripts/session-start.mjs.map +1 -1
  33. package/plugin/scripts/stop.mjs +1 -1
  34. package/plugin/scripts/stop.mjs.map +1 -1
  35. package/plugin/scripts/subagent-start.mjs +17 -18
  36. package/plugin/scripts/subagent-start.mjs.map +1 -1
  37. package/dist/image-refs-BfT7XAa-.mjs +0 -3
  38. package/dist/src-uDy2jLO-.mjs.map +0 -1
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
  }
@@ -1113,72 +1214,20 @@ var VectorIndex = class VectorIndex {
1113
1214
  };
1114
1215
 
1115
1216
  //#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);
1217
+ //#region src/state/memory-utils.ts
1218
+ function memoryToObservation(memory) {
1219
+ return {
1220
+ id: memory.id,
1221
+ sessionId: memory.sessionIds[0] ?? "memory",
1222
+ timestamp: memory.createdAt,
1223
+ type: "decision",
1224
+ title: memory.title,
1225
+ facts: [memory.content],
1226
+ narrative: memory.content,
1227
+ concepts: memory.concepts,
1228
+ files: memory.files,
1229
+ importance: memory.strength
1230
+ };
1182
1231
  }
1183
1232
 
1184
1233
  //#endregion
@@ -1745,7 +1794,12 @@ var HybridSearch = class {
1745
1794
  }
1746
1795
  async enrichResults(results, limit) {
1747
1796
  const sliced = results.slice(0, limit);
1748
- const observations = await Promise.all(sliced.map((r) => this.kv.get(KV.observations(r.sessionId), r.obsId).catch(() => null)));
1797
+ const observations = await Promise.all(sliced.map(async (r) => {
1798
+ const obs = await this.kv.get(KV.observations(r.sessionId), r.obsId).catch(() => null);
1799
+ if (obs) return obs;
1800
+ const mem = await this.kv.get(KV.memories, r.obsId).catch(() => null);
1801
+ return mem ? memoryToObservation(mem) : null;
1802
+ }));
1749
1803
  const enriched = [];
1750
1804
  for (let i = 0; i < sliced.length; i++) {
1751
1805
  const obs = observations[i];
@@ -2052,6 +2106,9 @@ var SearchIndex = class SearchIndex {
2052
2106
  }
2053
2107
  this.sortedTerms = null;
2054
2108
  }
2109
+ has(id) {
2110
+ return this.entries.has(id);
2111
+ }
2055
2112
  search(query, limit = 20) {
2056
2113
  const rawTerms = this.tokenize(query.toLowerCase());
2057
2114
  if (rawTerms.length === 0) return [];
@@ -2480,9 +2537,20 @@ function getSearchIndex() {
2480
2537
  async function rebuildIndex(kv) {
2481
2538
  const idx = getSearchIndex();
2482
2539
  idx.clear();
2483
- const sessions = await kv.list(KV.sessions);
2484
- if (!sessions.length) return 0;
2485
2540
  let count = 0;
2541
+ try {
2542
+ const memories = await kv.list(KV.memories);
2543
+ for (const memory of memories) {
2544
+ if (memory.isLatest === false) continue;
2545
+ if (!memory.title || !memory.content) continue;
2546
+ idx.add(memoryToObservation(memory));
2547
+ count++;
2548
+ }
2549
+ } catch (err) {
2550
+ logger.warn("rebuildIndex: failed to load memories", { error: err instanceof Error ? err.message : String(err) });
2551
+ }
2552
+ const sessions = await kv.list(KV.sessions);
2553
+ if (!sessions.length) return count;
2486
2554
  const obsPerSession = [];
2487
2555
  const failedSessions = [];
2488
2556
  for (let batch = 0; batch < sessions.length; batch += 10) {
@@ -2553,7 +2621,12 @@ function registerSearchFunction(sdk, kv) {
2553
2621
  }
2554
2622
  candidates.push(r);
2555
2623
  }
2556
- const obsResults = await Promise.all(candidates.map((r) => kv.get(KV.observations(r.sessionId), r.obsId)));
2624
+ const obsResults = await Promise.all(candidates.map(async (r) => {
2625
+ const obs = await kv.get(KV.observations(r.sessionId), r.obsId).catch(() => null);
2626
+ if (obs) return obs;
2627
+ const mem = await kv.get(KV.memories, r.obsId).catch(() => null);
2628
+ return mem ? memoryToObservation(mem) : null;
2629
+ }));
2557
2630
  const enriched = [];
2558
2631
  for (let i = 0; i < candidates.length; i++) {
2559
2632
  const obs = obsResults[i];
@@ -4853,6 +4926,14 @@ function registerRememberFunction(sdk, kv) {
4853
4926
  await kv.set(KV.memories, supersededMemory.id, supersededMemory);
4854
4927
  }
4855
4928
  await kv.set(KV.memories, memory.id, memory);
4929
+ try {
4930
+ getSearchIndex().add(memoryToObservation(memory));
4931
+ } catch (err) {
4932
+ logger.warn("Failed to index saved memory into BM25", {
4933
+ memId: memory.id,
4934
+ error: err instanceof Error ? err.message : String(err)
4935
+ });
4936
+ }
4856
4937
  if (supersededId) await sdk.trigger({
4857
4938
  function_id: "mem::cascade-update",
4858
4939
  payload: { supersededMemoryId: supersededId },
@@ -5650,7 +5731,7 @@ function registerAutoForgetFunction(sdk, kv) {
5650
5731
 
5651
5732
  //#endregion
5652
5733
  //#region src/version.ts
5653
- const VERSION = "0.9.4";
5734
+ const VERSION = "0.9.6";
5654
5735
 
5655
5736
  //#endregion
5656
5737
  //#region src/functions/export-import.ts
@@ -5772,7 +5853,9 @@ function registerExportImportFunction(sdk, kv) {
5772
5853
  "0.9.1",
5773
5854
  "0.9.2",
5774
5855
  "0.9.3",
5775
- "0.9.4"
5856
+ "0.9.4",
5857
+ "0.9.5",
5858
+ "0.9.6"
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`);