Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 14 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -146,13 +146,18 @@ JSON response means the server is up. API docs: `http://localhost:8000/docs`.
On your machine (use your actual plugin path):

```bash
# Install from npm (recommended for end users; OpenClaw downloads the package from the npm registry)
openclaw plugins install openclaw-extension-powermem

# Install from a local directory (e.g. cloned repo)
openclaw plugins install /path/to/openclaw-extension-powermem

# For development (symlink, no copy)
openclaw plugins install -l /path/to/openclaw-extension-powermem
```

**Note:** Running `npm i openclaw-extension-powermem` in a Node project only adds the package to that project’s `node_modules`; it does **not** register the plugin with OpenClaw. To use this as an OpenClaw plugin, you must run `openclaw plugins install openclaw-extension-powermem` (or install from a path as above), then configure OpenClaw and restart the gateway.

After install, run `openclaw plugins list` and confirm `memory-powermem` is listed.

---
Expand Down Expand Up @@ -283,6 +288,15 @@ Exposed to OpenClaw agents:
- Confirm `plugins.slots.memory` is `memory-powermem` and `plugins.entries["memory-powermem"].enabled` is `true`.
- Restart the gateway (or OpenClaw app) after config changes.

**4. Agent does not search memory until I ask it to**

- With `autoRecall: true`, the plugin injects system guidance so the agent is told to use `memory_recall` (or injected `<relevant-memories>`) when answering about past events, preferences, or people. Ensure `autoRecall` is not set to `false`.
- Auto-recall runs before each turn using the current user message (or the last user message if the prompt is very short). If you still see the agent answering without checking memory, try being explicit once (e.g. "check your memory for …") and ensure the run uses the plugin (e.g. web UI after `/new` uses the same gateway and plugin).

**5. Agent tries to read `memory/YYYY-MM-DD.md` and gets ENOENT**

- OpenClaw's built-in **session-memory** hook writes session snapshots to workspace `memory/YYYY-MM-DD-slug.md`. When you use PowerMem as the memory slot, the agent may still be told (by workspace docs or inference) to load those files, causing failed `read` calls. Disable the hook so only PowerMem is used: run `openclaw hooks disable session-memory`, or set `hooks.internal.entries["session-memory"].enabled` to `false` in `~/.openclaw/openclaw.json`. Restart the gateway after changing config.

---

## Development
Expand Down
14 changes: 14 additions & 0 deletions README_CN.md
Original file line number Diff line number Diff line change
Expand Up @@ -147,13 +147,18 @@ curl -s http://localhost:8000/api/v1/system/health
在**你本机**执行(路径改成你实际克隆的目录):

```bash
# 从 npm 安装(推荐给终端用户;会从 npm 官方源自动下载并安装)
openclaw plugins install openclaw-extension-powermem

# 若插件在本机目录(例如克隆下来的)
openclaw plugins install /path/to/openclaw-extension-powermem

# 开发时想改代码即生效,可用链接方式(不拷贝)
openclaw plugins install -l /path/to/openclaw-extension-powermem
```

**说明:** 在某个 Node 项目里执行 `npm i openclaw-extension-powermem` 只会把包装进该项目的 `node_modules`,**不会**在 OpenClaw 里注册插件。若要在 OpenClaw 里使用本插件,必须执行 `openclaw plugins install openclaw-extension-powermem`(或按上面用本地路径安装),再在 OpenClaw 中配置并重启 gateway。

安装成功后,可用 `openclaw plugins list` 确认能看到 `memory-powermem`。

---
Expand Down Expand Up @@ -284,6 +289,15 @@ openclaw ltm search "咖啡"
- 确认配置里 `plugins.slots.memory` 为 `memory-powermem`,且 `plugins.entries["memory-powermem"].enabled` 为 `true`。
- 改完配置后必须重启 gateway(或 OpenClaw 应用)。

**4. 不主动说「从 PowerMem 查」Agent 就不查记忆**

- 开启 `autoRecall: true` 后,插件会注入系统级指引,告诉 Agent 在回答与过去、偏好、人物相关的问题时先使用 `memory_recall` 或本轮已注入的 `<relevant-memories>`。请确认未把 `autoRecall` 设为 `false`。
- 自动回忆在每轮开始前用当前用户消息(若 prompt 过短则用上一条用户消息)做检索。若仍出现不查就回复的情况,可先显式说一句「查一下记忆里关于……」确认流程正常;并确认 /new 后的 Web 会话走的是同一 gateway 与插件。

**5. Agent 尝试读取 `memory/YYYY-MM-DD.md` 并报 ENOENT**

- OpenClaw 自带的 **session-memory** hook 会把会话摘要写到工作区的 `memory/YYYY-MM-DD-slug.md`。使用 PowerMem 作为记忆槽时,Agent 仍可能被工作区文档或模型推断引导去读这些文件,导致 `read` 报错。建议禁用该 hook,只使用 PowerMem:执行 `openclaw hooks disable session-memory`,或在 `~/.openclaw/openclaw.json` 里将 `hooks.internal.entries["session-memory"].enabled` 设为 `false`。修改配置后需重启 gateway。

---

## 本仓库开发命令
Expand Down
62 changes: 54 additions & 8 deletions index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -339,31 +339,77 @@ const memoryPlugin = {
// Lifecycle Hooks
// ========================================================================

const MEMORY_RECALL_GUIDANCE =
"## Long-term memory (PowerMem)\n" +
"When answering about past events, user preferences, people, or anything the user may have told you before: use the memory_recall tool to search long-term memory first, or use any <relevant-memories> already injected in this turn.\n";

function lastUserMessageText(messages: unknown[] | undefined): string {
if (!Array.isArray(messages) || messages.length === 0) return "";
for (let i = messages.length - 1; i >= 0; i--) {
const msg = messages[i];
if (!msg || typeof msg !== "object") continue;
const role = (msg as Record<string, unknown>).role;
if (role !== "user") continue;
const content = (msg as Record<string, unknown>).content;
if (typeof content === "string" && content.trim().length >= 5) return content.trim();
if (Array.isArray(content)) {
for (const block of content) {
if (
block &&
typeof block === "object" &&
(block as Record<string, unknown>).type === "text" &&
typeof (block as Record<string, unknown>).text === "string"
) {
const t = String((block as Record<string, unknown>).text).trim();
if (t.length >= 5) return t;
}
}
}
}
return "";
}

if (cfg.autoRecall) {
api.on("before_agent_start", async (event: unknown) => {
const e = event as { prompt: string; messages?: unknown[] };
if (!e.prompt || e.prompt.length < 5) return;
const query =
(typeof e.prompt === "string" && e.prompt.trim().length >= 5
? e.prompt.trim()
: lastUserMessageText(e.messages)) || "";
if (query.length < 5) {
return { prependSystemContext: MEMORY_RECALL_GUIDANCE };
}

const recallLimit = Math.max(1, Math.min(100, cfg.recallLimit ?? 5));
const scoreThreshold = Math.max(0, Math.min(1, cfg.recallScoreThreshold ?? 0));

try {
const requestLimit = Math.min(100, Math.max(recallLimit * 2, recallLimit + 10));
const raw = await client.search(e.prompt, requestLimit);
const raw = await client.search(query, requestLimit);
const results = raw
.filter((r) => (r.score ?? 0) >= scoreThreshold)
.slice(0, recallLimit);
if (results.length === 0) return;

const memoryContext = results.map((r) => `- ${r.content}`).join("\n");
api.logger.info(
`memory-powermem: injecting ${results.length} memories into context`,
);
const memoryContext =
results.length > 0
? results.map((r) => `- ${r.content}`).join("\n")
: "";
if (results.length > 0) {
api.logger.info(
`memory-powermem: injecting ${results.length} memories into context`,
);
}
return {
prependContext: `<relevant-memories>\nThe following memories may be relevant to this conversation:\n${memoryContext}\n</relevant-memories>`,
prependSystemContext: MEMORY_RECALL_GUIDANCE,
...(memoryContext
? {
prependContext: `<relevant-memories>\nThe following memories may be relevant to this conversation:\n${memoryContext}\n</relevant-memories>`,
}
: {}),
};
} catch (err) {
api.logger.warn(`memory-powermem: recall failed: ${String(err)}`);
return { prependSystemContext: MEMORY_RECALL_GUIDANCE };
}
});
}
Expand Down
Loading