memory-braid 0.6.1 → 0.7.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 CHANGED
@@ -5,9 +5,14 @@ Memory Braid is an OpenClaw `kind: "memory"` plugin that augments local memory s
5
5
  ## Features
6
6
 
7
7
  - Hybrid recall: local memory + Mem0, merged with weighted RRF.
8
+ - Layered Mem0 memory: episodic captures, semantic compendium memories, and procedural agent learnings.
8
9
  - Capture-first Mem0 memory: plugin writes only captured memories to Mem0 (no markdown/session indexing).
9
10
  - Capture pipeline modes: `local`, `hybrid`, `ml`.
11
+ - Deterministic memory selection policy: ML may suggest candidates, but plugin code decides `ignore|episodic|procedural|semantic` using local thresholds and heuristics.
10
12
  - Optional entity extraction: local multilingual NER or OpenAI NER with canonical `entity://...` URIs in memory metadata.
13
+ - Lightweight taxonomy on captured and semantic memories: `people`, `places`, `organizations`, `projects`, `tools`, `topics`.
14
+ - Time-aware retrieval for month-style prompts such as `in June`, `this month`, and `last month`.
15
+ - Automatic consolidation loop that promotes repeated episodic memories into semantic compendium memories.
11
16
  - Structured debug logs for troubleshooting and tuning.
12
17
  - Debug-only LLM usage observability: per-turn cache usage, rolling windows, and rising/stable/improving trend logs.
13
18
 
@@ -21,11 +26,14 @@ This release hardens capture and remediation for historical installs.
21
26
  - Metadata: new captured memories now include additive provenance fields such as `captureOrigin`, `captureMessageHash`, `captureTurnHash`, `capturePath`, and `pluginCaptureVersion`.
22
27
  - Historical installs: no startup mutation is performed automatically. Operators should audit first, then explicitly quarantine or delete suspicious captured memories.
23
28
 
24
- ## Remediation commands
29
+ ## Command surface
25
30
 
26
- Memory Braid now exposes read-only audit and explicit remediation commands:
31
+ Memory Braid exposes audit, search, consolidation, and remediation commands:
27
32
 
28
33
  ```bash
34
+ /memorybraid search standups --layer semantic --kind preference
35
+ /memorybraid search "What did we discuss in June?" --layer episodic
36
+ /memorybraid consolidate
29
37
  /memorybraid audit
30
38
  /memorybraid remediate audit
31
39
  /memorybraid remediate quarantine
@@ -36,6 +44,10 @@ Memory Braid now exposes read-only audit and explicit remediation commands:
36
44
 
37
45
  Notes:
38
46
 
47
+ - `search` is Mem0-only and intended for validating plugin-managed memory rather than local markdown memory.
48
+ - `search` supports `--limit`, `--layer`, `--kind`, `--from`, `--to`, and `--include-quarantined`.
49
+ - `consolidate` runs one compendium synthesis pass immediately and reports how many semantic memories were created or updated.
50
+ - New capture metadata includes deterministic selection fields such as `selectionDecision`, `rememberabilityScore`, and `rememberabilityReasons`.
39
51
  - Dry-run is the default for remediation commands. Nothing mutates until you pass `--apply`.
40
52
  - `audit` reports counts by `sourceType`, `captureOrigin`, and `pluginCaptureVersion`, plus suspicious legacy samples.
41
53
  - `quarantine --apply` excludes suspicious captured memories from future Mem0 injection. It records quarantine state locally and also tags Mem0 metadata where supported.
@@ -98,7 +110,7 @@ On the target machine:
98
110
  1. Install from npm:
99
111
 
100
112
  ```bash
101
- openclaw plugins install memory-braid@0.4.0
113
+ openclaw plugins install memory-braid@0.7.0
102
114
  ```
103
115
 
104
116
  2. Rebuild native dependencies inside the installed extension:
@@ -225,6 +237,17 @@ Add this under `plugins.entries["memory-braid"].config` in your OpenClaw config:
225
237
  "warmupText": "John works at Acme in Berlin."
226
238
  }
227
239
  },
240
+ "consolidation": {
241
+ "enabled": true,
242
+ "startupRun": true,
243
+ "intervalMinutes": 360,
244
+ "opportunisticNewMemoryThreshold": 5,
245
+ "opportunisticMinMinutesSinceLastRun": 30,
246
+ "minSupportCount": 2,
247
+ "minRecallCount": 2,
248
+ "semanticMaxSourceIds": 20,
249
+ "timeQueryParsing": true
250
+ },
228
251
  "debug": {
229
252
  "enabled": true
230
253
  }
@@ -283,6 +306,7 @@ Expected events:
283
306
  - `memory_braid.capture.extract`
284
307
  - `memory_braid.capture.ml` (for `capture.mode=hybrid|ml`)
285
308
  - `memory_braid.entity.extract`
309
+ - `memory_braid.capture.selection`
286
310
  - `memory_braid.capture.persist`
287
311
 
288
312
  ## Self-hosting quick guide
@@ -569,6 +593,11 @@ Capture defaults are:
569
593
  - `capture.enabled`: `true`
570
594
  - `capture.mode`: `"local"`
571
595
  - `capture.includeAssistant`: `false` (legacy alias for `capture.assistant.autoCapture`)
596
+ - `capture.selection.minPreferenceDecisionScore`: `0.45`
597
+ - `capture.selection.minFactScore`: `0.52`
598
+ - `capture.selection.minTaskScore`: `0.72`
599
+ - `capture.selection.minOtherScore`: `0.82`
600
+ - `capture.selection.minProceduralScore`: `0.58`
572
601
  - `capture.maxItemsPerRun`: `6`
573
602
  - `capture.assistant.enabled`: `true`
574
603
  - `capture.assistant.autoCapture`: `false`
@@ -588,6 +617,7 @@ Capture defaults are:
588
617
  - `timeDecay.enabled`: `false`
589
618
  - `lifecycle.enabled`: `false`
590
619
  - `lifecycle.captureTtlDays`: `90`
620
+ - `consolidation.minSelectionScore`: `0.56`
591
621
  - `lifecycle.cleanupIntervalMinutes`: `360`
592
622
  - `lifecycle.reinforceOnRecall`: `true`
593
623
 
@@ -679,6 +709,8 @@ Key events:
679
709
  - `memory_braid.search.local|mem0|merge|inject|skip`
680
710
  - `memory_braid.search.mem0_decay`
681
711
  - `memory_braid.capture.extract|ml|persist|skip`
712
+ - `memory_braid.capture.selection`
713
+ - `memory_braid.consolidation.plan|run|supersede`
682
714
  - `memory_braid.lifecycle.reinforce|cleanup`
683
715
  - `memory_braid.entity.model_load|warmup|extract`
684
716
  - `memory_braid.mem0.request|response|error`
@@ -696,6 +728,8 @@ Traceability tips:
696
728
  - `mem0AddWithoutId`
697
729
  - `entityAnnotatedCandidates`
698
730
  - `totalEntitiesAttached`
731
+ - `memory_braid.capture.selection` includes the deterministic routing decision, numeric rememberability score, and reasons used for `ignore|episodic|procedural`.
732
+ - `memory_braid.consolidation.plan` includes the compendium drafts that passed deterministic promotion, including promotion score and reasons.
699
733
  - `memory_braid.capture.ml` includes `fallbackUsed` and fallback reasons when ML is unavailable.
700
734
  - `memory_braid.entity.extract` includes `entityTypes` and `sampleEntityUris`.
701
735
 
@@ -72,6 +72,17 @@
72
72
  },
73
73
  "includeAssistant": { "type": "boolean", "default": false },
74
74
  "maxItemsPerRun": { "type": "integer", "minimum": 1, "maximum": 50, "default": 6 },
75
+ "selection": {
76
+ "type": "object",
77
+ "additionalProperties": false,
78
+ "properties": {
79
+ "minPreferenceDecisionScore": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.45 },
80
+ "minFactScore": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.52 },
81
+ "minTaskScore": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.72 },
82
+ "minOtherScore": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.82 },
83
+ "minProceduralScore": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.58 }
84
+ }
85
+ },
75
86
  "assistant": {
76
87
  "type": "object",
77
88
  "additionalProperties": false,
@@ -170,6 +181,42 @@
170
181
  "reinforceOnRecall": { "type": "boolean", "default": true }
171
182
  }
172
183
  },
184
+ "consolidation": {
185
+ "type": "object",
186
+ "additionalProperties": false,
187
+ "properties": {
188
+ "enabled": { "type": "boolean", "default": true },
189
+ "startupRun": { "type": "boolean", "default": true },
190
+ "intervalMinutes": {
191
+ "type": "integer",
192
+ "minimum": 1,
193
+ "maximum": 10080,
194
+ "default": 360
195
+ },
196
+ "opportunisticNewMemoryThreshold": {
197
+ "type": "integer",
198
+ "minimum": 1,
199
+ "maximum": 100,
200
+ "default": 5
201
+ },
202
+ "opportunisticMinMinutesSinceLastRun": {
203
+ "type": "integer",
204
+ "minimum": 1,
205
+ "maximum": 1440,
206
+ "default": 30
207
+ },
208
+ "minSupportCount": { "type": "integer", "minimum": 1, "maximum": 50, "default": 2 },
209
+ "minRecallCount": { "type": "integer", "minimum": 1, "maximum": 100, "default": 2 },
210
+ "minSelectionScore": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.56 },
211
+ "semanticMaxSourceIds": {
212
+ "type": "integer",
213
+ "minimum": 1,
214
+ "maximum": 200,
215
+ "default": 20
216
+ },
217
+ "timeQueryParsing": { "type": "boolean", "default": true }
218
+ }
219
+ },
173
220
  "debug": {
174
221
  "type": "object",
175
222
  "additionalProperties": false,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "memory-braid",
3
- "version": "0.6.1",
3
+ "version": "0.7.0",
4
4
  "description": "OpenClaw memory plugin that augments local memory with Mem0 capture and recall.",
5
5
  "type": "module",
6
6
  "main": "./src/index.ts",
@@ -32,6 +32,7 @@
32
32
  },
33
33
  "dependencies": {
34
34
  "@xenova/transformers": "^2.17.2",
35
+ "chrono-node": "^2.9.0",
35
36
  "mem0ai": "^2.2.3"
36
37
  },
37
38
  "devDependencies": {
package/src/config.ts CHANGED
@@ -33,6 +33,13 @@ export type MemoryBraidConfig = {
33
33
  mode: "local" | "hybrid" | "ml";
34
34
  includeAssistant: boolean;
35
35
  maxItemsPerRun: number;
36
+ selection: {
37
+ minPreferenceDecisionScore: number;
38
+ minFactScore: number;
39
+ minTaskScore: number;
40
+ minOtherScore: number;
41
+ minProceduralScore: number;
42
+ };
36
43
  assistant: {
37
44
  enabled: boolean;
38
45
  autoCapture: boolean;
@@ -79,6 +86,18 @@ export type MemoryBraidConfig = {
79
86
  cleanupIntervalMinutes: number;
80
87
  reinforceOnRecall: boolean;
81
88
  };
89
+ consolidation: {
90
+ enabled: boolean;
91
+ startupRun: boolean;
92
+ intervalMinutes: number;
93
+ opportunisticNewMemoryThreshold: number;
94
+ opportunisticMinMinutesSinceLastRun: number;
95
+ minSupportCount: number;
96
+ minRecallCount: number;
97
+ minSelectionScore: number;
98
+ semanticMaxSourceIds: number;
99
+ timeQueryParsing: boolean;
100
+ };
82
101
  debug: {
83
102
  enabled: boolean;
84
103
  includePayloads: boolean;
@@ -122,6 +141,13 @@ const DEFAULTS: MemoryBraidConfig = {
122
141
  mode: "local",
123
142
  includeAssistant: false,
124
143
  maxItemsPerRun: 6,
144
+ selection: {
145
+ minPreferenceDecisionScore: 0.45,
146
+ minFactScore: 0.52,
147
+ minTaskScore: 0.72,
148
+ minOtherScore: 0.82,
149
+ minProceduralScore: 0.58,
150
+ },
125
151
  assistant: {
126
152
  enabled: true,
127
153
  autoCapture: false,
@@ -168,6 +194,18 @@ const DEFAULTS: MemoryBraidConfig = {
168
194
  cleanupIntervalMinutes: 360,
169
195
  reinforceOnRecall: true,
170
196
  },
197
+ consolidation: {
198
+ enabled: true,
199
+ startupRun: true,
200
+ intervalMinutes: 360,
201
+ opportunisticNewMemoryThreshold: 5,
202
+ opportunisticMinMinutesSinceLastRun: 30,
203
+ minSupportCount: 2,
204
+ minRecallCount: 2,
205
+ minSelectionScore: 0.56,
206
+ semanticMaxSourceIds: 20,
207
+ timeQueryParsing: true,
208
+ },
171
209
  debug: {
172
210
  enabled: false,
173
211
  includePayloads: false,
@@ -212,6 +250,7 @@ export function parseConfig(raw: unknown): MemoryBraidConfig {
212
250
  const recallAgent = asRecord(recall.agent);
213
251
  const merge = asRecord(recall.merge);
214
252
  const capture = asRecord(root.capture);
253
+ const captureSelection = asRecord(capture.selection);
215
254
  const captureAssistant = asRecord(capture.assistant);
216
255
  const entityExtraction = asRecord(root.entityExtraction);
217
256
  const entityStartup = asRecord(entityExtraction.startup);
@@ -221,6 +260,7 @@ export function parseConfig(raw: unknown): MemoryBraidConfig {
221
260
  const semantic = asRecord(dedupe.semantic);
222
261
  const timeDecay = asRecord(root.timeDecay);
223
262
  const lifecycle = asRecord(root.lifecycle);
263
+ const consolidation = asRecord(root.consolidation);
224
264
  const debug = asRecord(root.debug);
225
265
 
226
266
  const mode = mem0.mode === "oss" ? "oss" : "cloud";
@@ -301,6 +341,38 @@ export function parseConfig(raw: unknown): MemoryBraidConfig {
301
341
  mode: captureMode,
302
342
  includeAssistant,
303
343
  maxItemsPerRun: asInt(capture.maxItemsPerRun, DEFAULTS.capture.maxItemsPerRun, 1, 50),
344
+ selection: {
345
+ minPreferenceDecisionScore: asNumber(
346
+ captureSelection.minPreferenceDecisionScore,
347
+ DEFAULTS.capture.selection.minPreferenceDecisionScore,
348
+ 0,
349
+ 1,
350
+ ),
351
+ minFactScore: asNumber(
352
+ captureSelection.minFactScore,
353
+ DEFAULTS.capture.selection.minFactScore,
354
+ 0,
355
+ 1,
356
+ ),
357
+ minTaskScore: asNumber(
358
+ captureSelection.minTaskScore,
359
+ DEFAULTS.capture.selection.minTaskScore,
360
+ 0,
361
+ 1,
362
+ ),
363
+ minOtherScore: asNumber(
364
+ captureSelection.minOtherScore,
365
+ DEFAULTS.capture.selection.minOtherScore,
366
+ 0,
367
+ 1,
368
+ ),
369
+ minProceduralScore: asNumber(
370
+ captureSelection.minProceduralScore,
371
+ DEFAULTS.capture.selection.minProceduralScore,
372
+ 0,
373
+ 1,
374
+ ),
375
+ },
304
376
  assistant: {
305
377
  enabled: asBoolean(
306
378
  captureAssistant.enabled,
@@ -411,6 +483,56 @@ export function parseConfig(raw: unknown): MemoryBraidConfig {
411
483
  DEFAULTS.lifecycle.reinforceOnRecall,
412
484
  ),
413
485
  },
486
+ consolidation: {
487
+ enabled: asBoolean(consolidation.enabled, DEFAULTS.consolidation.enabled),
488
+ startupRun: asBoolean(consolidation.startupRun, DEFAULTS.consolidation.startupRun),
489
+ intervalMinutes: asInt(
490
+ consolidation.intervalMinutes,
491
+ DEFAULTS.consolidation.intervalMinutes,
492
+ 1,
493
+ 10080,
494
+ ),
495
+ opportunisticNewMemoryThreshold: asInt(
496
+ consolidation.opportunisticNewMemoryThreshold,
497
+ DEFAULTS.consolidation.opportunisticNewMemoryThreshold,
498
+ 1,
499
+ 100,
500
+ ),
501
+ opportunisticMinMinutesSinceLastRun: asInt(
502
+ consolidation.opportunisticMinMinutesSinceLastRun,
503
+ DEFAULTS.consolidation.opportunisticMinMinutesSinceLastRun,
504
+ 1,
505
+ 1440,
506
+ ),
507
+ minSupportCount: asInt(
508
+ consolidation.minSupportCount,
509
+ DEFAULTS.consolidation.minSupportCount,
510
+ 1,
511
+ 50,
512
+ ),
513
+ minRecallCount: asInt(
514
+ consolidation.minRecallCount,
515
+ DEFAULTS.consolidation.minRecallCount,
516
+ 1,
517
+ 100,
518
+ ),
519
+ minSelectionScore: asNumber(
520
+ consolidation.minSelectionScore,
521
+ DEFAULTS.consolidation.minSelectionScore,
522
+ 0,
523
+ 1,
524
+ ),
525
+ semanticMaxSourceIds: asInt(
526
+ consolidation.semanticMaxSourceIds,
527
+ DEFAULTS.consolidation.semanticMaxSourceIds,
528
+ 1,
529
+ 200,
530
+ ),
531
+ timeQueryParsing: asBoolean(
532
+ consolidation.timeQueryParsing,
533
+ DEFAULTS.consolidation.timeQueryParsing,
534
+ ),
535
+ },
414
536
  debug: {
415
537
  enabled: asBoolean(debug.enabled, DEFAULTS.debug.enabled),
416
538
  includePayloads: asBoolean(debug.includePayloads, DEFAULTS.debug.includePayloads),