diff --git a/docs/changelog/openclaw.mdx b/docs/changelog/openclaw.mdx
index f56dffb15..9de66efa7 100644
--- a/docs/changelog/openclaw.mdx
+++ b/docs/changelog/openclaw.mdx
@@ -4,6 +4,19 @@ description: "Release notes for the OpenClaw plugin and agent harness."
mode: "wide"
---
+
+
+**New Features:**
+- **Chat-Based Setup:** Added chat-based Platform setup flow — users can now configure the plugin conversationally instead of editing config files manually
+- **Installation Docs Rewrite:** Rewrote README and integration docs with chat-first setup, numbered manual steps.
+
+**Improvements:**
+- **SDK Upgrade:** Bumped `mem0ai` dependency to 3.0.1 for V3 API compatibility
+- **Config Cleanup:** Dropped deprecated `orgId`, `projectId`, `enableGraph` config options; updated CLI prompts ([#4734](https://github.com/mem0ai/mem0/pull/4734), [#4764](https://github.com/mem0ai/mem0/pull/4764))
+- **Noise Filtering:** Expanded noise patterns in memory add tool; handle leading text in JSON extraction
+
+
+
**Bug Fixes:**
diff --git a/docs/integrations/openclaw.mdx b/docs/integrations/openclaw.mdx
index 1028ce86b..a48088a56 100644
--- a/docs/integrations/openclaw.mdx
+++ b/docs/integrations/openclaw.mdx
@@ -14,16 +14,37 @@ Add long-term memory to [OpenClaw](https://github.com/openclaw/openclaw) agents
The plugin provides:
1. **Auto-Recall** — Before the agent responds, memories matching the current message are injected into context
2. **Auto-Capture** — After the agent responds, the exchange is sent to Mem0 which decides what's worth keeping
-3. **Agent Tools** — Five tools for explicit memory operations during conversations
+3. **Agent Tools** — Eight tools for explicit memory operations during conversations
Both auto-recall and auto-capture run silently with no manual configuration required.
-## Installation
+## Requirements
+
+Check your OpenClaw version:
```bash
-openclaw plugins install @mem0/openclaw-mem0
+openclaw --version
+# OpenClaw 2026.4.15 (041266a)
```
+| OpenClaw Version | Plugin Support |
+|------------------|----------------|
+| `>= 2026.4.15` | Fully supported |
+
+## Installation
+
+The fastest way is to install directly from your OpenClaw chat, no CLI or config editing needed.
+
+**Copy and paste this into your OpenClaw chat**; Telegram, WhatsApp, default chat, or any channel where your agent lives:
+
+```
+Setup Mem0 from mem0.ai/claw-setup
+```
+
+OpenClaw installs the plugin, prompts you for your email, and connects your Mem0 account with OTP verification. See [Chat Setup](#option-1-chat-setup-recommended) below for the full walkthrough.
+
+If you prefer the OpenClaw CLI, or are setting up self-hosted / open-source mode, see [Manual Config](#option-2-manual-config) and [Open-Source Mode](#open-source-mode-self-hosted) below.
+
## Setup and Configuration
### Understanding `userId`
@@ -36,37 +57,113 @@ Pick any stable, unique identifier for the user. Common choices:
- A UUID (e.g. `"550e8400-e29b-41d4-a716-446655440000"`)
- A simple username (e.g. `"alice"`)
-All memories are scoped to this `userId` — different values create separate memory namespaces. If you don't set it, it defaults to `"default"`, which means all users share the same memory space.
+All memories are scoped to this `userId` — different values create separate memory namespaces. If you don't set it, it defaults to your OS username.
In a multi-user application, set `userId` dynamically per user (e.g. from your auth system) rather than hardcoding a single value.
### Platform Mode (Mem0 Cloud)
-Get your API key from app.mem0.ai.
+There are two ways to set up `@mem0/openclaw-mem0` on the Mem0 platform:
-Add to your `openclaw.json`:
+- **Chat setup (recommended)** — run the setup inside any OpenClaw chat. No config editing, no API key handling.
+- **Manual config** — edit `openclaw.json` directly.
-```json5
-// plugins.entries
-"openclaw-mem0": {
- "enabled": true,
- "config": {
- "apiKey": "${MEM0_API_KEY}",
- "userId": "alice" // any unique identifier you choose for this user
- }
-}
-```
+#### Option 1: Chat Setup (Recommended)
+
+You no longer need manual config editing to get started. Everything happens inside the OpenClaw chat itself.
+
+
+
+ Open any OpenClaw channel — Telegram, WhatsApp, your default chat, wherever your agent lives. Paste and send this command:
+
+ ```
+ Setup Mem0 from mem0.ai/claw-setup
+ ```
+
+ OpenClaw responds with a Mem0 setup card and immediately asks:
+
+ > "What's your email address? I'll send you a verification code to connect your Mem0 account."
+
+
+
+ Type your email address and send it. Mem0 sends back:
+
+ > "Check your email for a 6-digit code and paste it here."
+
+
+
+ Copy the 6-digit code from your email inbox and paste it into the chat.
+
+ You'll see the confirmation:
+
+ > "Connected to Mem0."
+
+
+
+That's it. No API key, no config file editing, no environment variables. The plugin is now active and auto-capture and auto-recall are running on every turn.
+
+The chat flow uses the same underlying config as manual setup — it writes `apiKey` and `userId` into `openclaw.json` for you. You can still open the file to inspect or override values afterward.
+
+#### Option 2: Manual Config
+
+
+
+ ```bash
+ openclaw plugins install @mem0/openclaw-mem0
+ ```
+
+
+
+ Get your API key from app.mem0.ai.
+
+
+
+ Add the full config to your `openclaw.json`:
+
+ ```json5
+ {
+ "plugins": {
+ "slots": {
+ "memory": "openclaw-mem0"
+ },
+ "entries": {
+ "openclaw-mem0": {
+ "enabled": true,
+ "config": {
+ "apiKey": "${MEM0_API_KEY}",
+ "userId": "alice" // any unique identifier you choose for this user
+ }
+ }
+ }
+ }
+ }
+ ```
+
+
+
+
+OpenClaw treats memory plugins as an exclusive slot. Installing the plugin alone does **not** activate it — you must also set `plugins.slots.memory` as shown above.
+
### Open-Source Mode (Self-hosted)
No Mem0 key needed. Requires `OPENAI_API_KEY` for default embeddings/LLM.
```json5
-"openclaw-mem0": {
- "enabled": true,
- "config": {
- "mode": "open-source",
- "userId": "alice" // any unique identifier you choose for this user
+{
+ "plugins": {
+ "slots": {
+ "memory": "openclaw-mem0"
+ },
+ "entries": {
+ "openclaw-mem0": {
+ "enabled": true,
+ "config": {
+ "mode": "open-source",
+ "userId": "alice" // any unique identifier you choose for this user
+ }
+ }
+ }
}
}
```
@@ -74,13 +171,25 @@ No Mem0 key needed. Requires `OPENAI_API_KEY` for default embeddings/LLM.
Sensible defaults work out of the box. To customize the embedder, vector store, or LLM:
```json5
-"config": {
- "mode": "open-source",
- "userId": "your-user-id",
- "oss": {
- "embedder": { "provider": "openai", "config": { "model": "text-embedding-3-small" } },
- "vectorStore": { "provider": "qdrant", "config": { "host": "localhost", "port": 6333 } },
- "llm": { "provider": "openai", "config": { "model": "gpt-4o" } }
+{
+ "plugins": {
+ "slots": {
+ "memory": "openclaw-mem0"
+ },
+ "entries": {
+ "openclaw-mem0": {
+ "enabled": true,
+ "config": {
+ "mode": "open-source",
+ "userId": "your-user-id",
+ "oss": {
+ "embedder": { "provider": "openai", "config": { "model": "text-embedding-3-small" } },
+ "vectorStore": { "provider": "qdrant", "config": { "host": "localhost", "port": 6333 } },
+ "llm": { "provider": "openai", "config": { "model": "gpt-4o" } }
+ }
+ }
+ }
+ }
}
}
```
@@ -93,23 +202,26 @@ Memories are organized into two scopes:
- **Session (short-term)** — Auto-capture stores memories scoped to the current session via Mem0's `run_id` / `runId` parameter. These are contextual to the ongoing conversation.
-- **User (long-term)** — The agent can explicitly store long-term memories using the `memory_store` tool (with `longTerm: true`, the default). These persist across all sessions for the user.
+- **User (long-term)** — The agent can explicitly store long-term memories using the `memory_add` tool (with `longTerm: true`, the default). These persist across all sessions for the user.
During **auto-recall**, the plugin searches both scopes and presents them separately — long-term memories first, then session memories — so the agent has full context.
## Agent Tools
-The agent gets five tools it can call during conversations:
+The agent gets eight tools it can call during conversations:
| Tool | Description |
|------|-------------|
-| `memory_search` | Search memories by natural language |
-| `memory_list` | List all stored memories for a user |
-| `memory_store` | Explicitly save a fact |
-| `memory_get` | Retrieve a memory by ID |
-| `memory_forget` | Delete by ID or by query |
+| `memory_search` | Search memories by natural language query. Supports `scope`, `categories`, `filters`. |
+| `memory_add` | Store facts. Accepts `text` or `facts` array, `category`, `importance`, `metadata`. |
+| `memory_get` | Retrieve a single memory by ID |
+| `memory_list` | List all memories. Filter by `userId`, `agentId`, `scope`. |
+| `memory_update` | Update a memory's text in place. Preserves history. |
+| `memory_delete` | Delete by `memoryId`, `query` (search-and-delete), or `all: true`. |
+| `memory_event_list` | List recent background processing events (platform mode only). |
+| `memory_event_status` | Get status of a specific event by ID (platform mode only). |
-The `memory_search` and `memory_list` tools accept a `scope` parameter (`"session"`, `"long-term"`, or `"all"`) to control which memories are queried. The `memory_store` tool accepts a `longTerm` boolean (default: `true`) to choose where to store.
+The `memory_search` and `memory_list` tools accept a `scope` parameter (`"session"`, `"long-term"`, or `"all"`) to control which memories are queried.
## CLI Commands
@@ -123,8 +235,9 @@ openclaw mem0 search "what languages does the user know" --scope long-term
# Search only session/short-term memories
openclaw mem0 search "what languages does the user know" --scope session
-# View stats
-openclaw mem0 stats
+# List all memories
+openclaw mem0 list
+openclaw mem0 list --user-id alice --top-k 20
```
## Configuration Options
@@ -134,7 +247,7 @@ openclaw mem0 stats
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `mode` | `"platform"` \| `"open-source"` | `"platform"` | Which backend to use |
-| `userId` | `string` | `"default"` | Scope memories per user |
+| `userId` | `string` | OS username | Scope memories per user |
| `autoRecall` | `boolean` | `true` | Inject memories before each turn |
| `autoCapture` | `boolean` | `true` | Store facts after each turn |
| `topK` | `number` | `5` | Max memories per recall |
@@ -145,8 +258,6 @@ openclaw mem0 stats
| Key | Type | Default | Description |
|-----|------|---------|-------------|
| `apiKey` | `string` | — | **Required.** Mem0 API key (supports `${MEM0_API_KEY}`) |
-| `orgId` | `string` | — | Organization ID |
-| `projectId` | `string` | — | Project ID |
| `customInstructions` | `string` | *(built-in)* | Extraction rules — what to store, how to format |
| `customCategories` | `object` | *(12 defaults)* | Category name → description map for tagging |
@@ -162,15 +273,77 @@ openclaw mem0 stats
| `oss.llm.provider` | `string` | `"openai"` | LLM provider (`"openai"`, `"anthropic"`, `"ollama"`, etc.) |
| `oss.llm.config` | `object` | — | Provider config: `apiKey`, `model`, `baseURL`, `temperature` |
| `oss.historyDbPath` | `string` | — | SQLite path for memory edit history |
+| `oss.disableHistory` | `boolean` | `false` | Disable memory edit history tracking |
Everything inside `oss` is optional — defaults use OpenAI embeddings (`text-embedding-3-small`), in-memory vector store, and OpenAI LLM.
+## Plugin Management
+
+### Updating the Plugin
+
+```bash
+openclaw plugins update @mem0/openclaw-mem0
+```
+
+Use the npm package name (`@mem0/openclaw-mem0`) for plugin management commands, not the plugin ID (`openclaw-mem0`).
+
+### Checking Plugin Status
+
+```bash
+openclaw plugins list
+openclaw plugins inspect openclaw-mem0
+```
+
+## Troubleshooting
+
+### "plugins.allow excludes mem0" Error
+
+If you see an error like:
+
+```
+[openclaw] Failed to start CLI: Error: The `openclaw mem0` command is unavailable
+because `plugins.allow` excludes "mem0". Add "mem0" to `plugins.allow` if you want
+that bundled plugin CLI surface.
+```
+
+Add `mem0` to your `plugins.allow` list in `openclaw.json`:
+
+```json5
+{
+ "plugins": {
+ "allow": ["mem0"],
+ "slots": {
+ "memory": "openclaw-mem0"
+ }
+ }
+}
+```
+
+### Plugin Not Activating
+
+If the plugin installs but doesn't work:
+
+1. Verify `plugins.slots.memory` is set to `"openclaw-mem0"` (not the npm package name)
+2. Check `openclaw plugins list --enabled` to confirm the plugin is loaded
+3. Run `openclaw mem0 status` to verify configuration
+
+### Plugin Update Not Working
+
+If `openclaw plugins update` fails:
+
+1. Use the full npm package name: `openclaw plugins update @mem0/openclaw-mem0`
+2. If that fails, uninstall and reinstall:
+ ```bash
+ openclaw plugins uninstall openclaw-mem0
+ openclaw plugins install @mem0/openclaw-mem0
+ ```
+
## Key Features
1. **Zero Configuration** — Auto-recall and auto-capture work out of the box with no prompting required
2. **Dual Memory Scopes** — Session-scoped short-term and user-scoped long-term memories
3. **Flexible Backend** — Use Mem0 Cloud for managed service or self-host with open-source mode
-4. **Rich Tool Suite** — Five agent tools for explicit memory operations when needed
+4. **Rich Tool Suite** — Eight agent tools for explicit memory operations when needed
## Conclusion
diff --git a/mem0-ts/src/oss/src/prompts/index.ts b/mem0-ts/src/oss/src/prompts/index.ts
index 98d3ab45d..700289d4f 100644
--- a/mem0-ts/src/oss/src/prompts/index.ts
+++ b/mem0-ts/src/oss/src/prompts/index.ts
@@ -883,38 +883,160 @@ export function removeCodeBlocks(text: string): string {
/**
* Extracts a JSON object from text that may be wrapped in explanation text.
*
- * Some LLMs (especially local models like Ollama/LM Studio) return JSON
- * wrapped in conversational text without code fences, e.g.:
+ * Some LLMs (especially local models like Ollama/LM Studio, or OpenRouter)
+ * return JSON wrapped in conversational text without code fences, e.g.:
*
* "Here are the facts I extracted:\n{\"facts\": [\"fact1\"]}\nI hope this helps!"
*
- * This function first tries `removeCodeBlocks` for code-fence-wrapped JSON,
- * then falls back to locating the first `{` and last `}` to extract the
- * outermost JSON object.
+ * This function:
+ * 1. Strips known noise tokens from OpenRouter and other providers
+ * 2. Removes code fences and blocks
+ * 3. Tries to find a valid JSON object by testing each `{` as a starting point
+ * 4. Falls back to first/last brace matching if validation isn't possible
*
* @param text - The raw LLM response text
* @returns The extracted JSON string, or the original text if no JSON object
* boundaries are found
*/
export function extractJson(text: string): string {
- // Step 1: Strip code fences if present
- const cleaned = removeCodeBlocks(text);
+ // Step 1: Strip known noise tokens from OpenRouter/local models
+ let cleaned = text
+ .replace(/<\|end_of_text\|>/g, "")
+ .replace(/<\|eot_id\|>/g, "")
+ .replace(/<\|im_end\|>/g, "")
+ .replace(/<\|im_start\|>/g, "")
+ .replace(/<\|endoftext\|>/g, "");
+
+ // Step 2: Strip code fences and blocks
+ cleaned = removeCodeBlocks(cleaned);
const trimmed = cleaned.trim();
- // Step 2: Try to locate a JSON object by first `{` and last `}` boundaries
+ if (!trimmed) return "";
+
+ // Step 3: Try to find valid JSON object by testing each `{` as potential start
+ // This handles cases like "Here's the {formatted} output: {...actual json...}"
+ const braceIndices: number[] = [];
+ for (let i = 0; i < trimmed.length; i++) {
+ if (trimmed[i] === "{") braceIndices.push(i);
+ }
+
+ for (const start of braceIndices) {
+ // Find the matching closing brace by tracking depth
+ let depth = 0;
+ let inString = false;
+ let escapeNext = false;
+
+ for (let i = start; i < trimmed.length; i++) {
+ const char = trimmed[i];
+
+ if (escapeNext) {
+ escapeNext = false;
+ continue;
+ }
+
+ if (char === "\\") {
+ escapeNext = true;
+ continue;
+ }
+
+ if (char === '"' && !escapeNext) {
+ inString = !inString;
+ continue;
+ }
+
+ if (inString) continue;
+
+ if (char === "{") depth++;
+ else if (char === "}") {
+ depth--;
+ if (depth === 0) {
+ const candidate = trimmed.substring(start, i + 1);
+ try {
+ JSON.parse(candidate);
+ return candidate; // Valid JSON found
+ } catch {
+ // Not valid JSON, try next starting brace
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Step 4: Fallback - try first/last brace (original behavior for edge cases)
+ // Only use this if it produces valid JSON
const firstBrace = trimmed.indexOf("{");
const lastBrace = trimmed.lastIndexOf("}");
if (firstBrace !== -1 && lastBrace > firstBrace) {
- return trimmed.substring(firstBrace, lastBrace + 1);
+ const candidate = trimmed.substring(firstBrace, lastBrace + 1);
+ try {
+ JSON.parse(candidate);
+ return candidate;
+ } catch {
+ // Not valid JSON, continue to array extraction
+ }
}
- // Step 3: Try to locate a JSON array by first `[` and last `]` boundaries
+ // Step 5: Try to locate a JSON array by testing each `[` as potential start
+ const bracketIndices: number[] = [];
+ for (let i = 0; i < trimmed.length; i++) {
+ if (trimmed[i] === "[") bracketIndices.push(i);
+ }
+
+ for (const start of bracketIndices) {
+ let depth = 0;
+ let inString = false;
+ let escapeNext = false;
+
+ for (let i = start; i < trimmed.length; i++) {
+ const char = trimmed[i];
+
+ if (escapeNext) {
+ escapeNext = false;
+ continue;
+ }
+
+ if (char === "\\") {
+ escapeNext = true;
+ continue;
+ }
+
+ if (char === '"' && !escapeNext) {
+ inString = !inString;
+ continue;
+ }
+
+ if (inString) continue;
+
+ if (char === "[") depth++;
+ else if (char === "]") {
+ depth--;
+ if (depth === 0) {
+ const candidate = trimmed.substring(start, i + 1);
+ try {
+ JSON.parse(candidate);
+ return candidate;
+ } catch {
+ break;
+ }
+ }
+ }
+ }
+ }
+
+ // Fallback for arrays - validate before returning
const firstBracket = trimmed.indexOf("[");
const lastBracket = trimmed.lastIndexOf("]");
if (firstBracket !== -1 && lastBracket > firstBracket) {
- return trimmed.substring(firstBracket, lastBracket + 1);
+ const candidate = trimmed.substring(firstBracket, lastBracket + 1);
+ try {
+ JSON.parse(candidate);
+ return candidate;
+ } catch {
+ // Not valid JSON
+ }
}
- // No JSON boundaries found — return as-is and let the caller handle the error
+ // No valid JSON found — return as-is and let the caller handle the error
return trimmed;
}
diff --git a/mem0-ts/src/oss/tests/extract-json.test.ts b/mem0-ts/src/oss/tests/extract-json.test.ts
index c1132c429..bf5335a02 100644
--- a/mem0-ts/src/oss/tests/extract-json.test.ts
+++ b/mem0-ts/src/oss/tests/extract-json.test.ts
@@ -148,4 +148,85 @@ That's all I found.`;
const result = extractJson(input);
expect(JSON.parse(result)).toEqual({ facts: ["test"] });
});
+
+ it("strips <|end_of_text|> tokens from OpenRouter responses", () => {
+ const input = '{"facts": ["test"]}<|end_of_text|>';
+ const result = extractJson(input);
+ expect(JSON.parse(result)).toEqual({ facts: ["test"] });
+ });
+
+ it("strips <|eot_id|> tokens from OpenRouter responses", () => {
+ const input = '{"facts": ["hello"]}<|eot_id|>';
+ const result = extractJson(input);
+ expect(JSON.parse(result)).toEqual({ facts: ["hello"] });
+ });
+
+ it("strips <|im_end|> tokens from ChatML responses", () => {
+ const input = '{"memory": [{"text": "test"}]}<|im_end|>';
+ const result = extractJson(input);
+ expect(JSON.parse(result)).toEqual({ memory: [{ text: "test" }] });
+ });
+
+ it("strips multiple noise tokens", () => {
+ const input =
+ '<|im_start|>assistant\n{"facts": ["data"]}<|im_end|><|end_of_text|>';
+ const result = extractJson(input);
+ expect(JSON.parse(result)).toEqual({ facts: ["data"] });
+ });
+
+ // Issue #4737: Leading text with braces that aren't JSON
+ it("handles leading text containing braces before actual JSON", () => {
+ const input = 'Here\'s the {formatted} output: {"facts": ["real data"]}';
+ const result = extractJson(input);
+ expect(JSON.parse(result)).toEqual({ facts: ["real data"] });
+ });
+
+ it("handles multiple fake braces in leading text", () => {
+ const input =
+ "I'll format this {nicely} with {proper} structure:\n" +
+ '{"memory": [{"id": "1", "text": "actual memory"}]}';
+ const result = extractJson(input);
+ expect(JSON.parse(result)).toEqual({
+ memory: [{ id: "1", text: "actual memory" }],
+ });
+ });
+
+ it("handles incomplete JSON-like structures in leading text", () => {
+ const input =
+ "Based on {user preferences} I found:\n" +
+ '{"facts": ["User likes TypeScript"]}';
+ const result = extractJson(input);
+ expect(JSON.parse(result)).toEqual({ facts: ["User likes TypeScript"] });
+ });
+
+ it("validates JSON and skips malformed candidates", () => {
+ const input = 'The result is {broken and {"facts": ["valid"]} is here';
+ const result = extractJson(input);
+ expect(JSON.parse(result)).toEqual({ facts: ["valid"] });
+ });
+
+ it("handles deeply nested valid JSON after invalid starts", () => {
+ const input =
+ "Here {is some {context}} for you:\n" +
+ '{"memory": [{"nested": {"deep": "value"}}]}';
+ const result = extractJson(input);
+ expect(JSON.parse(result)).toEqual({
+ memory: [{ nested: { deep: "value" } }],
+ });
+ });
+
+ it("handles JSON with escaped quotes correctly", () => {
+ const input =
+ 'Output: {"facts": ["User said \\"hello\\"", "Has a \\"test\\" project"]}';
+ const result = extractJson(input);
+ expect(JSON.parse(result)).toEqual({
+ facts: ['User said "hello"', 'Has a "test" project'],
+ });
+ });
+
+ it("handles arrays with leading brace-like text", () => {
+ const input = 'Here\'s {some context}. The array is: ["fact1", "fact2"]';
+ const result = extractJson(input);
+ expect(JSON.parse(result)).toEqual(["fact1", "fact2"]);
+ });
});
diff --git a/openclaw/.gitignore b/openclaw/.gitignore
index 4913e17ad..e1932d0b9 100644
--- a/openclaw/.gitignore
+++ b/openclaw/.gitignore
@@ -1,3 +1,5 @@
node_modules/
package-lock.json
*.db
+.claude/
+.claude-*/
\ No newline at end of file
diff --git a/openclaw/README.md b/openclaw/README.md
index 43f3f76a6..d554459c0 100644
--- a/openclaw/README.md
+++ b/openclaw/README.md
@@ -4,31 +4,98 @@ Long-term memory for [OpenClaw](https://github.com/openclaw/openclaw) agents, po
Your agent forgets everything between sessions. This plugin fixes that — it watches conversations, extracts what matters, and brings it back when relevant. Automatically.
-## Quick Start
+## Requirements
+
+Check your OpenClaw version:
```bash
-openclaw plugins install @mem0/openclaw-mem0
+openclaw --version
+# OpenClaw 2026.4.15 (041266a)
```
+| OpenClaw Version | Plugin Support |
+|------------------|----------------|
+| `>= 2026.4.15` | Fully supported |
+
+## Quick Start
+
+The fastest way is to install directly from your OpenClaw chat — no CLI or config editing needed.
+
+Copy and paste this into your OpenClaw chat (Telegram, WhatsApp, default chat, or any channel where your agent lives):
+
+```
+Setup Mem0 from mem0.ai/claw-setup
+```
+
+OpenClaw installs the plugin, prompts you for your email, and connects your Mem0 account with OTP verification. See [Chat Setup](#chat-setup-recommended) below for the full walkthrough.
+
+If you prefer the OpenClaw CLI, or are setting up self-hosted / open-source mode, see [Manual Config](#manual-config) and [Open-Source (Self-hosted)](#open-source-self-hosted) below.
+
### Platform (Mem0 Cloud)
-Get an API key from [app.mem0.ai](https://app.mem0.ai/dashboard/api-keys):
+There are two ways to set up `@mem0/openclaw-mem0` on the Mem0 platform:
-```bash
-openclaw mem0 init --api-key --user-id
-```
+- **Chat setup (recommended)** — run the setup inside any OpenClaw chat. No config editing, no API key handling.
+- **Manual config** — edit `openclaw.json` directly.
-Or configure manually in `openclaw.json`:
+#### Chat Setup (Recommended)
-```json5
-"openclaw-mem0": {
- "enabled": true,
- "config": {
- "apiKey": "${MEM0_API_KEY}",
- "userId": "alice"
- }
-}
-```
+You no longer need manual config editing to get started. Everything happens inside the OpenClaw chat itself.
+
+1. **Send the setup command to your OpenClaw agent.** Open any OpenClaw channel and paste:
+
+ ```
+ Setup Mem0 from mem0.ai/claw-setup
+ ```
+
+ OpenClaw responds with a Mem0 setup card and asks: *"What's your email address? I'll send you a verification code to connect your Mem0 account."*
+
+2. **Enter your email.** Type your email address and send it. Mem0 replies: *"Check your email for a 6-digit code and paste it here."*
+
+3. **Paste the OTP.** Copy the 6-digit code from your email inbox and paste it into the chat. You'll see: *"Connected to Mem0."*
+
+That's it. No API key, no config file editing, no environment variables. The plugin is now active and auto-capture and auto-recall are running on every turn.
+
+> The chat flow uses the same underlying config as manual setup — it writes `apiKey` and `userId` into `openclaw.json` for you. You can still open the file to inspect or override values afterward.
+
+#### Manual Config
+
+1. **Install the plugin via the OpenClaw CLI:**
+
+ ```bash
+ openclaw plugins install @mem0/openclaw-mem0
+ ```
+
+2. **Get your API key** from [app.mem0.ai](https://app.mem0.ai/dashboard/api-keys).
+
+3. **Select the plugin as your memory backend in `openclaw.json`.** Either initialize via the CLI:
+
+ ```bash
+ openclaw mem0 init --api-key --user-id
+ ```
+
+ Or add the full config to your `openclaw.json`:
+
+ ```json5
+ {
+ "plugins": {
+ "slots": {
+ "memory": "openclaw-mem0"
+ },
+ "entries": {
+ "openclaw-mem0": {
+ "enabled": true,
+ "config": {
+ "apiKey": "${MEM0_API_KEY}",
+ "userId": "alice"
+ }
+ }
+ }
+ }
+ }
+ ```
+
+> **Note:** OpenClaw memory plugins load through an exclusive slot, so install alone does not activate the plugin. You must set `plugins.slots.memory` as shown above.
### Open-Source (Self-hosted)
@@ -37,11 +104,20 @@ No Mem0 key needed. Requires `OPENAI_API_KEY` for default embeddings and LLM. Ve
Defaults: `text-embedding-3-small` for embeddings, `gpt-5.4` for fact extraction.
```json5
-"openclaw-mem0": {
- "enabled": true,
- "config": {
- "mode": "open-source",
- "userId": "alice"
+{
+ "plugins": {
+ "slots": {
+ "memory": "openclaw-mem0"
+ },
+ "entries": {
+ "openclaw-mem0": {
+ "enabled": true,
+ "config": {
+ "mode": "open-source",
+ "userId": "alice"
+ }
+ }
+ }
}
}
```
diff --git a/openclaw/backend/base.ts b/openclaw/backend/base.ts
index 6a52a3922..c46dc6d9f 100644
--- a/openclaw/backend/base.ts
+++ b/openclaw/backend/base.ts
@@ -10,9 +10,7 @@ export interface AddOptions {
appId?: string;
runId?: string;
metadata?: Record;
- immutable?: boolean;
infer?: boolean;
- expires?: string;
categories?: string[];
}
@@ -24,7 +22,6 @@ export interface SearchOptions {
topK?: number;
threshold?: number;
rerank?: boolean;
- keyword?: boolean;
filters?: Record;
fields?: string[];
}
diff --git a/openclaw/backend/platform.ts b/openclaw/backend/platform.ts
index 481de5873..06332453b 100644
--- a/openclaw/backend/platform.ts
+++ b/openclaw/backend/platform.ts
@@ -107,9 +107,7 @@ export class PlatformBackend implements Backend {
if (opts.appId) payload.app_id = opts.appId;
if (opts.runId) payload.run_id = opts.runId;
if (opts.metadata) payload.metadata = opts.metadata;
- if (opts.immutable) payload.immutable = true;
if (opts.infer === false) payload.infer = false;
- if (opts.expires) payload.expiration_date = opts.expires;
if (opts.categories) payload.categories = opts.categories;
return (await this._request("POST", "/v1/memories/", {
@@ -168,7 +166,6 @@ export class PlatformBackend implements Backend {
});
if (apiFilters) payload.filters = apiFilters;
if (opts.rerank) payload.rerank = true;
- if (opts.keyword) payload.keyword_search = true;
if (opts.fields) payload.fields = opts.fields;
const result = (await this._request("POST", "/v2/memories/search/", {
diff --git a/openclaw/cli/config-file.ts b/openclaw/cli/config-file.ts
index 8eacce714..3f9aa2ffe 100644
--- a/openclaw/cli/config-file.ts
+++ b/openclaw/cli/config-file.ts
@@ -19,6 +19,7 @@ export const OPENCLAW_CONFIG_FILE = join(OPENCLAW_CONFIG_DIR, "openclaw.json");
export const DEFAULT_BASE_URL = "https://api.mem0.ai";
const PLUGIN_ID = "openclaw-mem0";
+const NPM_PACKAGE = "@mem0/openclaw-mem0";
// ============================================================================
// Types
@@ -41,16 +42,38 @@ export interface PluginAuthConfig {
// OpenClaw config read/write
// ============================================================================
-/** Read the full ~/.openclaw/openclaw.json */
+/**
+ * Read the full ~/.openclaw/openclaw.json.
+ *
+ * Returns {} only when the file doesn't exist (first-time setup).
+ * Throws on parse errors to prevent writes from destroying existing config.
+ */
function readFullConfig(): Record {
- if (exists(OPENCLAW_CONFIG_FILE)) {
- try {
- return JSON.parse(readText(OPENCLAW_CONFIG_FILE));
- } catch {
- /* ignore parse errors */
- }
+ if (!exists(OPENCLAW_CONFIG_FILE)) {
+ return {};
+ }
+
+ const text = readText(OPENCLAW_CONFIG_FILE);
+
+ // Handle empty or whitespace-only files as first-time setup
+ if (!text.trim()) {
+ return {};
+ }
+
+ try {
+ const parsed = JSON.parse(text);
+ if (parsed === null || typeof parsed !== "object" || Array.isArray(parsed)) {
+ throw new Error("Config is not a JSON object");
+ }
+ return parsed;
+ } catch (err) {
+ // Fail closed: throw so writes don't proceed with empty config
+ const msg = err instanceof Error ? err.message : String(err);
+ throw new Error(
+ `[openclaw-mem0] Failed to parse ${OPENCLAW_CONFIG_FILE}: ${msg}\n` +
+ `Fix the JSON syntax error manually before running config commands.`,
+ );
}
- return {};
}
/** Write the full ~/.openclaw/openclaw.json (preserves all non-plugin config) */
@@ -87,15 +110,7 @@ export function readPluginAuth(): PluginAuthConfig {
export function writePluginAuth(auth: PluginAuthConfig): void {
const full = readFullConfig() as any;
- // Ensure nested structure exists
- if (!full.plugins) full.plugins = {};
- if (!full.plugins.entries) full.plugins.entries = {};
- if (!full.plugins.entries[PLUGIN_ID]) {
- full.plugins.entries[PLUGIN_ID] = { enabled: true, config: {} };
- }
- if (!full.plugins.entries[PLUGIN_ID].config) {
- full.plugins.entries[PLUGIN_ID].config = {};
- }
+ ensurePluginStructure(full);
const cfg = full.plugins.entries[PLUGIN_ID].config;
@@ -107,12 +122,81 @@ export function writePluginAuth(auth: PluginAuthConfig): void {
writeFullConfig(full);
}
-export function writePluginConfigField(
- path: string[],
- value: unknown,
-): void {
- const full = readFullConfig() as any;
+/**
+ * Ensure the plugin has a valid install record and is in plugins.allow.
+ *
+ * OpenClaw's `plugins update` command requires a `plugins.installs.`
+ * record with `source: "npm"` and `spec` to know how to update. Without
+ * this, `openclaw plugins update` prints "No install record" and skips.
+ *
+ * Similarly, if `plugins.allow` exists as an array, the plugin ID must
+ * be in it or OpenClaw treats the plugin as untrusted.
+ *
+ * This is safe to call multiple times — it only writes missing fields.
+ */
+export function ensureInstallRecord(): void {
+ try {
+ const full = readFullConfig() as any;
+ const entry = full?.plugins?.entries?.[PLUGIN_ID];
+ const record = full?.plugins?.installs?.[PLUGIN_ID];
+ const allow = full?.plugins?.allow;
+ if (
+ entry?.enabled === true &&
+ record?.source &&
+ record?.spec &&
+ Array.isArray(allow) &&
+ allow.includes(PLUGIN_ID)
+ ) {
+ return;
+ }
+
+ ensurePluginStructure(full);
+
+ let changed = false;
+
+ // Ensure install record exists for `openclaw plugins update` support
+ if (!full.plugins.installs) full.plugins.installs = {};
+ if (!full.plugins.installs[PLUGIN_ID]) {
+ full.plugins.installs[PLUGIN_ID] = {
+ source: "npm",
+ spec: `${NPM_PACKAGE}@latest`,
+ resolvedName: NPM_PACKAGE,
+ installedAt: new Date().toISOString(),
+ };
+ changed = true;
+ } else {
+ const record = full.plugins.installs[PLUGIN_ID];
+ if (!record.source) {
+ record.source = "npm";
+ changed = true;
+ }
+ if (!record.spec) {
+ record.spec = `${NPM_PACKAGE}@latest`;
+ changed = true;
+ }
+ if (!record.resolvedName) {
+ record.resolvedName = NPM_PACKAGE;
+ changed = true;
+ }
+ }
+
+ if (!Array.isArray(full.plugins.allow)) {
+ full.plugins.allow = [PLUGIN_ID];
+ changed = true;
+ } else if (!full.plugins.allow.includes(PLUGIN_ID)) {
+ full.plugins.allow.push(PLUGIN_ID);
+ changed = true;
+ }
+
+ if (changed) writeFullConfig(full);
+ } catch {
+ // Best-effort — don't break plugin loading if config is unreadable
+ }
+}
+
+/** Ensure the nested plugin entry structure exists in the config object. */
+function ensurePluginStructure(full: any): void {
if (!full.plugins) full.plugins = {};
if (!full.plugins.entries) full.plugins.entries = {};
if (!full.plugins.entries[PLUGIN_ID]) {
@@ -121,6 +205,15 @@ export function writePluginConfigField(
if (!full.plugins.entries[PLUGIN_ID].config) {
full.plugins.entries[PLUGIN_ID].config = {};
}
+}
+
+export function writePluginConfigField(
+ path: string[],
+ value: unknown,
+): void {
+ const full = readFullConfig() as any;
+
+ ensurePluginStructure(full);
let target = full.plugins.entries[PLUGIN_ID].config;
for (let i = 0; i < path.length - 1; i++) {
@@ -139,3 +232,13 @@ export function getBaseUrl(): string {
const auth = readPluginAuth();
return auth.baseUrl || DEFAULT_BASE_URL;
}
+
+/** Remove anonymousTelemetryId from config (after PostHog aliasing) */
+export function clearAnonymousTelemetryId(): void {
+ const full = readFullConfig() as any;
+ const cfg = full?.plugins?.entries?.[PLUGIN_ID]?.config;
+ if (cfg && "anonymousTelemetryId" in cfg) {
+ delete cfg.anonymousTelemetryId;
+ writeFullConfig(full);
+ }
+}
diff --git a/openclaw/config.ts b/openclaw/config.ts
index 32d6a8964..f413dade5 100644
--- a/openclaw/config.ts
+++ b/openclaw/config.ts
@@ -233,20 +233,19 @@ export const mem0ConfigSchema = {
})(),
autoCapture: cfg.autoCapture !== false,
autoRecall: cfg.autoRecall !== false,
+ // v3.0.0: customPrompt renamed to customInstructions (backwards-compat: accept either)
customInstructions:
typeof cfg.customInstructions === "string"
? cfg.customInstructions
- : DEFAULT_CUSTOM_INSTRUCTIONS,
+ : typeof cfg.customPrompt === "string"
+ ? cfg.customPrompt
+ : DEFAULT_CUSTOM_INSTRUCTIONS,
customCategories:
cfg.customCategories &&
typeof cfg.customCategories === "object" &&
!Array.isArray(cfg.customCategories)
? (cfg.customCategories as Record)
: DEFAULT_CUSTOM_CATEGORIES,
- customPrompt:
- typeof cfg.customPrompt === "string"
- ? cfg.customPrompt
- : DEFAULT_CUSTOM_INSTRUCTIONS,
searchThreshold:
typeof cfg.searchThreshold === "number" ? cfg.searchThreshold : 0.5,
topK: typeof cfg.topK === "number" ? cfg.topK : 5,
diff --git a/openclaw/filtering.ts b/openclaw/filtering.ts
index 6137777ab..6c51a2798 100644
--- a/openclaw/filtering.ts
+++ b/openclaw/filtering.ts
@@ -14,9 +14,41 @@ const NOISE_MESSAGE_PATTERNS: RegExp[] = [
/^(HEARTBEAT_OK|NO_REPLY)$/i,
/^Current time:.*\d{4}/,
/^Pre-compaction memory flush/i,
- /^(ok|yes|no|sir|sure|thanks|done|good|nice|cool|got it|it's on|continue)$/i,
+ /^(ok|yes|no|sir|sure|thanks|done|good|nice|cool|got it|it's on|continue|alright|okay|yep|nope|uh-huh|mm-hmm|hmm)$/i,
/^System: \[.*\] (Slack message edited|Gateway restart|Exec (failed|completed))/,
/^System: \[.*\] ⚠️ Post-Compaction Audit:/,
+ // JSON-only messages (tool results, metadata)
+ /^[\s]*\{[\s\S]*\}[\s]*$/,
+ /^[\s]*\[[\s\S]*\][\s]*$/,
+ // Empty or whitespace-only after trimming
+ /^[\s\n\r]*$/,
+ // Technical noise patterns
+ /^(Error|Warning|Info|Debug):/i,
+ /^(Loading|Loaded|Fetching|Fetched|Processing|Processed)\b/i,
+ /^\[[\d:T\-\.Z]+\]/, // Timestamps like [2024-01-01T12:00:00.000Z]
+ /^(SUCCESS|FAILURE|PENDING|COMPLETED|FAILED)$/i,
+ // Tool/function call noise
+ /^(Calling|Called|Invoking|Invoked|Executing|Executed)\s+(function|tool|method)/i,
+ /^Tool (call|result|output):/i,
+ // Single emoji or very short messages
+ /^[\p{Emoji}\s]{1,5}$/u,
+];
+
+/** Patterns for session-specific technical content that should not be stored as memories. */
+const SESSION_SPECIFIC_PATTERNS: RegExp[] = [
+ // Tool availability discussions
+ /tools?\s+(are|is)\s+(not\s+)?(exposed|available|accessible)/i,
+ /plugin\s+(does not|doesn't)\s+expose/i,
+ /I\s+(do not|don't)\s+(currently\s+)?see\s+.*tools?\s+exposed/i,
+ /memory_(search|get|add|update|delete|list)\s+(tool|is|are)/i,
+ // Session-specific capability statements
+ /in\s+this\s+session/i,
+ /my\s+(live\s+)?callable\s+tool\s+registry/i,
+ /tools?\s+I\s+(have|currently have)\s+access\s+to/i,
+ // Plugin/capability status statements
+ /openclaw-mem0\s+plugin/i,
+ /memory\s+wiki.*capability/i,
+ /workspace\s+memory\s+files/i,
];
/** Content fragments that should be stripped from otherwise-valid messages. */
@@ -50,6 +82,31 @@ const NOISE_CONTENT_PATTERNS: Array<{ pattern: RegExp; replacement: string }> =
/Replied message \(untrusted, for context\):\s*```json[\s\S]*?```/g,
replacement: "",
},
+ // Strip embedded JSON blocks that might contain metadata
+ {
+ pattern: /```json\s*\{[\s\S]*?\}\s*```/g,
+ replacement: "",
+ },
+ // Strip code blocks that are just tool outputs
+ {
+ pattern: /```(?:text|output|result|log)\s*[\s\S]*?```/gi,
+ replacement: "",
+ },
+ // Strip inline tool call IDs
+ {
+ pattern: /\[tool_call_id:[^\]]+\]/g,
+ replacement: "",
+ },
+ // Strip memory IDs from responses
+ {
+ pattern: /\(id:\s*[a-f0-9-]+\)/gi,
+ replacement: "",
+ },
+ // Strip session/run IDs
+ {
+ pattern: /(?:session|run|agent)[_-]?(?:id|key)?:\s*[a-zA-Z0-9_:-]+/gi,
+ replacement: "",
+ },
];
const MAX_MESSAGE_LENGTH = 2000;
@@ -81,6 +138,20 @@ export function isNoiseMessage(content: string): boolean {
return NOISE_MESSAGE_PATTERNS.some((p) => p.test(trimmed));
}
+/**
+ * Check whether a message contains session-specific technical content
+ * that should not be stored (tool availability discussions, plugin
+ * capability statements, etc.). These are facts about the current session
+ * that have no value in future sessions.
+ */
+export function isSessionSpecificContent(content: string): boolean {
+ const trimmed = content.trim();
+ if (!trimmed) return false;
+ // Check if multiple session-specific patterns match (more confident filtering)
+ const matches = SESSION_SPECIFIC_PATTERNS.filter((p) => p.test(trimmed));
+ return matches.length >= 2;
+}
+
/**
* Check whether an assistant message is a generic acknowledgment with no
* extractable facts (e.g. "I see you've shared an update. How can I help?").
@@ -120,7 +191,7 @@ function truncateMessage(content: string): string {
/**
* Full pre-extraction pipeline: drop noise messages, strip noise fragments,
- * and truncate remaining messages to a reasonable length.
+ * filter session-specific content, and truncate remaining messages.
*/
export function filterMessagesForExtraction(
messages: Array<{ role: string; content: string }>,
@@ -131,6 +202,8 @@ export function filterMessagesForExtraction(
// Drop generic assistant acknowledgments that contain no facts
if (msg.role === "assistant" && isGenericAssistantMessage(msg.content))
continue;
+ // Drop session-specific technical content (tool availability, plugin capabilities)
+ if (isSessionSpecificContent(msg.content)) continue;
const cleaned = stripNoiseFromContent(msg.content);
if (!cleaned) continue;
filtered.push({ role: msg.role, content: truncateMessage(cleaned) });
diff --git a/openclaw/index.ts b/openclaw/index.ts
index 0ec92f30b..1dd99021b 100644
--- a/openclaw/index.ts
+++ b/openclaw/index.ts
@@ -28,6 +28,7 @@ import type {
import { createProvider, providerToBackend } from "./providers.ts";
import { mem0ConfigSchema } from "./config.ts";
import type { FileConfig } from "./config.ts";
+import { createPublicArtifactsProvider } from "./public-artifacts.ts";
import { filterMessagesForExtraction } from "./filtering.ts";
import {
effectiveUserId,
@@ -53,13 +54,14 @@ import {
import { PlatformBackend } from "./backend/platform.ts";
import type { Backend } from "./backend/base.ts";
import { registerCliCommands } from "./cli/commands.ts";
-import { readPluginAuth } from "./cli/config-file.ts";
+import { readPluginAuth, ensureInstallRecord } from "./cli/config-file.ts";
import { registerAllTools } from "./tools/index.ts";
import type { ToolDeps } from "./tools/index.ts";
import { captureEvent } from "./telemetry.ts";
import { bootstrapTelemetryFlag } from "./fs-safe.ts";
bootstrapTelemetryFlag();
+ensureInstallRecord();
// ============================================================================
// Re-exports (for tests and external consumers)
@@ -76,6 +78,7 @@ export {
export {
isNoiseMessage,
isGenericAssistantMessage,
+ isSessionSpecificContent,
stripNoiseFromContent,
filterMessagesForExtraction,
} from "./filtering.ts";
@@ -191,24 +194,43 @@ const memoryPlugin = definePluginEntry({
`openclaw-mem0: registered (mode: ${cfg.mode}, user: ${cfg.userId}, autoRecall: ${cfg.autoRecall}, autoCapture: ${cfg.autoCapture}, skills: ${skillsActive})`,
);
+ // ========================================================================
+ // Public Artifacts (for memory-wiki bridge mode)
+ // ========================================================================
+ if (typeof api.registerMemoryCapability === "function") {
+ api.registerMemoryCapability({
+ publicArtifacts: createPublicArtifactsProvider({
+ provider,
+ cfg,
+ get stateDir() {
+ return pluginStateDir;
+ },
+ effectiveUserId: _effectiveUserId,
+ }),
+ });
+ api.logger.debug("openclaw-mem0: publicArtifacts capability registered");
+ }
+
// Helper: build add options
function buildAddOptions(
userIdOverride?: string,
runId?: string,
sessionKey?: string,
): AddOptions {
+ // v3.0.0: removed output_format, customPrompt renamed to customInstructions
const opts: AddOptions = {
user_id: userIdOverride || _effectiveUserId(sessionKey),
source: "OPENCLAW",
};
if (runId) opts.run_id = runId;
- if (cfg.mode === "platform") {
- opts.output_format = "v1.1";
- }
+ // Pass customInstructions and customCategories to control what Mem0 extracts
+ if (cfg.customInstructions) opts.custom_instructions = cfg.customInstructions;
+ if (cfg.customCategories) opts.custom_categories = cfg.customCategories;
return opts;
}
// Helper: build search options (skills config overrides legacy defaults)
+ // v3.0.0: removed keyword_search, reranking, filter_memories, limit
function buildSearchOptions(
userIdOverride?: string,
limit?: number,
@@ -219,13 +241,9 @@ const memoryPlugin = definePluginEntry({
const opts: SearchOptions = {
user_id: userIdOverride || _effectiveUserId(sessionKey),
top_k: limit ?? cfg.topK,
- limit: limit ?? cfg.topK,
threshold: recallCfg?.threshold ?? cfg.searchThreshold,
- keyword_search: recallCfg?.keywordSearch !== false,
- reranking: recallCfg?.rerank !== false,
source: "OPENCLAW",
};
- if (recallCfg?.filterMemories) opts.filter_memories = true;
if (runId) opts.run_id = runId;
return opts;
}
@@ -950,7 +968,7 @@ function registerHooks(
content: `Current date: ${timestamp}. The user is identified as "${cfg.userId}". Extract durable facts from this conversation. Include this date when storing time-sensitive information.`,
});
- const addOpts = buildAddOptions(undefined, sessionId, sessionId);
+ const addOpts = buildAddOptions(undefined, undefined, sessionId);
const captureStart = Date.now();
provider
.add(formattedMessages, addOpts)
diff --git a/openclaw/openclaw-plugin-sdk.d.ts b/openclaw/openclaw-plugin-sdk.d.ts
index 95d923fb3..7484e4813 100644
--- a/openclaw/openclaw-plugin-sdk.d.ts
+++ b/openclaw/openclaw-plugin-sdk.d.ts
@@ -1,4 +1,29 @@
declare module "openclaw/plugin-sdk" {
+ export interface MemoryArtifact {
+ id: string;
+ type: "memory" | "dream" | "digest" | "entity";
+ title: string;
+ content: string;
+ metadata?: Record;
+ createdAt?: string;
+ updatedAt?: string;
+ }
+
+ export interface PublicArtifactsProvider {
+ listArtifacts(options?: {
+ userId?: string;
+ types?: string[];
+ limit?: number;
+ }): Promise;
+ }
+
+ export interface MemoryCapabilityConfig {
+ promptBuilder?: (ctx: any) => Promise;
+ flushPlanResolver?: (ctx: any) => Promise;
+ runtime?: Record;
+ publicArtifacts?: PublicArtifactsProvider;
+ }
+
export interface OpenClawPluginApi {
pluginConfig: Record;
logger: {
@@ -32,6 +57,7 @@ declare module "openclaw/plugin-sdk" {
start: (...args: any[]) => void;
stop: () => void;
}): void;
+ registerMemoryCapability?(config: MemoryCapabilityConfig): void;
[key: string]: unknown;
}
}
diff --git a/openclaw/openclaw.plugin.json b/openclaw/openclaw.plugin.json
index 5bafe6d46..09a2d1105 100644
--- a/openclaw/openclaw.plugin.json
+++ b/openclaw/openclaw.plugin.json
@@ -1,10 +1,17 @@
{
"id": "openclaw-mem0",
"name": "Memory (Mem0)",
- "description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source. Injects recalled memories into agent context (auto-recall) and extracts facts after each turn (auto-capture). Both configurable via autoRecall/autoCapture settings.",
- "version": "1.0.5",
+ "description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source. PLATFORM MODE: Sends conversation data to mem0.ai cloud (requires MEM0_API_KEY). OPEN-SOURCE MODE: Stores vectors locally (~/.mem0/history.db) but uses external APIs for embeddings/LLM (default: OpenAI, requires OPENAI_API_KEY). Auto-recall injects memories before agent turns; auto-capture extracts facts after turns. Both configurable via autoRecall/autoCapture settings. Config stored in ~/.openclaw/openclaw.json.",
+ "version": "1.0.7",
"kind": "memory",
"skills": ["skills"],
+ "commandAliases": [
+ {
+ "name": "mem0",
+ "cliCommand": "mem0",
+ "description": "Mem0 memory plugin commands"
+ }
+ ],
"contracts": {
"tools": [
"memory_search", "memory_add", "memory_get", "memory_list",
@@ -13,7 +20,7 @@
},
"providerAuthEnvVars": {
"mem0": ["MEM0_API_KEY"],
- "openclaw-mem0-oss": ["OPENAI_API_KEY"]
+ "openclaw-mem0-oss": ["OPENAI_API_KEY", "ANTHROPIC_API_KEY", "AZURE_OPENAI_API_KEY", "COHERE_API_KEY"]
},
"providerAuthChoices": [
{
@@ -176,6 +183,9 @@
},
"historyDbPath": {
"type": "string"
+ },
+ "disableHistory": {
+ "type": "boolean"
}
}
},
diff --git a/openclaw/package.json b/openclaw/package.json
index 47e5a0edd..a7aa6a325 100644
--- a/openclaw/package.json
+++ b/openclaw/package.json
@@ -1,6 +1,6 @@
{
"name": "@mem0/openclaw-mem0",
- "version": "1.0.6",
+ "version": "1.0.7",
"type": "module",
"description": "Mem0 memory backend for OpenClaw — platform or self-hosted open-source",
"license": "Apache-2.0",
@@ -35,7 +35,7 @@
},
"dependencies": {
"@sinclair/typebox": "0.34.47",
- "mem0ai": "2.4.5"
+ "mem0ai": "3.0.1"
},
"openclaw": {
"extensions": [
@@ -48,6 +48,9 @@
"build": {
"openclawVersion": "2026.4.1",
"pluginSdkVersion": "2026.4.1"
+ },
+ "install": {
+ "npmSpec": "@mem0/openclaw-mem0"
}
},
"devDependencies": {
diff --git a/openclaw/pnpm-lock.yaml b/openclaw/pnpm-lock.yaml
index 8c5f7ac89..bdd3abd38 100644
--- a/openclaw/pnpm-lock.yaml
+++ b/openclaw/pnpm-lock.yaml
@@ -12,8 +12,8 @@ importers:
specifier: 0.34.47
version: 0.34.47
mem0ai:
- specifier: 2.4.5
- version: 2.4.5(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.0)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260313.1)(@google/genai@1.45.0)(@langchain/core@0.3.80(openai@4.104.0(ws@8.19.0)(zod@3.25.76)))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.13.0(typescript@5.9.3))(@supabase/supabase-js@2.99.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.8.0)(cloudflare@4.5.0)(groq-sdk@0.3.0)(neo4j-driver@5.28.3)(ollama@0.5.18)(pg@8.11.3)(redis@4.7.1)(ws@8.19.0)
+ specifier: 3.0.1
+ version: 3.0.1(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.0)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260313.1)(@google/genai@1.45.0)(@langchain/core@0.3.80(openai@4.104.0(ws@8.19.0)(zod@3.25.76)))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.13.0(typescript@5.9.3))(@supabase/supabase-js@2.99.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.8.0)(cloudflare@4.5.0)(compromise@14.15.0)(groq-sdk@0.3.0)(natural@8.1.1)(ollama@0.5.18)(pg@8.20.0)(redis@5.12.1)(ws@8.19.0)
devDependencies:
'@types/node':
specifier: ^22.15.0
@@ -340,6 +340,9 @@ packages:
'@mistralai/mistralai@1.15.1':
resolution: {integrity: sha512-fb995eiz3r0KsBGtRjFV+/iLbX+UpfalxpF+YitT3R6ukrPD4PN+FGwwmYcRFhNAzVzDUtTVxQYnjQWEnwV5nw==}
+ '@mongodb-js/saslprep@1.4.8':
+ resolution: {integrity: sha512-kpjr2jy2w71w0oqAMI8oibBmiF9lXxWkEQs5gMkW4hVE48bsqINGLxnCSYW62ck/NHXJQpQEfA9WlJ1sY0eqBg==}
+
'@napi-rs/wasm-runtime@1.1.1':
resolution: {integrity: sha512-p64ah1M1ld8xjWv3qbvFwHiFVWrq1yFvV4f7w+mzaqiR4IlSgkqhcRdHwsGgomwzBH51sRY4NEowLxnaBjcW/A==}
@@ -394,34 +397,41 @@ packages:
resolution: {integrity: sha512-oQG/FejNpItrxRHoyctYvT3rwGZOnK4jr3JdppO/c78ktDvkWiPXPHNsrDf33K9sZdRb6PR7gi4noIapu5q4HA==}
engines: {node: '>=18.0.0', pnpm: '>=8'}
- '@redis/bloom@1.2.0':
- resolution: {integrity: sha512-HG2DFjYKbpNmVXsa0keLHp/3leGJz1mjh09f2RLGGLQZzSHpkmZWuwJbAvo3QcRY8p80m5+ZdXZdYOSBLlp7Cg==}
+ '@redis/bloom@5.12.1':
+ resolution: {integrity: sha512-PUUfv+ms7jgPSBVoo/DN4AkPHj4D5TZSd6SbJX7egzBplkYUcKmHRE8RKia7UtZ8bSQbLguLvxVO+asKtQfZWA==}
+ engines: {node: '>= 18.19.0'}
peerDependencies:
- '@redis/client': ^1.0.0
+ '@redis/client': ^5.12.1
- '@redis/client@1.6.1':
- resolution: {integrity: sha512-/KCsg3xSlR+nCK8/8ZYSknYxvXHwubJrU82F3Lm1Fp6789VQ0/3RJKfsmRXjqfaTA++23CvC3hqmqe/2GEt6Kw==}
- engines: {node: '>=14'}
-
- '@redis/graph@1.1.1':
- resolution: {integrity: sha512-FEMTcTHZozZciLRl6GiiIB4zGm5z5F3F6a6FZCyrfxdKOhFlGkiAqlexWMBzCi4DcRoyiOsuLfW+cjlGWyExOw==}
+ '@redis/client@5.12.1':
+ resolution: {integrity: sha512-7aPGWeqA3uFm43o19umzdl16CEjK/JQGtSXVPevplTaOU3VJA/rseBC1QvYUz9lLDIMBimc4SW/zrW4S89BaCA==}
+ engines: {node: '>= 18.19.0'}
peerDependencies:
- '@redis/client': ^1.0.0
+ '@node-rs/xxhash': ^1.1.0
+ '@opentelemetry/api': '>=1 <2'
+ peerDependenciesMeta:
+ '@node-rs/xxhash':
+ optional: true
+ '@opentelemetry/api':
+ optional: true
- '@redis/json@1.0.7':
- resolution: {integrity: sha512-6UyXfjVaTBTJtKNG4/9Z8PSpKE6XgSyEb8iwaqDcy+uKrd/DGYHTWkUdnQDyzm727V7p21WUMhsqz5oy65kPcQ==}
+ '@redis/json@5.12.1':
+ resolution: {integrity: sha512-eOze75esLve4vfqDel7aMX08CNaiLLQS2fV8mpRN9NxPe1rVR4vQyYiW/OgtGUysF6QOr9ANhfxABKNOJfXdKg==}
+ engines: {node: '>= 18.19.0'}
peerDependencies:
- '@redis/client': ^1.0.0
+ '@redis/client': ^5.12.1
- '@redis/search@1.2.0':
- resolution: {integrity: sha512-tYoDBbtqOVigEDMAcTGsRlMycIIjwMCgD8eR2t0NANeQmgK/lvxNAvYyb6bZDD4frHRhIHkJu2TBRvB0ERkOmw==}
+ '@redis/search@5.12.1':
+ resolution: {integrity: sha512-ItlxbxC9cKI6IU1TLWoczwJCRb6TdmkEpWv05UrPawqaAnWGRu3rcIqsc5vN483T2fSociuyV1UkWIL5I4//2w==}
+ engines: {node: '>= 18.19.0'}
peerDependencies:
- '@redis/client': ^1.0.0
+ '@redis/client': ^5.12.1
- '@redis/time-series@1.1.0':
- resolution: {integrity: sha512-c1Q99M5ljsIuc4YdaCwfUEXsofakb9c8+Zse2qxTadu8TalLXuAESzLvFAvNVbkmSlvlzIQOLpBCmWI9wTOt+g==}
+ '@redis/time-series@5.12.1':
+ resolution: {integrity: sha512-c6JL6E3EcZJuNqKFz+KM+l9l5mpcQiKvTwgA3blt5glWJ8hjDk0yeHN3beE/MpqYIQ8UEX44ItQzgkE/gCBELQ==}
+ engines: {node: '>= 18.19.0'}
peerDependencies:
- '@redis/client': ^1.0.0
+ '@redis/client': ^5.12.1
'@rolldown/binding-android-arm64@1.0.0-rc.9':
resolution: {integrity: sha512-lcJL0bN5hpgJfSIz/8PIf02irmyL43P+j1pTCfbD1DbLkmGRuFIA4DD3B3ZOvGqG0XiVvRznbKtN0COQVaKUTg==}
@@ -724,6 +734,12 @@ packages:
'@types/uuid@10.0.0':
resolution: {integrity: sha512-7gqG38EyHgyP1S+7+xomFtL+ZNHcKv6DwNaCZmJmo1vgMugyF3TCnXVg4t1uk89mLNwnLtnY3TpOpCOyp1/xHQ==}
+ '@types/webidl-conversions@7.0.3':
+ resolution: {integrity: sha512-CiJJvcRtIgzadHCYXw7dqEnMNRjhGZlYK05Mj9OyktqV8uVT8fD2BFOB7S1uwBE3Kj2Z+4UyPmFw/Ixgw/LAlA==}
+
+ '@types/whatwg-url@13.0.0':
+ resolution: {integrity: sha512-N8WXpbE6Wgri7KUSvrmQcqrMllKZ9uxkYWMt+mCSGwNc0Hsw9VQTW7ApqI4XNrx6/SaM2QQJCzMPDEXE058s+Q==}
+
'@types/ws@8.18.1':
resolution: {integrity: sha512-ThVF6DCVhA8kUGy+aazFQ4kXQ7E1Ty7A3ypFOe0IcJV8O/M511G99AW24irKrW56Wt44yG9+ij8FaqoBGkuBXg==}
@@ -784,6 +800,12 @@ packages:
engines: {node: '>=0.4.0'}
hasBin: true
+ afinn-165-financialmarketnews@3.0.0:
+ resolution: {integrity: sha512-0g9A1S3ZomFIGDTzZ0t6xmv4AuokBvBmpes8htiyHpH7N4xDmvSQL6UxL/Zcs2ypRb3VwgCscaD8Q3zEawKYhw==}
+
+ afinn-165@2.0.2:
+ resolution: {integrity: sha512-mJ/RLUfpXfQA6bzugv+bBsc/QYkVrKaLYeS8fWBpKbTCsonv4iuV9ET0fgReEunm9vKLkaNgnekuSNlTC3WQ1Q==}
+
agent-base@7.1.4:
resolution: {integrity: sha512-MnA+YT8fwfJPgBx3m60MNqakm30XOkyIoH1y6huTQvC0PwZG7ki8NacLBcrPbNoo8vEZy7Jpuk7+jMO+CUovTQ==}
engines: {node: '>= 14'}
@@ -815,6 +837,10 @@ packages:
any-promise@1.3.0:
resolution: {integrity: sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==}
+ apparatus@0.0.10:
+ resolution: {integrity: sha512-KLy/ugo33KZA7nugtQ7O0E1c8kQ52N3IvD/XgIh4w/Nr28ypfkwDfA67F1ev4N1m5D+BOk1+b2dEJDfpj/VvZg==}
+ engines: {node: '>=0.2.6'}
+
assertion-error@2.0.1:
resolution: {integrity: sha512-Izi8RQcffqCeNVgFigKli1ssklIbpHnCYc6AknXGYoB6grJqyeby7jv12JUQgmTAnIDnbck1uxksT4dzN3PWBA==}
engines: {node: '>=12'}
@@ -857,19 +883,16 @@ packages:
resolution: {integrity: sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==}
engines: {node: '>=8'}
+ bson@7.2.0:
+ resolution: {integrity: sha512-YCEo7KjMlbNlyHhz7zAZNDpIpQbd+wOEHJYezv0nMYTn4x31eIUM2yomNNubclAt63dObUzKHWsBLJ9QcZNSnQ==}
+ engines: {node: '>=20.19.0'}
+
buffer-equal-constant-time@1.0.1:
resolution: {integrity: sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==}
- buffer-writer@2.0.0:
- resolution: {integrity: sha512-a7ZpuTZU1TRtnwyCNW3I5dc0wWNC3VR9S++Ewyk2HHZdrO3CQJqSpd+95Us590V6AL7JqUAH2IwZ/398PmNFgw==}
- engines: {node: '>=4'}
-
buffer@5.7.1:
resolution: {integrity: sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==}
- buffer@6.0.3:
- resolution: {integrity: sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==}
-
bundle-name@4.1.0:
resolution: {integrity: sha512-tjwM5exMg6BGRI+kNmTntNsvdZS1X8BFYS6tnJ2hdH0kVxM6/eVZ2xy+FqStSWvYmtfFMDLIxurorHwDKfDz5Q==}
engines: {node: '>=18'}
@@ -936,6 +959,10 @@ packages:
resolution: {integrity: sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==}
engines: {node: '>= 6'}
+ compromise@14.15.0:
+ resolution: {integrity: sha512-YEMv5JGWyqRJw5hdZqDVQF3MMlHA6TRiXreR8IYffk6xB7GA5p/8DeDzvg0Jy2tHNGpD+qJGl0+oJwA+5R/sVA==}
+ engines: {node: '>=12.0.0'}
+
confbox@0.1.8:
resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==}
@@ -1008,6 +1035,10 @@ packages:
digest-fetch@1.3.0:
resolution: {integrity: sha512-CGJuv6iKNM7QyZlM2T3sPAdZWd/p9zQiRNS9G+9COUCwzWFTs0Xp8NF5iePx7wtvhDykReiRRrSeNb4oMmB8lA==}
+ dotenv@17.4.2:
+ resolution: {integrity: sha512-nI4U3TottKAcAD9LLud4Cb7b2QztQMUEfHbvhTH09bqXTxnSie8WnjPALV/WMCrJZ6UV/qHJ6L03OqO3LcdYZw==}
+ engines: {node: '>=12'}
+
dunder-proto@1.0.1:
resolution: {integrity: sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==}
engines: {node: '>= 0.4'}
@@ -1018,6 +1049,10 @@ packages:
ecdsa-sig-formatter@1.0.11:
resolution: {integrity: sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==}
+ efrt@2.7.0:
+ resolution: {integrity: sha512-/RInbCy1d4P6Zdfa+TMVsf/ufZVotat5hCw3QXmWtjU+3pFEOvOQ7ibo3aIxyCJw2leIeAMjmPj+1SLJiCpdrQ==}
+ engines: {node: '>=12.0.0'}
+
emoji-regex@8.0.0:
resolution: {integrity: sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==}
@@ -1154,10 +1189,6 @@ packages:
resolution: {integrity: sha512-zV/5HKTfCeKWnxG0Dmrw51hEWFGfcF2xiXqcA3+J90WDuP0SvoiSO5ORvcBsifmx/FoIjgQN3oNOGaQ5PhLFkg==}
engines: {node: '>=18'}
- generic-pool@3.9.0:
- resolution: {integrity: sha512-hymDOu5B53XvN4QT9dBmZxPX4CWhBPPLguTZ9MMFeFa/Kg0xWVfylOVNlJji/E7yTZWFd/q9GO5TxDLq156D7g==}
- engines: {node: '>= 4'}
-
get-intrinsic@1.3.0:
resolution: {integrity: sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==}
engines: {node: '>= 0.4'}
@@ -1189,6 +1220,10 @@ packages:
graceful-fs@4.2.11:
resolution: {integrity: sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==}
+ grad-school@0.0.5:
+ resolution: {integrity: sha512-rXunEHF9M9EkMydTBux7+IryYXEZinRk6g8OBOGDBzo/qWJjhTxy86i5q7lQYpCLHN8Sqv1XX3OIOc7ka2gtvQ==}
+ engines: {node: '>=8.0.0'}
+
groq-sdk@0.3.0:
resolution: {integrity: sha512-Cdgjh4YoSBE2X4S9sxPGXaAy1dlN4bRtAaDZ3cnq+XsxhhN9WSBeHF64l7LWwuD5ntmw7YC5Vf4Ff1oHCg1LOg==}
@@ -1324,6 +1359,10 @@ packages:
jws@4.0.1:
resolution: {integrity: sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==}
+ kareem@3.2.0:
+ resolution: {integrity: sha512-VS8MWZz/cT+SqBCpVfNN4zoVz5VskR3N4+sTmUXme55e9avQHntpwpNq0yjnosISXqwJ3AQVjlbI4Dyzv//JtA==}
+ engines: {node: '>=18.0.0'}
+
langsmith@0.3.87:
resolution: {integrity: sha512-XXR1+9INH8YX96FKWc5tie0QixWz6tOqAsAKfcJyPkE0xPep+NDz0IQLR32q4bn10QK3LqD2HN6T3n6z1YLW7Q==}
peerDependencies:
@@ -1466,8 +1505,8 @@ packages:
md5@2.3.0:
resolution: {integrity: sha512-T1GITYmFaKuO91vxyoQMFETst+O71VUPEU3ze5GNzDm0OWdP8v1ziTaAEPUr/3kLsY3Sftgz242A1SetQiDL7g==}
- mem0ai@2.4.5:
- resolution: {integrity: sha512-0XMRe5/KKZXkSJDb2YDqgWdIfJpL2cn8Lelx9pvDPJOvBfXx8uHjtSc80jyGyb9C/1wW2uPcaE+WS+Rs/2phCg==}
+ mem0ai@3.0.1:
+ resolution: {integrity: sha512-6phM544/3NRcCg7n5DBNRc9uUEMqGTe7sksULM2KM/FTihH27yTl5nPms8cnNpSKM5/ZKJ9jnkpzwGlvYYbtBQ==}
engines: {node: '>=18'}
peerDependencies:
'@anthropic-ai/sdk': ^0.40.1
@@ -1483,12 +1522,20 @@ packages:
'@types/pg': 8.11.0
better-sqlite3: ^12.6.2
cloudflare: ^4.2.0
+ compromise: ^14.0.0
groq-sdk: 0.3.0
- neo4j-driver: ^5.28.1
+ natural: ^8.0.1
ollama: ^0.5.14
pg: 8.11.3
redis: ^4.6.13
+ memjs@1.3.2:
+ resolution: {integrity: sha512-qUEg2g8vxPe+zPn09KidjIStHPtoBO8Cttm8bgJFWWabbsjQ9Av9Ky+6UcvKx6ue0LLb/LEhtcyQpRyKfzeXcg==}
+ engines: {node: '>=0.10.0'}
+
+ memory-pager@1.5.0:
+ resolution: {integrity: sha512-ZS4Bp4r/Zoeq6+NLJpP+0Zzm0pR8whtGPf1XExKLJBAczGMnSi3It14OiNCStjQjM6NU1okjQGSxgEZN8eBYKg==}
+
micromatch@4.0.8:
resolution: {integrity: sha512-PXwfBhYu0hBCPw8Dn0E+WDYb7af3dSLVWKi3HGv84IdF4TyFoC0ysxFd0Goxw7nSv4T/PzEJQxsYsEiFCKo2BA==}
engines: {node: '>=8.6'}
@@ -1522,6 +1569,49 @@ packages:
mlly@1.8.1:
resolution: {integrity: sha512-SnL6sNutTwRWWR/vcmCYHSADjiEesp5TGQQ0pXyLhW5IoeibRlF/CbSLailbB3CNqJUk9cVJ9dUDnbD7GrcHBQ==}
+ mongodb-connection-string-url@7.0.1:
+ resolution: {integrity: sha512-h0AZ9A7IDVwwHyMxmdMXKy+9oNlF0zFoahHiX3vQ8e3KFcSP3VmsmfvtRSuLPxmyv2vjIDxqty8smTgie/SNRQ==}
+ engines: {node: '>=20.19.0'}
+
+ mongodb@7.1.1:
+ resolution: {integrity: sha512-067DXiMjcpYQl6bGjWQoTUEE9UoRViTtKFcoqX7z08I+iDZv/emH1g8XEFiO3qiDfXAheT5ozl1VffDTKhIW/w==}
+ engines: {node: '>=20.19.0'}
+ peerDependencies:
+ '@aws-sdk/credential-providers': ^3.806.0
+ '@mongodb-js/zstd': ^7.0.0
+ gcp-metadata: ^7.0.1
+ kerberos: ^7.0.0
+ mongodb-client-encryption: '>=7.0.0 <7.1.0'
+ snappy: ^7.3.2
+ socks: ^2.8.6
+ peerDependenciesMeta:
+ '@aws-sdk/credential-providers':
+ optional: true
+ '@mongodb-js/zstd':
+ optional: true
+ gcp-metadata:
+ optional: true
+ kerberos:
+ optional: true
+ mongodb-client-encryption:
+ optional: true
+ snappy:
+ optional: true
+ socks:
+ optional: true
+
+ mongoose@9.4.1:
+ resolution: {integrity: sha512-4rFBWa+/wdBQSfvnOPJBpiSG6UCEbhSQh865dEdaH9Y8WfHBUC+I2XT28dp0IBIGrEwmh+gzrgZgea5PbmrHWA==}
+ engines: {node: '>=20.19.0'}
+
+ mpath@0.9.0:
+ resolution: {integrity: sha512-ikJRQTk8hw5DEoFVxHG1Gn9T/xcjtdnOKIU1JTmGjZZlg9LST2mBLmcX3/ICIbgJydT2GOc15RnNy5mHmzfSew==}
+ engines: {node: '>=4.0.0'}
+
+ mquery@6.0.0:
+ resolution: {integrity: sha512-b2KQNsmgtkscfeDgkYMcWGn9vZI9YoXh802VDEwE6qc50zxBFQ0Oo8ROkawbPAsXCY1/Z1yp0MagqsZStPWJjw==}
+ engines: {node: '>=20.19.0'}
+
ms@2.1.3:
resolution: {integrity: sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==}
@@ -1540,14 +1630,9 @@ packages:
napi-build-utils@2.0.0:
resolution: {integrity: sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==}
- neo4j-driver-bolt-connection@5.28.3:
- resolution: {integrity: sha512-wqHBYcU0FVRDmdsoZ+Fk0S/InYmu9/4BT6fPYh45Jimg/J7vQBUcdkiHGU7nop7HRb1ZgJmL305mJb6g5Bv35Q==}
-
- neo4j-driver-core@5.28.3:
- resolution: {integrity: sha512-Jk+hAmjFmO5YzVH/U7FyKXigot9zmIfLz6SZQy0xfr4zfTE/S8fOYFOGqKQTHBE86HHOWH2RbTslbxIb+XtU2g==}
-
- neo4j-driver@5.28.3:
- resolution: {integrity: sha512-k7c0wEh3HoONv1v5AyLp9/BDAbYHJhz2TZvzWstSEU3g3suQcXmKEaYBfrK2UMzxcy3bCT0DrnfRbzsOW5G/Ag==}
+ natural@8.1.1:
+ resolution: {integrity: sha512-Ucb+lsUcGxUqu3rn8cwHjT6gJQosO63nIX/aBQXB3+IDkNbFV7PuviysO+Rzz3aKn7PZhPj3bNF4PS9gDVjYCQ==}
+ engines: {node: '>=0.4.10'}
node-abi@3.88.0:
resolution: {integrity: sha512-At6b4UqIEVudaqPsXjmUO1r/N5BUr4yhDGs5PkBE8/oG5+TfLPhFechiskFsnT6Ql0VfUXbalUUCbfXxtj7K+w==}
@@ -1622,9 +1707,6 @@ packages:
package-json-from-dist@1.0.1:
resolution: {integrity: sha512-UEZIS3/by4OC8vL3P2dTXRETpebLI2NiI5vIrjaD/5UtrkFX/tNbwjTSRAGC/+7CAo2pIcBaRgWmcBBHcsaCIw==}
- packet-reader@1.0.0:
- resolution: {integrity: sha512-HAKu/fG3HpHFO0AA8WE8q2g+gBJaZ9MG7fcKk+IJPLTGAD6Psw4443l+9DGRbOIh3/aXr7Phy0TjilYivJo5XQ==}
-
path-key@3.1.1:
resolution: {integrity: sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==}
engines: {node: '>=8'}
@@ -1666,9 +1748,9 @@ packages:
resolution: {integrity: sha512-o2XFanIMy/3+mThw69O8d4n1E5zsLhdO+OPqswezu7Z5ekP4hYDqlDjlmOpYMbzY2Br0ufCwJLdDIXeNVwcWFg==}
engines: {node: '>=10'}
- pg@8.11.3:
- resolution: {integrity: sha512-+9iuvG8QfaaUrrph+kpF24cXkH1YOOUeArRNYIxq1viYHZagBxrTno7cecY1Fa44tJeZvaoG+Djpkc3JwehN5g==}
- engines: {node: '>= 8.0.0'}
+ pg@8.20.0:
+ resolution: {integrity: sha512-ldhMxz2r8fl/6QkXnBD3CR9/xg694oT6DZQ2s6c/RI28OjtSOpxnPrUCGOBJ46RCUxcWdx3p6kw/xnDHjKvaRA==}
+ engines: {node: '>= 16.0.0'}
peerDependencies:
pg-native: '>=3.0.1'
peerDependenciesMeta:
@@ -1773,6 +1855,10 @@ packages:
pump@3.0.4:
resolution: {integrity: sha512-VS7sjc6KR7e1ukRFhQSY5LM2uBWAUPiOPa/A3mkKmiMwSmRFUITt0xuj+/lesgnCv+dPIEYlkzrcyXgquIHMcA==}
+ punycode@2.3.1:
+ resolution: {integrity: sha512-vYt7UD1U9Wg6138shLtLOvdAu+8DsC/ilFtEVHcH+wydcSpNE20AfSOduf6MkRFahL5FY7X1oU7nKVZFtfq8Fg==}
+ engines: {node: '>=6'}
+
rc@1.2.8:
resolution: {integrity: sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==}
hasBin: true
@@ -1788,8 +1874,9 @@ packages:
resolution: {integrity: sha512-GDhwkLfywWL2s6vEjyhri+eXmfH6j1L7JE27WhqLeYzoh/A3DBaYGEj2H/HFZCn/kMfim73FXxEJTw06WtxQwg==}
engines: {node: '>= 14.18.0'}
- redis@4.7.1:
- resolution: {integrity: sha512-S1bJDnqLftzHXHP8JsT5II/CtHWQrASX5K96REjWjlmWKrviSOLWmM7QnRLstAWsu1VBBV1ffV6DzCvxNP0UJQ==}
+ redis@5.12.1:
+ resolution: {integrity: sha512-LDsoVvb/CpoV9EN3FXvgvSHNJWuCIzl9MiO3ppOevuGLpSGJhwfQjpEwfFJcQvNSddHADDdZaWx0HnmMxRXG7g==}
+ engines: {node: '>= 18.19.0'}
resolve-from@5.0.0:
resolution: {integrity: sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==}
@@ -1817,12 +1904,13 @@ packages:
resolution: {integrity: sha512-DPe5pVFaAsinSaV6QjQ6gdiedWDcRCbUuiQfQa2wmWV7+xC9bGulGI8+TdRmoFkAPaBXk8CrAbnlY2ISniJ47Q==}
engines: {node: '>=18'}
- rxjs@7.8.2:
- resolution: {integrity: sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==}
-
safe-buffer@5.2.1:
resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==}
+ safe-stable-stringify@2.5.0:
+ resolution: {integrity: sha512-b3rppTKm9T+PsVCBEOUR46GWI7fdOs00VKZ1+9c1EWDaDMvjQc6tUwuFyIprgGgTcWoVHSKrU8H31ZHA2e0RHA==}
+ engines: {node: '>=10'}
+
semver@7.7.4:
resolution: {integrity: sha512-vFKC2IEtQnVhpT78h1Yp8wzwrf8CM+MzKMHGJZfBtzhZNycRFnXsHk6E5TxIkkMsgNS7mdX3AGB7x2QM2di4lA==}
engines: {node: '>=10'}
@@ -1836,6 +1924,9 @@ packages:
resolution: {integrity: sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==}
engines: {node: '>=8'}
+ sift@17.1.3:
+ resolution: {integrity: sha512-Rtlj66/b0ICeFzYTuNvX/EF1igRbbnGSvEyT79McoZa/DeGhMyC5pWKOEsZKnpkqtSeovd5FL/bjHWC3CIIvCQ==}
+
siginfo@2.0.0:
resolution: {integrity: sha512-ybx0WO1/8bSBLEWXZvEd7gMW3Sn3JFlW3TvX1nREbDLRNQNaeNN8WK0meBwPdAaOI7TtRRRJn/Es1zhrrCHu7g==}
@@ -1864,6 +1955,9 @@ packages:
resolution: {integrity: sha512-i5uvt8C3ikiWeNZSVZNWcfZPItFQOsYTUAOkcUPGd8DqDy1uOUikjt5dG+uRlwyvR108Fb9DOd4GvXfT0N2/uQ==}
engines: {node: '>= 12'}
+ sparse-bitfield@3.0.3:
+ resolution: {integrity: sha512-kvzhi7vqKTfkh0PZU+2D2PIllw2ymqJKujUcyPMd9Y75Nv4nPbGJZXNhxsgdQab2BmlDct1YnfQCguEvHr7VsQ==}
+
split2@4.2.0:
resolution: {integrity: sha512-UcjcJOWknrNkF6PLX83qcHM6KHgVKNkV62Y8a5uYDVv9ydGQVwAHMKqHdJje1VTWpljG0WYpCDhrCdAOYH4TWg==}
engines: {node: '>= 10.x'}
@@ -1878,6 +1972,10 @@ packages:
std-env@4.0.0:
resolution: {integrity: sha512-zUMPtQ/HBY3/50VbpkupYHbRroTRZJPRLvreamgErJVys0ceuzMkD44J/QjqhHjOzK42GQ3QZIeFG1OYfOtKqQ==}
+ stopwords-iso@1.1.0:
+ resolution: {integrity: sha512-I6GPS/E0zyieHehMRPQcqkiBMJKGgLta+1hREixhoLPqEA0AlVFiC43dl8uPpmkkeRdDMzYRWFWk5/l9x7nmNg==}
+ engines: {node: '>=0.10.0'}
+
string-width@4.2.3:
resolution: {integrity: sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==}
engines: {node: '>=8'}
@@ -1906,10 +2004,17 @@ packages:
engines: {node: '>=16 || 14 >=14.17'}
hasBin: true
+ suffix-thumb@5.0.2:
+ resolution: {integrity: sha512-I5PWXAFKx3FYnI9a+dQMWNqTxoRt6vdBdb0O+BJ1sxXCWtSoQCusc13E58f+9p4MYx/qCnEMkD5jac6K2j3dgA==}
+
supports-color@7.2.0:
resolution: {integrity: sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==}
engines: {node: '>=8'}
+ sylvester@0.0.21:
+ resolution: {integrity: sha512-yUT0ukFkFEt4nb+NY+n2ag51aS/u9UHXoZw+A4jgD77/jzZsBoSDHuqysrVCBC4CYR4TYvUJq54ONpXgDBH8tA==}
+ engines: {node: '>=0.2.6'}
+
tar-fs@2.1.4:
resolution: {integrity: sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==}
@@ -1949,6 +2054,10 @@ packages:
tr46@0.0.3:
resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==}
+ tr46@5.1.1:
+ resolution: {integrity: sha512-hdF5ZgjTqgAntKkklYw0R03MG2x/bSzTtkxmIRw/sTNV8YXsCJ1tfLAX23lhxhHJlEf3CRCOCGGWw3vI3GaSPw==}
+ engines: {node: '>=18'}
+
tree-kill@1.2.2:
resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==}
hasBin: true
@@ -1989,6 +2098,9 @@ packages:
ufo@1.6.3:
resolution: {integrity: sha512-yDJTmhydvl5lJzBmy/hyOAA0d+aqCBuwl818haVdYCRrWV84o7YyeVm4QlVHStqNrrJSTb6jKuFAVqAFsr+K3Q==}
+ underscore@1.13.8:
+ resolution: {integrity: sha512-DXtD3ZtEQzc7M8m4cXotyHR+FAS18C64asBYY5vqZexfYryNNnDc02W4hKg3rdQuqOYas1jkseX0+nZXjTXnvQ==}
+
undici-types@5.26.5:
resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==}
@@ -2006,6 +2118,10 @@ packages:
resolution: {integrity: sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==}
hasBin: true
+ uuid@13.0.0:
+ resolution: {integrity: sha512-XQegIaBTVUjSHliKqcnFqYypAd4S+WCYt5NIeRs6w/UAry7z8Y9j5ZwRRL4kzq9U3sD6v+85er9FvkEaBpji2w==}
+ hasBin: true
+
uuid@8.3.2:
resolution: {integrity: sha512-+NYs2QeMWy+GWFOEm9xnn6HCDp0l7QBD7ml8zLUmJ+93Q5NF0NocErnwkTkXVFNiX3/fpC6afS8Dhb/gz7R7eg==}
hasBin: true
@@ -2103,9 +2219,17 @@ packages:
webidl-conversions@3.0.1:
resolution: {integrity: sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==}
+ webidl-conversions@7.0.0:
+ resolution: {integrity: sha512-VwddBukDzu71offAQR975unBIGqfKZpM+8ZX6ySk8nYhVoo5CYaZyzt3YBvYtRtO+aoGlqxPg/B87NGVZ/fu6g==}
+ engines: {node: '>=12'}
+
whatwg-fetch@3.6.20:
resolution: {integrity: sha512-EqhiFU6daOA8kpjOWTL0olhVOF3i7OrFzSYiGsEMB8GcXS+RrzauAERX65xMeNWVqxA6HXH2m69Z9LaKKdisfg==}
+ whatwg-url@14.2.0:
+ resolution: {integrity: sha512-De72GdQZzNTUBBChsXueQUnPKDkg/5A5zp7pFDuQAj5UFoENpiACU0wlCvzpAGnTkj++ihpKwKyYewn/XNUbKw==}
+ engines: {node: '>=18'}
+
whatwg-url@5.0.0:
resolution: {integrity: sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==}
@@ -2119,6 +2243,10 @@ packages:
engines: {node: '>=8'}
hasBin: true
+ wordnet-db@3.1.14:
+ resolution: {integrity: sha512-zVyFsvE+mq9MCmwXUWHIcpfbrHHClZWZiVOzKSxNJruIcFn2RbY55zkhiAMMxM8zCVSmtNiViq8FsAZSFpMYag==}
+ engines: {node: '>=0.6.0'}
+
wrap-ansi@7.0.0:
resolution: {integrity: sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==}
engines: {node: '>=10'}
@@ -2150,9 +2278,6 @@ packages:
resolution: {integrity: sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==}
engines: {node: '>=0.4'}
- yallist@4.0.0:
- resolution: {integrity: sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==}
-
zod-to-json-schema@3.25.1:
resolution: {integrity: sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==}
peerDependencies:
@@ -2487,6 +2612,10 @@ snapshots:
- bufferutil
- utf-8-validate
+ '@mongodb-js/saslprep@1.4.8':
+ dependencies:
+ sparse-bitfield: 3.0.3
+
'@napi-rs/wasm-runtime@1.1.1':
dependencies:
'@emnapi/core': 1.9.0
@@ -2533,31 +2662,25 @@ snapshots:
'@qdrant/openapi-typescript-fetch@1.2.6': {}
- '@redis/bloom@1.2.0(@redis/client@1.6.1)':
+ '@redis/bloom@5.12.1(@redis/client@5.12.1)':
dependencies:
- '@redis/client': 1.6.1
+ '@redis/client': 5.12.1
- '@redis/client@1.6.1':
+ '@redis/client@5.12.1':
dependencies:
cluster-key-slot: 1.1.2
- generic-pool: 3.9.0
- yallist: 4.0.0
- '@redis/graph@1.1.1(@redis/client@1.6.1)':
+ '@redis/json@5.12.1(@redis/client@5.12.1)':
dependencies:
- '@redis/client': 1.6.1
+ '@redis/client': 5.12.1
- '@redis/json@1.0.7(@redis/client@1.6.1)':
+ '@redis/search@5.12.1(@redis/client@5.12.1)':
dependencies:
- '@redis/client': 1.6.1
+ '@redis/client': 5.12.1
- '@redis/search@1.2.0(@redis/client@1.6.1)':
+ '@redis/time-series@5.12.1(@redis/client@5.12.1)':
dependencies:
- '@redis/client': 1.6.1
-
- '@redis/time-series@1.1.0(@redis/client@1.6.1)':
- dependencies:
- '@redis/client': 1.6.1
+ '@redis/client': 5.12.1
'@rolldown/binding-android-arm64@1.0.0-rc.9':
optional: true
@@ -2785,6 +2908,12 @@ snapshots:
'@types/uuid@10.0.0': {}
+ '@types/webidl-conversions@7.0.3': {}
+
+ '@types/whatwg-url@13.0.0':
+ dependencies:
+ '@types/webidl-conversions': 7.0.3
+
'@types/ws@8.18.1':
dependencies:
'@types/node': 22.19.15
@@ -2864,6 +2993,10 @@ snapshots:
acorn@8.16.0: {}
+ afinn-165-financialmarketnews@3.0.0: {}
+
+ afinn-165@2.0.2: {}
+
agent-base@7.1.4: {}
agentkeepalive@4.6.0:
@@ -2884,6 +3017,10 @@ snapshots:
any-promise@1.3.0: {}
+ apparatus@0.0.10:
+ dependencies:
+ sylvester: 0.0.21
+
assertion-error@2.0.1: {}
ast-v8-to-istanbul@1.0.0:
@@ -2933,20 +3070,15 @@ snapshots:
dependencies:
fill-range: 7.1.1
+ bson@7.2.0: {}
+
buffer-equal-constant-time@1.0.1: {}
- buffer-writer@2.0.0: {}
-
buffer@5.7.1:
dependencies:
base64-js: 1.5.1
ieee754: 1.2.1
- buffer@6.0.3:
- dependencies:
- base64-js: 1.5.1
- ieee754: 1.2.1
-
bundle-name@4.1.0:
dependencies:
run-applescript: 7.1.0
@@ -3008,6 +3140,12 @@ snapshots:
commander@4.1.1: {}
+ compromise@14.15.0:
+ dependencies:
+ efrt: 2.7.0
+ grad-school: 0.0.5
+ suffix-thumb: 5.0.2
+
confbox@0.1.8: {}
consola@3.4.2: {}
@@ -3060,6 +3198,8 @@ snapshots:
base-64: 0.1.0
md5: 2.3.0
+ dotenv@17.4.2: {}
+
dunder-proto@1.0.1:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -3072,6 +3212,8 @@ snapshots:
dependencies:
safe-buffer: 5.2.1
+ efrt@2.7.0: {}
+
emoji-regex@8.0.0: {}
emoji-regex@9.2.2: {}
@@ -3223,8 +3365,6 @@ snapshots:
transitivePeerDependencies:
- supports-color
- generic-pool@3.9.0: {}
-
get-intrinsic@1.3.0:
dependencies:
call-bind-apply-helpers: 1.0.2
@@ -3271,6 +3411,8 @@ snapshots:
graceful-fs@4.2.11: {}
+ grad-school@0.0.5: {}
+
groq-sdk@0.3.0:
dependencies:
'@types/node': 18.19.130
@@ -3437,6 +3579,8 @@ snapshots:
jwa: 2.0.1
safe-buffer: 5.2.1
+ kareem@3.2.0: {}
+
langsmith@0.3.87(openai@4.104.0(ws@8.19.0)(zod@3.25.76)):
dependencies:
'@types/uuid': 10.0.0
@@ -3543,7 +3687,7 @@ snapshots:
crypt: 0.0.2
is-buffer: 1.1.6
- mem0ai@2.4.5(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.0)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260313.1)(@google/genai@1.45.0)(@langchain/core@0.3.80(openai@4.104.0(ws@8.19.0)(zod@3.25.76)))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.13.0(typescript@5.9.3))(@supabase/supabase-js@2.99.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.8.0)(cloudflare@4.5.0)(groq-sdk@0.3.0)(neo4j-driver@5.28.3)(ollama@0.5.18)(pg@8.11.3)(redis@4.7.1)(ws@8.19.0):
+ mem0ai@3.0.1(@anthropic-ai/sdk@0.40.1)(@azure/identity@4.13.0)(@azure/search-documents@12.2.0)(@cloudflare/workers-types@4.20260313.1)(@google/genai@1.45.0)(@langchain/core@0.3.80(openai@4.104.0(ws@8.19.0)(zod@3.25.76)))(@mistralai/mistralai@1.15.1)(@qdrant/js-client-rest@1.13.0(typescript@5.9.3))(@supabase/supabase-js@2.99.1)(@types/jest@29.5.14)(@types/pg@8.11.0)(better-sqlite3@12.8.0)(cloudflare@4.5.0)(compromise@14.15.0)(groq-sdk@0.3.0)(natural@8.1.1)(ollama@0.5.18)(pg@8.20.0)(redis@5.12.1)(ws@8.19.0):
dependencies:
'@anthropic-ai/sdk': 0.40.1
'@azure/identity': 4.13.0
@@ -3559,12 +3703,13 @@ snapshots:
axios: 1.13.6
better-sqlite3: 12.8.0
cloudflare: 4.5.0
+ compromise: 14.15.0
groq-sdk: 0.3.0
- neo4j-driver: 5.28.3
+ natural: 8.1.1
ollama: 0.5.18
openai: 4.104.0(ws@8.19.0)(zod@3.25.76)
- pg: 8.11.3
- redis: 4.7.1
+ pg: 8.20.0
+ redis: 5.12.1
uuid: 9.0.1
zod: 3.25.76
transitivePeerDependencies:
@@ -3572,6 +3717,10 @@ snapshots:
- encoding
- ws
+ memjs@1.3.2: {}
+
+ memory-pager@1.5.0: {}
+
micromatch@4.0.8:
dependencies:
braces: 3.0.3
@@ -3602,6 +3751,38 @@ snapshots:
pkg-types: 1.3.1
ufo: 1.6.3
+ mongodb-connection-string-url@7.0.1:
+ dependencies:
+ '@types/whatwg-url': 13.0.0
+ whatwg-url: 14.2.0
+
+ mongodb@7.1.1:
+ dependencies:
+ '@mongodb-js/saslprep': 1.4.8
+ bson: 7.2.0
+ mongodb-connection-string-url: 7.0.1
+
+ mongoose@9.4.1:
+ dependencies:
+ kareem: 3.2.0
+ mongodb: 7.1.1
+ mpath: 0.9.0
+ mquery: 6.0.0
+ ms: 2.1.3
+ sift: 17.1.3
+ transitivePeerDependencies:
+ - '@aws-sdk/credential-providers'
+ - '@mongodb-js/zstd'
+ - gcp-metadata
+ - kerberos
+ - mongodb-client-encryption
+ - snappy
+ - socks
+
+ mpath@0.9.0: {}
+
+ mquery@6.0.0: {}
+
ms@2.1.3: {}
mustache@4.2.0: {}
@@ -3616,19 +3797,33 @@ snapshots:
napi-build-utils@2.0.0: {}
- neo4j-driver-bolt-connection@5.28.3:
+ natural@8.1.1:
dependencies:
- buffer: 6.0.3
- neo4j-driver-core: 5.28.3
- string_decoder: 1.3.0
-
- neo4j-driver-core@5.28.3: {}
-
- neo4j-driver@5.28.3:
- dependencies:
- neo4j-driver-bolt-connection: 5.28.3
- neo4j-driver-core: 5.28.3
- rxjs: 7.8.2
+ afinn-165: 2.0.2
+ afinn-165-financialmarketnews: 3.0.0
+ apparatus: 0.0.10
+ dotenv: 17.4.2
+ memjs: 1.3.2
+ mongoose: 9.4.1
+ pg: 8.20.0
+ redis: 5.12.1
+ safe-stable-stringify: 2.5.0
+ stopwords-iso: 1.1.0
+ sylvester: 0.0.21
+ underscore: 1.13.8
+ uuid: 13.0.0
+ wordnet-db: 3.1.14
+ transitivePeerDependencies:
+ - '@aws-sdk/credential-providers'
+ - '@mongodb-js/zstd'
+ - '@node-rs/xxhash'
+ - '@opentelemetry/api'
+ - gcp-metadata
+ - kerberos
+ - mongodb-client-encryption
+ - pg-native
+ - snappy
+ - socks
node-abi@3.88.0:
dependencies:
@@ -3700,8 +3895,6 @@ snapshots:
package-json-from-dist@1.0.1: {}
- packet-reader@1.0.0: {}
-
path-key@3.1.1: {}
path-scurry@1.11.1:
@@ -3720,9 +3913,9 @@ snapshots:
pg-numeric@1.0.2: {}
- pg-pool@3.13.0(pg@8.11.3):
+ pg-pool@3.13.0(pg@8.20.0):
dependencies:
- pg: 8.11.3
+ pg: 8.20.0
pg-protocol@1.13.0: {}
@@ -3744,12 +3937,10 @@ snapshots:
postgres-interval: 3.0.0
postgres-range: 1.1.4
- pg@8.11.3:
+ pg@8.20.0:
dependencies:
- buffer-writer: 2.0.0
- packet-reader: 1.0.0
pg-connection-string: 2.12.0
- pg-pool: 3.13.0(pg@8.11.3)
+ pg-pool: 3.13.0(pg@8.20.0)
pg-protocol: 1.13.0
pg-types: 2.2.0
pgpass: 1.0.5
@@ -3851,6 +4042,8 @@ snapshots:
end-of-stream: 1.4.5
once: 1.4.0
+ punycode@2.3.1: {}
+
rc@1.2.8:
dependencies:
deep-extend: 0.6.0
@@ -3868,14 +4061,16 @@ snapshots:
readdirp@4.1.2: {}
- redis@4.7.1:
+ redis@5.12.1:
dependencies:
- '@redis/bloom': 1.2.0(@redis/client@1.6.1)
- '@redis/client': 1.6.1
- '@redis/graph': 1.1.1(@redis/client@1.6.1)
- '@redis/json': 1.0.7(@redis/client@1.6.1)
- '@redis/search': 1.2.0(@redis/client@1.6.1)
- '@redis/time-series': 1.1.0(@redis/client@1.6.1)
+ '@redis/bloom': 5.12.1(@redis/client@5.12.1)
+ '@redis/client': 5.12.1
+ '@redis/json': 5.12.1(@redis/client@5.12.1)
+ '@redis/search': 5.12.1(@redis/client@5.12.1)
+ '@redis/time-series': 5.12.1(@redis/client@5.12.1)
+ transitivePeerDependencies:
+ - '@node-rs/xxhash'
+ - '@opentelemetry/api'
resolve-from@5.0.0: {}
@@ -3939,12 +4134,10 @@ snapshots:
run-applescript@7.1.0: {}
- rxjs@7.8.2:
- dependencies:
- tslib: 2.8.1
-
safe-buffer@5.2.1: {}
+ safe-stable-stringify@2.5.0: {}
+
semver@7.7.4: {}
shebang-command@2.0.0:
@@ -3953,6 +4146,8 @@ snapshots:
shebang-regex@3.0.0: {}
+ sift@17.1.3: {}
+
siginfo@2.0.0: {}
signal-exit@4.1.0: {}
@@ -3973,6 +4168,10 @@ snapshots:
source-map@0.7.6: {}
+ sparse-bitfield@3.0.3:
+ dependencies:
+ memory-pager: 1.5.0
+
split2@4.2.0: {}
stack-utils@2.0.6:
@@ -3983,6 +4182,8 @@ snapshots:
std-env@4.0.0: {}
+ stopwords-iso@1.1.0: {}
+
string-width@4.2.3:
dependencies:
emoji-regex: 8.0.0
@@ -4019,10 +4220,14 @@ snapshots:
tinyglobby: 0.2.15
ts-interface-checker: 0.1.13
+ suffix-thumb@5.0.2: {}
+
supports-color@7.2.0:
dependencies:
has-flag: 4.0.0
+ sylvester@0.0.21: {}
+
tar-fs@2.1.4:
dependencies:
chownr: 1.1.4
@@ -4065,6 +4270,10 @@ snapshots:
tr46@0.0.3: {}
+ tr46@5.1.1:
+ dependencies:
+ punycode: 2.3.1
+
tree-kill@1.2.2: {}
ts-interface-checker@0.1.13: {}
@@ -4107,6 +4316,8 @@ snapshots:
ufo@1.6.3: {}
+ underscore@1.13.8: {}
+
undici-types@5.26.5: {}
undici-types@6.21.0: {}
@@ -4119,6 +4330,8 @@ snapshots:
uuid@10.0.0: {}
+ uuid@13.0.0: {}
+
uuid@8.3.2: {}
uuid@9.0.1: {}
@@ -4169,8 +4382,15 @@ snapshots:
webidl-conversions@3.0.1: {}
+ webidl-conversions@7.0.0: {}
+
whatwg-fetch@3.6.20: {}
+ whatwg-url@14.2.0:
+ dependencies:
+ tr46: 5.1.1
+ webidl-conversions: 7.0.0
+
whatwg-url@5.0.0:
dependencies:
tr46: 0.0.3
@@ -4185,6 +4405,8 @@ snapshots:
siginfo: 2.0.0
stackback: 0.0.2
+ wordnet-db@3.1.14: {}
+
wrap-ansi@7.0.0:
dependencies:
ansi-styles: 4.3.0
@@ -4207,8 +4429,6 @@ snapshots:
xtend@4.0.2: {}
- yallist@4.0.0: {}
-
zod-to-json-schema@3.25.1(zod@3.25.76):
dependencies:
zod: 3.25.76
diff --git a/openclaw/providers.ts b/openclaw/providers.ts
index 1048586a5..6acf6cfcd 100644
--- a/openclaw/providers.ts
+++ b/openclaw/providers.ts
@@ -111,21 +111,19 @@ class PlatformProvider implements Mem0Provider {
options: AddOptions,
): Promise {
await this.ensureClient();
- const opts: Record = { user_id: options.user_id };
- if (options.run_id) opts.run_id = options.run_id;
+ // v3.0.0: SDK uses camelCase (userId, runId, etc.) - it converts to snake_case internally
+ const opts: Record = { userId: options.user_id };
+ if (options.run_id) opts.runId = options.run_id;
if (options.custom_instructions)
- opts.custom_instructions = options.custom_instructions;
+ opts.customInstructions = options.custom_instructions;
if (options.custom_categories)
- opts.custom_categories = options.custom_categories;
- if (options.output_format) opts.output_format = options.output_format;
+ opts.customCategories = options.custom_categories;
if (options.source) opts.source = options.source;
// Agentic harness: direct storage bypass
if (options.infer !== undefined) opts.infer = options.infer;
if (options.deduced_memories)
- opts.deduced_memories = options.deduced_memories;
+ opts.deducedMemories = options.deduced_memories;
if (options.metadata) opts.metadata = options.metadata;
- if (options.expiration_date) opts.expiration_date = options.expiration_date;
- if (options.immutable) opts.immutable = options.immutable;
const result = await this.client.add(messages, opts);
return normalizeAddResult(result);
@@ -133,19 +131,15 @@ class PlatformProvider implements Mem0Provider {
async search(query: string, options: SearchOptions): Promise {
await this.ensureClient();
- const opts: Record = {
- api_version: "v2",
- user_id: options.user_id,
- };
- if (options.run_id) opts.run_id = options.run_id;
- if (options.top_k != null) opts.top_k = options.top_k;
+ // v3.0.0: SDK uses camelCase options, userId must be in filters
+ const opts: Record = {};
+ if (options.top_k != null) opts.topK = options.top_k;
if (options.threshold != null) opts.threshold = options.threshold;
- if (options.keyword_search != null)
- opts.keyword_search = options.keyword_search;
- if (options.reranking != null) opts.rerank = options.reranking;
- if (options.filter_memories != null)
- opts.filter_memories = options.filter_memories;
if (options.categories != null) opts.categories = options.categories;
+
+ // Build filters with user_id/run_id inside (v3.0.0 requirement)
+ // Filters use snake_case as they're passed directly to the API
+ // Note: source is NOT a valid filter field - only used when adding
const baseFilters: Record = { user_id: options.user_id };
if (options.run_id) baseFilters.run_id = options.run_id;
@@ -167,16 +161,14 @@ class PlatformProvider implements Mem0Provider {
async getAll(options: ListOptions): Promise {
await this.ensureClient();
- const opts: Record = {
- api_version: "v2",
- user_id: options.user_id,
- filters: { user_id: options.user_id },
- };
- if (options.run_id) {
- opts.run_id = options.run_id;
- (opts.filters as Record).run_id = options.run_id;
- }
- if (options.page_size != null) opts.page_size = options.page_size;
+ // v3.0.0: SDK uses camelCase options, userId must be in filters
+ // Filters use snake_case as they're passed directly to the API
+ // Note: source is NOT a valid filter field - only used when adding
+ const filters: Record = { user_id: options.user_id };
+ if (options.run_id) filters.run_id = options.run_id;
+
+ const opts: Record = { filters };
+ if (options.page_size != null) opts.pageSize = options.page_size;
const results = await this.client.getAll(opts);
if (Array.isArray(results)) return results.map(normalizeMemoryItem);
@@ -198,7 +190,8 @@ class PlatformProvider implements Mem0Provider {
async deleteAll(userId: string): Promise {
await this.ensureClient();
- await this.client.deleteAll({ user_id: userId });
+ // v3.0.0: SDK uses camelCase
+ await this.client.deleteAll({ userId });
}
async history(memoryId: string): Promise<
@@ -226,7 +219,7 @@ class OSSProvider implements Mem0Provider {
constructor(
private readonly ossConfig?: Mem0Config["oss"],
- private readonly customPrompt?: string,
+ private readonly customInstructions?: string,
private readonly resolvePath?: (p: string) => string,
) {}
@@ -241,7 +234,8 @@ class OSSProvider implements Mem0Provider {
}
private _buildConfig(disableHistory = false): Record {
- const config: Record = { version: "v1.1" };
+ // v3.0.0: removed version field
+ const config: Record = {};
const defaultEmbedder = {
provider: "openai",
@@ -299,7 +293,8 @@ class OSSProvider implements Mem0Provider {
config.disableHistory = true;
}
- if (this.customPrompt) config.customPrompt = this.customPrompt;
+ // v3.0.0: customPrompt renamed to customInstructions
+ if (this.customInstructions) config.customInstructions = this.customInstructions;
return config;
}
@@ -347,7 +342,8 @@ class OSSProvider implements Mem0Provider {
}
}
- await mem.getAll({ userId: "__mem0_warmup__" });
+ // v3.0.0: entity IDs must be in filters, not top-level
+ await mem.getAll({ filters: { user_id: "__mem0_warmup__" } });
this.memory = mem;
}
@@ -364,9 +360,7 @@ class OSSProvider implements Mem0Provider {
// Agentic harness: direct storage bypass
if (options.infer !== undefined) addOpts.infer = options.infer;
if (options.metadata) addOpts.metadata = options.metadata;
- if (options.expiration_date)
- addOpts.expirationDate = options.expiration_date;
- if (options.immutable) addOpts.immutable = options.immutable;
+ // v3.0.0: removed expiration_date, immutable
// OSS SDK doesn't support deduced_memories — when infer=false, it stores
// raw message content directly. Rewrite messages to contain the facts so
@@ -385,17 +379,24 @@ class OSSProvider implements Mem0Provider {
async search(query: string, options: SearchOptions): Promise {
await this.ensureMemory();
- // OSS SDK uses camelCase: userId/runId, not user_id/run_id
- const opts: Record = { userId: options.user_id };
- if (options.run_id) opts.runId = options.run_id;
- if (options.limit != null) opts.limit = options.limit;
- else if (options.top_k != null) opts.limit = options.top_k;
- if (options.keyword_search != null)
- opts.keyword_search = options.keyword_search;
- if (options.reranking != null) opts.reranking = options.reranking;
- if (options.source) opts.source = options.source;
+ // v3.0.0: entity IDs must be in filters, not top-level; limit renamed to topK
+ const opts: Record = {};
+ if (options.top_k != null) opts.topK = options.top_k;
if (options.threshold != null) opts.threshold = options.threshold;
+ // Build filters with user_id/run_id inside (v3.0.0 requirement)
+ // Filters use snake_case as they're passed directly to the vector store
+ // Note: source is NOT a valid filter field - only used when adding
+ const baseFilters: Record = { user_id: options.user_id };
+ if (options.run_id) baseFilters.run_id = options.run_id;
+
+ // Merge with any additional user-provided filters
+ if (options.filters) {
+ opts.filters = { AND: [baseFilters, options.filters] };
+ } else {
+ opts.filters = baseFilters;
+ }
+
const results = await this.memory.search(query, opts);
const normalized = normalizeSearchResults(results);
@@ -417,10 +418,16 @@ class OSSProvider implements Mem0Provider {
async getAll(options: ListOptions): Promise {
await this.ensureMemory();
- // OSS SDK uses camelCase: userId/runId, not user_id/run_id
- const getAllOpts: Record = { userId: options.user_id };
- if (options.run_id) getAllOpts.runId = options.run_id;
- if (options.source) getAllOpts.source = options.source;
+ // v3.0.0: entity IDs must be in filters, not top-level
+ // Filters use snake_case as they're passed directly to the vector store
+ // Note: source is NOT a valid filter field - only used when adding
+ const filters: Record = { user_id: options.user_id };
+ if (options.run_id) filters.run_id = options.run_id;
+
+ // OSS SDK uses topK for limiting results (not pageSize like Platform)
+ const getAllOpts: Record = { filters };
+ if (options.page_size != null) getAllOpts.topK = options.page_size;
+
const results = await this.memory.getAll(getAllOpts);
if (Array.isArray(results)) return results.map(normalizeMemoryItem);
if (results?.results && Array.isArray(results.results))
@@ -476,7 +483,8 @@ export function createProvider(
api: OpenClawPluginApi,
): Mem0Provider {
if (cfg.mode === "open-source") {
- return new OSSProvider(cfg.oss, cfg.customPrompt, (p) =>
+ // v3.0.0: use customInstructions (was customPrompt)
+ return new OSSProvider(cfg.oss, cfg.customInstructions, (p) =>
api.resolvePath(p),
);
}
@@ -502,6 +510,7 @@ export function providerToBackend(
return {
async add(content, messages, opts = {}) {
const msgs = messages ?? (content ? [{ role: "user", content }] : []);
+ // v3.0.0: removed immutable, expiration_date
const result = await provider.add(
msgs as Array<{ role: string; content: string }>,
{
@@ -509,21 +518,18 @@ export function providerToBackend(
source: "OPENCLAW",
...(opts.runId && { run_id: opts.runId }),
...(opts.metadata && { metadata: opts.metadata }),
- ...(opts.immutable && { immutable: true }),
...(opts.infer === false && { infer: false }),
- ...(opts.expires && { expiration_date: opts.expires }),
},
);
return result as unknown as Record;
},
async search(query, opts = {}) {
+ // v3.0.0: removed keyword_search, reranking
const results = await provider.search(query, {
user_id: opts.userId ?? userId,
top_k: opts.topK,
threshold: opts.threshold,
- keyword_search: opts.keyword,
- reranking: opts.rerank,
filters: opts.filters,
source: "OPENCLAW",
});
diff --git a/openclaw/public-artifacts.ts b/openclaw/public-artifacts.ts
new file mode 100644
index 000000000..a8ba105f5
--- /dev/null
+++ b/openclaw/public-artifacts.ts
@@ -0,0 +1,159 @@
+/**
+ * Public Artifacts Provider for OpenClaw memory-wiki bridge mode.
+ *
+ * Exposes Mem0 memories and dream state as artifacts that can be
+ * consumed by other plugins (e.g., memory-wiki in bridge mode).
+ */
+
+import type { Mem0Provider, MemoryItem, Mem0Config } from "./types.ts";
+import type { MemoryArtifact } from "openclaw/plugin-sdk";
+import { getDreamState } from "./dream-gate.ts";
+
+export interface PublicArtifactsContext {
+ provider: Mem0Provider;
+ cfg: Mem0Config;
+ stateDir?: string;
+ effectiveUserId: (sessionKey?: string) => string;
+}
+
+/**
+ * Create a publicArtifacts provider that exposes Mem0 data to other plugins.
+ */
+export function createPublicArtifactsProvider(ctx: PublicArtifactsContext) {
+ return {
+ async listArtifacts(options?: {
+ userId?: string;
+ types?: string[];
+ limit?: number;
+ }): Promise {
+ const artifacts: MemoryArtifact[] = [];
+ const userId = options?.userId ?? ctx.effectiveUserId();
+ const types = options?.types ?? ["memory", "dream", "entity"];
+ const limit = options?.limit ?? 100;
+
+ try {
+ // Memory artifacts
+ if (types.includes("memory")) {
+ const memories = await ctx.provider.getAll({
+ user_id: userId,
+ page_size: limit,
+ });
+
+ for (const mem of memories) {
+ artifacts.push(memoryToArtifact(mem));
+ }
+ }
+
+ // Dream state artifact (if dream enabled and stateDir available)
+ if (types.includes("dream") && ctx.stateDir && ctx.cfg.skills?.dream?.enabled) {
+ const dreamArtifact = getDreamArtifact(ctx.stateDir, userId);
+ if (dreamArtifact) {
+ artifacts.push(dreamArtifact);
+ }
+ }
+
+ // Entity artifacts (grouped memories by category)
+ if (types.includes("entity")) {
+ const entityArtifacts = extractEntityArtifacts(artifacts.filter(a => a.type === "memory"));
+ artifacts.push(...entityArtifacts);
+ }
+
+ } catch (err) {
+ console.warn(
+ "[mem0] publicArtifacts.listArtifacts failed:",
+ err instanceof Error ? err.message : err,
+ );
+ }
+
+ return artifacts.slice(0, limit);
+ },
+ };
+}
+
+/**
+ * Convert a MemoryItem to a MemoryArtifact.
+ */
+function memoryToArtifact(mem: MemoryItem): MemoryArtifact {
+ return {
+ id: `mem0:memory:${mem.id}`,
+ type: "memory",
+ title: mem.memory.slice(0, 80) + (mem.memory.length > 80 ? "..." : ""),
+ content: mem.memory,
+ metadata: {
+ score: mem.score,
+ categories: mem.categories,
+ user_id: mem.user_id,
+ ...mem.metadata,
+ },
+ createdAt: mem.created_at,
+ updatedAt: mem.updated_at,
+ };
+}
+
+/**
+ * Get dream consolidation state as an artifact.
+ */
+function getDreamArtifact(stateDir: string, userId: string): MemoryArtifact | null {
+ try {
+ const state = getDreamState(stateDir);
+ if (state.lastConsolidatedAt === 0) {
+ return null; // No consolidation has occurred yet
+ }
+
+ const lastDate = new Date(state.lastConsolidatedAt).toISOString();
+ return {
+ id: `mem0:dream:${userId}:state`,
+ type: "dream",
+ title: `Dream State (last: ${lastDate.split("T")[0]})`,
+ content: [
+ `Last consolidation: ${lastDate}`,
+ `Sessions since: ${state.sessionsSince}`,
+ `Last session: ${state.lastSessionId ?? "none"}`,
+ ].join("\n"),
+ metadata: {
+ lastConsolidatedAt: state.lastConsolidatedAt,
+ sessionsSince: state.sessionsSince,
+ lastSessionId: state.lastSessionId,
+ user_id: userId,
+ },
+ updatedAt: lastDate,
+ };
+ } catch {
+ return null;
+ }
+}
+
+/**
+ * Extract entity artifacts from memories (grouped by category).
+ */
+function extractEntityArtifacts(memoryArtifacts: MemoryArtifact[]): MemoryArtifact[] {
+ const byCategory = new Map();
+
+ for (const artifact of memoryArtifacts) {
+ const categories = (artifact.metadata?.categories as string[]) ?? ["uncategorized"];
+ for (const cat of categories) {
+ const existing = byCategory.get(cat) ?? [];
+ existing.push(artifact);
+ byCategory.set(cat, existing);
+ }
+ }
+
+ const entities: MemoryArtifact[] = [];
+ for (const [category, mems] of byCategory) {
+ if (mems.length >= 2) {
+ entities.push({
+ id: `mem0:entity:${category}`,
+ type: "entity",
+ title: `${category.charAt(0).toUpperCase() + category.slice(1)} (${mems.length} memories)`,
+ content: mems.map(m => `- ${m.content}`).join("\n"),
+ metadata: {
+ category,
+ memoryCount: mems.length,
+ memoryIds: mems.map(m => m.id),
+ },
+ });
+ }
+ }
+
+ return entities;
+}
diff --git a/openclaw/recall.ts b/openclaw/recall.ts
index 2f4f77263..90a5c68a7 100644
--- a/openclaw/recall.ts
+++ b/openclaw/recall.ts
@@ -2,7 +2,7 @@
* Token-budgeted, category-ranked recall engine.
*
* Replaces naive "dump all search results" with:
- * 1. Search with rerank + keyword_search
+ * 1. Search memories with threshold filtering
* 2. Rank by category priority (identity first)
* 3. Token-budget the results
* 4. Format by category with importance scores
@@ -253,18 +253,13 @@ export async function recall(
const categoryOrder = recallConfig.categoryOrder ?? DEFAULT_CATEGORY_ORDER;
const identityAlwaysInclude = recallConfig.identityAlwaysInclude !== false;
- // Build search options with enhanced features
+ // Build search options (v3.0.0: keyword_search, reranking, filter_memories removed)
const searchOpts: SearchOptions = {
user_id: userId,
top_k: maxMemories * 2, // Over-fetch for ranking
threshold,
- keyword_search: recallConfig.keywordSearch !== false, // Default on
- reranking: recallConfig.rerank !== false, // Default on
source: "OPENCLAW",
};
- if (recallConfig.filterMemories) {
- searchOpts.filter_memories = true;
- }
// Sanitize query: strip OpenClaw metadata prefix before searching
const cleanQuery = sanitizeQuery(query);
diff --git a/openclaw/sqlite-resilience.test.ts b/openclaw/sqlite-resilience.test.ts
index 026e0b94d..484f75390 100644
--- a/openclaw/sqlite-resilience.test.ts
+++ b/openclaw/sqlite-resilience.test.ts
@@ -654,9 +654,9 @@ describe("OSSProvider — history error handling", () => {
});
// ---------------------------------------------------------------------------
-// 9. OSSProvider: customPrompt passthrough
+// 9. OSSProvider: customInstructions passthrough (v3.0.0: renamed from customPrompt)
// ---------------------------------------------------------------------------
-describe("OSSProvider — customPrompt passthrough", () => {
+describe("OSSProvider — customInstructions passthrough", () => {
let capturedConfig: Record | undefined;
beforeEach(() => {
@@ -682,12 +682,13 @@ describe("OSSProvider — customPrompt passthrough", () => {
vi.restoreAllMocks();
});
- it("passes customPrompt to Memory config when provided", async () => {
+ // v3.0.0: customPrompt renamed to customInstructions
+ it("passes customInstructions to Memory config when provided", async () => {
const { createProvider } = await import("./index.ts");
const cfg = mem0ConfigSchema.parse({
mode: "open-source",
oss: { disableHistory: true },
- customPrompt: "Extract only user preferences.",
+ customInstructions: "Extract only user preferences.",
});
const api = { resolvePath: (p: string) => p } as any;
const provider = createProvider(cfg, api);
@@ -695,6 +696,6 @@ describe("OSSProvider — customPrompt passthrough", () => {
await provider.search("test", { user_id: "u1" });
expect(capturedConfig).toBeDefined();
- expect(capturedConfig!.customPrompt).toBe("Extract only user preferences.");
+ expect(capturedConfig!.customInstructions).toBe("Extract only user preferences.");
});
});
diff --git a/openclaw/telemetry.ts b/openclaw/telemetry.ts
index ee0522851..93497f2a5 100644
--- a/openclaw/telemetry.ts
+++ b/openclaw/telemetry.ts
@@ -9,9 +9,9 @@
*/
import { createHash, randomUUID } from "node:crypto";
-import { readPluginAuth, writePluginAuth, getBaseUrl } from "./cli/config-file.ts";
+import { readPluginAuth, writePluginAuth, getBaseUrl, clearAnonymousTelemetryId } from "./cli/config-file.ts";
-export const PLUGIN_VERSION = "1.0.6";
+export const PLUGIN_VERSION = "1.0.7";
const POSTHOG_API_KEY = "phc_hgJkUVJFYtmaJqrvf6CYN67TIQ8yhXAkWzUn9AMU4yX";
const POSTHOG_HOST = "https://us.i.posthog.com/i/v0/e/";
@@ -84,8 +84,9 @@ function maybeBuildIdentifyEvent(
$lib: "posthog-node",
},
};
+ // Clear the anonymous ID from config after aliasing (don't write empty string)
try {
- writePluginAuth({ anonymousTelemetryId: "" });
+ clearAnonymousTelemetryId();
} catch {
/* ignore — alias may double-fire next session, harmless */
}
diff --git a/openclaw/tests/config-file.test.ts b/openclaw/tests/config-file.test.ts
index f2d535757..e059ddff3 100644
--- a/openclaw/tests/config-file.test.ts
+++ b/openclaw/tests/config-file.test.ts
@@ -113,9 +113,15 @@ describe("readPluginAuth", () => {
expect(auth.userId).toBe("user-snake");
});
- it("returns empty object when JSON is invalid", () => {
+ it("throws error when JSON is invalid (prevents config destruction)", () => {
mockExists.mockReturnValue(true);
mockReadText.mockReturnValue("not valid json {{{");
+ expect(() => readPluginAuth()).toThrow(/Failed to parse[\s\S]*Fix the JSON syntax error/);
+ });
+
+ it("returns empty object when config file is empty", () => {
+ mockExists.mockReturnValue(true);
+ mockReadText.mockReturnValue(" ");
expect(readPluginAuth()).toEqual({});
});
});
diff --git a/openclaw/tests/config.test.ts b/openclaw/tests/config.test.ts
index 6e99a0f39..4b630f704 100644
--- a/openclaw/tests/config.test.ts
+++ b/openclaw/tests/config.test.ts
@@ -74,9 +74,10 @@ describe("mem0ConfigSchema.parse() — defaults", () => {
expect(cfg.customCategories).toBe(DEFAULT_CUSTOM_CATEGORIES);
});
- it("customPrompt defaults to DEFAULT_CUSTOM_INSTRUCTIONS", () => {
- const cfg = mem0ConfigSchema.parse({ apiKey: "test-key" });
- expect(cfg.customPrompt).toBe(DEFAULT_CUSTOM_INSTRUCTIONS);
+ // v3.0.0: customPrompt removed, use customInstructions instead
+ it("customPrompt input falls back to customInstructions", () => {
+ const cfg = mem0ConfigSchema.parse({ apiKey: "test-key", customPrompt: "My prompt" });
+ expect(cfg.customInstructions).toBe("My prompt");
});
it("oss defaults to undefined", () => {
@@ -296,13 +297,14 @@ describe("mem0ConfigSchema.parse() — explicit overrides", () => {
expect(cfg.customInstructions).toBe(custom);
});
- it("custom customPrompt overrides defaults", () => {
+ // v3.0.0: customPrompt renamed to customInstructions (backwards compat: customPrompt maps to customInstructions)
+ it("customPrompt input maps to customInstructions output", () => {
const custom = "My custom prompt";
const cfg = mem0ConfigSchema.parse({
apiKey: "k",
customPrompt: custom,
});
- expect(cfg.customPrompt).toBe(custom);
+ expect(cfg.customInstructions).toBe(custom);
});
it("custom customCategories override defaults", () => {
diff --git a/openclaw/tests/filtering.test.ts b/openclaw/tests/filtering.test.ts
new file mode 100644
index 000000000..501a434f7
--- /dev/null
+++ b/openclaw/tests/filtering.test.ts
@@ -0,0 +1,96 @@
+import { describe, it, expect } from "vitest";
+import {
+ isNoiseMessage,
+ isGenericAssistantMessage,
+ isSessionSpecificContent,
+ stripNoiseFromContent,
+ filterMessagesForExtraction,
+} from "../filtering.ts";
+
+describe("isNoiseMessage", () => {
+ it("returns true for heartbeat messages", () => {
+ expect(isNoiseMessage("HEARTBEAT_OK")).toBe(true);
+ expect(isNoiseMessage("NO_REPLY")).toBe(true);
+ });
+
+ it("returns true for single-word acknowledgments", () => {
+ expect(isNoiseMessage("ok")).toBe(true);
+ expect(isNoiseMessage("done")).toBe(true);
+ expect(isNoiseMessage("thanks")).toBe(true);
+ });
+
+ it("returns false for substantive content", () => {
+ expect(isNoiseMessage("My name is John and I live in Tokyo")).toBe(false);
+ });
+});
+
+describe("isGenericAssistantMessage", () => {
+ it("returns true for generic acknowledgments", () => {
+ expect(isGenericAssistantMessage("I see you've shared this. How can I help?")).toBe(true);
+ expect(isGenericAssistantMessage("Got it! How can I assist you?")).toBe(true);
+ });
+
+ it("returns false for substantive responses", () => {
+ expect(isGenericAssistantMessage("Based on the code, the bug is in line 42 where the null check is missing.")).toBe(false);
+ });
+});
+
+describe("isSessionSpecificContent", () => {
+ it("returns true for tool availability discussions", () => {
+ expect(isSessionSpecificContent(
+ "I do not currently see Mem0 write/update/delete tools exposed in this session."
+ )).toBe(true);
+ });
+
+ it("returns true for plugin capability statements", () => {
+ expect(isSessionSpecificContent(
+ "As of 2026-04-18, the openclaw-mem0 plugin does not expose a memory wiki capability."
+ )).toBe(true);
+ });
+
+ it("returns true for session-specific tool lists", () => {
+ expect(isSessionSpecificContent(
+ "The tools I have access to in this session are memory_search and memory_get."
+ )).toBe(true);
+ });
+
+ it("returns false for user preferences", () => {
+ expect(isSessionSpecificContent(
+ "My name is Kartik and I like to watch Pokemon."
+ )).toBe(false);
+ });
+
+ it("returns false for technical facts that span sessions", () => {
+ expect(isSessionSpecificContent(
+ "The project uses TypeScript 5.0 and Node.js 20."
+ )).toBe(false);
+ });
+});
+
+describe("filterMessagesForExtraction", () => {
+ it("filters out session-specific tool discussions", () => {
+ const messages = [
+ { role: "user", content: "What tools do you have access to?" },
+ { role: "assistant", content: "I have memory_search and memory_get tools. The other tools are not exposed in this session." },
+ { role: "user", content: "My name is Kartik" },
+ { role: "assistant", content: "Got it, I'll remember that!" },
+ ];
+
+ const filtered = filterMessagesForExtraction(messages);
+
+ // Should keep the name statement, filter the tool discussion
+ expect(filtered.some(m => m.content.includes("Kartik"))).toBe(true);
+ expect(filtered.some(m => m.content.includes("not exposed in this session"))).toBe(false);
+ });
+
+ it("keeps valuable user facts", () => {
+ const messages = [
+ { role: "user", content: "I prefer dark mode and use VS Code for development." },
+ { role: "assistant", content: "Noted! Dark mode in VS Code is great for reducing eye strain." },
+ ];
+
+ const filtered = filterMessagesForExtraction(messages);
+ expect(filtered.length).toBe(2);
+ expect(filtered[0].content).toContain("dark mode");
+ });
+});
diff --git a/openclaw/tests/providers.test.ts b/openclaw/tests/providers.test.ts
index f1e54fc25..024bf3d91 100644
--- a/openclaw/tests/providers.test.ts
+++ b/openclaw/tests/providers.test.ts
@@ -42,6 +42,7 @@ beforeEach(() => {
// ---------------------------------------------------------------------------
describe("providerToBackend — search", () => {
+ // v3.0.0: keyword_search, reranking removed from SDK
it("delegates to provider.search with correct options", async () => {
const provider = createMockProvider();
const backend = providerToBackend(provider as any, DEFAULT_USER);
@@ -49,8 +50,6 @@ describe("providerToBackend — search", () => {
const results = await backend.search("hello world", {
topK: 10,
threshold: 0.5,
- keyword: true,
- rerank: true,
filters: { category: "preference" },
});
@@ -58,8 +57,6 @@ describe("providerToBackend — search", () => {
user_id: DEFAULT_USER,
top_k: 10,
threshold: 0.5,
- keyword_search: true,
- reranking: true,
filters: { category: "preference" },
source: "OPENCLAW",
});
@@ -73,12 +70,11 @@ describe("providerToBackend — search", () => {
await backend.search("query");
+ // v3.0.0: keyword_search, reranking removed
expect(provider.search).toHaveBeenCalledWith("query", {
user_id: DEFAULT_USER,
top_k: undefined,
threshold: undefined,
- keyword_search: undefined,
- reranking: undefined,
filters: undefined,
source: "OPENCLAW",
});
@@ -119,6 +115,7 @@ describe("providerToBackend — add", () => {
);
});
+ // v3.0.0: immutable, expiration_date removed from SDK
it("forwards optional add options", async () => {
const provider = createMockProvider();
const backend = providerToBackend(provider as any, DEFAULT_USER);
@@ -126,9 +123,7 @@ describe("providerToBackend — add", () => {
await backend.add("fact", undefined, {
runId: "run-1",
metadata: { source: "test" },
- immutable: true,
infer: false,
- expires: "2027-01-01",
});
expect(provider.add).toHaveBeenCalledWith(
@@ -137,9 +132,7 @@ describe("providerToBackend — add", () => {
user_id: DEFAULT_USER,
run_id: "run-1",
metadata: { source: "test" },
- immutable: true,
infer: false,
- expiration_date: "2027-01-01",
}),
);
});
diff --git a/openclaw/tests/telemetry.test.ts b/openclaw/tests/telemetry.test.ts
index 893d47d10..20abcaaa2 100644
--- a/openclaw/tests/telemetry.test.ts
+++ b/openclaw/tests/telemetry.test.ts
@@ -24,7 +24,7 @@ describe("telemetry", () => {
});
it("exports PLUGIN_VERSION", () => {
- expect(PLUGIN_VERSION).toBe("1.0.6");
+ expect(PLUGIN_VERSION).toBe("1.0.7");
});
it("captureEvent does not throw", () => {
diff --git a/openclaw/tests/tools.test.ts b/openclaw/tests/tools.test.ts
index bbddc29ce..1c6f843ca 100644
--- a/openclaw/tests/tools.test.ts
+++ b/openclaw/tests/tools.test.ts
@@ -37,7 +37,6 @@ function createMockToolDeps(overrides = {}): ToolDeps {
searchThreshold: 0.5,
customInstructions: "test",
customCategories: {},
- customPrompt: "test",
} as any,
provider: {
search: vi
@@ -108,13 +107,14 @@ describe("registerAllTools", () => {
]);
});
- it("registers tools without a second argument (required, not optional)", () => {
+ it("registers tools with optional: false metadata", () => {
const ctx = createMockToolDeps();
registerAllTools(ctx);
const calls = (ctx.api.registerTool as ReturnType).mock.calls;
for (const call of calls) {
- expect(call).toHaveLength(1);
+ expect(call).toHaveLength(2);
+ expect(call[1]).toEqual({ optional: false });
}
});
});
diff --git a/openclaw/tools/index.ts b/openclaw/tools/index.ts
index 14219d4de..878bb228b 100644
--- a/openclaw/tools/index.ts
+++ b/openclaw/tools/index.ts
@@ -28,13 +28,14 @@ export interface ToolDeps {
export function registerAllTools(deps: ToolDeps): void {
const { api } = deps;
+ const nonOptional = { optional: false };
- api.registerTool(createMemorySearchTool(deps));
- api.registerTool(createMemoryAddTool(deps));
- api.registerTool(createMemoryGetTool(deps));
- api.registerTool(createMemoryListTool(deps));
- api.registerTool(createMemoryUpdateTool(deps));
- api.registerTool(createMemoryDeleteTool(deps));
- api.registerTool(createMemoryEventListTool(deps));
- api.registerTool(createMemoryEventStatusTool(deps));
+ api.registerTool(createMemorySearchTool(deps), nonOptional);
+ api.registerTool(createMemoryAddTool(deps), nonOptional);
+ api.registerTool(createMemoryGetTool(deps), nonOptional);
+ api.registerTool(createMemoryListTool(deps), nonOptional);
+ api.registerTool(createMemoryUpdateTool(deps), nonOptional);
+ api.registerTool(createMemoryDeleteTool(deps), nonOptional);
+ api.registerTool(createMemoryEventListTool(deps), nonOptional);
+ api.registerTool(createMemoryEventStatusTool(deps), nonOptional);
}
diff --git a/openclaw/tools/memory-add.ts b/openclaw/tools/memory-add.ts
index 46e2f20b2..c021b30ec 100644
--- a/openclaw/tools/memory-add.ts
+++ b/openclaw/tools/memory-add.ts
@@ -1,11 +1,12 @@
import { Type } from "@sinclair/typebox";
import type { AddOptions } from "../types.ts";
import { isSubagentSession } from "../isolation.ts";
-import { resolveCategories, ttlToExpirationDate } from "../skill-loader.ts";
+import { isNoiseMessage, stripNoiseFromContent } from "../filtering.ts";
+// v3.0.0: resolveCategories/ttlToExpirationDate removed - expiration_date/immutable no longer supported
import type { ToolDeps } from "./index.ts";
export function createMemoryAddTool(deps: ToolDeps) {
- const { api, cfg, provider, resolveUserId, getCurrentSessionId, buildAddOptions, buildSearchOptions, skillsActive } = deps;
+ const { api, provider, resolveUserId, getCurrentSessionId, buildAddOptions, buildSearchOptions, skillsActive } = deps;
return {
name: "memory_add",
@@ -28,11 +29,20 @@ export function createMemoryAddTool(deps: ToolDeps) {
userId?: string; agentId?: string; metadata?: Record; longTerm?: boolean;
};
- const allFacts: string[] = p.facts?.length ? p.facts : (p.text ? [p.text] : []);
- if (allFacts.length === 0) {
+ const rawFacts: string[] = p.facts?.length ? p.facts : (p.text ? [p.text] : []);
+ if (rawFacts.length === 0) {
return { content: [{ type: "text", text: "No facts provided. Pass 'text' or 'facts' array." }], details: { error: "missing_facts" } };
}
+ // Filter out noise and clean the facts before storing
+ const allFacts = rawFacts
+ .map((f) => stripNoiseFromContent(f))
+ .filter((f) => f.length > 0 && !isNoiseMessage(f));
+
+ if (allFacts.length === 0) {
+ return { content: [{ type: "text", text: "All provided facts were filtered as noise. Nothing stored." }], details: { error: "all_noise" } };
+ }
+
const start = Date.now();
try {
const currentSessionId = getCurrentSessionId();
@@ -53,21 +63,12 @@ export function createMemoryAddTool(deps: ToolDeps) {
...(category && { category }),
...(importance !== undefined && { importance }),
};
- const categories = resolveCategories(cfg.skills);
- const catConfig = category ? categories[category] : undefined;
- const expirationDate = catConfig ? ttlToExpirationDate(catConfig.ttl) : undefined;
- const isImmutable = catConfig?.immutable ?? false;
const addOpts: AddOptions = {
user_id: uid, source: "OPENCLAW", infer: false,
deduced_memories: allFacts, metadata: parsedMetadata ?? {},
- ...(expirationDate && { expiration_date: expirationDate }),
- ...(isImmutable && { immutable: true }),
};
if (runId) addOpts.run_id = runId;
- if (cfg.mode === "platform") {
- addOpts.output_format = "v1.1";
- }
const result = await provider.add([{ role: "user", content: allFacts.join("\n") }], addOpts);
const count = result.results?.length ?? 0;
diff --git a/openclaw/tools/memory-search.ts b/openclaw/tools/memory-search.ts
index c9f666127..5c8f7f46a 100644
--- a/openclaw/tools/memory-search.ts
+++ b/openclaw/tools/memory-search.ts
@@ -16,7 +16,7 @@ export function createMemorySearchTool(deps: ToolDeps) {
agentId: Type.Optional(Type.String({ description: "Agent ID to search a specific agent's memories" })),
scope: Type.Optional(
Type.Union([Type.Literal("session"), Type.Literal("long-term"), Type.Literal("all")], {
- description: 'Scope: "long-term" (default), "session", or "all"',
+ description: 'Scope: "all" (default), "session", or "long-term"',
}),
),
categories: Type.Optional(Type.Array(Type.String(), { description: "Filter by category" })),
@@ -25,7 +25,7 @@ export function createMemorySearchTool(deps: ToolDeps) {
async execute(_toolCallId: string, params: Record) {
const {
- query, limit, userId, agentId, scope = "long-term",
+ query, limit, userId, agentId, scope = "all",
categories: filterCategories, filters: agentFilters,
} = params as {
query: string; limit?: number; userId?: string; agentId?: string;
diff --git a/openclaw/types.ts b/openclaw/types.ts
index 5aef97a4c..5c7b1ac88 100644
--- a/openclaw/types.ts
+++ b/openclaw/types.ts
@@ -12,8 +12,7 @@ export type Mem0Config = {
baseUrl?: string;
customInstructions: string;
customCategories: Record;
- // OSS-specific
- customPrompt?: string;
+ // OSS-specific (customPrompt renamed to customInstructions in v3.0.0)
oss?: {
embedder?: { provider: string; config: Record };
vectorStore?: { provider: string; config: Record };
@@ -37,15 +36,12 @@ export interface AddOptions {
user_id: string;
run_id?: string;
custom_instructions?: string;
- custom_categories?: Array>;
- output_format?: string;
+ custom_categories?: Record;
source?: string;
// Agentic harness additions
infer?: boolean;
deduced_memories?: string[];
metadata?: Record;
- expiration_date?: string;
- immutable?: boolean;
}
export interface SearchOptions {
@@ -53,10 +49,6 @@ export interface SearchOptions {
run_id?: string;
top_k?: number;
threshold?: number;
- limit?: number;
- keyword_search?: boolean;
- reranking?: boolean;
- filter_memories?: boolean;
categories?: string[];
filters?: Record;
source?: string;