memory-braid 0.5.0 → 0.6.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 +61 -4
- package/openclaw.plugin.json +32 -0
- package/package.json +1 -1
- package/src/capture.ts +128 -2
- package/src/config.ts +127 -2
- package/src/extract.ts +9 -4
- package/src/index.ts +817 -141
- package/src/state.ts +9 -0
- package/src/types.ts +27 -0
package/README.md
CHANGED
|
@@ -488,7 +488,17 @@ Use this preset when:
|
|
|
488
488
|
"memory-braid": {
|
|
489
489
|
"recall": {
|
|
490
490
|
"maxResults": 8,
|
|
491
|
-
"injectTopK":
|
|
491
|
+
"injectTopK": 4,
|
|
492
|
+
"user": {
|
|
493
|
+
"enabled": true,
|
|
494
|
+
"injectTopK": 4
|
|
495
|
+
},
|
|
496
|
+
"agent": {
|
|
497
|
+
"enabled": true,
|
|
498
|
+
"injectTopK": 2,
|
|
499
|
+
"minScore": 0.78,
|
|
500
|
+
"onlyPlanning": true
|
|
501
|
+
},
|
|
492
502
|
"merge": {
|
|
493
503
|
"rrfK": 60,
|
|
494
504
|
"localWeight": 1,
|
|
@@ -500,6 +510,16 @@ Use this preset when:
|
|
|
500
510
|
"mode": "hybrid",
|
|
501
511
|
"includeAssistant": false,
|
|
502
512
|
"maxItemsPerRun": 6,
|
|
513
|
+
"assistant": {
|
|
514
|
+
"enabled": true,
|
|
515
|
+
"autoCapture": false,
|
|
516
|
+
"explicitTool": true,
|
|
517
|
+
"maxItemsPerRun": 2,
|
|
518
|
+
"minUtilityScore": 0.8,
|
|
519
|
+
"minNoveltyScore": 0.85,
|
|
520
|
+
"maxWritesPerSessionWindow": 3,
|
|
521
|
+
"cooldownMinutes": 5
|
|
522
|
+
},
|
|
503
523
|
"ml": {
|
|
504
524
|
"provider": "openai",
|
|
505
525
|
"model": "gpt-4o-mini",
|
|
@@ -548,8 +568,20 @@ Capture defaults are:
|
|
|
548
568
|
|
|
549
569
|
- `capture.enabled`: `true`
|
|
550
570
|
- `capture.mode`: `"local"`
|
|
551
|
-
- `capture.includeAssistant`: `false` (
|
|
571
|
+
- `capture.includeAssistant`: `false` (legacy alias for `capture.assistant.autoCapture`)
|
|
552
572
|
- `capture.maxItemsPerRun`: `6`
|
|
573
|
+
- `capture.assistant.enabled`: `true`
|
|
574
|
+
- `capture.assistant.autoCapture`: `false`
|
|
575
|
+
- `capture.assistant.explicitTool`: `true`
|
|
576
|
+
- `capture.assistant.maxItemsPerRun`: `2`
|
|
577
|
+
- `capture.assistant.minUtilityScore`: `0.8`
|
|
578
|
+
- `capture.assistant.minNoveltyScore`: `0.85`
|
|
579
|
+
- `capture.assistant.maxWritesPerSessionWindow`: `3`
|
|
580
|
+
- `capture.assistant.cooldownMinutes`: `5`
|
|
581
|
+
- `recall.user.injectTopK`: `5` (legacy `recall.injectTopK` still works)
|
|
582
|
+
- `recall.agent.injectTopK`: `2`
|
|
583
|
+
- `recall.agent.minScore`: `0.78`
|
|
584
|
+
- `recall.agent.onlyPlanning`: `true`
|
|
553
585
|
- `capture.ml.provider`: unset
|
|
554
586
|
- `capture.ml.model`: unset
|
|
555
587
|
- `capture.ml.timeoutMs`: `2500`
|
|
@@ -564,14 +596,39 @@ Important behavior:
|
|
|
564
596
|
- `capture.mode = "local"`: heuristic-only extraction.
|
|
565
597
|
- `capture.mode = "hybrid"`: heuristic extraction + ML enrichment when ML config is set.
|
|
566
598
|
- `capture.mode = "ml"`: ML-first extraction; falls back to heuristic if ML config/call is unavailable.
|
|
567
|
-
-
|
|
568
|
-
-
|
|
599
|
+
- New memories are persisted by `workspace + agent`, not by session. `sessionKey` is kept only as metadata and for assistant-learning cooldown/window logic.
|
|
600
|
+
- Recall still performs a legacy dual-read fallback for older session-scoped Mem0 records, without rewriting them.
|
|
601
|
+
- `capture.includeAssistant = false` (default): assistant auto-capture is off.
|
|
602
|
+
- `capture.includeAssistant = true` or `capture.assistant.autoCapture = true`: assistant messages are eligible for strict agent-learning auto-capture.
|
|
603
|
+
- `capture.assistant.explicitTool = true`: exposes the `remember_learning` tool.
|
|
604
|
+
- `recall.user.*` controls injected user memories.
|
|
605
|
+
- `recall.agent.*` controls injected agent learnings.
|
|
569
606
|
- ML calls run only when both `capture.ml.provider` and `capture.ml.model` are set.
|
|
570
607
|
- `timeDecay.enabled = true`: applies temporal decay to Mem0 results using Memory Core's `agents.*.memorySearch.query.hybrid.temporalDecay` settings.
|
|
571
608
|
- If Memory Core temporal decay is disabled, Mem0 decay is skipped even when `timeDecay.enabled = true`.
|
|
572
609
|
- `lifecycle.enabled = true`: tracks captured Mem0 IDs, applies TTL cleanup, and exposes `/memorybraid cleanup`.
|
|
573
610
|
- `lifecycle.reinforceOnRecall = true`: successful recalls refresh lifecycle timestamps, extending TTL survival for frequently used memories.
|
|
574
611
|
|
|
612
|
+
## Agent learnings
|
|
613
|
+
|
|
614
|
+
Memory Braid v2 adds explicit and implicit agent learnings.
|
|
615
|
+
|
|
616
|
+
- `remember_learning` stores compact reusable heuristics, lessons, and strategies for future runs.
|
|
617
|
+
- Use it for operational guidance that helps the agent avoid repeated mistakes or reduce tool cost/noise.
|
|
618
|
+
- Do not use it for long summaries, transient details, or raw reasoning.
|
|
619
|
+
- Assistant auto-capture is still available, but it is stricter than user-memory capture and only persists compact learnings that pass utility, novelty, and cooldown checks.
|
|
620
|
+
|
|
621
|
+
Recall is now split into two dynamic blocks:
|
|
622
|
+
|
|
623
|
+
- `<user-memories>`: user facts, preferences, decisions, and tasks.
|
|
624
|
+
- `<agent-learnings>`: reusable agent heuristics, lessons, and strategies.
|
|
625
|
+
|
|
626
|
+
Cache safety:
|
|
627
|
+
|
|
628
|
+
- Tool awareness for `remember_learning` is injected through a stable `systemPrompt`.
|
|
629
|
+
- Retrieved memories stay in dynamic `prependContext`, not in the stable prompt body.
|
|
630
|
+
- Agent learnings use low `top-k`, high relevance thresholds, and deterministic formatting to avoid unnecessary prompt churn.
|
|
631
|
+
|
|
575
632
|
## Entity extraction defaults
|
|
576
633
|
|
|
577
634
|
Entity extraction defaults are:
|
package/openclaw.plugin.json
CHANGED
|
@@ -30,6 +30,24 @@
|
|
|
30
30
|
"properties": {
|
|
31
31
|
"maxResults": { "type": "integer", "minimum": 1, "maximum": 50, "default": 8 },
|
|
32
32
|
"injectTopK": { "type": "integer", "minimum": 1, "maximum": 20, "default": 5 },
|
|
33
|
+
"user": {
|
|
34
|
+
"type": "object",
|
|
35
|
+
"additionalProperties": false,
|
|
36
|
+
"properties": {
|
|
37
|
+
"enabled": { "type": "boolean", "default": true },
|
|
38
|
+
"injectTopK": { "type": "integer", "minimum": 1, "maximum": 20, "default": 5 }
|
|
39
|
+
}
|
|
40
|
+
},
|
|
41
|
+
"agent": {
|
|
42
|
+
"type": "object",
|
|
43
|
+
"additionalProperties": false,
|
|
44
|
+
"properties": {
|
|
45
|
+
"enabled": { "type": "boolean", "default": true },
|
|
46
|
+
"injectTopK": { "type": "integer", "minimum": 1, "maximum": 20, "default": 2 },
|
|
47
|
+
"minScore": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.78 },
|
|
48
|
+
"onlyPlanning": { "type": "boolean", "default": true }
|
|
49
|
+
}
|
|
50
|
+
},
|
|
33
51
|
"merge": {
|
|
34
52
|
"type": "object",
|
|
35
53
|
"additionalProperties": false,
|
|
@@ -54,6 +72,20 @@
|
|
|
54
72
|
},
|
|
55
73
|
"includeAssistant": { "type": "boolean", "default": false },
|
|
56
74
|
"maxItemsPerRun": { "type": "integer", "minimum": 1, "maximum": 50, "default": 6 },
|
|
75
|
+
"assistant": {
|
|
76
|
+
"type": "object",
|
|
77
|
+
"additionalProperties": false,
|
|
78
|
+
"properties": {
|
|
79
|
+
"enabled": { "type": "boolean", "default": true },
|
|
80
|
+
"autoCapture": { "type": "boolean", "default": false },
|
|
81
|
+
"explicitTool": { "type": "boolean", "default": true },
|
|
82
|
+
"maxItemsPerRun": { "type": "integer", "minimum": 1, "maximum": 10, "default": 2 },
|
|
83
|
+
"minUtilityScore": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.8 },
|
|
84
|
+
"minNoveltyScore": { "type": "number", "minimum": 0, "maximum": 1, "default": 0.85 },
|
|
85
|
+
"maxWritesPerSessionWindow": { "type": "integer", "minimum": 1, "maximum": 20, "default": 3 },
|
|
86
|
+
"cooldownMinutes": { "type": "integer", "minimum": 0, "maximum": 240, "default": 5 }
|
|
87
|
+
}
|
|
88
|
+
},
|
|
57
89
|
"ml": {
|
|
58
90
|
"type": "object",
|
|
59
91
|
"additionalProperties": false,
|
package/package.json
CHANGED
package/src/capture.ts
CHANGED
|
@@ -17,9 +17,70 @@ function asRecord(value: unknown): Record<string, unknown> {
|
|
|
17
17
|
return value as Record<string, unknown>;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
function extractStructuredTextCandidate(value: unknown, depth = 0): string {
|
|
21
|
+
if (depth > 5) {
|
|
22
|
+
return "";
|
|
23
|
+
}
|
|
24
|
+
if (typeof value === "string") {
|
|
25
|
+
return normalizeWhitespace(value);
|
|
26
|
+
}
|
|
27
|
+
if (!value || typeof value !== "object") {
|
|
28
|
+
return "";
|
|
29
|
+
}
|
|
30
|
+
if (Array.isArray(value)) {
|
|
31
|
+
const parts = value
|
|
32
|
+
.map((entry) => extractStructuredTextCandidate(entry, depth + 1))
|
|
33
|
+
.filter(Boolean);
|
|
34
|
+
return normalizeWhitespace(parts.join(" "));
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
const record = value as Record<string, unknown>;
|
|
38
|
+
const directText = typeof record.text === "string" ? normalizeWhitespace(record.text) : "";
|
|
39
|
+
if (directText) {
|
|
40
|
+
return directText;
|
|
41
|
+
}
|
|
42
|
+
const caption = typeof record.caption === "string" ? normalizeWhitespace(record.caption) : "";
|
|
43
|
+
if (caption) {
|
|
44
|
+
return caption;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
const nestedCandidates = [
|
|
48
|
+
record.message,
|
|
49
|
+
record.data,
|
|
50
|
+
record.payload,
|
|
51
|
+
record.update,
|
|
52
|
+
record.edited_message,
|
|
53
|
+
record.channel_post,
|
|
54
|
+
record.callback_query,
|
|
55
|
+
];
|
|
56
|
+
for (const candidate of nestedCandidates) {
|
|
57
|
+
const extracted = extractStructuredTextCandidate(candidate, depth + 1);
|
|
58
|
+
if (extracted) {
|
|
59
|
+
return extracted;
|
|
60
|
+
}
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
return "";
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
export function extractStructuredTextFromString(content: string): string | undefined {
|
|
67
|
+
const normalized = normalizeWhitespace(content);
|
|
68
|
+
if (!normalized || !/^[{\[]/.test(normalized)) {
|
|
69
|
+
return undefined;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
try {
|
|
73
|
+
const parsed = JSON.parse(normalized) as unknown;
|
|
74
|
+
const extracted = extractStructuredTextCandidate(parsed);
|
|
75
|
+
return extracted || undefined;
|
|
76
|
+
} catch {
|
|
77
|
+
return undefined;
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
|
|
20
81
|
export function extractHookMessageText(content: unknown): string {
|
|
21
82
|
if (typeof content === "string") {
|
|
22
|
-
return normalizeWhitespace(content);
|
|
83
|
+
return extractStructuredTextFromString(content) ?? normalizeWhitespace(content);
|
|
23
84
|
}
|
|
24
85
|
if (!Array.isArray(content)) {
|
|
25
86
|
return "";
|
|
@@ -32,7 +93,8 @@ export function extractHookMessageText(content: unknown): string {
|
|
|
32
93
|
}
|
|
33
94
|
const item = block as { type?: unknown; text?: unknown };
|
|
34
95
|
if (item.type === "text" && typeof item.text === "string") {
|
|
35
|
-
const normalized =
|
|
96
|
+
const normalized =
|
|
97
|
+
extractStructuredTextFromString(item.text) ?? normalizeWhitespace(item.text);
|
|
36
98
|
if (normalized) {
|
|
37
99
|
parts.push(normalized);
|
|
38
100
|
}
|
|
@@ -249,3 +311,67 @@ export function isOversizedAtomicMemory(text: string): boolean {
|
|
|
249
311
|
const lines = normalized.split(/\r?\n/).filter((line) => line.trim().length > 0);
|
|
250
312
|
return normalized.length > 1600 || lines.length > 18;
|
|
251
313
|
}
|
|
314
|
+
|
|
315
|
+
const RECAP_PREFIXES = [
|
|
316
|
+
/^the user\b/i,
|
|
317
|
+
/^user\b/i,
|
|
318
|
+
/^usuario\b/i,
|
|
319
|
+
/^in this (?:turn|conversation)\b/i,
|
|
320
|
+
/^(?:we|i) (?:discussed|talked about|went over|covered)\b/i,
|
|
321
|
+
/^(?:summary|recap)\b/i,
|
|
322
|
+
];
|
|
323
|
+
|
|
324
|
+
const TEMPORAL_REFERENCE_PATTERN =
|
|
325
|
+
/\b(?:today|tomorrow|yesterday|this turn|this session|earlier in this session|just now|in this chat)\b/i;
|
|
326
|
+
|
|
327
|
+
export function isLikelyTurnRecap(text: string): boolean {
|
|
328
|
+
const normalized = normalizeWhitespace(text);
|
|
329
|
+
if (!normalized) {
|
|
330
|
+
return false;
|
|
331
|
+
}
|
|
332
|
+
if (normalized.length > 260 && /\b(?:asked|wanted|needed|said|requested)\b/i.test(normalized)) {
|
|
333
|
+
return true;
|
|
334
|
+
}
|
|
335
|
+
return RECAP_PREFIXES.some((pattern) => pattern.test(normalized));
|
|
336
|
+
}
|
|
337
|
+
|
|
338
|
+
function splitIntoSentences(text: string): string[] {
|
|
339
|
+
return text
|
|
340
|
+
.split(/(?<=[.!?])\s+/)
|
|
341
|
+
.map((sentence) => normalizeWhitespace(sentence))
|
|
342
|
+
.filter(Boolean);
|
|
343
|
+
}
|
|
344
|
+
|
|
345
|
+
function looksReusableLearning(text: string): boolean {
|
|
346
|
+
if (text.length < 24 || text.length > 220) {
|
|
347
|
+
return false;
|
|
348
|
+
}
|
|
349
|
+
if (TEMPORAL_REFERENCE_PATTERN.test(text)) {
|
|
350
|
+
return false;
|
|
351
|
+
}
|
|
352
|
+
if (isLikelyTranscriptLikeText(text) || isLikelyTurnRecap(text)) {
|
|
353
|
+
return false;
|
|
354
|
+
}
|
|
355
|
+
return /\b(?:prefer|avoid|use|keep|store|remember|dedupe|inject|search|persist|reject|limit|filter|only|always|never|when)\b/i.test(
|
|
356
|
+
text,
|
|
357
|
+
);
|
|
358
|
+
}
|
|
359
|
+
|
|
360
|
+
export function compactAgentLearning(text: string): string | undefined {
|
|
361
|
+
const normalized = normalizeWhitespace(text);
|
|
362
|
+
if (!normalized || isOversizedAtomicMemory(normalized) || isLikelyTranscriptLikeText(normalized)) {
|
|
363
|
+
return undefined;
|
|
364
|
+
}
|
|
365
|
+
if (looksReusableLearning(normalized)) {
|
|
366
|
+
return normalized;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const sentences = splitIntoSentences(normalized);
|
|
370
|
+
for (const sentence of sentences) {
|
|
371
|
+
if (looksReusableLearning(sentence)) {
|
|
372
|
+
return sentence;
|
|
373
|
+
}
|
|
374
|
+
}
|
|
375
|
+
|
|
376
|
+
return undefined;
|
|
377
|
+
}
|
package/src/config.ts
CHANGED
|
@@ -11,6 +11,16 @@ export type MemoryBraidConfig = {
|
|
|
11
11
|
recall: {
|
|
12
12
|
maxResults: number;
|
|
13
13
|
injectTopK: number;
|
|
14
|
+
user: {
|
|
15
|
+
enabled: boolean;
|
|
16
|
+
injectTopK: number;
|
|
17
|
+
};
|
|
18
|
+
agent: {
|
|
19
|
+
enabled: boolean;
|
|
20
|
+
injectTopK: number;
|
|
21
|
+
minScore: number;
|
|
22
|
+
onlyPlanning: boolean;
|
|
23
|
+
};
|
|
14
24
|
merge: {
|
|
15
25
|
strategy: "rrf";
|
|
16
26
|
rrfK: number;
|
|
@@ -23,6 +33,16 @@ export type MemoryBraidConfig = {
|
|
|
23
33
|
mode: "local" | "hybrid" | "ml";
|
|
24
34
|
includeAssistant: boolean;
|
|
25
35
|
maxItemsPerRun: number;
|
|
36
|
+
assistant: {
|
|
37
|
+
enabled: boolean;
|
|
38
|
+
autoCapture: boolean;
|
|
39
|
+
explicitTool: boolean;
|
|
40
|
+
maxItemsPerRun: number;
|
|
41
|
+
minUtilityScore: number;
|
|
42
|
+
minNoveltyScore: number;
|
|
43
|
+
maxWritesPerSessionWindow: number;
|
|
44
|
+
cooldownMinutes: number;
|
|
45
|
+
};
|
|
26
46
|
ml: {
|
|
27
47
|
provider?: "openai" | "anthropic" | "gemini";
|
|
28
48
|
model?: string;
|
|
@@ -80,6 +100,16 @@ const DEFAULTS: MemoryBraidConfig = {
|
|
|
80
100
|
recall: {
|
|
81
101
|
maxResults: 8,
|
|
82
102
|
injectTopK: 5,
|
|
103
|
+
user: {
|
|
104
|
+
enabled: true,
|
|
105
|
+
injectTopK: 5,
|
|
106
|
+
},
|
|
107
|
+
agent: {
|
|
108
|
+
enabled: true,
|
|
109
|
+
injectTopK: 2,
|
|
110
|
+
minScore: 0.78,
|
|
111
|
+
onlyPlanning: true,
|
|
112
|
+
},
|
|
83
113
|
merge: {
|
|
84
114
|
strategy: "rrf",
|
|
85
115
|
rrfK: 60,
|
|
@@ -92,6 +122,16 @@ const DEFAULTS: MemoryBraidConfig = {
|
|
|
92
122
|
mode: "local",
|
|
93
123
|
includeAssistant: false,
|
|
94
124
|
maxItemsPerRun: 6,
|
|
125
|
+
assistant: {
|
|
126
|
+
enabled: true,
|
|
127
|
+
autoCapture: false,
|
|
128
|
+
explicitTool: true,
|
|
129
|
+
maxItemsPerRun: 2,
|
|
130
|
+
minUtilityScore: 0.8,
|
|
131
|
+
minNoveltyScore: 0.85,
|
|
132
|
+
maxWritesPerSessionWindow: 3,
|
|
133
|
+
cooldownMinutes: 5,
|
|
134
|
+
},
|
|
95
135
|
ml: {
|
|
96
136
|
provider: undefined,
|
|
97
137
|
model: undefined,
|
|
@@ -168,8 +208,11 @@ export function parseConfig(raw: unknown): MemoryBraidConfig {
|
|
|
168
208
|
const root = asRecord(raw);
|
|
169
209
|
const mem0 = asRecord(root.mem0);
|
|
170
210
|
const recall = asRecord(root.recall);
|
|
211
|
+
const recallUser = asRecord(recall.user);
|
|
212
|
+
const recallAgent = asRecord(recall.agent);
|
|
171
213
|
const merge = asRecord(recall.merge);
|
|
172
214
|
const capture = asRecord(root.capture);
|
|
215
|
+
const captureAssistant = asRecord(capture.assistant);
|
|
173
216
|
const entityExtraction = asRecord(root.entityExtraction);
|
|
174
217
|
const entityStartup = asRecord(entityExtraction.startup);
|
|
175
218
|
const ml = asRecord(capture.ml);
|
|
@@ -194,6 +237,16 @@ export function parseConfig(raw: unknown): MemoryBraidConfig {
|
|
|
194
237
|
? parsedEntityModel
|
|
195
238
|
: "gpt-4o-mini"
|
|
196
239
|
: parsedEntityModel ?? DEFAULTS.entityExtraction.model;
|
|
240
|
+
const includeAssistant = asBoolean(
|
|
241
|
+
capture.includeAssistant,
|
|
242
|
+
DEFAULTS.capture.includeAssistant,
|
|
243
|
+
);
|
|
244
|
+
const legacyInjectTopK = asInt(
|
|
245
|
+
recall.injectTopK,
|
|
246
|
+
DEFAULTS.recall.injectTopK,
|
|
247
|
+
1,
|
|
248
|
+
20,
|
|
249
|
+
);
|
|
197
250
|
|
|
198
251
|
return {
|
|
199
252
|
enabled: asBoolean(root.enabled, DEFAULTS.enabled),
|
|
@@ -207,7 +260,35 @@ export function parseConfig(raw: unknown): MemoryBraidConfig {
|
|
|
207
260
|
},
|
|
208
261
|
recall: {
|
|
209
262
|
maxResults: asInt(recall.maxResults, DEFAULTS.recall.maxResults, 1, 50),
|
|
210
|
-
injectTopK:
|
|
263
|
+
injectTopK: legacyInjectTopK,
|
|
264
|
+
user: {
|
|
265
|
+
enabled: asBoolean(recallUser.enabled, DEFAULTS.recall.user.enabled),
|
|
266
|
+
injectTopK: asInt(
|
|
267
|
+
recallUser.injectTopK,
|
|
268
|
+
legacyInjectTopK,
|
|
269
|
+
1,
|
|
270
|
+
20,
|
|
271
|
+
),
|
|
272
|
+
},
|
|
273
|
+
agent: {
|
|
274
|
+
enabled: asBoolean(recallAgent.enabled, DEFAULTS.recall.agent.enabled),
|
|
275
|
+
injectTopK: asInt(
|
|
276
|
+
recallAgent.injectTopK,
|
|
277
|
+
DEFAULTS.recall.agent.injectTopK,
|
|
278
|
+
1,
|
|
279
|
+
20,
|
|
280
|
+
),
|
|
281
|
+
minScore: asNumber(
|
|
282
|
+
recallAgent.minScore,
|
|
283
|
+
DEFAULTS.recall.agent.minScore,
|
|
284
|
+
0,
|
|
285
|
+
1,
|
|
286
|
+
),
|
|
287
|
+
onlyPlanning: asBoolean(
|
|
288
|
+
recallAgent.onlyPlanning,
|
|
289
|
+
DEFAULTS.recall.agent.onlyPlanning,
|
|
290
|
+
),
|
|
291
|
+
},
|
|
211
292
|
merge: {
|
|
212
293
|
strategy: "rrf",
|
|
213
294
|
rrfK: asInt(merge.rrfK, DEFAULTS.recall.merge.rrfK, 1, 500),
|
|
@@ -218,8 +299,52 @@ export function parseConfig(raw: unknown): MemoryBraidConfig {
|
|
|
218
299
|
capture: {
|
|
219
300
|
enabled: asBoolean(capture.enabled, DEFAULTS.capture.enabled),
|
|
220
301
|
mode: captureMode,
|
|
221
|
-
includeAssistant
|
|
302
|
+
includeAssistant,
|
|
222
303
|
maxItemsPerRun: asInt(capture.maxItemsPerRun, DEFAULTS.capture.maxItemsPerRun, 1, 50),
|
|
304
|
+
assistant: {
|
|
305
|
+
enabled: asBoolean(
|
|
306
|
+
captureAssistant.enabled,
|
|
307
|
+
DEFAULTS.capture.assistant.enabled,
|
|
308
|
+
),
|
|
309
|
+
autoCapture: asBoolean(
|
|
310
|
+
captureAssistant.autoCapture,
|
|
311
|
+
includeAssistant,
|
|
312
|
+
),
|
|
313
|
+
explicitTool: asBoolean(
|
|
314
|
+
captureAssistant.explicitTool,
|
|
315
|
+
DEFAULTS.capture.assistant.explicitTool,
|
|
316
|
+
),
|
|
317
|
+
maxItemsPerRun: asInt(
|
|
318
|
+
captureAssistant.maxItemsPerRun,
|
|
319
|
+
DEFAULTS.capture.assistant.maxItemsPerRun,
|
|
320
|
+
1,
|
|
321
|
+
10,
|
|
322
|
+
),
|
|
323
|
+
minUtilityScore: asNumber(
|
|
324
|
+
captureAssistant.minUtilityScore,
|
|
325
|
+
DEFAULTS.capture.assistant.minUtilityScore,
|
|
326
|
+
0,
|
|
327
|
+
1,
|
|
328
|
+
),
|
|
329
|
+
minNoveltyScore: asNumber(
|
|
330
|
+
captureAssistant.minNoveltyScore,
|
|
331
|
+
DEFAULTS.capture.assistant.minNoveltyScore,
|
|
332
|
+
0,
|
|
333
|
+
1,
|
|
334
|
+
),
|
|
335
|
+
maxWritesPerSessionWindow: asInt(
|
|
336
|
+
captureAssistant.maxWritesPerSessionWindow,
|
|
337
|
+
DEFAULTS.capture.assistant.maxWritesPerSessionWindow,
|
|
338
|
+
1,
|
|
339
|
+
20,
|
|
340
|
+
),
|
|
341
|
+
cooldownMinutes: asInt(
|
|
342
|
+
captureAssistant.cooldownMinutes,
|
|
343
|
+
DEFAULTS.capture.assistant.cooldownMinutes,
|
|
344
|
+
0,
|
|
345
|
+
240,
|
|
346
|
+
),
|
|
347
|
+
},
|
|
223
348
|
ml: {
|
|
224
349
|
provider:
|
|
225
350
|
ml.provider === "openai" || ml.provider === "anthropic" || ml.provider === "gemini"
|
package/src/extract.ts
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
import { normalizeForHash, normalizeWhitespace, sha256 } from "./chunking.js";
|
|
2
2
|
import type { MemoryBraidConfig } from "./config.js";
|
|
3
|
-
import {
|
|
3
|
+
import {
|
|
4
|
+
extractStructuredTextFromString,
|
|
5
|
+
isLikelyTranscriptLikeText,
|
|
6
|
+
isOversizedAtomicMemory,
|
|
7
|
+
} from "./capture.js";
|
|
4
8
|
import { MemoryBraidLogger } from "./logger.js";
|
|
5
9
|
import type { ExtractedCandidate } from "./types.js";
|
|
6
10
|
|
|
@@ -47,7 +51,7 @@ function isLikelyFeedOrImportedText(text: string): boolean {
|
|
|
47
51
|
|
|
48
52
|
function extractMessageText(content: unknown): string {
|
|
49
53
|
if (typeof content === "string") {
|
|
50
|
-
return normalizeWhitespace(content);
|
|
54
|
+
return extractStructuredTextFromString(content) ?? normalizeWhitespace(content);
|
|
51
55
|
}
|
|
52
56
|
if (!Array.isArray(content)) {
|
|
53
57
|
return "";
|
|
@@ -59,7 +63,8 @@ function extractMessageText(content: unknown): string {
|
|
|
59
63
|
}
|
|
60
64
|
const item = block as { type?: unknown; text?: unknown };
|
|
61
65
|
if (item.type === "text" && typeof item.text === "string") {
|
|
62
|
-
const normalized =
|
|
66
|
+
const normalized =
|
|
67
|
+
extractStructuredTextFromString(item.text) ?? normalizeWhitespace(item.text);
|
|
63
68
|
if (normalized) {
|
|
64
69
|
parts.push(normalized);
|
|
65
70
|
}
|
|
@@ -432,7 +437,7 @@ export async function extractCandidates(params: {
|
|
|
432
437
|
runId?: string;
|
|
433
438
|
}): Promise<ExtractedCandidate[]> {
|
|
434
439
|
const normalized = normalizeMessages(params.messages);
|
|
435
|
-
const captureFromAssistant = params.cfg.capture.
|
|
440
|
+
const captureFromAssistant = params.cfg.capture.assistant.autoCapture;
|
|
436
441
|
const candidatesInput = normalized.filter((message) =>
|
|
437
442
|
captureFromAssistant
|
|
438
443
|
? message.role === "user" || message.role === "assistant"
|