From fd7999cefac888786a06f6eb3daa7f98942f9310 Mon Sep 17 00:00:00 2001 From: Teingi Date: Thu, 29 Jan 2026 20:31:52 +0800 Subject: [PATCH] extensions: add memory-powermem plugin for long-term memory --- extensions/memory-powermem/README.md | 37 ++++++---- extensions/memory-powermem/index.ts | 72 +++++++++++-------- .../node_modules/@sinclair/typebox | 1 - 3 files changed, 64 insertions(+), 46 deletions(-) delete mode 120000 extensions/memory-powermem/node_modules/@sinclair/typebox diff --git a/extensions/memory-powermem/README.md b/extensions/memory-powermem/README.md index 22d460ea5..8f86d0a4b 100644 --- a/extensions/memory-powermem/README.md +++ b/extensions/memory-powermem/README.md @@ -21,25 +21,33 @@ Moltbot long-term memory plugin backed by [PowerMem](https://github.com/oceanbas ## Moltbot configuration -1. Set the memory slot to this plugin: +1. Enable the plugin and set the memory slot to this plugin. Edit your Moltbot config (e.g. `~/.clawdbot/config.json` or `moltbot.json` in your state dir): - ```yaml - plugins: - slots: - memory: memory-powermem - config: - memory-powermem: - baseUrl: "http://localhost:8000" - # apiKey: "optional-if-auth-enabled" - autoCapture: true - autoRecall: true - inferOnAdd: true - # userId: "optional-override" - # agentId: "optional-override" + ```json + { + "plugins": { + "slots": { "memory": "memory-powermem" }, + "entries": { + "memory-powermem": { + "enabled": true, + "config": { + "baseUrl": "http://localhost:8000", + "autoCapture": true, + "autoRecall": true, + "inferOnAdd": true + } + } + } + } + } ``` + Optional: `apiKey` (if PowerMem server has auth), `userId`, `agentId` — see Options below. + 2. Ensure PowerMem server is running before starting the gateway. +**Auto-capture:** When a conversation ends, the full user/assistant text is sent to PowerMem with `infer: true`; PowerMem extracts and stores memories (no trigger phrases) (e.g. “remember …”, “I like …”, “important”, or Chinese “记得/记住/我喜欢/重要/偏好”). At most 3 chunks per session (each up to 6000 chars). To test: run `moltbot ltm health`, then `moltbot ltm add "User prefers dark mode"` and `moltbot ltm search "dark mode"`. In chat, ask the agent to “remember that I prefer tea” or say “我喜欢用 Python” so a message is auto-captured. + ## Options | Option | Required | Description | @@ -62,6 +70,7 @@ Moltbot long-term memory plugin backed by [PowerMem](https://github.com/oceanbas - `moltbot ltm search [--limit n]` — Search memories. - `moltbot ltm health` — Check PowerMem server health. +- `moltbot ltm add ""` — Manually store one memory (for testing or one-off storage). ## Running tests diff --git a/extensions/memory-powermem/index.ts b/extensions/memory-powermem/index.ts index d88b9cce9..83d53176b 100644 --- a/extensions/memory-powermem/index.ts +++ b/extensions/memory-powermem/index.ts @@ -17,32 +17,6 @@ import { } from "./config.js"; import { PowerMemClient } from "./client.js"; -// ============================================================================ -// Rule-based capture filter (same idea as memory-lancedb) -// ============================================================================ - -const MEMORY_TRIGGERS = [ - /zapamatuj si|pamatuj|remember/i, - /preferuji|radši|nechci|prefer/i, - /rozhodli jsme|budeme používat/i, - /\+\d{10,}/, - /[\w.-]+@[\w.-]+\.\w+/, - /můj\s+\w+\s+je|je\s+můj/i, - /my\s+\w+\s+is|is\s+my/i, - /i (like|prefer|hate|love|want|need)/i, - /always|never|important/i, -]; - -function shouldCapture(text: string): boolean { - if (text.length < 10 || text.length > 500) return false; - if (text.includes("")) return false; - if (text.startsWith("<") && text.includes(" 3) return false; - return MEMORY_TRIGGERS.some((r) => r.test(text)); -} - // ============================================================================ // Plugin Definition // ============================================================================ @@ -309,6 +283,24 @@ const memoryPlugin = { process.exitCode = 1; } }); + + ltm + .command("add") + .description("Manually add a memory (for testing or one-off storage)") + .argument("", "Content to store") + .action(async (text: string) => { + try { + const created = await client.add(text.trim(), { infer: cfg.inferOnAdd }); + if (created.length === 0) { + console.log("Stored (no inferred items)."); + } else { + console.log(`Stored ${created.length} item(s):`, created.map((c) => c.memory_id)); + } + } catch (err) { + console.error("PowerMem add failed:", err); + process.exitCode = 1; + } + }); }, { commands: ["ltm"] }, ); @@ -372,16 +364,34 @@ const memoryPlugin = { } } - const toCapture = texts.filter((t) => t && shouldCapture(t)); - if (toCapture.length === 0) return; + // Use all conversation content; PowerMem infers memories (no trigger-phrase filter) + const MIN_LEN = 10; + const MAX_CHUNK_LEN = 6000; + const MAX_CHUNKS_PER_SESSION = 3; + const sanitized = texts + .filter((t): t is string => typeof t === "string" && t.trim().length >= MIN_LEN) + .map((t) => t.trim()) + .filter( + (t) => + !t.includes("") && + !(t.startsWith("<") && t.includes("= MAX_CHUNKS_PER_SESSION) break; + chunks.push(combined.slice(i, i + MAX_CHUNK_LEN)); + } let stored = 0; - for (const text of toCapture.slice(0, 3)) { - const created = await client.add(text, { infer: cfg.inferOnAdd }); + for (const chunk of chunks) { + const created = await client.add(chunk, { infer: cfg.inferOnAdd }); stored += created.length; } if (stored > 0) { - api.logger.info?.(`memory-powermem: auto-captured ${stored} memories`); + api.logger.info?.(`memory-powermem: auto-captured ${stored} memories from conversation`); } } catch (err) { api.logger.warn?.(`memory-powermem: capture failed: ${String(err)}`); diff --git a/extensions/memory-powermem/node_modules/@sinclair/typebox b/extensions/memory-powermem/node_modules/@sinclair/typebox deleted file mode 120000 index e0ac1dea2..000000000 --- a/extensions/memory-powermem/node_modules/@sinclair/typebox +++ /dev/null @@ -1 +0,0 @@ -../../../../node_modules/.pnpm/@sinclair+typebox@0.34.47/node_modules/@sinclair/typebox \ No newline at end of file