openclaw-amem 2.0.1 → 2.1.1

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/README.md CHANGED
@@ -57,6 +57,17 @@ pnpm --filter openclaw-amem build
57
57
  openclaw plugins install --link ./packages/openclaw-amem
58
58
  ```
59
59
 
60
+ Updating:
61
+
62
+ ```bash
63
+ openclaw plugins update openclaw-amem
64
+ openclaw gateway restart
65
+ ```
66
+
67
+ An update rebuilds `node_modules`, and the model cache lives there by default — so
68
+ it re-downloads 2.27 GB unless `AMEM_MODEL_CACHE` points somewhere outside the
69
+ plugin directory. Set that once and updates cost nothing.
70
+
60
71
  ### 2. Configure `~/.openclaw/openclaw.json`
61
72
 
62
73
  Add `openclaw-amem` to your allowed plugins and hook it into the `memory` slot:
@@ -89,10 +100,11 @@ Add `openclaw-amem` to your allowed plugins and hook it into the `memory` slot:
89
100
  openclaw gateway restart
90
101
  ```
91
102
 
92
- First run downloads the embedding model (`bge-m3`, 1.08 GB) and caches it. Later
93
- restarts are instant. Want something smaller? `AMEM_EMBED_MODEL=onnx-community/gte-multilingual-base`
94
- is about a third the size and reads text just as long — set it **before** you have
95
- memories, since changing it afterwards means a [migration](https://amem.owo.lc/reference/embedding-models#changing-the-model-on-a-store-you-already-have).
103
+ First run downloads the embedding model (`bge-m3`, 2.27 GB) and caches it. Later
104
+ restarts are instant. If that is too much, the only smaller model worth setting is
105
+ `AMEM_EMBED_MODEL=Xenova/bge-small-zh-v1.5` 25 MB, Chinese only, and it caps at
106
+ 512 tokens. Set it **before** you have memories, since changing it afterwards means
107
+ a [migration](https://amem.owo.lc/reference/embedding-models#changing-the-model-on-a-store-you-already-have).
96
108
 
97
109
  Upgrading from 1.x downloads nothing. Your existing memories keep the model that
98
110
  built them, and the plugin says so at startup along with the one command that
package/dist/index.js CHANGED
@@ -76,9 +76,28 @@ function getEmbeddingDevice() {
76
76
  return process.env.AMEM_EMBED_DEVICE?.trim() || void 0;
77
77
  }
78
78
  function getEmbeddingDtype() {
79
- const explicit = process.env.AMEM_EMBED_DTYPE?.trim();
80
- if (explicit) return explicit;
81
- return getEmbeddingModel() === DEFAULT_EMBEDDING_MODEL ? DEFAULT_MODEL_DTYPE : void 0;
79
+ return process.env.AMEM_EMBED_DTYPE?.trim() || void 0;
80
+ }
81
+ function applyModelPaths(env) {
82
+ const cache = process.env.AMEM_MODEL_CACHE?.trim();
83
+ if (cache) env.cacheDir = cache;
84
+ const local = process.env.AMEM_MODEL_DIR?.trim();
85
+ if (local) {
86
+ env.localModelPath = local;
87
+ env.allowLocalModels = true;
88
+ }
89
+ }
90
+ function makeProgressReporter() {
91
+ const lastPct = /* @__PURE__ */ new Map();
92
+ return (e) => {
93
+ if (e.status !== "progress" || !e.file || typeof e.progress !== "number") return;
94
+ if (!e.file.endsWith(".onnx") && !e.file.endsWith(".onnx_data")) return;
95
+ const pct = Math.floor(e.progress / 10) * 10;
96
+ if (lastPct.get(e.file) === pct) return;
97
+ lastPct.set(e.file, pct);
98
+ const size = e.total ? ` of ${(e.total / 1e9).toFixed(2)} GB` : "";
99
+ console.log(`[amem] downloading ${e.file}: ${pct}%${size}`);
100
+ };
82
101
  }
83
102
  function extractorKey() {
84
103
  return `${getEmbeddingModel()}|${getEmbeddingDevice() ?? ""}|${getEmbeddingDtype() ?? ""}`;
@@ -89,11 +108,13 @@ async function getExtractor() {
89
108
  if (!pipeline) {
90
109
  const mod = await import("@huggingface/transformers");
91
110
  pipeline = mod.pipeline;
111
+ applyModelPaths(mod.env);
92
112
  }
93
113
  const device = getEmbeddingDevice();
94
114
  const dtype = getEmbeddingDtype();
95
115
  extractor = await pipeline("feature-extraction", getEmbeddingModel(), {
96
116
  revision: "main",
117
+ progress_callback: makeProgressReporter(),
97
118
  // Omitted entirely when unset, so an unconfigured install gets exactly the
98
119
  // library defaults it got before these existed.
99
120
  ...device ? { device } : {},
@@ -167,7 +188,7 @@ function cosineSimilarity(a, b) {
167
188
  for (let i = 0; i < a.length; i++) dot += a[i] * b[i];
168
189
  return dot;
169
190
  }
170
- var pipeline, extractor, loadedKey, cachedDim, DEFAULT_EMBEDDING_MODEL, LEGACY_DEFAULT_EMBEDDING_MODEL, LEGACY_DEFAULT_DIM, DEFAULT_MODEL_DTYPE, pinnedModel, CLS_POOLED_MODELS;
191
+ var pipeline, extractor, loadedKey, cachedDim, DEFAULT_EMBEDDING_MODEL, LEGACY_DEFAULT_EMBEDDING_MODEL, LEGACY_DEFAULT_DIM, pinnedModel, CLS_POOLED_MODELS;
171
192
  var init_embedding = __esm({
172
193
  "../amem-core/src/embedding.ts"() {
173
194
  "use strict";
@@ -178,7 +199,6 @@ var init_embedding = __esm({
178
199
  DEFAULT_EMBEDDING_MODEL = "Xenova/bge-m3";
179
200
  LEGACY_DEFAULT_EMBEDDING_MODEL = "Xenova/paraphrase-multilingual-MiniLM-L12-v2";
180
201
  LEGACY_DEFAULT_DIM = 384;
181
- DEFAULT_MODEL_DTYPE = "fp16";
182
202
  pinnedModel = null;
183
203
  CLS_POOLED_MODELS = /* @__PURE__ */ new Set([
184
204
  "bge-m3",
@@ -377,6 +397,10 @@ async function scrollIdsRaw(collection, limit = 1e4) {
377
397
  async function deleteCollectionRaw(collection) {
378
398
  await qdrant("DELETE", `/collections/${collection}`);
379
399
  }
400
+ async function snapshotCollectionRaw(collection) {
401
+ const r = await qdrant("POST", `/collections/${collection}/snapshots`);
402
+ return { name: r.name, size: r.size ?? 0 };
403
+ }
380
404
  async function resolveAliasRaw(alias) {
381
405
  try {
382
406
  const res = await qdrant("GET", `/aliases`);
@@ -1918,7 +1942,12 @@ async function searchMemory(query, topK = 5, agentId = "main", opts) {
1918
1942
  keywords: note.keywords,
1919
1943
  links: note.links,
1920
1944
  timestamp: note.timestamp,
1921
- similarity: embSimMap.get(id) ?? bfsSimMap.get(id) ?? 0,
1945
+ // Neither map covers a note that got here on BM25 alone: it was never in
1946
+ // the dense results, and it was not expanded into. That is a real cosine
1947
+ // nobody had measured, not a zero — and reporting 0 made a lexical match
1948
+ // look like the least relevant row in the list. Both vectors are already
1949
+ // in hand, so measuring it is one dot product.
1950
+ similarity: embSimMap.get(id) ?? bfsSimMap.get(id) ?? cosineSimilarity(queryEmbedding, note.embedding),
1922
1951
  rrf: rrfMap.get(id) ?? 0,
1923
1952
  via,
1924
1953
  topics: note.topics ?? [],
@@ -2559,6 +2588,7 @@ async function switchToMigrated(opts) {
2559
2588
  const { name, to } = opts;
2560
2589
  const log = opts.logger?.info ?? ((m) => console.log(m));
2561
2590
  if (name === to) throw new Error(`switch: "${name}" and "${to}" are the same collection`);
2591
+ let snap;
2562
2592
  const already = await resolveAliasRaw(name);
2563
2593
  if (already === to) {
2564
2594
  log(`[switch] "${name}" already points at "${to}" \u2014 nothing to do`);
@@ -2574,6 +2604,10 @@ async function switchToMigrated(opts) {
2574
2604
  );
2575
2605
  }
2576
2606
  log(`[switch] verified ${targetCount} in "${to}" against ${sourceCount} in "${name}"`);
2607
+ if (opts.snapshot !== false) {
2608
+ snap = await snapshotCollectionRaw(name);
2609
+ log(`[switch] snapshotted "${name}" \u2192 ${snap.name} (${(snap.size / 1e6).toFixed(0)} MB)`);
2610
+ }
2577
2611
  await deleteCollectionRaw(name);
2578
2612
  log(`[switch] dropped "${name}"`);
2579
2613
  await createAliasRaw(name, to);
@@ -2581,7 +2615,7 @@ async function switchToMigrated(opts) {
2581
2615
  await setAliasRaw(name, to);
2582
2616
  }
2583
2617
  log(`[switch] "${name}" now resolves to "${to}"`);
2584
- return { name, to, moved: targetCount };
2618
+ return { name, to, moved: targetCount, snapshot: snap };
2585
2619
  }
2586
2620
  var init_migrate = __esm({
2587
2621
  "../amem-core/src/migrate.ts"() {
@@ -3,7 +3,7 @@
3
3
  "name": "amem",
4
4
  "description": "Catches memories that contradict each other. Notes rewrite themselves as new ones arrive, link into a graph, and stay separated per agent and per person. Runs on a small model. Requires a local Qdrant. 中文走 jieba 分词,提示词有中文版。",
5
5
  "icon": "https://amem.owo.lc/logo.png",
6
- "version": "2.0.1",
6
+ "version": "2.1.1",
7
7
  "kind": "memory",
8
8
  "contracts": {
9
9
  "tools": [
@@ -35,6 +35,8 @@
35
35
  "AMEM_EMBED_POOLING",
36
36
  "AMEM_EMBED_DEVICE",
37
37
  "AMEM_EMBED_DTYPE",
38
+ "AMEM_MODEL_CACHE",
39
+ "AMEM_MODEL_DIR",
38
40
  "AMEM_COLLECTION",
39
41
  "AMEM_DATA_DIR",
40
42
  "AMEM_EVO_COUNTER_PATH",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-amem",
3
- "version": "2.0.1",
3
+ "version": "2.1.1",
4
4
  "description": "Catches memories that contradict each other. Notes rewrite themselves as new ones arrive, link into a graph, and stay separated per agent and per person. Runs on a small model. Requires a local Qdrant. 中文走 jieba 分词,提示词有中文版。",
5
5
  "license": "MIT",
6
6
  "main": "dist/index.js",
@@ -26,7 +26,6 @@
26
26
  "devDependencies": {
27
27
  "@eslint/js": "^10.0.1",
28
28
  "@types/node": "^26.1.1",
29
- "@types/uuid": "^11.0.0",
30
29
  "eslint": "^10.7.0",
31
30
  "prettier": "^3.9.6",
32
31
  "tsup": "^8.4.0",