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 +300 -0
- package/openclaw.plugin.json +129 -0
- package/package.json +46 -0
- package/src/bootstrap.ts +82 -0
- package/src/chunking.ts +280 -0
- package/src/config.ts +271 -0
- package/src/dedupe.ts +96 -0
- package/src/extract.ts +351 -0
- package/src/index.ts +489 -0
- package/src/local-memory.ts +128 -0
- package/src/logger.ts +128 -0
- package/src/mem0-client.ts +605 -0
- package/src/merge.ts +56 -0
- package/src/reconcile.ts +278 -0
- package/src/state.ts +130 -0
- package/src/types.ts +96 -0
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
|
+
}
|
package/src/bootstrap.ts
ADDED
|
@@ -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
|
+
}
|