openclaw-memory-decay 0.1.6 → 0.1.8

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -63,12 +63,11 @@ Activation
63
63
  python3 -m venv ~/.openclaw/venvs/memory-decay
64
64
  ~/.openclaw/venvs/memory-decay/bin/pip install memory-decay
65
65
 
66
- # 2. Install this plugin while that venv is active
67
- source ~/.openclaw/venvs/memory-decay/bin/activate
66
+ # 2. Install this plugin from npm
68
67
  openclaw plugins install openclaw-memory-decay
69
68
 
70
69
  # 3. (Optional but recommended) Restrict auto-load to trusted plugins only
71
- openclaw config set plugins.allow '["memory-decay"]'
70
+ openclaw config set plugins.allow '["openclaw-memory-decay"]'
72
71
 
73
72
  # 4. Restart the gateway
74
73
  openclaw gateway restart
@@ -94,14 +93,14 @@ openclaw plugins install openclaw-memory-decay
94
93
  openclaw gateway restart
95
94
 
96
95
  # 3. Verify
97
- openclaw plugins list | grep memory-decay # should show: memory-decay | loaded
96
+ openclaw plugins list | grep openclaw-memory-decay # should show: openclaw-memory-decay | loaded
98
97
  curl -s http://127.0.0.1:8100/health # should show: {"status":"ok","current_tick":0}
99
98
  ```
100
99
 
101
100
  If auto-detection does not recover your backend path after migration, set the interpreter explicitly:
102
101
 
103
102
  ```bash
104
- openclaw config set plugins.entries.memory-decay.config.pythonPath "~/.openclaw/venvs/memory-decay/bin/python"
103
+ openclaw config set plugins.entries.openclaw-memory-decay.config.pythonPath "~/.openclaw/venvs/memory-decay/bin/python"
105
104
  openclaw gateway restart
106
105
  ```
107
106
 
@@ -109,19 +108,19 @@ openclaw gateway restart
109
108
 
110
109
  To update in the future:
111
110
  ```bash
112
- openclaw plugins update memory-decay
111
+ openclaw plugins update openclaw-memory-decay
113
112
  openclaw gateway restart
114
113
  ```
115
114
 
116
115
  ## Configuration
117
116
 
118
- Add to `~/.openclaw/openclaw.json` under `plugins.entries.memory-decay.config`:
117
+ Add to `~/.openclaw/openclaw.json` under `plugins.entries.openclaw-memory-decay.config`:
119
118
 
120
119
  ```json
121
120
  {
122
121
  "plugins": {
123
122
  "entries": {
124
- "memory-decay": {
123
+ "openclaw-memory-decay": {
125
124
  "enabled": true,
126
125
  "config": {
127
126
  "dbPath": "~/.openclaw/memory-decay-data/memories.db",
@@ -138,7 +137,7 @@ Add to `~/.openclaw/openclaw.json` under `plugins.entries.memory-decay.config`:
138
137
  |--------|---------|-------------|
139
138
  | `serverPort` | `8100` | Port for the memory-decay HTTP server |
140
139
  | `memoryDecayPath` | (auto) | Path to memory-decay-core. Auto-detected from the install-time Python environment if not set |
141
- | `pythonPath` | `python3` | Path to Python interpreter. Set this explicitly if the gateway runs outside the install-time venv |
140
+ | `pythonPath` | `python3` | Path to Python interpreter. Set this explicitly if you use a custom venv path instead of `~/.openclaw/venvs/memory-decay` |
142
141
  | `dbPath` | `~/.openclaw/memory-decay-data/memories.db` | SQLite database location |
143
142
  | `autoSave` | `true` | Auto-save every conversation turn at low importance. Set `false` to let the agent decide what to save |
144
143
  | `embeddingProvider` | `local` | Embedding provider: `local`, `openai`, or `gemini` |
@@ -244,7 +243,7 @@ The plugin registers these tools:
244
243
  This warning appears when `plugins.allow` is not set. While the plugin still loads (since it is explicitly configured in `plugins.entries`), it is good practice to restrict auto-load to trusted plugins only:
245
244
 
246
245
  ```bash
247
- openclaw config set plugins.allow '["memory-decay"]'
246
+ openclaw config set plugins.allow '["openclaw-memory-decay"]'
248
247
  openclaw gateway restart
249
248
  ```
250
249
 
@@ -297,20 +296,17 @@ openclaw plugins doctor
297
296
  python3 -m venv ~/.openclaw/venvs/memory-decay
298
297
  ~/.openclaw/venvs/memory-decay/bin/pip install memory-decay
299
298
 
300
- # 2. Activate the venv so postinstall detects the same interpreter
301
- source ~/.openclaw/venvs/memory-decay/bin/activate
302
-
303
- # 3. Install plugin from npm
299
+ # 2. Install plugin from npm
304
300
  openclaw plugins install openclaw-memory-decay
305
301
 
306
- # 4. Restart gateway
302
+ # 3. Restart gateway
307
303
  openclaw gateway restart
308
304
 
309
- # 5. Verify plugin is loaded
305
+ # 4. Verify plugin is loaded
310
306
  openclaw plugins list
311
- # Look for: memory-decay | loaded
307
+ # Look for: openclaw-memory-decay | loaded
312
308
 
313
- # 6. Check server health
309
+ # 5. Check server health
314
310
  curl -s http://127.0.0.1:8100/health
315
311
  # Expected: {"status":"ok","current_tick":0}
316
312
  ```
@@ -1,8 +1,8 @@
1
1
  {
2
- "id": "memory-decay",
2
+ "id": "openclaw-memory-decay",
3
3
  "name": "Memory Decay",
4
4
  "description": "Human-like memory with decay and reinforcement for OpenClaw agents",
5
- "version": "0.1.6",
5
+ "version": "0.1.8",
6
6
  "configSchema": {
7
7
  "type": "object",
8
8
  "additionalProperties": true,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "openclaw-memory-decay",
3
- "version": "0.1.6",
3
+ "version": "0.1.8",
4
4
  "type": "module",
5
5
  "description": "OpenClaw memory plugin backed by memory-decay engine",
6
6
  "main": "./src/index.js",
@@ -1,6 +1,6 @@
1
1
  import { execFileSync as nodeExecFileSync } from "node:child_process";
2
2
  import { existsSync as nodeExistsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
3
- import { tmpdir } from "node:os";
3
+ import { homedir as nodeHomedir, tmpdir } from "node:os";
4
4
  import { basename, dirname, isAbsolute, join, resolve } from "node:path";
5
5
 
6
6
  function resolvePythonPath(command, { execFileSync = nodeExecFileSync, isWin = process.platform === "win32" } = {}) {
@@ -23,7 +23,9 @@ export function buildPythonCandidates({
23
23
  env = process.env,
24
24
  isWin = process.platform === "win32",
25
25
  existsSync = nodeExistsSync,
26
+ homedir = nodeHomedir,
26
27
  } = {}) {
28
+ const openclawStateDir = resolve(env.OPENCLAW_HOME || homedir(), ".openclaw");
27
29
  const siblingRoots = [
28
30
  resolve(pluginRoot, "../memory-decay"),
29
31
  resolve(pluginRoot, "../memory-decay-core"),
@@ -31,6 +33,9 @@ export function buildPythonCandidates({
31
33
  const pathCandidates = [];
32
34
 
33
35
  if (env.MD_PYTHON_PATH) pathCandidates.push(env.MD_PYTHON_PATH);
36
+ pathCandidates.push(
37
+ join(openclawStateDir, "venvs", "memory-decay", isWin ? "Scripts/python.exe" : "bin/python"),
38
+ );
34
39
  if (env.VIRTUAL_ENV) {
35
40
  pathCandidates.push(join(env.VIRTUAL_ENV, isWin ? "Scripts/python.exe" : "bin/python"));
36
41
  }
package/src/index.ts CHANGED
@@ -1,9 +1,10 @@
1
1
  import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
2
2
  import { emptyPluginConfigSchema } from "openclaw/plugin-sdk";
3
3
  import { MemoryDecayClient } from "./client.js";
4
+ import { loadLegacyPluginConfig } from "./legacy-config.js";
4
5
  import { MemoryDecayService, type ServiceConfig } from "./service.js";
5
6
  import { shouldMigrate, migrateMarkdownMemories } from "./migrator.js";
6
- import { mergePythonEnv } from "./python-env.js";
7
+ import { detectPythonEnv, mergePythonEnv } from "./python-env.js";
7
8
  import { toFreshness } from "./types.js";
8
9
 
9
10
  const BOOTSTRAP_PROMPT = `## Memory System (memory-decay)
@@ -39,14 +40,14 @@ Your memories naturally decay over time. Frequently recalled memories grow stron
39
40
  const MIN_MESSAGE_LENGTH = 20;
40
41
 
41
42
  const memoryDecayPlugin = {
42
- id: "memory-decay",
43
+ id: "openclaw-memory-decay",
43
44
  name: "Memory Decay",
44
45
  description: "Human-like memory with decay and reinforcement",
45
46
  kind: "memory" as const,
46
47
  configSchema: emptyPluginConfigSchema(),
47
48
 
48
49
  register(api: OpenClawPluginApi) {
49
- const cfg = api.pluginConfig ?? {};
50
+ const cfg = loadLegacyPluginConfig((api.pluginConfig ?? {}) as Record<string, unknown>);
50
51
  const port = (cfg.serverPort as number) ?? 8100;
51
52
  const autoSave = cfg.autoSave !== false; // default true
52
53
 
@@ -90,15 +91,21 @@ const memoryDecayPlugin = {
90
91
  let memoryDecayPath = (cfg.memoryDecayPath as string) ?? "";
91
92
  let pythonPath = (cfg.pythonPath as string) ?? "";
92
93
  let detectedEnv: { memoryDecayPath?: string; pythonPath?: string } = {};
94
+ const { dirname, resolve } = await import("node:path");
95
+ const { fileURLToPath } = await import("node:url");
96
+ const pluginRoot = dirname(fileURLToPath(import.meta.url));
93
97
  if (!memoryDecayPath || !pythonPath) {
94
98
  try {
95
99
  const { readFileSync } = await import("node:fs");
96
- const { resolve, dirname } = await import("node:path");
97
- const { fileURLToPath } = await import("node:url");
98
- const pluginRoot = dirname(fileURLToPath(import.meta.url));
99
100
  detectedEnv = JSON.parse(readFileSync(resolve(pluginRoot, "../.python-env.json"), "utf8"));
100
101
  } catch {}
101
102
  }
103
+ if (!detectedEnv.memoryDecayPath || !detectedEnv.pythonPath) {
104
+ detectedEnv = mergePythonEnv(
105
+ detectedEnv,
106
+ detectPythonEnv({ pluginRoot }) ?? {},
107
+ );
108
+ }
102
109
  ({ memoryDecayPath, pythonPath } = mergePythonEnv(
103
110
  { memoryDecayPath, pythonPath },
104
111
  detectedEnv,
@@ -106,7 +113,7 @@ const memoryDecayPlugin = {
106
113
  if (!memoryDecayPath) {
107
114
  ctx.logger.error(
108
115
  "Could not auto-detect memory-decay installation. " +
109
- "Run `pip install memory-decay` or set memoryDecayPath in plugin config."
116
+ "Install the backend into ~/.openclaw/venvs/memory-decay or set pythonPath/memoryDecayPath in plugin config."
110
117
  );
111
118
  return;
112
119
  }
@@ -0,0 +1,35 @@
1
+ import { existsSync, readFileSync } from "node:fs";
2
+ import { homedir } from "node:os";
3
+ import { join, resolve } from "node:path";
4
+
5
+ export type PluginConfigLike = Record<string, unknown>;
6
+
7
+ function resolveOpenClawConfigPath(env: NodeJS.ProcessEnv = process.env): string {
8
+ return resolve(env.OPENCLAW_HOME || homedir(), ".openclaw", "openclaw.json");
9
+ }
10
+
11
+ export function loadLegacyPluginConfig(
12
+ currentConfig: PluginConfigLike,
13
+ {
14
+ env = process.env,
15
+ configPath = resolveOpenClawConfigPath(env),
16
+ }: { env?: NodeJS.ProcessEnv; configPath?: string } = {},
17
+ ): PluginConfigLike {
18
+ if (Object.keys(currentConfig).length > 0) {
19
+ return currentConfig;
20
+ }
21
+
22
+ if (!existsSync(configPath)) {
23
+ return currentConfig;
24
+ }
25
+
26
+ try {
27
+ const root = JSON.parse(readFileSync(configPath, "utf8"));
28
+ const legacyConfig = root?.plugins?.entries?.["memory-decay"]?.config;
29
+ return legacyConfig && typeof legacyConfig === "object"
30
+ ? { ...legacyConfig }
31
+ : currentConfig;
32
+ } catch {
33
+ return currentConfig;
34
+ }
35
+ }
package/src/python-env.ts CHANGED
@@ -1,8 +1,25 @@
1
+ import { execFileSync as nodeExecFileSync } from "node:child_process";
2
+ import { existsSync as nodeExistsSync, mkdtempSync, readFileSync, rmSync } from "node:fs";
3
+ import { homedir as nodeHomedir, tmpdir } from "node:os";
4
+ import { basename, dirname, isAbsolute, join, resolve } from "node:path";
5
+
1
6
  export interface PythonEnvLike {
2
7
  memoryDecayPath?: string;
3
8
  pythonPath?: string;
4
9
  }
5
10
 
11
+ interface DetectPythonEnvOptions {
12
+ pluginRoot: string;
13
+ env?: NodeJS.ProcessEnv;
14
+ isWin?: boolean;
15
+ homedir?: () => string;
16
+ existsSync?: (path: string) => boolean;
17
+ execFileSync?: typeof nodeExecFileSync;
18
+ makeTempDir?: () => string;
19
+ readPathFile?: (path: string) => string;
20
+ removeTempDir?: (path: string) => void;
21
+ }
22
+
6
23
  export function mergePythonEnv(
7
24
  configured: PythonEnvLike,
8
25
  detected: PythonEnvLike = {},
@@ -12,3 +29,105 @@ export function mergePythonEnv(
12
29
  pythonPath: configured.pythonPath || detected.pythonPath || "",
13
30
  };
14
31
  }
32
+
33
+ function resolveOpenClawStateDir(
34
+ env: NodeJS.ProcessEnv,
35
+ homedir: () => string,
36
+ ): string {
37
+ return resolve(env.OPENCLAW_HOME || homedir(), ".openclaw");
38
+ }
39
+
40
+ export function resolveMemoryDecayPath(moduleFile: string): string {
41
+ const packageDir = dirname(resolve(moduleFile));
42
+ const packageParent = dirname(packageDir);
43
+ return basename(packageParent) === "src" ? dirname(packageParent) : packageParent;
44
+ }
45
+
46
+ export function buildPythonCandidates({
47
+ pluginRoot,
48
+ env = process.env,
49
+ isWin = process.platform === "win32",
50
+ homedir = nodeHomedir,
51
+ existsSync = nodeExistsSync,
52
+ }: Omit<DetectPythonEnvOptions, "execFileSync" | "makeTempDir" | "readPathFile" | "removeTempDir">): string[] {
53
+ const siblingRoots = [
54
+ resolve(pluginRoot, "../../memory-decay"),
55
+ resolve(pluginRoot, "../../memory-decay-core"),
56
+ resolve(pluginRoot, "../memory-decay"),
57
+ resolve(pluginRoot, "../memory-decay-core"),
58
+ ];
59
+ const openclawStateDir = resolveOpenClawStateDir(env, homedir);
60
+ const recommendedVenv = join(
61
+ openclawStateDir,
62
+ "venvs",
63
+ "memory-decay",
64
+ isWin ? "Scripts/python.exe" : "bin/python",
65
+ );
66
+ const pathCandidates = [
67
+ env.MD_PYTHON_PATH,
68
+ recommendedVenv,
69
+ env.VIRTUAL_ENV
70
+ ? join(env.VIRTUAL_ENV, isWin ? "Scripts/python.exe" : "bin/python")
71
+ : undefined,
72
+ ...siblingRoots.map((root) =>
73
+ join(root, isWin ? ".venv/Scripts/python.exe" : ".venv/bin/python")),
74
+ ].filter((candidate): candidate is string => typeof candidate === "string" && candidate.length > 0 && existsSync(candidate));
75
+
76
+ const commandCandidates = isWin ? ["python"] : ["python3", "python"];
77
+ return [...new Set([...pathCandidates, ...commandCandidates])];
78
+ }
79
+
80
+ function resolvePythonPath(
81
+ command: string,
82
+ {
83
+ execFileSync = nodeExecFileSync,
84
+ isWin = process.platform === "win32",
85
+ }: Pick<DetectPythonEnvOptions, "execFileSync" | "isWin"> = {},
86
+ ): string {
87
+ if (isAbsolute(command)) {
88
+ return command;
89
+ }
90
+
91
+ const resolver = isWin ? "where" : "which";
92
+ return execFileSync(resolver, [command], { encoding: "utf8" }).trim().split(/\r?\n/)[0];
93
+ }
94
+
95
+ export function detectPythonEnv({
96
+ pluginRoot,
97
+ env = process.env,
98
+ isWin = process.platform === "win32",
99
+ homedir = nodeHomedir,
100
+ existsSync = nodeExistsSync,
101
+ execFileSync = nodeExecFileSync,
102
+ makeTempDir = () => mkdtempSync(join(tmpdir(), "memory-decay-python-env-")),
103
+ readPathFile = (path) => readFileSync(path, "utf8"),
104
+ removeTempDir = (path) => rmSync(path, { recursive: true, force: true }),
105
+ }: DetectPythonEnvOptions): { memoryDecayPath: string; pythonPath: string } | null {
106
+ for (const candidate of buildPythonCandidates({ pluginRoot, env, isWin, homedir, existsSync })) {
107
+ try {
108
+ execFileSync(candidate, ["-c", "import memory_decay.server"], { stdio: "ignore" });
109
+ const tempDir = makeTempDir();
110
+ const outputPath = join(tempDir, "memory-decay-module-path.txt");
111
+
112
+ try {
113
+ execFileSync(candidate, [
114
+ "-c",
115
+ `import memory_decay, pathlib; pathlib.Path(${JSON.stringify(outputPath)}).write_text(str(pathlib.Path(memory_decay.__file__).resolve()))`,
116
+ ], { stdio: "ignore" });
117
+ } catch (error) {
118
+ removeTempDir(tempDir);
119
+ throw error;
120
+ }
121
+
122
+ const moduleFile = readPathFile(outputPath).trim();
123
+ removeTempDir(tempDir);
124
+
125
+ return {
126
+ pythonPath: resolvePythonPath(candidate, { execFileSync, isWin }),
127
+ memoryDecayPath: resolveMemoryDecayPath(moduleFile),
128
+ };
129
+ } catch {}
130
+ }
131
+
132
+ return null;
133
+ }