memory-braid 0.2.0

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 ADDED
@@ -0,0 +1,300 @@
1
+ # Memory Braid
2
+
3
+ Memory Braid is an OpenClaw `kind: "memory"` plugin that augments local memory search (core/QMD) with Mem0.
4
+
5
+ ## Features
6
+
7
+ - Hybrid recall: local memory + Mem0, merged with weighted RRF.
8
+ - Install-time bootstrap import: indexes existing `MEMORY.md`, `memory.md`, `memory/**/*.md`, and recent sessions.
9
+ - Periodic reconcile: keeps remote Mem0 chunks updated and deletes stale remote chunks.
10
+ - Capture pipeline: heuristic extraction with optional ML enrichment mode.
11
+ - Structured debug logs for troubleshooting and tuning.
12
+
13
+ ## Install
14
+
15
+ Add this plugin to your OpenClaw plugin load path, then enable it as the active memory plugin.
16
+
17
+ ## Self-hosting quick guide
18
+
19
+ Memory Braid supports two self-hosted setups:
20
+
21
+ 1. **API-compatible mode** (`mem0.mode: "cloud"` + `mem0.host`): run a Mem0-compatible API service and point the plugin to it.
22
+ 2. **OSS in-process mode** (`mem0.mode: "oss"` + `mem0.ossConfig`): run Mem0 OSS directly in the OpenClaw process.
23
+
24
+ ### Option A: self-hosted API-compatible mode
25
+
26
+ 1. Deploy your Mem0 API-compatible stack in your infra (Docker/K8s/VM).
27
+ 2. Make sure it is reachable from the OpenClaw host.
28
+ 3. Configure Memory Braid with:
29
+ - `mem0.mode: "cloud"`
30
+ - `mem0.host: "http://<your-mem0-host>:<port>"`
31
+ - `mem0.apiKey`
32
+ 4. Restart OpenClaw.
33
+ 5. Validate connectivity:
34
+ - `curl -sS http://<your-mem0-host>:<port>/v1/ping/`
35
+ - Then run one OpenClaw turn and confirm logs include `memory_braid.mem0.request` and `memory_braid.mem0.response`.
36
+
37
+ ### Option B: OSS in-process mode (recommended for local/self-hosted tests)
38
+
39
+ 1. Set `mem0.mode` to `oss`.
40
+ 2. Provide `mem0.ossConfig` with:
41
+ - `embedder` (provider + credentials/model)
42
+ - `vectorStore` (provider + connection/config)
43
+ - `llm` (provider + model; used by Mem0 OSS internals)
44
+ 3. Restart OpenClaw.
45
+ 4. Send at least one message to trigger capture/recall.
46
+ 5. Check logs for:
47
+ - `memory_braid.startup`
48
+ - `memory_braid.bootstrap.begin|complete`
49
+ - `memory_braid.reconcile.begin|complete`
50
+ - `memory_braid.mem0.request|response`
51
+
52
+ ### Smoke test checklist
53
+
54
+ 1. Enable debug:
55
+ - `plugins.memory-braid.debug.enabled: true`
56
+ 2. Start OpenClaw and wait for bootstrap to finish.
57
+ 3. Ask a query that should match existing memory (from `MEMORY.md` or recent sessions).
58
+ 4. Confirm `memory_search` returns merged results.
59
+ 5. Send a preference/decision statement and verify subsequent turns can recall it.
60
+
61
+ ### Notes
62
+
63
+ - Bootstrap imports existing markdown memory + recent sessions once, then reconcile keeps remote state aligned.
64
+ - If self-hosted infra is down, local memory tools continue working; Mem0 side degrades gracefully.
65
+ - For Mem0 platform/API specifics, see official docs: [Mem0 OSS quickstart](https://docs.mem0.ai/open-source/node-quickstart) and [Mem0 API reference](https://docs.mem0.ai/api-reference).
66
+
67
+ ## Required config (Cloud API mode)
68
+
69
+ At minimum, provide Mem0 credentials:
70
+
71
+ ```json
72
+ {
73
+ "plugins": {
74
+ "slots": {
75
+ "memory": "memory-braid"
76
+ },
77
+ "memory-braid": {
78
+ "mem0": {
79
+ "apiKey": "${MEM0_API_KEY}"
80
+ }
81
+ }
82
+ }
83
+ }
84
+ ```
85
+
86
+ For self-hosted Mem0 API-compatible deployments, also set `mem0.host`:
87
+
88
+ ```json
89
+ {
90
+ "plugins": {
91
+ "memory-braid": {
92
+ "mem0": {
93
+ "mode": "cloud",
94
+ "host": "http://localhost:8000",
95
+ "apiKey": "${MEM0_API_KEY}"
96
+ }
97
+ }
98
+ }
99
+ }
100
+ ```
101
+
102
+ ## Required config (OSS self-hosted mode)
103
+
104
+ If you want `mem0ai/oss` directly, set `mem0.mode` to `oss` and pass an `ossConfig`.
105
+
106
+ By default, Memory Braid now auto-creates a state folder at
107
+ `<OPENCLAW_STATE_DIR>/memory-braid` (typically `~/.openclaw/memory-braid`) and uses:
108
+
109
+ - `mem0-history.db` for Mem0 history SQLite
110
+ - `mem0-vector-store.db` for Mem0 in-memory vector store SQLite backend (`vectorStore.provider: "memory"`)
111
+
112
+ You only need to set explicit DB paths if you want non-default locations.
113
+
114
+ ```json
115
+ {
116
+ "plugins": {
117
+ "memory-braid": {
118
+ "mem0": {
119
+ "mode": "oss",
120
+ "ossConfig": {
121
+ "version": "v1.1",
122
+ "embedder": {
123
+ "provider": "openai",
124
+ "config": {
125
+ "apiKey": "${OPENAI_API_KEY}",
126
+ "model": "text-embedding-3-small"
127
+ }
128
+ },
129
+ "vectorStore": {
130
+ "provider": "memory",
131
+ "config": {
132
+ "collectionName": "memories",
133
+ "dimension": 1536
134
+ }
135
+ },
136
+ "llm": {
137
+ "provider": "openai",
138
+ "config": {
139
+ "apiKey": "${OPENAI_API_KEY}",
140
+ "model": "gpt-4o-mini"
141
+ }
142
+ },
143
+ "enableGraph": false
144
+ }
145
+ }
146
+ }
147
+ }
148
+ }
149
+ ```
150
+
151
+ ### Ready-made OSS preset: Qdrant + Ollama
152
+
153
+ Use this preset when:
154
+
155
+ - Your vector database is Qdrant.
156
+ - Your embedding and LLM provider is Ollama.
157
+ - OpenClaw can reach both services on your network.
158
+
159
+ ```json
160
+ {
161
+ "plugins": {
162
+ "memory-braid": {
163
+ "mem0": {
164
+ "mode": "oss",
165
+ "ossConfig": {
166
+ "version": "v1.1",
167
+ "embedder": {
168
+ "provider": "ollama",
169
+ "config": {
170
+ "url": "http://127.0.0.1:11434",
171
+ "model": "nomic-embed-text"
172
+ }
173
+ },
174
+ "vectorStore": {
175
+ "provider": "qdrant",
176
+ "config": {
177
+ "url": "http://127.0.0.1:6333",
178
+ "collectionName": "openclaw_memory_braid",
179
+ "dimension": 768
180
+ }
181
+ },
182
+ "llm": {
183
+ "provider": "ollama",
184
+ "config": {
185
+ "baseURL": "http://127.0.0.1:11434",
186
+ "model": "llama3.1:8b"
187
+ }
188
+ },
189
+ "enableGraph": false,
190
+ "disableHistory": true
191
+ }
192
+ }
193
+ }
194
+ }
195
+ }
196
+ ```
197
+
198
+ #### Quick validation for this preset
199
+
200
+ 1. Ensure models are available in Ollama:
201
+ - `ollama pull nomic-embed-text`
202
+ - `ollama pull llama3.1:8b`
203
+ 2. Ensure Qdrant is reachable:
204
+ - `curl -sS http://127.0.0.1:6333/collections`
205
+ 3. Start OpenClaw with `debug.enabled: true` and verify:
206
+ - `memory_braid.startup`
207
+ - `memory_braid.mem0.response` with `mode: "oss"`
208
+ - `memory_braid.bootstrap.complete`
209
+
210
+ ## Recommended config
211
+
212
+ ```json
213
+ {
214
+ "plugins": {
215
+ "slots": {
216
+ "memory": "memory-braid"
217
+ },
218
+ "memory-braid": {
219
+ "recall": {
220
+ "maxResults": 8,
221
+ "injectTopK": 5,
222
+ "merge": {
223
+ "rrfK": 60,
224
+ "localWeight": 1,
225
+ "mem0Weight": 1
226
+ }
227
+ },
228
+ "bootstrap": {
229
+ "enabled": true,
230
+ "includeMarkdown": true,
231
+ "includeSessions": true,
232
+ "sessionLookbackDays": 90,
233
+ "batchSize": 50,
234
+ "concurrency": 3
235
+ },
236
+ "reconcile": {
237
+ "enabled": true,
238
+ "intervalMinutes": 30,
239
+ "batchSize": 100,
240
+ "deleteStale": true
241
+ },
242
+ "capture": {
243
+ "enabled": true,
244
+ "extraction": {
245
+ "mode": "heuristic"
246
+ },
247
+ "ml": {
248
+ "provider": "openai",
249
+ "model": "gpt-4o-mini",
250
+ "timeoutMs": 2500,
251
+ "maxItemsPerRun": 6
252
+ }
253
+ },
254
+ "dedupe": {
255
+ "lexical": { "minJaccard": 0.3 },
256
+ "semantic": { "enabled": true, "minScore": 0.92 }
257
+ },
258
+ "debug": {
259
+ "enabled": false,
260
+ "includePayloads": false,
261
+ "maxSnippetChars": 500,
262
+ "logSamplingRate": 1
263
+ }
264
+ }
265
+ }
266
+ }
267
+ ```
268
+
269
+ ## Debugging
270
+
271
+ Set:
272
+
273
+ ```json
274
+ {
275
+ "plugins": {
276
+ "memory-braid": {
277
+ "debug": {
278
+ "enabled": true
279
+ }
280
+ }
281
+ }
282
+ }
283
+ ```
284
+
285
+ Key events:
286
+
287
+ - `memory_braid.startup`
288
+ - `memory_braid.bootstrap.begin|complete|error`
289
+ - `memory_braid.reconcile.begin|progress|complete|error`
290
+ - `memory_braid.search.local|mem0|merge|inject`
291
+ - `memory_braid.capture.extract|ml|persist|skip`
292
+ - `memory_braid.mem0.request|response|error`
293
+
294
+ `debug.includePayloads=true` includes payload fields; otherwise sensitive text fields are omitted.
295
+
296
+ ## Tests
297
+
298
+ ```bash
299
+ npm test
300
+ ```
@@ -0,0 +1,129 @@
1
+ {
2
+ "id": "memory-braid",
3
+ "name": "Memory Braid",
4
+ "description": "Hybrid memory plugin that augments local core/QMD memory with Mem0 recall, capture, bootstrap import, and reconcile.",
5
+ "kind": "memory",
6
+ "configSchema": {
7
+ "type": "object",
8
+ "additionalProperties": false,
9
+ "properties": {
10
+ "enabled": { "type": "boolean", "default": true },
11
+ "mem0": {
12
+ "type": "object",
13
+ "additionalProperties": false,
14
+ "properties": {
15
+ "mode": { "type": "string", "enum": ["cloud", "oss"], "default": "cloud" },
16
+ "apiKey": { "type": "string" },
17
+ "host": { "type": "string" },
18
+ "organizationId": { "type": "string" },
19
+ "projectId": { "type": "string" },
20
+ "ossConfig": {
21
+ "type": "object",
22
+ "additionalProperties": true,
23
+ "default": {}
24
+ }
25
+ }
26
+ },
27
+ "recall": {
28
+ "type": "object",
29
+ "additionalProperties": false,
30
+ "properties": {
31
+ "maxResults": { "type": "integer", "minimum": 1, "maximum": 50, "default": 8 },
32
+ "injectTopK": { "type": "integer", "minimum": 1, "maximum": 20, "default": 5 },
33
+ "merge": {
34
+ "type": "object",
35
+ "additionalProperties": false,
36
+ "properties": {
37
+ "strategy": { "type": "string", "enum": ["rrf"], "default": "rrf" },
38
+ "rrfK": { "type": "integer", "minimum": 1, "maximum": 500, "default": 60 },
39
+ "localWeight": { "type": "number", "minimum": 0, "maximum": 5, "default": 1 },
40
+ "mem0Weight": { "type": "number", "minimum": 0, "maximum": 5, "default": 1 }
41
+ }
42
+ }
43
+ }
44
+ },
45
+ "capture": {
46
+ "type": "object",
47
+ "additionalProperties": false,
48
+ "properties": {
49
+ "enabled": { "type": "boolean", "default": true },
50
+ "extraction": {
51
+ "type": "object",
52
+ "additionalProperties": false,
53
+ "properties": {
54
+ "mode": {
55
+ "type": "string",
56
+ "enum": ["heuristic", "heuristic_plus_ml"],
57
+ "default": "heuristic"
58
+ }
59
+ }
60
+ },
61
+ "ml": {
62
+ "type": "object",
63
+ "additionalProperties": false,
64
+ "properties": {
65
+ "provider": { "type": "string", "enum": ["openai", "anthropic", "gemini"] },
66
+ "model": { "type": "string" },
67
+ "timeoutMs": { "type": "integer", "minimum": 250, "maximum": 30000, "default": 2500 },
68
+ "maxItemsPerRun": { "type": "integer", "minimum": 1, "maximum": 50, "default": 6 }
69
+ }
70
+ }
71
+ }
72
+ },
73
+ "bootstrap": {
74
+ "type": "object",
75
+ "additionalProperties": false,
76
+ "properties": {
77
+ "enabled": { "type": "boolean", "default": true },
78
+ "startupMode": { "type": "string", "enum": ["async"], "default": "async" },
79
+ "includeMarkdown": { "type": "boolean", "default": true },
80
+ "includeSessions": { "type": "boolean", "default": true },
81
+ "sessionLookbackDays": { "type": "integer", "minimum": 1, "maximum": 3650, "default": 90 },
82
+ "batchSize": { "type": "integer", "minimum": 1, "maximum": 1000, "default": 50 },
83
+ "concurrency": { "type": "integer", "minimum": 1, "maximum": 16, "default": 3 }
84
+ }
85
+ },
86
+ "reconcile": {
87
+ "type": "object",
88
+ "additionalProperties": false,
89
+ "properties": {
90
+ "enabled": { "type": "boolean", "default": true },
91
+ "intervalMinutes": { "type": "integer", "minimum": 1, "maximum": 1440, "default": 30 },
92
+ "batchSize": { "type": "integer", "minimum": 1, "maximum": 5000, "default": 100 },
93
+ "deleteStale": { "type": "boolean", "default": true }
94
+ }
95
+ },
96
+ "dedupe": {
97
+ "type": "object",
98
+ "additionalProperties": false,
99
+ "properties": {
100
+ "lexical": {
101
+ "type": "object",
102
+ "additionalProperties": false,
103
+ "properties": {
104
+ "minJaccard": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.3 }
105
+ }
106
+ },
107
+ "semantic": {
108
+ "type": "object",
109
+ "additionalProperties": false,
110
+ "properties": {
111
+ "enabled": { "type": "boolean", "default": true },
112
+ "minScore": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.92 }
113
+ }
114
+ }
115
+ }
116
+ },
117
+ "debug": {
118
+ "type": "object",
119
+ "additionalProperties": false,
120
+ "properties": {
121
+ "enabled": { "type": "boolean", "default": false },
122
+ "includePayloads": { "type": "boolean", "default": false },
123
+ "maxSnippetChars": { "type": "integer", "minimum": 40, "maximum": 8000, "default": 500 },
124
+ "logSamplingRate": { "type": "number", "minimum": 0, "maximum": 1, "default": 1 }
125
+ }
126
+ }
127
+ }
128
+ }
129
+ }
package/package.json ADDED
@@ -0,0 +1,46 @@
1
+ {
2
+ "name": "memory-braid",
3
+ "version": "0.2.0",
4
+ "description": "OpenClaw memory plugin that augments local memory with Mem0, bootstrap import, reconcile, and capture.",
5
+ "type": "module",
6
+ "main": "./src/index.ts",
7
+ "exports": {
8
+ ".": "./src/index.ts"
9
+ },
10
+ "files": [
11
+ "src",
12
+ "openclaw.plugin.json",
13
+ "README.md"
14
+ ],
15
+ "scripts": {
16
+ "build": "echo 'No build required. OpenClaw loads src/index.ts via jiti.'",
17
+ "test": "vitest run",
18
+ "test:watch": "vitest"
19
+ },
20
+ "keywords": [
21
+ "openclaw",
22
+ "plugin",
23
+ "memory",
24
+ "mem0"
25
+ ],
26
+ "license": "MIT",
27
+ "engines": {
28
+ "node": ">=22"
29
+ },
30
+ "peerDependencies": {
31
+ "openclaw": ">=2026.2.18"
32
+ },
33
+ "dependencies": {
34
+ "mem0ai": "^2.2.3"
35
+ },
36
+ "devDependencies": {
37
+ "@types/node": "^22.13.8",
38
+ "typescript": "^5.8.2",
39
+ "vitest": "^3.0.7"
40
+ },
41
+ "openclaw": {
42
+ "extensions": [
43
+ "./src/index.ts"
44
+ ]
45
+ }
46
+ }
@@ -0,0 +1,82 @@
1
+ import type { MemoryBraidConfig } from "./config.js";
2
+ import { MemoryBraidLogger } from "./logger.js";
3
+ import { Mem0Adapter } from "./mem0-client.js";
4
+ import {
5
+ readBootstrapState,
6
+ type StatePaths,
7
+ writeBootstrapState,
8
+ } from "./state.js";
9
+ import type { TargetWorkspace } from "./types.js";
10
+ import { runReconcileOnce } from "./reconcile.js";
11
+
12
+ export async function runBootstrapIfNeeded(params: {
13
+ cfg: MemoryBraidConfig;
14
+ mem0: Mem0Adapter;
15
+ statePaths: StatePaths;
16
+ log: MemoryBraidLogger;
17
+ targets: TargetWorkspace[];
18
+ runId?: string;
19
+ }): Promise<{ started: boolean; completed: boolean }> {
20
+ const runId = params.runId ?? params.log.newRunId();
21
+
22
+ if (!params.cfg.bootstrap.enabled) {
23
+ return { started: false, completed: false };
24
+ }
25
+
26
+ const existing = await readBootstrapState(params.statePaths);
27
+ if (existing.completed) {
28
+ return { started: false, completed: true };
29
+ }
30
+
31
+ params.log.debug("memory_braid.bootstrap.begin", {
32
+ runId,
33
+ targets: params.targets.length,
34
+ }, true);
35
+
36
+ await writeBootstrapState(params.statePaths, {
37
+ version: 1,
38
+ completed: false,
39
+ startedAt: new Date().toISOString(),
40
+ lastError: undefined,
41
+ });
42
+
43
+ try {
44
+ const summary = await runReconcileOnce({
45
+ cfg: params.cfg,
46
+ mem0: params.mem0,
47
+ statePaths: params.statePaths,
48
+ log: params.log,
49
+ targets: params.targets,
50
+ reason: "bootstrap",
51
+ runId,
52
+ deleteStale: false,
53
+ });
54
+
55
+ await writeBootstrapState(params.statePaths, {
56
+ version: 1,
57
+ completed: true,
58
+ startedAt: existing.startedAt ?? new Date().toISOString(),
59
+ completedAt: new Date().toISOString(),
60
+ summary,
61
+ });
62
+
63
+ params.log.debug("memory_braid.bootstrap.complete", {
64
+ runId,
65
+ ...summary,
66
+ }, true);
67
+ return { started: true, completed: true };
68
+ } catch (err) {
69
+ const error = err instanceof Error ? err.message : String(err);
70
+ await writeBootstrapState(params.statePaths, {
71
+ version: 1,
72
+ completed: false,
73
+ startedAt: existing.startedAt ?? new Date().toISOString(),
74
+ lastError: error,
75
+ });
76
+ params.log.warn("memory_braid.bootstrap.error", {
77
+ runId,
78
+ error,
79
+ });
80
+ return { started: true, completed: false };
81
+ }
82
+ }