memory-lancedb-pro 1.0.22 → 1.0.24

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.
@@ -0,0 +1,81 @@
1
+ name: 🐛 Bug Report
2
+ description: Report a bug or unexpected behavior
3
+ labels: ["bug"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Thanks for reporting a bug! Please fill out the sections below.
9
+
10
+ - type: input
11
+ id: version
12
+ attributes:
13
+ label: Plugin Version
14
+ description: "Run `openclaw memory-pro version` to check"
15
+ placeholder: "e.g. 1.0.22"
16
+ validations:
17
+ required: true
18
+
19
+ - type: input
20
+ id: openclaw-version
21
+ attributes:
22
+ label: OpenClaw Version
23
+ description: "Run `openclaw --version` to check"
24
+ placeholder: "e.g. 2026.3.1"
25
+ validations:
26
+ required: true
27
+
28
+ - type: textarea
29
+ id: description
30
+ attributes:
31
+ label: Bug Description
32
+ description: A clear description of what happened
33
+ placeholder: "When I run `openclaw memory-pro search ...`, it throws..."
34
+ validations:
35
+ required: true
36
+
37
+ - type: textarea
38
+ id: expected
39
+ attributes:
40
+ label: Expected Behavior
41
+ description: What did you expect to happen?
42
+ validations:
43
+ required: true
44
+
45
+ - type: textarea
46
+ id: reproduce
47
+ attributes:
48
+ label: Steps to Reproduce
49
+ description: Minimal steps to reproduce the issue
50
+ placeholder: |
51
+ 1. Set config ...
52
+ 2. Run command ...
53
+ 3. See error ...
54
+ validations:
55
+ required: true
56
+
57
+ - type: textarea
58
+ id: logs
59
+ attributes:
60
+ label: Error Logs / Screenshots
61
+ description: Paste relevant error output or screenshots
62
+ render: shell
63
+
64
+ - type: dropdown
65
+ id: embedding
66
+ attributes:
67
+ label: Embedding Provider
68
+ options:
69
+ - OpenAI
70
+ - Jina
71
+ - Gemini
72
+ - Ollama
73
+ - Other
74
+ validations:
75
+ required: false
76
+
77
+ - type: input
78
+ id: os
79
+ attributes:
80
+ label: OS / Platform
81
+ placeholder: "e.g. Ubuntu 24.04, macOS 15, Windows 11"
@@ -0,0 +1,5 @@
1
+ blank_issues_enabled: false
2
+ contact_links:
3
+ - name: ❓ Question / Help
4
+ url: https://discord.com/invite/clawd
5
+ about: For questions and support, join the OpenClaw Discord community
@@ -0,0 +1,57 @@
1
+ name: ✨ Feature Request
2
+ description: Suggest a new feature or improvement
3
+ labels: ["enhancement"]
4
+ body:
5
+ - type: markdown
6
+ attributes:
7
+ value: |
8
+ Thanks for suggesting a feature! Please describe your idea below.
9
+
10
+ - type: textarea
11
+ id: problem
12
+ attributes:
13
+ label: Problem / Motivation
14
+ description: What problem does this solve? Why do you need it?
15
+ placeholder: "I often need to ... but currently there's no way to ..."
16
+ validations:
17
+ required: true
18
+
19
+ - type: textarea
20
+ id: solution
21
+ attributes:
22
+ label: Proposed Solution
23
+ description: How would you like this to work?
24
+ placeholder: "It would be great if `openclaw memory-pro` could ..."
25
+ validations:
26
+ required: true
27
+
28
+ - type: textarea
29
+ id: alternatives
30
+ attributes:
31
+ label: Alternatives Considered
32
+ description: Any workarounds or alternative approaches you've tried?
33
+
34
+ - type: dropdown
35
+ id: scope
36
+ attributes:
37
+ label: Area
38
+ description: Which part of the plugin does this affect?
39
+ options:
40
+ - Retrieval / Search
41
+ - Storage / Database
42
+ - Embedding
43
+ - CLI Commands
44
+ - Configuration
45
+ - Auto-capture / Auto-recall
46
+ - Scopes / Access Control
47
+ - Migration
48
+ - Documentation
49
+ - Other
50
+ validations:
51
+ required: false
52
+
53
+ - type: textarea
54
+ id: context
55
+ attributes:
56
+ label: Additional Context
57
+ description: Screenshots, examples, or links that help explain the feature
package/README.md CHANGED
@@ -710,6 +710,51 @@ On LanceDB 0.26+ (via Apache Arrow), some numeric columns may be returned as `Bi
710
710
 
711
711
  upgrade to **memory-lancedb-pro >= 1.0.14**. This plugin now coerces these values using `Number(...)` before doing arithmetic (for example, when computing scores or sorting by timestamp).
712
712
 
713
+ ## Iron Rules for AI Agents (铁律)
714
+
715
+ > **For OpenClaw users**: copy the code block below into your `AGENTS.md` so your agent enforces these rules automatically.
716
+
717
+ ```markdown
718
+ ## Rule 1 — 双层记忆存储(铁律)
719
+
720
+ Every pitfall/lesson learned → IMMEDIATELY store TWO memories to LanceDB before moving on:
721
+
722
+ - **Technical layer**: Pitfall: [symptom]. Cause: [root cause]. Fix: [solution]. Prevention: [how to avoid]
723
+ (category: fact, importance ≥ 0.8)
724
+ - **Principle layer**: Decision principle ([tag]): [behavioral rule]. Trigger: [when it applies]. Action: [what to do]
725
+ (category: decision, importance ≥ 0.85)
726
+ - After each store, immediately `memory_recall` with anchor keywords to verify retrieval.
727
+ If not found, rewrite and re-store.
728
+ - Missing either layer = incomplete.
729
+ Do NOT proceed to next topic until both are stored and verified.
730
+ - Also update relevant SKILL.md files to prevent recurrence.
731
+
732
+ ## Rule 2 — LanceDB 卫生
733
+
734
+ Entries must be short and atomic (< 500 chars). Never store raw conversation summaries, large blobs, or duplicates.
735
+ Prefer structured format with keywords for retrieval.
736
+
737
+ ## Rule 3 — Recall before retry
738
+
739
+ On ANY tool failure, repeated error, or unexpected behavior, ALWAYS `memory_recall` with relevant keywords
740
+ (error message, tool name, symptom) BEFORE retrying. LanceDB likely already has the fix.
741
+ Blind retries waste time and repeat known mistakes.
742
+
743
+ ## Rule 4 — 编辑前确认目标代码库
744
+
745
+ When working on memory plugins, confirm you are editing the intended package
746
+ (e.g., `memory-lancedb-pro` vs built-in `memory-lancedb`) before making changes;
747
+ use `memory_recall` + filesystem search to avoid patching the wrong repo.
748
+
749
+ ## Rule 5 — 插件代码变更必须清 jiti 缓存(MANDATORY)
750
+
751
+ After modifying ANY `.ts` file under `plugins/`, MUST run `rm -rf /tmp/jiti/` BEFORE `openclaw gateway restart`.
752
+ jiti caches compiled TS; restart alone loads STALE code. This has caused silent bugs multiple times.
753
+ Config-only changes do NOT need cache clearing.
754
+ ```
755
+
756
+ ---
757
+
713
758
  ## Dependencies
714
759
 
715
760
  | Package | Purpose |
@@ -720,16 +765,6 @@ upgrade to **memory-lancedb-pro >= 1.0.14**. This plugin now coerces these value
720
765
 
721
766
  ---
722
767
 
723
- ## ⭐ Star History
724
-
725
- <a href="https://star-history.com/#win4r/memory-lancedb-pro&Date">
726
- <picture>
727
- <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&theme=dark&transparent=true" />
728
- <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
729
- <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
730
- </picture>
731
- </a>
732
-
733
768
  ## Contributors
734
769
 
735
770
  Top contributors (from GitHub’s contributors list, sorted by commit contributions; bots excluded):
@@ -742,11 +777,15 @@ Top contributors (from GitHub’s contributors list, sorted by commit contributi
742
777
  <a href="https://github.com/Minidoracat"><img src="https://avatars.githubusercontent.com/u/11269639?v=4" width="48" height="48" alt="@Minidoracat" /></a>
743
778
  <a href="https://github.com/furedericca-lab"><img src="https://avatars.githubusercontent.com/u/263020793?v=4" width="48" height="48" alt="@furedericca-lab" /></a>
744
779
  <a href="https://github.com/joe2643"><img src="https://avatars.githubusercontent.com/u/19421931?v=4" width="48" height="48" alt="@joe2643" /></a>
780
+ <a href="https://github.com/AliceLJY"><img src="https://avatars.githubusercontent.com/u/136287420?v=4" width="48" height="48" alt="@AliceLJY" /></a>
781
+ <a href="https://github.com/chenjiyong"><img src="https://avatars.githubusercontent.com/u/8199522?v=4" width="48" height="48" alt="@chenjiyong" /></a>
745
782
  </p>
746
783
 
747
784
  - [@win4r](https://github.com/win4r) (3 commits)
748
785
  - [@kctony](https://github.com/kctony) (2 commits)
749
786
  - [@Akatsuki-Ryu](https://github.com/Akatsuki-Ryu) (1 commit)
787
+ - [@AliceLJY](https://github.com/AliceLJY) (1 commit)
788
+ - [@chenjiyong](https://github.com/chenjiyong) (1 commit)
750
789
  - [@JasonSuz](https://github.com/JasonSuz) (1 commit)
751
790
  - [@Minidoracat](https://github.com/Minidoracat) (1 commit)
752
791
  - [@furedericca-lab](https://github.com/furedericca-lab) (1 commit)
@@ -754,6 +793,16 @@ Top contributors (from GitHub’s contributors list, sorted by commit contributi
754
793
 
755
794
  Full list: https://github.com/win4r/memory-lancedb-pro/graphs/contributors
756
795
 
796
+ ## ⭐ Star History
797
+
798
+ <a href="https://star-history.com/#win4r/memory-lancedb-pro&Date">
799
+ <picture>
800
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&theme=dark&transparent=true" />
801
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
802
+ <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
803
+ </picture>
804
+ </a>
805
+
757
806
  ## License
758
807
 
759
808
  MIT
package/README_CN.md CHANGED
@@ -584,6 +584,51 @@ LanceDB 表 `memories`:
584
584
 
585
585
  请升级到 **memory-lancedb-pro >= 1.0.14**。插件已对这些字段统一做 `Number(...)` 转换后再参与运算(例如:计算分数、按时间排序)。
586
586
 
587
+ ## AI Agent 铁律(Iron Rules)
588
+
589
+ > **OpenClaw 用户**:将下方代码块复制到你的 `AGENTS.md` 中,让 Agent 自动遵守这些规则。
590
+
591
+ ```markdown
592
+ ## Rule 1 — 双层记忆存储(铁律)
593
+
594
+ Every pitfall/lesson learned → IMMEDIATELY store TWO memories to LanceDB before moving on:
595
+
596
+ - **Technical layer**: Pitfall: [symptom]. Cause: [root cause]. Fix: [solution]. Prevention: [how to avoid]
597
+ (category: fact, importance ≥ 0.8)
598
+ - **Principle layer**: Decision principle ([tag]): [behavioral rule]. Trigger: [when it applies]. Action: [what to do]
599
+ (category: decision, importance ≥ 0.85)
600
+ - After each store, immediately `memory_recall` with anchor keywords to verify retrieval.
601
+ If not found, rewrite and re-store.
602
+ - Missing either layer = incomplete.
603
+ Do NOT proceed to next topic until both are stored and verified.
604
+ - Also update relevant SKILL.md files to prevent recurrence.
605
+
606
+ ## Rule 2 — LanceDB 卫生
607
+
608
+ Entries must be short and atomic (< 500 chars). Never store raw conversation summaries, large blobs, or duplicates.
609
+ Prefer structured format with keywords for retrieval.
610
+
611
+ ## Rule 3 — Recall before retry
612
+
613
+ On ANY tool failure, repeated error, or unexpected behavior, ALWAYS `memory_recall` with relevant keywords
614
+ (error message, tool name, symptom) BEFORE retrying. LanceDB likely already has the fix.
615
+ Blind retries waste time and repeat known mistakes.
616
+
617
+ ## Rule 4 — 编辑前确认目标代码库
618
+
619
+ When working on memory plugins, confirm you are editing the intended package
620
+ (e.g., `memory-lancedb-pro` vs built-in `memory-lancedb`) before making changes;
621
+ use `memory_recall` + filesystem search to avoid patching the wrong repo.
622
+
623
+ ## Rule 5 — 插件代码变更必须清 jiti 缓存(MANDATORY)
624
+
625
+ After modifying ANY `.ts` file under `plugins/`, MUST run `rm -rf /tmp/jiti/` BEFORE `openclaw gateway restart`.
626
+ jiti caches compiled TS; restart alone loads STALE code. This has caused silent bugs multiple times.
627
+ Config-only changes do NOT need cache clearing.
628
+ ```
629
+
630
+ ---
631
+
587
632
  ## 依赖
588
633
 
589
634
  | 包 | 用途 |
@@ -594,16 +639,6 @@ LanceDB 表 `memories`:
594
639
 
595
640
  ---
596
641
 
597
- ## ⭐ Star 趋势
598
-
599
- <a href="https://star-history.com/#win4r/memory-lancedb-pro&Date">
600
- <picture>
601
- <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&theme=dark&transparent=true" />
602
- <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
603
- <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
604
- </picture>
605
- </a>
606
-
607
642
  ## 主要贡献者
608
643
 
609
644
  按 GitHub Contributors 列表自动生成(按 commit 贡献数排序,已排除 bot):
@@ -616,11 +651,15 @@ LanceDB 表 `memories`:
616
651
  <a href="https://github.com/Minidoracat"><img src="https://avatars.githubusercontent.com/u/11269639?v=4" width="48" height="48" alt="@Minidoracat" /></a>
617
652
  <a href="https://github.com/furedericca-lab"><img src="https://avatars.githubusercontent.com/u/263020793?v=4" width="48" height="48" alt="@furedericca-lab" /></a>
618
653
  <a href="https://github.com/joe2643"><img src="https://avatars.githubusercontent.com/u/19421931?v=4" width="48" height="48" alt="@joe2643" /></a>
654
+ <a href="https://github.com/AliceLJY"><img src="https://avatars.githubusercontent.com/u/136287420?v=4" width="48" height="48" alt="@AliceLJY" /></a>
655
+ <a href="https://github.com/chenjiyong"><img src="https://avatars.githubusercontent.com/u/8199522?v=4" width="48" height="48" alt="@chenjiyong" /></a>
619
656
  </p>
620
657
 
621
658
  - [@win4r](https://github.com/win4r)(3 次提交)
622
659
  - [@kctony](https://github.com/kctony)(2 次提交)
623
660
  - [@Akatsuki-Ryu](https://github.com/Akatsuki-Ryu)(1 次提交)
661
+ - [@AliceLJY](https://github.com/AliceLJY)(1 次提交)
662
+ - [@chenjiyong](https://github.com/chenjiyong)(1 次提交)
624
663
  - [@JasonSuz](https://github.com/JasonSuz)(1 次提交)
625
664
  - [@Minidoracat](https://github.com/Minidoracat)(1 次提交)
626
665
  - [@furedericca-lab](https://github.com/furedericca-lab)(1 次提交)
@@ -628,6 +667,16 @@ LanceDB 表 `memories`:
628
667
 
629
668
  完整列表:https://github.com/win4r/memory-lancedb-pro/graphs/contributors
630
669
 
670
+ ## ⭐ Star 趋势
671
+
672
+ <a href="https://star-history.com/#win4r/memory-lancedb-pro&Date">
673
+ <picture>
674
+ <source media="(prefers-color-scheme: dark)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&theme=dark&transparent=true" />
675
+ <source media="(prefers-color-scheme: light)" srcset="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
676
+ <img alt="Star History Chart" src="https://api.star-history.com/svg?repos=win4r/memory-lancedb-pro&type=Date&transparent=true" />
677
+ </picture>
678
+ </a>
679
+
631
680
  ## License
632
681
 
633
682
  MIT
package/index.ts CHANGED
@@ -336,7 +336,7 @@ const memoryLanceDBProPlugin = {
336
336
  const store = new MemoryStore({ dbPath: resolvedDbPath, vectorDim });
337
337
  const embedder = createEmbedder({
338
338
  provider: "openai-compatible",
339
- apiKey: resolveEnvVars(config.embedding.apiKey),
339
+ apiKey: config.embedding.apiKey,
340
340
  model: config.embedding.model || "text-embedding-3-small",
341
341
  baseURL: config.embedding.baseURL,
342
342
  dimensions: config.embedding.dimensions,
@@ -742,11 +742,14 @@ function parsePluginConfig(value: unknown): PluginConfig {
742
742
  throw new Error("embedding config is required");
743
743
  }
744
744
 
745
- const apiKey = typeof embedding.apiKey === "string"
745
+ // Accept single key (string) or array of keys for round-robin rotation
746
+ const apiKey: string | string[] = typeof embedding.apiKey === "string"
746
747
  ? embedding.apiKey
747
- : process.env.OPENAI_API_KEY || "";
748
+ : Array.isArray(embedding.apiKey) && embedding.apiKey.length > 0 && embedding.apiKey.every((k: unknown) => typeof k === "string")
749
+ ? (embedding.apiKey as string[])
750
+ : process.env.OPENAI_API_KEY || "";
748
751
 
749
- if (!apiKey) {
752
+ if (!apiKey || (Array.isArray(apiKey) && apiKey.length === 0)) {
750
753
  throw new Error("embedding.apiKey is required (set directly or via OPENAI_API_KEY env var)");
751
754
  }
752
755
 
@@ -2,7 +2,7 @@
2
2
  "id": "memory-lancedb-pro",
3
3
  "name": "Memory (LanceDB Pro)",
4
4
  "description": "Enhanced LanceDB-backed long-term memory with hybrid retrieval, multi-scope isolation, long-context chunking, and management CLI",
5
- "version": "1.0.21",
5
+ "version": "1.0.23",
6
6
  "kind": "memory",
7
7
  "configSchema": {
8
8
  "type": "object",
@@ -17,7 +17,11 @@
17
17
  "const": "openai-compatible"
18
18
  },
19
19
  "apiKey": {
20
- "type": "string"
20
+ "oneOf": [
21
+ { "type": "string" },
22
+ { "type": "array", "items": { "type": "string" }, "minItems": 1 }
23
+ ],
24
+ "description": "Single API key or array of keys for round-robin rotation"
21
25
  },
22
26
  "model": {
23
27
  "type": "string"
@@ -243,10 +247,10 @@
243
247
  },
244
248
  "uiHints": {
245
249
  "embedding.apiKey": {
246
- "label": "API Key",
250
+ "label": "API Key(s)",
247
251
  "sensitive": true,
248
- "placeholder": "sk-proj-... or ${GEMINI_API_KEY} or 'ollama'",
249
- "help": "API key for the embedding provider (or use ${OPENAI_API_KEY}; use a dummy value for keyless local endpoints)"
252
+ "placeholder": "sk-proj-... or [\"key1\", \"key2\"] for rotation",
253
+ "help": "Single API key or array of keys for round-robin rotation with automatic failover on rate limits (or use ${OPENAI_API_KEY}; use a dummy value for keyless local endpoints)"
250
254
  },
251
255
  "embedding.model": {
252
256
  "label": "Embedding Model",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-lancedb-pro",
3
- "version": "1.0.22",
3
+ "version": "1.0.24",
4
4
  "description": "OpenClaw enhanced LanceDB memory plugin with hybrid retrieval (Vector + BM25), cross-encoder rerank, multi-scope isolation, long-context chunking, and management CLI",
5
5
  "type": "module",
6
6
  "main": "index.ts",
package/src/embedder.ts CHANGED
@@ -85,7 +85,8 @@ class EmbeddingCache {
85
85
 
86
86
  export interface EmbeddingConfig {
87
87
  provider: "openai-compatible";
88
- apiKey: string;
88
+ /** Single API key or array of keys for round-robin rotation with failover. */
89
+ apiKey: string | string[];
89
90
  model: string;
90
91
  baseURL?: string;
91
92
  dimensions?: number;
@@ -151,7 +152,11 @@ export function getVectorDimensions(model: string, overrideDims?: number): numbe
151
152
  // ============================================================================
152
153
 
153
154
  export class Embedder {
154
- private client: OpenAI;
155
+ /** Pool of OpenAI clients — one per API key for round-robin rotation. */
156
+ private clients: OpenAI[];
157
+ /** Round-robin index for client rotation. */
158
+ private _clientIndex: number = 0;
159
+
155
160
  public readonly dimensions: number;
156
161
  private readonly _cache: EmbeddingCache;
157
162
 
@@ -166,8 +171,9 @@ export class Embedder {
166
171
  private readonly _autoChunk: boolean;
167
172
 
168
173
  constructor(config: EmbeddingConfig & { chunking?: boolean }) {
169
- // Resolve environment variables in API key
170
- const resolvedApiKey = resolveEnvVars(config.apiKey);
174
+ // Normalize apiKey to array and resolve environment variables
175
+ const apiKeys = Array.isArray(config.apiKey) ? config.apiKey : [config.apiKey];
176
+ const resolvedKeys = apiKeys.map(k => resolveEnvVars(k));
171
177
 
172
178
  this._model = config.model;
173
179
  this._taskQuery = config.taskQuery;
@@ -177,15 +183,74 @@ export class Embedder {
177
183
  // Enable auto-chunking by default for better handling of long documents
178
184
  this._autoChunk = config.chunking !== false;
179
185
 
180
- this.client = new OpenAI({
181
- apiKey: resolvedApiKey,
186
+ // Create a client pool one OpenAI client per key
187
+ this.clients = resolvedKeys.map(key => new OpenAI({
188
+ apiKey: key,
182
189
  ...(config.baseURL ? { baseURL: config.baseURL } : {}),
183
- });
190
+ }));
191
+
192
+ if (this.clients.length > 1) {
193
+ console.log(`[memory-lancedb-pro] Initialized ${this.clients.length} API keys for round-robin rotation`);
194
+ }
184
195
 
185
196
  this.dimensions = getVectorDimensions(config.model, config.dimensions);
186
197
  this._cache = new EmbeddingCache(256, 30); // 256 entries, 30 min TTL
187
198
  }
188
199
 
200
+ // --------------------------------------------------------------------------
201
+ // Multi-key rotation helpers
202
+ // --------------------------------------------------------------------------
203
+
204
+ /** Return the next client in round-robin order. */
205
+ private nextClient(): OpenAI {
206
+ const client = this.clients[this._clientIndex % this.clients.length];
207
+ this._clientIndex = (this._clientIndex + 1) % this.clients.length;
208
+ return client;
209
+ }
210
+
211
+ /** Check whether an error is a rate-limit / quota-exceeded error. */
212
+ private isRateLimitError(error: unknown): boolean {
213
+ // OpenAI SDK typed error
214
+ if (error && typeof error === "object" && "status" in error && (error as any).status === 429) {
215
+ return true;
216
+ }
217
+ const msg = error instanceof Error ? error.message : String(error);
218
+ return /rate.limit|quota|too many requests|insufficient.*credit|429/i.test(msg);
219
+ }
220
+
221
+ /**
222
+ * Call embeddings.create with automatic key rotation on rate-limit errors.
223
+ * Tries each key in the pool at most once before giving up.
224
+ */
225
+ private async embedWithRetry(payload: any): Promise<any> {
226
+ const maxAttempts = this.clients.length;
227
+ let lastError: Error | undefined;
228
+
229
+ for (let attempt = 0; attempt < maxAttempts; attempt++) {
230
+ const client = this.nextClient();
231
+ try {
232
+ return await client.embeddings.create(payload);
233
+ } catch (error) {
234
+ if (this.isRateLimitError(error) && attempt < maxAttempts - 1) {
235
+ console.log(
236
+ `[memory-lancedb-pro] API key ${attempt + 1}/${maxAttempts} hit rate limit, rotating to next key...`
237
+ );
238
+ lastError = error instanceof Error ? error : new Error(String(error));
239
+ continue;
240
+ }
241
+ // Non-rate-limit error or last attempt — let caller handle
242
+ throw error;
243
+ }
244
+ }
245
+
246
+ throw lastError || new Error("All API keys exhausted (rate limited)");
247
+ }
248
+
249
+ /** Number of API keys in the rotation pool. */
250
+ get keyCount(): number {
251
+ return this.clients.length;
252
+ }
253
+
189
254
  // --------------------------------------------------------------------------
190
255
  // Backward-compatible API
191
256
  // --------------------------------------------------------------------------
@@ -271,7 +336,7 @@ export class Embedder {
271
336
  if (cached) return cached;
272
337
 
273
338
  try {
274
- const response = await this.client.embeddings.create(this.buildPayload(text, task) as any);
339
+ const response = await this.embedWithRetry(this.buildPayload(text, task));
275
340
  const embedding = response.data[0]?.embedding as number[] | undefined;
276
341
  if (!embedding) {
277
342
  throw new Error("No embedding returned from provider");
@@ -361,8 +426,8 @@ export class Embedder {
361
426
  }
362
427
 
363
428
  try {
364
- const response = await this.client.embeddings.create(
365
- this.buildPayload(validTexts, task) as any
429
+ const response = await this.embedWithRetry(
430
+ this.buildPayload(validTexts, task)
366
431
  );
367
432
 
368
433
  // Create result array with proper length
@@ -479,7 +544,10 @@ export class Embedder {
479
544
  }
480
545
 
481
546
  get cacheStats() {
482
- return this._cache.stats;
547
+ return {
548
+ ...this._cache.stats,
549
+ keyCount: this.clients.length,
550
+ };
483
551
  }
484
552
  }
485
553
 
package/src/tools.ts CHANGED
@@ -26,6 +26,12 @@ interface ToolContext {
26
26
  agentId?: string;
27
27
  }
28
28
 
29
+ function resolveAgentId(runtimeAgentId: unknown, fallback?: string): string | undefined {
30
+ if (typeof runtimeAgentId === "string" && runtimeAgentId.trim().length > 0) return runtimeAgentId;
31
+ if (typeof fallback === "string" && fallback.trim().length > 0) return fallback;
32
+ return undefined;
33
+ }
34
+
29
35
  // ============================================================================
30
36
  // Utility Functions
31
37
  // ============================================================================
@@ -58,8 +64,10 @@ function sanitizeMemoryForSerialization(results: RetrievalResult[]) {
58
64
 
59
65
  export function registerMemoryRecallTool(api: OpenClawPluginApi, context: ToolContext) {
60
66
  api.registerTool(
61
- {
62
- name: "memory_recall",
67
+ (toolCtx) => {
68
+ const agentId = resolveAgentId((toolCtx as any)?.agentId, context.agentId) ?? "main";
69
+ return {
70
+ name: "memory_recall",
63
71
  label: "Memory Recall",
64
72
  description: "Search through long-term memories using hybrid retrieval (vector + keyword search). Use when you need context about user preferences, past decisions, or previously discussed topics.",
65
73
  parameters: Type.Object({
@@ -80,9 +88,9 @@ export function registerMemoryRecallTool(api: OpenClawPluginApi, context: ToolCo
80
88
  const safeLimit = clampInt(limit, 1, 20);
81
89
 
82
90
  // Determine accessible scopes
83
- let scopeFilter = context.scopeManager.getAccessibleScopes(context.agentId);
91
+ let scopeFilter = context.scopeManager.getAccessibleScopes(agentId);
84
92
  if (scope) {
85
- if (context.scopeManager.isAccessible(scope, context.agentId)) {
93
+ if (context.scopeManager.isAccessible(scope, agentId)) {
86
94
  scopeFilter = [scope];
87
95
  } else {
88
96
  return {
@@ -134,6 +142,7 @@ export function registerMemoryRecallTool(api: OpenClawPluginApi, context: ToolCo
134
142
  };
135
143
  }
136
144
  },
145
+ };
137
146
  },
138
147
  { name: "memory_recall" }
139
148
  );
@@ -141,8 +150,10 @@ export function registerMemoryRecallTool(api: OpenClawPluginApi, context: ToolCo
141
150
 
142
151
  export function registerMemoryStoreTool(api: OpenClawPluginApi, context: ToolContext) {
143
152
  api.registerTool(
144
- {
145
- name: "memory_store",
153
+ (toolCtx) => {
154
+ const agentId = resolveAgentId((toolCtx as any)?.agentId, context.agentId) ?? "main";
155
+ return {
156
+ name: "memory_store",
146
157
  label: "Memory Store",
147
158
  description: "Save important information in long-term memory. Use for preferences, facts, decisions, and other notable information.",
148
159
  parameters: Type.Object({
@@ -166,10 +177,10 @@ export function registerMemoryStoreTool(api: OpenClawPluginApi, context: ToolCon
166
177
 
167
178
  try {
168
179
  // Determine target scope
169
- let targetScope = scope || context.scopeManager.getDefaultScope(context.agentId);
180
+ let targetScope = scope || context.scopeManager.getDefaultScope(agentId);
170
181
 
171
182
  // Validate scope access
172
- if (!context.scopeManager.isAccessible(targetScope, context.agentId)) {
183
+ if (!context.scopeManager.isAccessible(targetScope, agentId)) {
173
184
  return {
174
185
  content: [{ type: "text", text: `Access denied to scope: ${targetScope}` }],
175
186
  details: { error: "scope_access_denied", requestedScope: targetScope },
@@ -233,6 +244,7 @@ export function registerMemoryStoreTool(api: OpenClawPluginApi, context: ToolCon
233
244
  };
234
245
  }
235
246
  },
247
+ };
236
248
  },
237
249
  { name: "memory_store" }
238
250
  );
@@ -240,8 +252,10 @@ export function registerMemoryStoreTool(api: OpenClawPluginApi, context: ToolCon
240
252
 
241
253
  export function registerMemoryForgetTool(api: OpenClawPluginApi, context: ToolContext) {
242
254
  api.registerTool(
243
- {
244
- name: "memory_forget",
255
+ (toolCtx) => {
256
+ const agentId = resolveAgentId((toolCtx as any)?.agentId, context.agentId) ?? "main";
257
+ return {
258
+ name: "memory_forget",
245
259
  label: "Memory Forget",
246
260
  description: "Delete specific memories. Supports both search-based and direct ID-based deletion.",
247
261
  parameters: Type.Object({
@@ -258,9 +272,9 @@ export function registerMemoryForgetTool(api: OpenClawPluginApi, context: ToolCo
258
272
 
259
273
  try {
260
274
  // Determine accessible scopes
261
- let scopeFilter = context.scopeManager.getAccessibleScopes(context.agentId);
275
+ let scopeFilter = context.scopeManager.getAccessibleScopes(agentId);
262
276
  if (scope) {
263
- if (context.scopeManager.isAccessible(scope, context.agentId)) {
277
+ if (context.scopeManager.isAccessible(scope, agentId)) {
264
278
  scopeFilter = [scope];
265
279
  } else {
266
280
  return {
@@ -338,6 +352,7 @@ export function registerMemoryForgetTool(api: OpenClawPluginApi, context: ToolCo
338
352
  };
339
353
  }
340
354
  },
355
+ };
341
356
  },
342
357
  { name: "memory_forget" }
343
358
  );
@@ -349,8 +364,10 @@ export function registerMemoryForgetTool(api: OpenClawPluginApi, context: ToolCo
349
364
 
350
365
  export function registerMemoryUpdateTool(api: OpenClawPluginApi, context: ToolContext) {
351
366
  api.registerTool(
352
- {
353
- name: "memory_update",
367
+ (toolCtx) => {
368
+ const agentId = resolveAgentId((toolCtx as any)?.agentId, context.agentId) ?? "main";
369
+ return {
370
+ name: "memory_update",
354
371
  label: "Memory Update",
355
372
  description: "Update an existing memory in-place. Preserves original timestamp. Use when correcting outdated info or adjusting importance/category without losing creation date.",
356
373
  parameters: Type.Object({
@@ -376,7 +393,7 @@ export function registerMemoryUpdateTool(api: OpenClawPluginApi, context: ToolCo
376
393
  }
377
394
 
378
395
  // Determine accessible scopes
379
- const scopeFilter = context.scopeManager.getAccessibleScopes(context.agentId);
396
+ const scopeFilter = context.scopeManager.getAccessibleScopes(agentId);
380
397
 
381
398
  // Resolve memoryId: if it doesn't look like a UUID, try search
382
399
  let resolvedId = memoryId;
@@ -452,6 +469,7 @@ export function registerMemoryUpdateTool(api: OpenClawPluginApi, context: ToolCo
452
469
  };
453
470
  }
454
471
  },
472
+ };
455
473
  },
456
474
  { name: "memory_update" }
457
475
  );
@@ -463,8 +481,10 @@ export function registerMemoryUpdateTool(api: OpenClawPluginApi, context: ToolCo
463
481
 
464
482
  export function registerMemoryStatsTool(api: OpenClawPluginApi, context: ToolContext) {
465
483
  api.registerTool(
466
- {
467
- name: "memory_stats",
484
+ (toolCtx) => {
485
+ const agentId = resolveAgentId((toolCtx as any)?.agentId, context.agentId) ?? "main";
486
+ return {
487
+ name: "memory_stats",
468
488
  label: "Memory Statistics",
469
489
  description: "Get statistics about memory usage, scopes, and categories.",
470
490
  parameters: Type.Object({
@@ -475,9 +495,9 @@ export function registerMemoryStatsTool(api: OpenClawPluginApi, context: ToolCon
475
495
 
476
496
  try {
477
497
  // Determine accessible scopes
478
- let scopeFilter = context.scopeManager.getAccessibleScopes(context.agentId);
498
+ let scopeFilter = context.scopeManager.getAccessibleScopes(agentId);
479
499
  if (scope) {
480
- if (context.scopeManager.isAccessible(scope, context.agentId)) {
500
+ if (context.scopeManager.isAccessible(scope, agentId)) {
481
501
  scopeFilter = [scope];
482
502
  } else {
483
503
  return {
@@ -524,6 +544,7 @@ export function registerMemoryStatsTool(api: OpenClawPluginApi, context: ToolCon
524
544
  };
525
545
  }
526
546
  },
547
+ };
527
548
  },
528
549
  { name: "memory_stats" }
529
550
  );
@@ -531,8 +552,10 @@ export function registerMemoryStatsTool(api: OpenClawPluginApi, context: ToolCon
531
552
 
532
553
  export function registerMemoryListTool(api: OpenClawPluginApi, context: ToolContext) {
533
554
  api.registerTool(
534
- {
535
- name: "memory_list",
555
+ (toolCtx) => {
556
+ const agentId = resolveAgentId((toolCtx as any)?.agentId, context.agentId) ?? "main";
557
+ return {
558
+ name: "memory_list",
536
559
  label: "Memory List",
537
560
  description: "List recent memories with optional filtering by scope and category.",
538
561
  parameters: Type.Object({
@@ -559,9 +582,9 @@ export function registerMemoryListTool(api: OpenClawPluginApi, context: ToolCont
559
582
  const safeOffset = clampInt(offset, 0, 1000);
560
583
 
561
584
  // Determine accessible scopes
562
- let scopeFilter = context.scopeManager.getAccessibleScopes(context.agentId);
585
+ let scopeFilter = context.scopeManager.getAccessibleScopes(agentId);
563
586
  if (scope) {
564
- if (context.scopeManager.isAccessible(scope, context.agentId)) {
587
+ if (context.scopeManager.isAccessible(scope, agentId)) {
565
588
  scopeFilter = [scope];
566
589
  } else {
567
590
  return {
@@ -609,6 +632,7 @@ export function registerMemoryListTool(api: OpenClawPluginApi, context: ToolCont
609
632
  };
610
633
  }
611
634
  },
635
+ };
612
636
  },
613
637
  { name: "memory_list" }
614
638
  );