@mem9/mem9 0.4.0-rc.0 → 0.4.1
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 +17 -18
- package/index.ts +2 -12
- package/openclaw.plugin.json +0 -7
- package/package.json +1 -3
- package/server-backend.ts +4 -21
- package/types.ts +0 -1
- package/server-backend.test.ts +0 -82
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
|
-
# OpenClaw Plugin for
|
|
1
|
+
# OpenClaw Plugin for mnemos
|
|
2
2
|
|
|
3
|
-
Memory plugin for [OpenClaw](https://github.com/openclaw) — replaces the built-in memory slot with cloud-persistent shared memory. Runs in server mode only, connecting to `mnemo-server` via `apiUrl` + `apiKey` (preferred) or legacy `tenantID`.
|
|
3
|
+
Memory plugin for [OpenClaw](https://github.com/openclaw) — replaces the built-in memory slot with cloud-persistent shared memory. Runs in server mode only, connecting to `mnemo-server` via `apiUrl` + `apiKey` (preferred) or legacy `tenantID`.
|
|
4
4
|
|
|
5
5
|
## 🚀 Quick Start (Server Mode)
|
|
6
6
|
|
|
@@ -20,14 +20,14 @@ curl -s -X POST http://localhost:8080/v1alpha1/mem9s \
|
|
|
20
20
|
# {"id": "uuid"}
|
|
21
21
|
```
|
|
22
22
|
|
|
23
|
-
Add
|
|
23
|
+
Add mnemo to your project's `openclaw.json`:
|
|
24
24
|
|
|
25
25
|
```json
|
|
26
26
|
{
|
|
27
27
|
"plugins": {
|
|
28
|
-
"slots": { "memory": "
|
|
28
|
+
"slots": { "memory": "openclaw" },
|
|
29
29
|
"entries": {
|
|
30
|
-
"
|
|
30
|
+
"openclaw": {
|
|
31
31
|
"enabled": true,
|
|
32
32
|
"config": {
|
|
33
33
|
"apiUrl": "http://localhost:8080",
|
|
@@ -64,8 +64,8 @@ This is a `kind: "memory"` plugin — OpenClaw's framework manages when to load/
|
|
|
64
64
|
|
|
65
65
|
| Hook | Trigger | What it does |
|
|
66
66
|
|---|---|---|
|
|
67
|
-
| `before_prompt_build` | Every LLM call | Searches memories by current prompt
|
|
68
|
-
| `after_compaction` | After `/compact` |
|
|
67
|
+
| `before_prompt_build` | Every LLM call | Searches memories by current prompt, injects relevant ones as context (3-min TTL cache) |
|
|
68
|
+
| `after_compaction` | After `/compact` | Invalidates cache so the next prompt gets fresh memories from the database |
|
|
69
69
|
| `before_reset` | Before `/reset` | Saves a session summary (last 3 user messages) as memory before context is wiped |
|
|
70
70
|
| `agent_end` | Agent finishes | Auto-captures the last assistant response as memory (if substantial) |
|
|
71
71
|
|
|
@@ -73,7 +73,7 @@ This is a `kind: "memory"` plugin — OpenClaw's framework manages when to load/
|
|
|
73
73
|
|
|
74
74
|
| Tool | Description |
|
|
75
75
|
|---|---|
|
|
76
|
-
| `memory_store` | Store a new memory |
|
|
76
|
+
| `memory_store` | Store a new memory (upsert by key) |
|
|
77
77
|
| `memory_search` | Hybrid vector + keyword search (or keyword-only) |
|
|
78
78
|
| `memory_get` | Retrieve a single memory by ID |
|
|
79
79
|
| `memory_update` | Update an existing memory |
|
|
@@ -91,7 +91,7 @@ This is a `kind: "memory"` plugin — OpenClaw's framework manages when to load/
|
|
|
91
91
|
### Method A: npm install (Recommended)
|
|
92
92
|
|
|
93
93
|
```bash
|
|
94
|
-
openclaw plugins install @mem9/
|
|
94
|
+
openclaw plugins install @mem9/openclaw
|
|
95
95
|
```
|
|
96
96
|
|
|
97
97
|
### Method B: From source
|
|
@@ -104,7 +104,7 @@ npm install
|
|
|
104
104
|
|
|
105
105
|
### Configure OpenClaw
|
|
106
106
|
|
|
107
|
-
Add
|
|
107
|
+
Add mnemo to your project's `openclaw.json`:
|
|
108
108
|
|
|
109
109
|
OpenClaw is often deployed across teams with multiple agents. Server mode gives you:
|
|
110
110
|
|
|
@@ -139,10 +139,10 @@ Each agent uses the same `apiKey` for the shared memory pool. The plugin sends t
|
|
|
139
139
|
{
|
|
140
140
|
"plugins": {
|
|
141
141
|
"slots": {
|
|
142
|
-
"memory": "
|
|
142
|
+
"memory": "openclaw"
|
|
143
143
|
},
|
|
144
144
|
"entries": {
|
|
145
|
-
"
|
|
145
|
+
"openclaw": {
|
|
146
146
|
"enabled": true,
|
|
147
147
|
"config": {
|
|
148
148
|
"apiUrl": "http://your-server:8080",
|
|
@@ -160,8 +160,8 @@ That's it. The server handles scoping and conflict resolution. Conceptually, the
|
|
|
160
160
|
|
|
161
161
|
Start OpenClaw. You should see:
|
|
162
162
|
|
|
163
|
-
```
|
|
164
|
-
[mem9] Server mode
|
|
163
|
+
```
|
|
164
|
+
[mem9] Server mode
|
|
165
165
|
```
|
|
166
166
|
|
|
167
167
|
If you see `[mem9] No mode configured...`, check your `openclaw.json` config.
|
|
@@ -174,12 +174,11 @@ Defined in `openclaw.plugin.json`:
|
|
|
174
174
|
|---|---|---|
|
|
175
175
|
| `apiUrl` | string | mnemo-server URL |
|
|
176
176
|
| `apiKey` | string | Preferred key. Uses `/v1alpha2/mem9s/...` with `X-API-Key` header |
|
|
177
|
-
| `provisionQueryParams` | object | Optional `utm_*` map forwarded only to the initial `POST /v1alpha1/mem9s` auto-provision request when `apiKey` is absent |
|
|
178
177
|
| `defaultTimeoutMs` | number | Default timeout for non-search mem9 API requests in milliseconds. Default: `8000` |
|
|
179
178
|
| `searchTimeoutMs` | number | Timeout for `memory_search` and automatic recall search in milliseconds. Default: `15000` |
|
|
180
179
|
| `tenantID` | string | Legacy alias for `apiKey`. The plugin still uses `/v1alpha2/mem9s/...` with `X-API-Key`. |
|
|
181
180
|
|
|
182
|
-
> **Note**: `apiKey` takes precedence when both fields are set. If only `tenantID` is present, the plugin treats it as a legacy alias for `apiKey`, still uses v1alpha2, and logs a deprecation warning once at startup.
|
|
181
|
+
> **Note**: `apiKey` takes precedence when both fields are set. If only `tenantID` is present, the plugin treats it as a legacy alias for `apiKey`, still uses v1alpha2, and logs a deprecation warning once at startup.
|
|
183
182
|
|
|
184
183
|
## Timeout Behavior
|
|
185
184
|
|
|
@@ -214,7 +213,7 @@ Example:
|
|
|
214
213
|
openclaw-plugin/
|
|
215
214
|
├── README.md # This file
|
|
216
215
|
├── openclaw.plugin.json # Plugin metadata + config schema
|
|
217
|
-
├── package.json # npm package (@mem9/
|
|
216
|
+
├── package.json # npm package (@mem9/openclaw)
|
|
218
217
|
├── index.ts # Plugin entry point + tool registration
|
|
219
218
|
├── backend.ts # MemoryBackend interface
|
|
220
219
|
├── server-backend.ts # Server mode: fetch → mnemo API
|
|
@@ -229,4 +228,4 @@ openclaw-plugin/
|
|
|
229
228
|
| `No mode configured` | Missing config | Add `apiUrl` and `apiKey` (or legacy `tenantID`) to plugin config |
|
|
230
229
|
| `Server mode requires...` | Missing key | Add `apiKey` (or legacy `tenantID`) to config |
|
|
231
230
|
| Search requests time out | Hybrid/vector search exceeds plugin timeout | Increase `searchTimeoutMs` in plugin config |
|
|
232
|
-
| Plugin not loading | Not in memory slot | Set `"slots": {"memory": "
|
|
231
|
+
| Plugin not loading | Not in memory slot | Set `"slots": {"memory": "openclaw"}` in openclaw.json |
|
package/index.ts
CHANGED
|
@@ -314,15 +314,7 @@ const mnemoPlugin = {
|
|
|
314
314
|
api.logger.info("[mem9] tenantID is deprecated; treating it as apiKey for v1alpha2");
|
|
315
315
|
}
|
|
316
316
|
const registerTenant = async (agentName: string): Promise<string> => {
|
|
317
|
-
const backend = new ServerBackend(
|
|
318
|
-
effectiveApiUrl,
|
|
319
|
-
"",
|
|
320
|
-
agentName,
|
|
321
|
-
{
|
|
322
|
-
timeouts: timeoutConfig,
|
|
323
|
-
provisionQueryParams: cfg.provisionQueryParams ?? {},
|
|
324
|
-
},
|
|
325
|
-
);
|
|
317
|
+
const backend = new ServerBackend(effectiveApiUrl, "", agentName, timeoutConfig);
|
|
326
318
|
const result = await backend.register();
|
|
327
319
|
api.logger.info(
|
|
328
320
|
`[mem9] *** Auto-provisioned apiKey=${result.id} *** Save this to your config as apiKey`
|
|
@@ -414,9 +406,7 @@ class LazyServerBackend implements MemoryBackend {
|
|
|
414
406
|
|
|
415
407
|
this.resolving = this.apiKeyProvider().then((apiKey) =>
|
|
416
408
|
Promise.resolve().then(() => {
|
|
417
|
-
this.resolved = new ServerBackend(this.apiUrl, apiKey, this.agentId,
|
|
418
|
-
timeouts: this.timeouts,
|
|
419
|
-
});
|
|
409
|
+
this.resolved = new ServerBackend(this.apiUrl, apiKey, this.agentId, this.timeouts);
|
|
420
410
|
return this.resolved;
|
|
421
411
|
})
|
|
422
412
|
).catch((err) => {
|
package/openclaw.plugin.json
CHANGED
|
@@ -14,13 +14,6 @@
|
|
|
14
14
|
"type": "string",
|
|
15
15
|
"description": "mem9 API key (secret — do not share)"
|
|
16
16
|
},
|
|
17
|
-
"provisionQueryParams": {
|
|
18
|
-
"type": "object",
|
|
19
|
-
"description": "Optional utm_* params forwarded only during first-time auto-provisioning",
|
|
20
|
-
"additionalProperties": {
|
|
21
|
-
"type": "string"
|
|
22
|
-
}
|
|
23
|
-
},
|
|
24
17
|
"defaultTimeoutMs": {
|
|
25
18
|
"type": "number",
|
|
26
19
|
"minimum": 1,
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@mem9/mem9",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.1",
|
|
4
4
|
"description": "OpenClaw shared memory plugin — cloud-persistent memory with hybrid vector + keyword search via mnemo-server",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"license": "Apache-2.0",
|
|
@@ -34,7 +34,6 @@
|
|
|
34
34
|
"access": "public"
|
|
35
35
|
},
|
|
36
36
|
"scripts": {
|
|
37
|
-
"test": "node --experimental-strip-types --test ./server-backend.test.ts",
|
|
38
37
|
"typecheck": "tsc --noEmit",
|
|
39
38
|
"prepublishOnly": "npm run typecheck"
|
|
40
39
|
},
|
|
@@ -43,7 +42,6 @@
|
|
|
43
42
|
},
|
|
44
43
|
"dependencies": {},
|
|
45
44
|
"devDependencies": {
|
|
46
|
-
"@types/node": "^22.15.30",
|
|
47
45
|
"typescript": "^5.5.0"
|
|
48
46
|
},
|
|
49
47
|
"openclaw": {
|
package/server-backend.ts
CHANGED
|
@@ -22,11 +22,6 @@ export interface BackendTimeouts {
|
|
|
22
22
|
searchTimeoutMs?: number;
|
|
23
23
|
}
|
|
24
24
|
|
|
25
|
-
interface ServerBackendOptions {
|
|
26
|
-
timeouts?: BackendTimeouts;
|
|
27
|
-
provisionQueryParams?: Record<string, string>;
|
|
28
|
-
}
|
|
29
|
-
|
|
30
25
|
interface RequestOptions {
|
|
31
26
|
timeoutMs?: number;
|
|
32
27
|
}
|
|
@@ -35,37 +30,25 @@ export class ServerBackend implements MemoryBackend {
|
|
|
35
30
|
private baseUrl: string;
|
|
36
31
|
private apiKey: string;
|
|
37
32
|
private agentName: string;
|
|
38
|
-
private provisionQueryParams: Record<string, string>;
|
|
39
33
|
private timeouts: Required<BackendTimeouts>;
|
|
40
34
|
|
|
41
35
|
constructor(
|
|
42
36
|
apiUrl: string,
|
|
43
37
|
apiKey: string,
|
|
44
38
|
agentName: string,
|
|
45
|
-
|
|
39
|
+
timeouts: BackendTimeouts = {},
|
|
46
40
|
) {
|
|
47
41
|
this.baseUrl = apiUrl.replace(/\/+$/, "");
|
|
48
42
|
this.apiKey = apiKey;
|
|
49
43
|
this.agentName = agentName;
|
|
50
|
-
this.provisionQueryParams = options.provisionQueryParams ?? {};
|
|
51
44
|
this.timeouts = {
|
|
52
|
-
defaultTimeoutMs:
|
|
53
|
-
searchTimeoutMs:
|
|
45
|
+
defaultTimeoutMs: timeouts.defaultTimeoutMs ?? DEFAULT_TIMEOUT_MS,
|
|
46
|
+
searchTimeoutMs: timeouts.searchTimeoutMs ?? DEFAULT_SEARCH_TIMEOUT_MS,
|
|
54
47
|
};
|
|
55
48
|
}
|
|
56
49
|
|
|
57
50
|
async register(): Promise<ProvisionMem9sResponse> {
|
|
58
|
-
const
|
|
59
|
-
for (const [key, value] of Object.entries(this.provisionQueryParams)) {
|
|
60
|
-
if (!key.startsWith("utm_") || typeof value !== "string" || value === "") {
|
|
61
|
-
continue;
|
|
62
|
-
}
|
|
63
|
-
|
|
64
|
-
query.set(key, value);
|
|
65
|
-
}
|
|
66
|
-
|
|
67
|
-
const qs = query.toString();
|
|
68
|
-
const resp = await fetch(this.baseUrl + "/v1alpha1/mem9s" + (qs ? `?${qs}` : ""), {
|
|
51
|
+
const resp = await fetch(this.baseUrl + "/v1alpha1/mem9s", {
|
|
69
52
|
method: "POST",
|
|
70
53
|
signal: AbortSignal.timeout(this.timeouts.defaultTimeoutMs),
|
|
71
54
|
});
|
package/types.ts
CHANGED
package/server-backend.test.ts
DELETED
|
@@ -1,82 +0,0 @@
|
|
|
1
|
-
import assert from "node:assert/strict";
|
|
2
|
-
import test from "node:test";
|
|
3
|
-
|
|
4
|
-
import { ServerBackend } from "./server-backend.ts";
|
|
5
|
-
|
|
6
|
-
test("register forwards only utm_* params during auto-provision", async () => {
|
|
7
|
-
const originalFetch = globalThis.fetch;
|
|
8
|
-
let requestedURL = "";
|
|
9
|
-
|
|
10
|
-
globalThis.fetch = async (input, init) => {
|
|
11
|
-
requestedURL = String(input);
|
|
12
|
-
assert.equal(init?.method, "POST");
|
|
13
|
-
|
|
14
|
-
return new Response(JSON.stringify({ id: "space-1" }), {
|
|
15
|
-
status: 201,
|
|
16
|
-
headers: {
|
|
17
|
-
"Content-Type": "application/json",
|
|
18
|
-
},
|
|
19
|
-
});
|
|
20
|
-
};
|
|
21
|
-
|
|
22
|
-
try {
|
|
23
|
-
const backend = new ServerBackend("https://api.mem9.ai", "", "agent-1", {
|
|
24
|
-
provisionQueryParams: {
|
|
25
|
-
utm_source: "bosn",
|
|
26
|
-
foo: "bar",
|
|
27
|
-
utm_campaign: "spring",
|
|
28
|
-
utm_medium: "",
|
|
29
|
-
},
|
|
30
|
-
});
|
|
31
|
-
|
|
32
|
-
const result = await backend.register();
|
|
33
|
-
assert.equal(result.id, "space-1");
|
|
34
|
-
|
|
35
|
-
const url = new URL(requestedURL);
|
|
36
|
-
assert.equal(url.origin + url.pathname, "https://api.mem9.ai/v1alpha1/mem9s");
|
|
37
|
-
assert.equal(url.searchParams.get("utm_source"), "bosn");
|
|
38
|
-
assert.equal(url.searchParams.get("utm_campaign"), "spring");
|
|
39
|
-
assert.equal(url.searchParams.has("foo"), false);
|
|
40
|
-
assert.equal(url.searchParams.has("utm_medium"), false);
|
|
41
|
-
} finally {
|
|
42
|
-
globalThis.fetch = originalFetch;
|
|
43
|
-
}
|
|
44
|
-
});
|
|
45
|
-
|
|
46
|
-
test("normal memory requests do not append provision query params", async () => {
|
|
47
|
-
const originalFetch = globalThis.fetch;
|
|
48
|
-
let requestedURL = "";
|
|
49
|
-
|
|
50
|
-
globalThis.fetch = async (input) => {
|
|
51
|
-
requestedURL = String(input);
|
|
52
|
-
|
|
53
|
-
return new Response(
|
|
54
|
-
JSON.stringify({
|
|
55
|
-
id: "mem-1",
|
|
56
|
-
content: "remember this",
|
|
57
|
-
created_at: "2026-04-05T00:00:00Z",
|
|
58
|
-
updated_at: "2026-04-05T00:00:00Z",
|
|
59
|
-
}),
|
|
60
|
-
{
|
|
61
|
-
status: 200,
|
|
62
|
-
headers: {
|
|
63
|
-
"Content-Type": "application/json",
|
|
64
|
-
},
|
|
65
|
-
},
|
|
66
|
-
);
|
|
67
|
-
};
|
|
68
|
-
|
|
69
|
-
try {
|
|
70
|
-
const backend = new ServerBackend("https://api.mem9.ai", "space-key", "agent-1", {
|
|
71
|
-
provisionQueryParams: {
|
|
72
|
-
utm_source: "bosn",
|
|
73
|
-
},
|
|
74
|
-
});
|
|
75
|
-
|
|
76
|
-
await backend.store({ content: "remember this" });
|
|
77
|
-
|
|
78
|
-
assert.equal(requestedURL, "https://api.mem9.ai/v1alpha2/mem9s/memories");
|
|
79
|
-
} finally {
|
|
80
|
-
globalThis.fetch = originalFetch;
|
|
81
|
-
}
|
|
82
|
-
});
|