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 +37 -3
- package/openclaw.plugin.json +47 -0
- package/package.json +2 -1
- package/src/config.ts +122 -0
- package/src/consolidation.ts +403 -0
- package/src/index.ts +775 -10
- package/src/memory-model.ts +336 -0
- package/src/memory-selection.ts +257 -0
- package/src/state.ts +40 -0
- package/src/temporal.ts +292 -0
- package/src/types.ts +40 -0
package/src/index.ts
CHANGED
|
@@ -11,11 +11,25 @@ import {
|
|
|
11
11
|
normalizeHookMessages,
|
|
12
12
|
} from "./capture.js";
|
|
13
13
|
import { parseConfig, pluginConfigSchema } from "./config.js";
|
|
14
|
+
import {
|
|
15
|
+
buildConsolidationDrafts,
|
|
16
|
+
findSupersededSemanticMemories,
|
|
17
|
+
} from "./consolidation.js";
|
|
14
18
|
import { stagedDedupe } from "./dedupe.js";
|
|
15
19
|
import { EntityExtractionManager } from "./entities.js";
|
|
16
20
|
import { extractCandidates } from "./extract.js";
|
|
17
21
|
import { MemoryBraidLogger } from "./logger.js";
|
|
18
22
|
import { resolveLocalTools, runLocalGet, runLocalSearch } from "./local-memory.js";
|
|
23
|
+
import {
|
|
24
|
+
buildTaxonomy,
|
|
25
|
+
formatTaxonomySummary,
|
|
26
|
+
inferMemoryLayer as inferNormalizedMemoryLayer,
|
|
27
|
+
normalizeTaxonomy,
|
|
28
|
+
} from "./memory-model.js";
|
|
29
|
+
import {
|
|
30
|
+
scoreObservedMemory,
|
|
31
|
+
scoreProceduralMemory,
|
|
32
|
+
} from "./memory-selection.js";
|
|
19
33
|
import { Mem0Adapter } from "./mem0-client.js";
|
|
20
34
|
import { mergeWithRrf } from "./merge.js";
|
|
21
35
|
import {
|
|
@@ -36,20 +50,32 @@ import {
|
|
|
36
50
|
createStatePaths,
|
|
37
51
|
ensureStateDir,
|
|
38
52
|
readCaptureDedupeState,
|
|
53
|
+
readConsolidationState,
|
|
39
54
|
readLifecycleState,
|
|
40
55
|
readRemediationState,
|
|
41
56
|
readStatsState,
|
|
42
57
|
type StatePaths,
|
|
43
58
|
withStateLock,
|
|
44
59
|
writeCaptureDedupeState,
|
|
60
|
+
writeConsolidationState,
|
|
45
61
|
writeLifecycleState,
|
|
46
62
|
writeRemediationState,
|
|
47
63
|
writeStatsState,
|
|
48
64
|
} from "./state.js";
|
|
65
|
+
import {
|
|
66
|
+
buildTimeRange,
|
|
67
|
+
formatTimeRange,
|
|
68
|
+
inferQuerySpecificity,
|
|
69
|
+
isResultInTimeRange,
|
|
70
|
+
resolveResultTimeMs,
|
|
71
|
+
type TimeRange,
|
|
72
|
+
} from "./temporal.js";
|
|
49
73
|
import type {
|
|
50
74
|
CaptureIntent,
|
|
75
|
+
ConsolidationReason,
|
|
51
76
|
LifecycleEntry,
|
|
52
77
|
MemoryKind,
|
|
78
|
+
MemoryLayer,
|
|
53
79
|
MemoryOwner,
|
|
54
80
|
MemoryBraidResult,
|
|
55
81
|
PendingInboundTurn,
|
|
@@ -71,7 +97,7 @@ function jsonToolResult(payload: unknown) {
|
|
|
71
97
|
return {
|
|
72
98
|
content: [
|
|
73
99
|
{
|
|
74
|
-
type: "text",
|
|
100
|
+
type: "text" as const,
|
|
75
101
|
text: JSON.stringify(payload, null, 2),
|
|
76
102
|
},
|
|
77
103
|
],
|
|
@@ -163,6 +189,13 @@ function resolveCommandScope(config?: unknown): {
|
|
|
163
189
|
};
|
|
164
190
|
}
|
|
165
191
|
|
|
192
|
+
function resolveCommandPersistentScope(config?: unknown): ScopeKey {
|
|
193
|
+
return {
|
|
194
|
+
workspaceHash: workspaceHashFromDir(resolveWorkspaceDirFromConfig(config)),
|
|
195
|
+
agentId: "main",
|
|
196
|
+
};
|
|
197
|
+
}
|
|
198
|
+
|
|
166
199
|
function resolveLatestUserTurnSignature(messages?: unknown[]): string | undefined {
|
|
167
200
|
if (!Array.isArray(messages) || messages.length === 0) {
|
|
168
201
|
return undefined;
|
|
@@ -472,6 +505,10 @@ function inferRecallTarget(result: MemoryBraidResult): RecallTarget {
|
|
|
472
505
|
return normalizeRecallTarget(metadata.recallTarget) ?? "both";
|
|
473
506
|
}
|
|
474
507
|
|
|
508
|
+
function inferMemoryLayer(result: MemoryBraidResult): MemoryLayer {
|
|
509
|
+
return inferNormalizedMemoryLayer(result);
|
|
510
|
+
}
|
|
511
|
+
|
|
475
512
|
function normalizeSessionKey(raw: unknown): string | undefined {
|
|
476
513
|
if (typeof raw !== "string") {
|
|
477
514
|
return undefined;
|
|
@@ -480,6 +517,16 @@ function normalizeSessionKey(raw: unknown): string | undefined {
|
|
|
480
517
|
return trimmed || undefined;
|
|
481
518
|
}
|
|
482
519
|
|
|
520
|
+
function summarizeResultTaxonomy(result: MemoryBraidResult): string {
|
|
521
|
+
const metadata = asRecord(result.metadata);
|
|
522
|
+
const taxonomy = buildTaxonomy({
|
|
523
|
+
text: result.snippet,
|
|
524
|
+
entities: metadata.entities,
|
|
525
|
+
existingTaxonomy: metadata.taxonomy,
|
|
526
|
+
});
|
|
527
|
+
return formatTaxonomySummary(taxonomy) || "n/a";
|
|
528
|
+
}
|
|
529
|
+
|
|
483
530
|
function isGenericUserSummary(text: string): boolean {
|
|
484
531
|
const normalized = text.trim().toLowerCase();
|
|
485
532
|
return (
|
|
@@ -504,6 +551,7 @@ function applyMem0QualityAdjustments(params: {
|
|
|
504
551
|
query: string;
|
|
505
552
|
scope: ScopeKey;
|
|
506
553
|
nowMs: number;
|
|
554
|
+
timeRange?: TimeRange;
|
|
507
555
|
}): {
|
|
508
556
|
results: MemoryBraidResult[];
|
|
509
557
|
adjusted: number;
|
|
@@ -528,6 +576,7 @@ function applyMem0QualityAdjustments(params: {
|
|
|
528
576
|
}
|
|
529
577
|
|
|
530
578
|
const queryTokens = tokenizeForOverlap(params.query);
|
|
579
|
+
const specificity = inferQuerySpecificity(params.query);
|
|
531
580
|
let adjusted = 0;
|
|
532
581
|
let overlapBoosted = 0;
|
|
533
582
|
let overlapPenalized = 0;
|
|
@@ -542,6 +591,7 @@ function applyMem0QualityAdjustments(params: {
|
|
|
542
591
|
const overlap = lexicalOverlap(queryTokens, result.snippet);
|
|
543
592
|
const category = normalizeCategory(metadata.category);
|
|
544
593
|
const isGeneric = isGenericUserSummary(result.snippet);
|
|
594
|
+
const layer = inferMemoryLayer(result);
|
|
545
595
|
const ts = resolveTimestampMs(result);
|
|
546
596
|
const ageDays = ts ? Math.max(0, (params.nowMs - ts) / (24 * 60 * 60 * 1000)) : undefined;
|
|
547
597
|
|
|
@@ -590,6 +640,26 @@ function applyMem0QualityAdjustments(params: {
|
|
|
590
640
|
genericPenalized += 1;
|
|
591
641
|
}
|
|
592
642
|
|
|
643
|
+
if (metadata.supersededBy || metadata.supersededAt) {
|
|
644
|
+
multiplier *= 0.2;
|
|
645
|
+
}
|
|
646
|
+
|
|
647
|
+
if (params.timeRange) {
|
|
648
|
+
if (isResultInTimeRange(result, params.timeRange)) {
|
|
649
|
+
multiplier *= layer === "episodic" ? 1.6 : 1.05;
|
|
650
|
+
} else {
|
|
651
|
+
multiplier *= layer === "episodic" ? 0.15 : 0.55;
|
|
652
|
+
}
|
|
653
|
+
} else if (specificity === "broad") {
|
|
654
|
+
if (layer === "semantic") {
|
|
655
|
+
multiplier *= 1.18;
|
|
656
|
+
} else if (layer === "episodic" && (metadata.consolidatedAt || metadata.compendiumKey)) {
|
|
657
|
+
multiplier *= 0.82;
|
|
658
|
+
}
|
|
659
|
+
} else if (specificity === "specific" && layer === "episodic") {
|
|
660
|
+
multiplier *= 1.1;
|
|
661
|
+
}
|
|
662
|
+
|
|
593
663
|
const normalizedMultiplier = Math.min(2.5, Math.max(0.1, multiplier));
|
|
594
664
|
const nextScore = result.score * normalizedMultiplier;
|
|
595
665
|
if (nextScore !== result.score) {
|
|
@@ -754,6 +824,10 @@ function resolveDateFromPath(pathValue?: string): number | undefined {
|
|
|
754
824
|
}
|
|
755
825
|
|
|
756
826
|
function resolveTimestampMs(result: MemoryBraidResult): number | undefined {
|
|
827
|
+
const normalized = resolveResultTimeMs(result);
|
|
828
|
+
if (normalized) {
|
|
829
|
+
return normalized;
|
|
830
|
+
}
|
|
757
831
|
const metadata = asRecord(result.metadata);
|
|
758
832
|
const fields = [
|
|
759
833
|
metadata.indexedAt,
|
|
@@ -1106,13 +1180,21 @@ async function runMem0Recall(params: {
|
|
|
1106
1180
|
statePaths?: StatePaths | null;
|
|
1107
1181
|
runId: string;
|
|
1108
1182
|
}): Promise<MemoryBraidResult[]> {
|
|
1183
|
+
const builtTimeRange = buildTimeRange({
|
|
1184
|
+
query: params.query,
|
|
1185
|
+
enabled: params.cfg.consolidation.timeQueryParsing,
|
|
1186
|
+
});
|
|
1187
|
+
const effectiveQuery = builtTimeRange.queryWithoutTime || params.query;
|
|
1188
|
+
const fetchLimit = builtTimeRange.range
|
|
1189
|
+
? Math.max(params.maxResults, Math.min(200, params.maxResults * 4))
|
|
1190
|
+
: params.maxResults;
|
|
1109
1191
|
const remediationState = params.statePaths
|
|
1110
1192
|
? await readRemediationState(params.statePaths)
|
|
1111
1193
|
: undefined;
|
|
1112
1194
|
|
|
1113
1195
|
const persistentRaw = await params.mem0.searchMemories({
|
|
1114
|
-
query:
|
|
1115
|
-
maxResults:
|
|
1196
|
+
query: effectiveQuery,
|
|
1197
|
+
maxResults: fetchLimit,
|
|
1116
1198
|
scope: params.persistentScope,
|
|
1117
1199
|
runId: params.runId,
|
|
1118
1200
|
});
|
|
@@ -1129,8 +1211,8 @@ async function runMem0Recall(params: {
|
|
|
1129
1211
|
params.legacyScope.sessionKey !== params.persistentScope.sessionKey
|
|
1130
1212
|
) {
|
|
1131
1213
|
const legacyRaw = await params.mem0.searchMemories({
|
|
1132
|
-
query:
|
|
1133
|
-
maxResults:
|
|
1214
|
+
query: effectiveQuery,
|
|
1215
|
+
maxResults: fetchLimit,
|
|
1134
1216
|
scope: params.legacyScope,
|
|
1135
1217
|
runId: params.runId,
|
|
1136
1218
|
});
|
|
@@ -1143,6 +1225,14 @@ async function runMem0Recall(params: {
|
|
|
1143
1225
|
}
|
|
1144
1226
|
|
|
1145
1227
|
let combined = [...persistentFiltered.results, ...legacyFiltered];
|
|
1228
|
+
if (builtTimeRange.range) {
|
|
1229
|
+
combined = combined.filter((result) => {
|
|
1230
|
+
if (inferMemoryLayer(result) === "semantic") {
|
|
1231
|
+
return true;
|
|
1232
|
+
}
|
|
1233
|
+
return isResultInTimeRange(result, builtTimeRange.range);
|
|
1234
|
+
});
|
|
1235
|
+
}
|
|
1146
1236
|
if (params.cfg.timeDecay.enabled) {
|
|
1147
1237
|
const coreDecay = resolveCoreTemporalDecay({
|
|
1148
1238
|
config: params.coreConfig,
|
|
@@ -1159,9 +1249,10 @@ async function runMem0Recall(params: {
|
|
|
1159
1249
|
|
|
1160
1250
|
combined = applyMem0QualityAdjustments({
|
|
1161
1251
|
results: combined,
|
|
1162
|
-
query:
|
|
1252
|
+
query: effectiveQuery,
|
|
1163
1253
|
scope: params.runtimeScope,
|
|
1164
1254
|
nowMs: Date.now(),
|
|
1255
|
+
timeRange: builtTimeRange.range,
|
|
1165
1256
|
}).results;
|
|
1166
1257
|
|
|
1167
1258
|
const deduped = await stagedDedupe(sortMemoriesStable(combined), {
|
|
@@ -1186,6 +1277,7 @@ async function runMem0Recall(params: {
|
|
|
1186
1277
|
legacyCount: legacyFiltered.length,
|
|
1187
1278
|
quarantinedFiltered:
|
|
1188
1279
|
persistentFiltered.quarantinedFiltered + legacyQuarantinedFiltered,
|
|
1280
|
+
timeRange: builtTimeRange.range ? formatTimeRange(builtTimeRange.range) : "n/a",
|
|
1189
1281
|
dedupedCount: deduped.length,
|
|
1190
1282
|
});
|
|
1191
1283
|
|
|
@@ -1365,6 +1457,41 @@ function parseIntegerFlag(tokens: string[], flag: string, fallback: number): num
|
|
|
1365
1457
|
return Math.max(1, Math.round(raw));
|
|
1366
1458
|
}
|
|
1367
1459
|
|
|
1460
|
+
function parseStringFlag(tokens: string[], flag: string): string | undefined {
|
|
1461
|
+
const index = tokens.findIndex((token) => token === flag);
|
|
1462
|
+
if (index < 0 || index === tokens.length - 1) {
|
|
1463
|
+
return undefined;
|
|
1464
|
+
}
|
|
1465
|
+
const raw = tokens[index + 1]?.trim();
|
|
1466
|
+
return raw || undefined;
|
|
1467
|
+
}
|
|
1468
|
+
|
|
1469
|
+
function hasFlag(tokens: string[], flag: string): boolean {
|
|
1470
|
+
return tokens.includes(flag);
|
|
1471
|
+
}
|
|
1472
|
+
|
|
1473
|
+
function stripFlags(tokens: string[]): string[] {
|
|
1474
|
+
const out: string[] = [];
|
|
1475
|
+
for (let index = 0; index < tokens.length; index += 1) {
|
|
1476
|
+
const token = tokens[index];
|
|
1477
|
+
if (
|
|
1478
|
+
token === "--limit" ||
|
|
1479
|
+
token === "--layer" ||
|
|
1480
|
+
token === "--kind" ||
|
|
1481
|
+
token === "--from" ||
|
|
1482
|
+
token === "--to"
|
|
1483
|
+
) {
|
|
1484
|
+
index += 1;
|
|
1485
|
+
continue;
|
|
1486
|
+
}
|
|
1487
|
+
if (token === "--include-quarantined") {
|
|
1488
|
+
continue;
|
|
1489
|
+
}
|
|
1490
|
+
out.push(token);
|
|
1491
|
+
}
|
|
1492
|
+
return out;
|
|
1493
|
+
}
|
|
1494
|
+
|
|
1368
1495
|
function resolveRecordScope(
|
|
1369
1496
|
memory: MemoryBraidResult,
|
|
1370
1497
|
fallbackScope: { workspaceHash: string; agentId?: string; sessionKey?: string },
|
|
@@ -1557,6 +1684,7 @@ const memoryBraidPlugin = {
|
|
|
1557
1684
|
const assistantLearningWritesByRunScope = new Map<string, number[]>();
|
|
1558
1685
|
|
|
1559
1686
|
let lifecycleTimer: NodeJS.Timeout | null = null;
|
|
1687
|
+
let consolidationTimer: NodeJS.Timeout | null = null;
|
|
1560
1688
|
let statePaths: StatePaths | null = null;
|
|
1561
1689
|
|
|
1562
1690
|
async function ensureRuntimeStatePaths(): Promise<StatePaths | null> {
|
|
@@ -1616,6 +1744,7 @@ const memoryBraidPlugin = {
|
|
|
1616
1744
|
}): Promise<{ accepted: boolean; reason: string; normalizedText: string; memoryId?: string }> {
|
|
1617
1745
|
const validated = validateAtomicMemoryText(params.text);
|
|
1618
1746
|
if (!validated.ok) {
|
|
1747
|
+
const failedReason = validated.reason;
|
|
1619
1748
|
if (params.runtimeStatePaths) {
|
|
1620
1749
|
await withStateLock(params.runtimeStatePaths.stateLockFile, async () => {
|
|
1621
1750
|
const stats = await readStatsState(params.runtimeStatePaths!);
|
|
@@ -1625,7 +1754,7 @@ const memoryBraidPlugin = {
|
|
|
1625
1754
|
}
|
|
1626
1755
|
return {
|
|
1627
1756
|
accepted: false,
|
|
1628
|
-
reason:
|
|
1757
|
+
reason: failedReason,
|
|
1629
1758
|
normalizedText: normalizeWhitespace(params.text),
|
|
1630
1759
|
};
|
|
1631
1760
|
}
|
|
@@ -1679,8 +1808,56 @@ const memoryBraidPlugin = {
|
|
|
1679
1808
|
};
|
|
1680
1809
|
}
|
|
1681
1810
|
|
|
1811
|
+
const selection = scoreProceduralMemory({
|
|
1812
|
+
text: validated.normalized,
|
|
1813
|
+
confidence: params.confidence,
|
|
1814
|
+
captureIntent: params.captureIntent,
|
|
1815
|
+
cfg,
|
|
1816
|
+
});
|
|
1817
|
+
if (selection.decision !== "procedural") {
|
|
1818
|
+
log.debug("memory_braid.capture.selection", {
|
|
1819
|
+
runId: params.runId,
|
|
1820
|
+
target: "procedural",
|
|
1821
|
+
decision: selection.decision,
|
|
1822
|
+
kind: params.kind,
|
|
1823
|
+
captureIntent: params.captureIntent,
|
|
1824
|
+
score: selection.score,
|
|
1825
|
+
reasons: selection.reasons,
|
|
1826
|
+
workspaceHash: params.runtimeScope.workspaceHash,
|
|
1827
|
+
agentId: params.runtimeScope.agentId,
|
|
1828
|
+
sessionKey: params.runtimeScope.sessionKey,
|
|
1829
|
+
contentHashPrefix: exactHash.slice(0, 12),
|
|
1830
|
+
});
|
|
1831
|
+
if (params.runtimeStatePaths) {
|
|
1832
|
+
await withStateLock(params.runtimeStatePaths.stateLockFile, async () => {
|
|
1833
|
+
const stats = await readStatsState(params.runtimeStatePaths!);
|
|
1834
|
+
stats.capture.agentLearningRejectedSelection += 1;
|
|
1835
|
+
await writeStatsState(params.runtimeStatePaths!, stats);
|
|
1836
|
+
});
|
|
1837
|
+
}
|
|
1838
|
+
return {
|
|
1839
|
+
accepted: false,
|
|
1840
|
+
reason: "selection_rejected",
|
|
1841
|
+
normalizedText: validated.normalized,
|
|
1842
|
+
};
|
|
1843
|
+
}
|
|
1844
|
+
log.debug("memory_braid.capture.selection", {
|
|
1845
|
+
runId: params.runId,
|
|
1846
|
+
target: "procedural",
|
|
1847
|
+
decision: selection.decision,
|
|
1848
|
+
kind: params.kind,
|
|
1849
|
+
captureIntent: params.captureIntent,
|
|
1850
|
+
score: selection.score,
|
|
1851
|
+
reasons: selection.reasons,
|
|
1852
|
+
workspaceHash: params.runtimeScope.workspaceHash,
|
|
1853
|
+
agentId: params.runtimeScope.agentId,
|
|
1854
|
+
sessionKey: params.runtimeScope.sessionKey,
|
|
1855
|
+
contentHashPrefix: exactHash.slice(0, 12),
|
|
1856
|
+
});
|
|
1857
|
+
|
|
1682
1858
|
const metadata: Record<string, unknown> = {
|
|
1683
1859
|
sourceType: "agent_learning",
|
|
1860
|
+
memoryLayer: "procedural",
|
|
1684
1861
|
memoryOwner: "agent",
|
|
1685
1862
|
memoryKind: params.kind,
|
|
1686
1863
|
captureIntent: params.captureIntent,
|
|
@@ -1690,7 +1867,13 @@ const memoryBraidPlugin = {
|
|
|
1690
1867
|
agentId: params.runtimeScope.agentId,
|
|
1691
1868
|
sessionKey: params.runtimeScope.sessionKey,
|
|
1692
1869
|
indexedAt: new Date().toISOString(),
|
|
1870
|
+
firstSeenAt: new Date().toISOString(),
|
|
1871
|
+
lastSeenAt: new Date().toISOString(),
|
|
1872
|
+
eventAt: new Date().toISOString(),
|
|
1693
1873
|
contentHash: exactHash,
|
|
1874
|
+
selectionDecision: selection.decision,
|
|
1875
|
+
rememberabilityScore: selection.score,
|
|
1876
|
+
rememberabilityReasons: selection.reasons,
|
|
1694
1877
|
};
|
|
1695
1878
|
if (typeof params.confidence === "number") {
|
|
1696
1879
|
metadata.confidence = Math.max(0, Math.min(1, params.confidence));
|
|
@@ -1725,6 +1908,288 @@ const memoryBraidPlugin = {
|
|
|
1725
1908
|
};
|
|
1726
1909
|
}
|
|
1727
1910
|
|
|
1911
|
+
async function runConsolidationOnce(params: {
|
|
1912
|
+
scope: ScopeKey;
|
|
1913
|
+
reason: ConsolidationReason;
|
|
1914
|
+
runtimeStatePaths?: StatePaths | null;
|
|
1915
|
+
runId: string;
|
|
1916
|
+
}): Promise<{
|
|
1917
|
+
candidates: number;
|
|
1918
|
+
clusters: number;
|
|
1919
|
+
created: number;
|
|
1920
|
+
updated: number;
|
|
1921
|
+
episodicMarked: number;
|
|
1922
|
+
contradictions: number;
|
|
1923
|
+
superseded: number;
|
|
1924
|
+
}> {
|
|
1925
|
+
if (!cfg.consolidation.enabled) {
|
|
1926
|
+
return {
|
|
1927
|
+
candidates: 0,
|
|
1928
|
+
clusters: 0,
|
|
1929
|
+
created: 0,
|
|
1930
|
+
updated: 0,
|
|
1931
|
+
episodicMarked: 0,
|
|
1932
|
+
contradictions: 0,
|
|
1933
|
+
superseded: 0,
|
|
1934
|
+
};
|
|
1935
|
+
}
|
|
1936
|
+
const runtimeStatePaths = params.runtimeStatePaths ?? (await ensureRuntimeStatePaths());
|
|
1937
|
+
if (!runtimeStatePaths) {
|
|
1938
|
+
return {
|
|
1939
|
+
candidates: 0,
|
|
1940
|
+
clusters: 0,
|
|
1941
|
+
created: 0,
|
|
1942
|
+
updated: 0,
|
|
1943
|
+
episodicMarked: 0,
|
|
1944
|
+
contradictions: 0,
|
|
1945
|
+
superseded: 0,
|
|
1946
|
+
};
|
|
1947
|
+
}
|
|
1948
|
+
|
|
1949
|
+
const [allMemories, remediationState, lifecycle, consolidation] = await Promise.all([
|
|
1950
|
+
mem0.getAllMemories({
|
|
1951
|
+
scope: params.scope,
|
|
1952
|
+
limit: 500,
|
|
1953
|
+
runId: params.runId,
|
|
1954
|
+
}),
|
|
1955
|
+
readRemediationState(runtimeStatePaths),
|
|
1956
|
+
readLifecycleState(runtimeStatePaths),
|
|
1957
|
+
readConsolidationState(runtimeStatePaths),
|
|
1958
|
+
]);
|
|
1959
|
+
|
|
1960
|
+
const activeMemories = allMemories.filter((memory) => !isQuarantinedMemory(memory, remediationState).quarantined);
|
|
1961
|
+
const episodic = activeMemories.filter((memory) => inferMemoryLayer(memory) === "episodic");
|
|
1962
|
+
const semantic = activeMemories.filter((memory) => inferMemoryLayer(memory) === "semantic");
|
|
1963
|
+
|
|
1964
|
+
const draftPlan = await buildConsolidationDrafts({
|
|
1965
|
+
episodic,
|
|
1966
|
+
existingSemantic: semantic,
|
|
1967
|
+
lifecycleEntries: lifecycle.entries,
|
|
1968
|
+
cfg,
|
|
1969
|
+
minSupportCount: cfg.consolidation.minSupportCount,
|
|
1970
|
+
minRecallCount: cfg.consolidation.minRecallCount,
|
|
1971
|
+
semanticMaxSourceIds: cfg.consolidation.semanticMaxSourceIds,
|
|
1972
|
+
state: consolidation,
|
|
1973
|
+
semanticSimilarity: async (leftText, rightText) =>
|
|
1974
|
+
mem0.semanticSimilarity({
|
|
1975
|
+
leftText,
|
|
1976
|
+
rightText,
|
|
1977
|
+
scope: params.scope,
|
|
1978
|
+
runId: params.runId,
|
|
1979
|
+
}),
|
|
1980
|
+
});
|
|
1981
|
+
log.debug("memory_braid.consolidation.plan", {
|
|
1982
|
+
runId: params.runId,
|
|
1983
|
+
reason: params.reason,
|
|
1984
|
+
workspaceHash: params.scope.workspaceHash,
|
|
1985
|
+
agentId: params.scope.agentId,
|
|
1986
|
+
candidates: draftPlan.candidates,
|
|
1987
|
+
clusters: draftPlan.clustersFormed,
|
|
1988
|
+
promotedDrafts: draftPlan.drafts.length,
|
|
1989
|
+
drafts: draftPlan.drafts.map((draft) => ({
|
|
1990
|
+
compendiumKeyPrefix: draft.compendiumKey.slice(0, 12),
|
|
1991
|
+
existingMemoryId: draft.existingMemoryId ?? null,
|
|
1992
|
+
kind: draft.kind,
|
|
1993
|
+
anchor: draft.anchor ?? null,
|
|
1994
|
+
sourceCount: draft.sourceMemories.length,
|
|
1995
|
+
supportCount:
|
|
1996
|
+
typeof asRecord(draft.metadata).supportCount === "number"
|
|
1997
|
+
? asRecord(draft.metadata).supportCount
|
|
1998
|
+
: null,
|
|
1999
|
+
promotionScore:
|
|
2000
|
+
typeof asRecord(draft.metadata).promotionScore === "number"
|
|
2001
|
+
? asRecord(draft.metadata).promotionScore
|
|
2002
|
+
: null,
|
|
2003
|
+
promotionReasons: asRecord(draft.metadata).promotionReasons,
|
|
2004
|
+
})),
|
|
2005
|
+
});
|
|
2006
|
+
|
|
2007
|
+
let created = 0;
|
|
2008
|
+
let updated = 0;
|
|
2009
|
+
let episodicMarked = 0;
|
|
2010
|
+
const semanticByKey = { ...consolidation.semanticByCompendiumKey };
|
|
2011
|
+
const semanticMemoryIds = new Map<string, string>();
|
|
2012
|
+
const contradictionCandidates = [...semantic];
|
|
2013
|
+
|
|
2014
|
+
for (const draft of draftPlan.drafts) {
|
|
2015
|
+
let semanticMemoryId = draft.existingMemoryId;
|
|
2016
|
+
if (draft.existingMemoryId) {
|
|
2017
|
+
const updatedRemote = await mem0.updateMemoryMetadata({
|
|
2018
|
+
memoryId: draft.existingMemoryId,
|
|
2019
|
+
scope: params.scope,
|
|
2020
|
+
text: draft.text,
|
|
2021
|
+
metadata: draft.metadata,
|
|
2022
|
+
runId: params.runId,
|
|
2023
|
+
});
|
|
2024
|
+
if (!updatedRemote) {
|
|
2025
|
+
continue;
|
|
2026
|
+
}
|
|
2027
|
+
updated += 1;
|
|
2028
|
+
} else {
|
|
2029
|
+
const createdRemote = await mem0.addMemory({
|
|
2030
|
+
text: draft.text,
|
|
2031
|
+
scope: params.scope,
|
|
2032
|
+
metadata: draft.metadata,
|
|
2033
|
+
runId: params.runId,
|
|
2034
|
+
});
|
|
2035
|
+
if (!createdRemote.id) {
|
|
2036
|
+
continue;
|
|
2037
|
+
}
|
|
2038
|
+
semanticMemoryId = createdRemote.id;
|
|
2039
|
+
created += 1;
|
|
2040
|
+
}
|
|
2041
|
+
|
|
2042
|
+
if (!semanticMemoryId) {
|
|
2043
|
+
continue;
|
|
2044
|
+
}
|
|
2045
|
+
semanticMemoryIds.set(draft.compendiumKey, semanticMemoryId);
|
|
2046
|
+
semanticByKey[draft.compendiumKey] = {
|
|
2047
|
+
memoryId: semanticMemoryId,
|
|
2048
|
+
updatedAt: Date.now(),
|
|
2049
|
+
};
|
|
2050
|
+
|
|
2051
|
+
contradictionCandidates.push({
|
|
2052
|
+
id: semanticMemoryId,
|
|
2053
|
+
source: "mem0",
|
|
2054
|
+
snippet: draft.text,
|
|
2055
|
+
score: 0,
|
|
2056
|
+
metadata: draft.metadata,
|
|
2057
|
+
});
|
|
2058
|
+
|
|
2059
|
+
for (const sourceMemory of draft.sourceMemories) {
|
|
2060
|
+
if (!sourceMemory.id) {
|
|
2061
|
+
continue;
|
|
2062
|
+
}
|
|
2063
|
+
const updatedSource = await mem0.updateMemoryMetadata({
|
|
2064
|
+
memoryId: sourceMemory.id,
|
|
2065
|
+
scope: params.scope,
|
|
2066
|
+
text: sourceMemory.snippet,
|
|
2067
|
+
metadata: {
|
|
2068
|
+
...asRecord(sourceMemory.metadata),
|
|
2069
|
+
memoryLayer: "episodic",
|
|
2070
|
+
consolidatedAt: new Date().toISOString(),
|
|
2071
|
+
compendiumKey: draft.compendiumKey,
|
|
2072
|
+
semanticMemoryId,
|
|
2073
|
+
},
|
|
2074
|
+
runId: params.runId,
|
|
2075
|
+
});
|
|
2076
|
+
if (updatedSource) {
|
|
2077
|
+
episodicMarked += 1;
|
|
2078
|
+
}
|
|
2079
|
+
}
|
|
2080
|
+
}
|
|
2081
|
+
|
|
2082
|
+
const superseded = await findSupersededSemanticMemories({
|
|
2083
|
+
semanticMemories: contradictionCandidates,
|
|
2084
|
+
semanticSimilarity: async (leftText, rightText) =>
|
|
2085
|
+
mem0.semanticSimilarity({
|
|
2086
|
+
leftText,
|
|
2087
|
+
rightText,
|
|
2088
|
+
scope: params.scope,
|
|
2089
|
+
runId: params.runId,
|
|
2090
|
+
}),
|
|
2091
|
+
});
|
|
2092
|
+
let supersededMarked = 0;
|
|
2093
|
+
if (superseded.length > 0) {
|
|
2094
|
+
log.debug("memory_braid.consolidation.supersede", {
|
|
2095
|
+
runId: params.runId,
|
|
2096
|
+
reason: params.reason,
|
|
2097
|
+
workspaceHash: params.scope.workspaceHash,
|
|
2098
|
+
agentId: params.scope.agentId,
|
|
2099
|
+
count: superseded.length,
|
|
2100
|
+
updates: superseded.map((target) => ({
|
|
2101
|
+
memoryId: target.memoryId,
|
|
2102
|
+
supersededBy:
|
|
2103
|
+
typeof asRecord(target.metadata).supersededBy === "string"
|
|
2104
|
+
? asRecord(target.metadata).supersededBy
|
|
2105
|
+
: null,
|
|
2106
|
+
})),
|
|
2107
|
+
});
|
|
2108
|
+
}
|
|
2109
|
+
for (const target of superseded) {
|
|
2110
|
+
const updatedRemote = await mem0.updateMemoryMetadata({
|
|
2111
|
+
memoryId: target.memoryId,
|
|
2112
|
+
scope: params.scope,
|
|
2113
|
+
text: target.text,
|
|
2114
|
+
metadata: target.metadata,
|
|
2115
|
+
runId: params.runId,
|
|
2116
|
+
});
|
|
2117
|
+
if (updatedRemote) {
|
|
2118
|
+
supersededMarked += 1;
|
|
2119
|
+
}
|
|
2120
|
+
}
|
|
2121
|
+
|
|
2122
|
+
const nowIso = new Date().toISOString();
|
|
2123
|
+
await withStateLock(runtimeStatePaths.stateLockFile, async () => {
|
|
2124
|
+
const stats = await readStatsState(runtimeStatePaths);
|
|
2125
|
+
const next = await readConsolidationState(runtimeStatePaths);
|
|
2126
|
+
next.lastConsolidationAt = nowIso;
|
|
2127
|
+
next.lastConsolidationReason = params.reason;
|
|
2128
|
+
next.newEpisodicSinceLastRun = 0;
|
|
2129
|
+
next.semanticByCompendiumKey = semanticByKey;
|
|
2130
|
+
stats.capture.consolidationRuns += 1;
|
|
2131
|
+
stats.capture.consolidationCandidates += draftPlan.candidates;
|
|
2132
|
+
stats.capture.clustersFormed += draftPlan.clustersFormed;
|
|
2133
|
+
stats.capture.semanticCreated += created;
|
|
2134
|
+
stats.capture.semanticUpdated += updated;
|
|
2135
|
+
stats.capture.episodicMarkedConsolidated += episodicMarked;
|
|
2136
|
+
stats.capture.contradictionsDetected += superseded.length;
|
|
2137
|
+
stats.capture.supersededMarked += supersededMarked;
|
|
2138
|
+
stats.capture.lastConsolidationAt = nowIso;
|
|
2139
|
+
await writeConsolidationState(runtimeStatePaths, next);
|
|
2140
|
+
await writeStatsState(runtimeStatePaths, stats);
|
|
2141
|
+
});
|
|
2142
|
+
|
|
2143
|
+
log.debug("memory_braid.consolidation.run", {
|
|
2144
|
+
runId: params.runId,
|
|
2145
|
+
reason: params.reason,
|
|
2146
|
+
workspaceHash: params.scope.workspaceHash,
|
|
2147
|
+
agentId: params.scope.agentId,
|
|
2148
|
+
candidates: draftPlan.candidates,
|
|
2149
|
+
clusters: draftPlan.clustersFormed,
|
|
2150
|
+
created,
|
|
2151
|
+
updated,
|
|
2152
|
+
episodicMarked,
|
|
2153
|
+
contradictions: superseded.length,
|
|
2154
|
+
supersededMarked,
|
|
2155
|
+
});
|
|
2156
|
+
|
|
2157
|
+
return {
|
|
2158
|
+
candidates: draftPlan.candidates,
|
|
2159
|
+
clusters: draftPlan.clustersFormed,
|
|
2160
|
+
created,
|
|
2161
|
+
updated,
|
|
2162
|
+
episodicMarked,
|
|
2163
|
+
contradictions: superseded.length,
|
|
2164
|
+
superseded: supersededMarked,
|
|
2165
|
+
};
|
|
2166
|
+
}
|
|
2167
|
+
|
|
2168
|
+
async function maybeRunOpportunisticConsolidation(params: {
|
|
2169
|
+
scope: ScopeKey;
|
|
2170
|
+
runtimeStatePaths: StatePaths;
|
|
2171
|
+
runId: string;
|
|
2172
|
+
}): Promise<void> {
|
|
2173
|
+
if (!cfg.consolidation.enabled) {
|
|
2174
|
+
return;
|
|
2175
|
+
}
|
|
2176
|
+
const state = await readConsolidationState(params.runtimeStatePaths);
|
|
2177
|
+
const lastRunAt = state.lastConsolidationAt ? Date.parse(state.lastConsolidationAt) : 0;
|
|
2178
|
+
const minutesSinceLastRun =
|
|
2179
|
+
lastRunAt > 0 ? (Date.now() - lastRunAt) / (60 * 1000) : Number.POSITIVE_INFINITY;
|
|
2180
|
+
const enoughNew = state.newEpisodicSinceLastRun >= cfg.consolidation.opportunisticNewMemoryThreshold;
|
|
2181
|
+
const enoughTime = minutesSinceLastRun >= cfg.consolidation.opportunisticMinMinutesSinceLastRun;
|
|
2182
|
+
if (!enoughNew && !enoughTime) {
|
|
2183
|
+
return;
|
|
2184
|
+
}
|
|
2185
|
+
await runConsolidationOnce({
|
|
2186
|
+
scope: params.scope,
|
|
2187
|
+
reason: "opportunistic",
|
|
2188
|
+
runtimeStatePaths: params.runtimeStatePaths,
|
|
2189
|
+
runId: params.runId,
|
|
2190
|
+
});
|
|
2191
|
+
}
|
|
2192
|
+
|
|
1728
2193
|
api.registerTool(
|
|
1729
2194
|
(ctx) => {
|
|
1730
2195
|
const local = resolveLocalTools(api, ctx);
|
|
@@ -1915,7 +2380,8 @@ const memoryBraidPlugin = {
|
|
|
1915
2380
|
|
|
1916
2381
|
api.registerCommand({
|
|
1917
2382
|
name: "memorybraid",
|
|
1918
|
-
description:
|
|
2383
|
+
description:
|
|
2384
|
+
"Memory Braid status, stats, search, consolidation, remediation, lifecycle cleanup, and entity extraction warmup.",
|
|
1919
2385
|
acceptsArgs: true,
|
|
1920
2386
|
handler: async (ctx) => {
|
|
1921
2387
|
const args = ctx.args?.trim() ?? "";
|
|
@@ -1927,6 +2393,15 @@ const memoryBraidPlugin = {
|
|
|
1927
2393
|
config: ctx.config,
|
|
1928
2394
|
});
|
|
1929
2395
|
const paths = await ensureRuntimeStatePaths();
|
|
2396
|
+
const consolidation = paths
|
|
2397
|
+
? await readConsolidationState(paths)
|
|
2398
|
+
: {
|
|
2399
|
+
version: 1 as const,
|
|
2400
|
+
semanticByCompendiumKey: {},
|
|
2401
|
+
newEpisodicSinceLastRun: 0,
|
|
2402
|
+
lastConsolidationAt: undefined,
|
|
2403
|
+
lastConsolidationReason: undefined,
|
|
2404
|
+
};
|
|
1930
2405
|
const lifecycle =
|
|
1931
2406
|
cfg.lifecycle.enabled && paths
|
|
1932
2407
|
? await readLifecycleState(paths)
|
|
@@ -1935,6 +2410,11 @@ const memoryBraidPlugin = {
|
|
|
1935
2410
|
text: [
|
|
1936
2411
|
`capture.mode: ${cfg.capture.mode}`,
|
|
1937
2412
|
`capture.includeAssistant: ${cfg.capture.includeAssistant}`,
|
|
2413
|
+
`capture.selection.minPreferenceDecisionScore: ${cfg.capture.selection.minPreferenceDecisionScore}`,
|
|
2414
|
+
`capture.selection.minFactScore: ${cfg.capture.selection.minFactScore}`,
|
|
2415
|
+
`capture.selection.minTaskScore: ${cfg.capture.selection.minTaskScore}`,
|
|
2416
|
+
`capture.selection.minOtherScore: ${cfg.capture.selection.minOtherScore}`,
|
|
2417
|
+
`capture.selection.minProceduralScore: ${cfg.capture.selection.minProceduralScore}`,
|
|
1938
2418
|
`capture.assistant.autoCapture: ${cfg.capture.assistant.autoCapture}`,
|
|
1939
2419
|
`capture.assistant.explicitTool: ${cfg.capture.assistant.explicitTool}`,
|
|
1940
2420
|
`recall.user.injectTopK: ${cfg.recall.user.injectTopK}`,
|
|
@@ -1947,6 +2427,14 @@ const memoryBraidPlugin = {
|
|
|
1947
2427
|
`lifecycle.captureTtlDays: ${cfg.lifecycle.captureTtlDays}`,
|
|
1948
2428
|
`lifecycle.cleanupIntervalMinutes: ${cfg.lifecycle.cleanupIntervalMinutes}`,
|
|
1949
2429
|
`lifecycle.reinforceOnRecall: ${cfg.lifecycle.reinforceOnRecall}`,
|
|
2430
|
+
`consolidation.enabled: ${cfg.consolidation.enabled}`,
|
|
2431
|
+
`consolidation.startupRun: ${cfg.consolidation.startupRun}`,
|
|
2432
|
+
`consolidation.intervalMinutes: ${cfg.consolidation.intervalMinutes}`,
|
|
2433
|
+
`consolidation.minSelectionScore: ${cfg.consolidation.minSelectionScore}`,
|
|
2434
|
+
`consolidation.newEpisodicSinceLastRun: ${consolidation.newEpisodicSinceLastRun}`,
|
|
2435
|
+
`consolidation.semanticTracked: ${Object.keys(consolidation.semanticByCompendiumKey).length}`,
|
|
2436
|
+
`consolidation.lastConsolidationAt: ${consolidation.lastConsolidationAt ?? "n/a"}`,
|
|
2437
|
+
`consolidation.lastConsolidationReason: ${consolidation.lastConsolidationReason ?? "n/a"}`,
|
|
1950
2438
|
`lifecycle.tracked: ${Object.keys(lifecycle.entries).length}`,
|
|
1951
2439
|
`lifecycle.lastCleanupAt: ${lifecycle.lastCleanupAt ?? "n/a"}`,
|
|
1952
2440
|
`lifecycle.lastCleanupReason: ${lifecycle.lastCleanupReason ?? "n/a"}`,
|
|
@@ -1966,6 +2454,7 @@ const memoryBraidPlugin = {
|
|
|
1966
2454
|
|
|
1967
2455
|
const stats = await readStatsState(paths);
|
|
1968
2456
|
const lifecycle = await readLifecycleState(paths);
|
|
2457
|
+
const consolidation = await readConsolidationState(paths);
|
|
1969
2458
|
const capture = stats.capture;
|
|
1970
2459
|
const mem0SuccessRate =
|
|
1971
2460
|
capture.mem0AddAttempts > 0
|
|
@@ -2010,8 +2499,19 @@ const memoryBraidPlugin = {
|
|
|
2010
2499
|
`- agentLearningAutoRejected: ${capture.agentLearningAutoRejected}`,
|
|
2011
2500
|
`- agentLearningInjected: ${capture.agentLearningInjected}`,
|
|
2012
2501
|
`- agentLearningRecallHits: ${capture.agentLearningRecallHits}`,
|
|
2502
|
+
`- selectionSkipped: ${capture.selectionSkipped}`,
|
|
2503
|
+
`- agentLearningRejectedSelection: ${capture.agentLearningRejectedSelection}`,
|
|
2504
|
+
`- consolidationRuns: ${capture.consolidationRuns}`,
|
|
2505
|
+
`- consolidationCandidates: ${capture.consolidationCandidates}`,
|
|
2506
|
+
`- clustersFormed: ${capture.clustersFormed}`,
|
|
2507
|
+
`- semanticCreated: ${capture.semanticCreated}`,
|
|
2508
|
+
`- semanticUpdated: ${capture.semanticUpdated}`,
|
|
2509
|
+
`- episodicMarkedConsolidated: ${capture.episodicMarkedConsolidated}`,
|
|
2510
|
+
`- contradictionsDetected: ${capture.contradictionsDetected}`,
|
|
2511
|
+
`- supersededMarked: ${capture.supersededMarked}`,
|
|
2013
2512
|
`- lastRunAt: ${capture.lastRunAt ?? "n/a"}`,
|
|
2014
2513
|
`- lastRemediationAt: ${capture.lastRemediationAt ?? "n/a"}`,
|
|
2514
|
+
`- lastConsolidationAt: ${capture.lastConsolidationAt ?? "n/a"}`,
|
|
2015
2515
|
"",
|
|
2016
2516
|
"Lifecycle:",
|
|
2017
2517
|
`- enabled: ${cfg.lifecycle.enabled}`,
|
|
@@ -2025,10 +2525,144 @@ const memoryBraidPlugin = {
|
|
|
2025
2525
|
`- lastCleanupExpired: ${lifecycle.lastCleanupExpired ?? "n/a"}`,
|
|
2026
2526
|
`- lastCleanupDeleted: ${lifecycle.lastCleanupDeleted ?? "n/a"}`,
|
|
2027
2527
|
`- lastCleanupFailed: ${lifecycle.lastCleanupFailed ?? "n/a"}`,
|
|
2528
|
+
"",
|
|
2529
|
+
"Consolidation:",
|
|
2530
|
+
`- enabled: ${cfg.consolidation.enabled}`,
|
|
2531
|
+
`- startupRun: ${cfg.consolidation.startupRun}`,
|
|
2532
|
+
`- intervalMinutes: ${cfg.consolidation.intervalMinutes}`,
|
|
2533
|
+
`- minSelectionScore: ${cfg.consolidation.minSelectionScore}`,
|
|
2534
|
+
`- newEpisodicSinceLastRun: ${consolidation.newEpisodicSinceLastRun}`,
|
|
2535
|
+
`- semanticTracked: ${Object.keys(consolidation.semanticByCompendiumKey).length}`,
|
|
2536
|
+
`- lastConsolidationAt: ${consolidation.lastConsolidationAt ?? "n/a"}`,
|
|
2537
|
+
`- lastConsolidationReason: ${consolidation.lastConsolidationReason ?? "n/a"}`,
|
|
2028
2538
|
].join("\n"),
|
|
2029
2539
|
};
|
|
2030
2540
|
}
|
|
2031
2541
|
|
|
2542
|
+
if (action === "search") {
|
|
2543
|
+
const paths = await ensureRuntimeStatePaths();
|
|
2544
|
+
if (!paths) {
|
|
2545
|
+
return {
|
|
2546
|
+
text: "Search unavailable: state directory is not ready.",
|
|
2547
|
+
isError: true,
|
|
2548
|
+
};
|
|
2549
|
+
}
|
|
2550
|
+
|
|
2551
|
+
const queryTokens = stripFlags(tokens.slice(1));
|
|
2552
|
+
const queryText = normalizeWhitespace(queryTokens.join(" "));
|
|
2553
|
+
if (!queryText) {
|
|
2554
|
+
return {
|
|
2555
|
+
text:
|
|
2556
|
+
"Usage: /memorybraid search <query> [--limit N] [--layer episodic|semantic|procedural|all] [--kind fact|preference|decision|task|heuristic|lesson|strategy|other] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--include-quarantined]",
|
|
2557
|
+
isError: true,
|
|
2558
|
+
};
|
|
2559
|
+
}
|
|
2560
|
+
|
|
2561
|
+
const layer = parseStringFlag(tokens, "--layer") ?? "all";
|
|
2562
|
+
const allowedLayers = new Set(["episodic", "semantic", "procedural", "all"]);
|
|
2563
|
+
if (!allowedLayers.has(layer)) {
|
|
2564
|
+
return {
|
|
2565
|
+
text: "Invalid --layer. Use episodic, semantic, procedural, or all.",
|
|
2566
|
+
isError: true,
|
|
2567
|
+
};
|
|
2568
|
+
}
|
|
2569
|
+
|
|
2570
|
+
const kind = parseStringFlag(tokens, "--kind");
|
|
2571
|
+
if (
|
|
2572
|
+
kind &&
|
|
2573
|
+
![
|
|
2574
|
+
"fact",
|
|
2575
|
+
"preference",
|
|
2576
|
+
"decision",
|
|
2577
|
+
"task",
|
|
2578
|
+
"heuristic",
|
|
2579
|
+
"lesson",
|
|
2580
|
+
"strategy",
|
|
2581
|
+
"other",
|
|
2582
|
+
].includes(kind)
|
|
2583
|
+
) {
|
|
2584
|
+
return {
|
|
2585
|
+
text: "Invalid --kind.",
|
|
2586
|
+
isError: true,
|
|
2587
|
+
};
|
|
2588
|
+
}
|
|
2589
|
+
|
|
2590
|
+
const limit = Math.min(50, Math.max(1, parseIntegerFlag(tokens, "--limit", 10)));
|
|
2591
|
+
const includeQuarantined = hasFlag(tokens, "--include-quarantined");
|
|
2592
|
+
const timeRangeResult = buildTimeRange({
|
|
2593
|
+
query: queryText,
|
|
2594
|
+
from: parseStringFlag(tokens, "--from"),
|
|
2595
|
+
to: parseStringFlag(tokens, "--to"),
|
|
2596
|
+
enabled: cfg.consolidation.timeQueryParsing,
|
|
2597
|
+
});
|
|
2598
|
+
const effectiveQuery = timeRangeResult.queryWithoutTime || queryText;
|
|
2599
|
+
const commandScope = resolveCommandPersistentScope(ctx.config);
|
|
2600
|
+
const fetched = await mem0.searchMemories({
|
|
2601
|
+
query: effectiveQuery,
|
|
2602
|
+
maxResults: Math.min(200, Math.max(25, limit * 5)),
|
|
2603
|
+
scope: commandScope,
|
|
2604
|
+
runId: log.newRunId(),
|
|
2605
|
+
});
|
|
2606
|
+
const remediationState = await readRemediationState(paths);
|
|
2607
|
+
const lifecycle = await readLifecycleState(paths);
|
|
2608
|
+
let hiddenQuarantined = 0;
|
|
2609
|
+
const filtered = fetched.filter((result) => {
|
|
2610
|
+
const quarantine = isQuarantinedMemory(result, remediationState);
|
|
2611
|
+
if (quarantine.quarantined && !includeQuarantined) {
|
|
2612
|
+
hiddenQuarantined += 1;
|
|
2613
|
+
return false;
|
|
2614
|
+
}
|
|
2615
|
+
if (layer !== "all" && inferMemoryLayer(result) !== layer) {
|
|
2616
|
+
return false;
|
|
2617
|
+
}
|
|
2618
|
+
if (kind && inferMemoryKind(result) !== kind) {
|
|
2619
|
+
return false;
|
|
2620
|
+
}
|
|
2621
|
+
if (timeRangeResult.range && !isResultInTimeRange(result, timeRangeResult.range)) {
|
|
2622
|
+
return false;
|
|
2623
|
+
}
|
|
2624
|
+
return true;
|
|
2625
|
+
});
|
|
2626
|
+
const rows = filtered.slice(0, limit);
|
|
2627
|
+
const lines = [
|
|
2628
|
+
"Memory Braid search",
|
|
2629
|
+
`- query: ${queryText}`,
|
|
2630
|
+
`- effectiveQuery: ${effectiveQuery}`,
|
|
2631
|
+
`- scope.workspaceHash: ${commandScope.workspaceHash}`,
|
|
2632
|
+
`- scope.agentId: ${commandScope.agentId}`,
|
|
2633
|
+
`- layer: ${layer}`,
|
|
2634
|
+
`- kind: ${kind ?? "all"}`,
|
|
2635
|
+
`- timeRange: ${formatTimeRange(timeRangeResult.range)}`,
|
|
2636
|
+
`- fetched: ${fetched.length}`,
|
|
2637
|
+
`- shown: ${rows.length}`,
|
|
2638
|
+
`- hiddenQuarantined: ${hiddenQuarantined}`,
|
|
2639
|
+
];
|
|
2640
|
+
if (rows.length === 0) {
|
|
2641
|
+
lines.push("", "No matching memories.");
|
|
2642
|
+
return { text: lines.join("\n") };
|
|
2643
|
+
}
|
|
2644
|
+
for (const [index, result] of rows.entries()) {
|
|
2645
|
+
const metadata = asRecord(result.metadata);
|
|
2646
|
+
const quarantine = isQuarantinedMemory(result, remediationState);
|
|
2647
|
+
const lifecycleEntry =
|
|
2648
|
+
result.id && lifecycle.entries[result.id] ? lifecycle.entries[result.id] : undefined;
|
|
2649
|
+
lines.push(
|
|
2650
|
+
"",
|
|
2651
|
+
`${index + 1}. [${result.score.toFixed(3)}] ${result.id ?? "unknown"} ${result.snippet}`,
|
|
2652
|
+
` sourceType=${metadata.sourceType ?? "n/a"} layer=${inferMemoryLayer(result)} owner=${inferMemoryOwner(result)} kind=${inferMemoryKind(result)} stability=${metadata.stability ?? "n/a"}`,
|
|
2653
|
+
` selection=decision:${metadata.selectionDecision ?? "n/a"} score:${typeof metadata.rememberabilityScore === "number" ? metadata.rememberabilityScore.toFixed(2) : "n/a"} reasons:${Array.isArray(metadata.rememberabilityReasons) ? metadata.rememberabilityReasons.join(", ") : "n/a"}`,
|
|
2654
|
+
` timestamps=eventAt:${metadata.eventAt ?? "n/a"} firstSeenAt:${metadata.firstSeenAt ?? "n/a"} indexedAt:${metadata.indexedAt ?? "n/a"} updatedAt:${metadata.updatedAt ?? "n/a"}`,
|
|
2655
|
+
` taxonomy=${summarizeResultTaxonomy(result)}`,
|
|
2656
|
+
` provenance=captureOrigin:${metadata.captureOrigin ?? "n/a"} capturePath:${metadata.capturePath ?? "n/a"} pluginCaptureVersion:${metadata.pluginCaptureVersion ?? "n/a"}`,
|
|
2657
|
+
` quarantine=${quarantine.quarantined ? `yes (${quarantine.reason ?? "n/a"})` : "no"}`,
|
|
2658
|
+
` lifecycle=recallCount:${lifecycleEntry?.recallCount ?? "n/a"} lastRecalledAt:${lifecycleEntry?.lastRecalledAt ? new Date(lifecycleEntry.lastRecalledAt).toISOString() : "n/a"}`,
|
|
2659
|
+
);
|
|
2660
|
+
}
|
|
2661
|
+
return {
|
|
2662
|
+
text: lines.join("\n"),
|
|
2663
|
+
};
|
|
2664
|
+
}
|
|
2665
|
+
|
|
2032
2666
|
if (action === "audit" || action === "remediate") {
|
|
2033
2667
|
const subAction = action === "audit" ? "audit" : (tokens[1] ?? "audit").toLowerCase();
|
|
2034
2668
|
if (
|
|
@@ -2101,6 +2735,34 @@ const memoryBraidPlugin = {
|
|
|
2101
2735
|
};
|
|
2102
2736
|
}
|
|
2103
2737
|
|
|
2738
|
+
if (action === "consolidate") {
|
|
2739
|
+
const paths = await ensureRuntimeStatePaths();
|
|
2740
|
+
if (!paths) {
|
|
2741
|
+
return {
|
|
2742
|
+
text: "Consolidation unavailable: state directory is not ready.",
|
|
2743
|
+
isError: true,
|
|
2744
|
+
};
|
|
2745
|
+
}
|
|
2746
|
+
const summary = await runConsolidationOnce({
|
|
2747
|
+
scope: resolveCommandPersistentScope(ctx.config),
|
|
2748
|
+
reason: "command",
|
|
2749
|
+
runtimeStatePaths: paths,
|
|
2750
|
+
runId: log.newRunId(),
|
|
2751
|
+
});
|
|
2752
|
+
return {
|
|
2753
|
+
text: [
|
|
2754
|
+
"Consolidation complete.",
|
|
2755
|
+
`- candidates: ${summary.candidates}`,
|
|
2756
|
+
`- clusters: ${summary.clusters}`,
|
|
2757
|
+
`- semanticCreated: ${summary.created}`,
|
|
2758
|
+
`- semanticUpdated: ${summary.updated}`,
|
|
2759
|
+
`- episodicMarked: ${summary.episodicMarked}`,
|
|
2760
|
+
`- contradictions: ${summary.contradictions}`,
|
|
2761
|
+
`- supersededMarked: ${summary.superseded}`,
|
|
2762
|
+
].join("\n"),
|
|
2763
|
+
};
|
|
2764
|
+
}
|
|
2765
|
+
|
|
2104
2766
|
if (action === "warmup") {
|
|
2105
2767
|
const runId = log.newRunId();
|
|
2106
2768
|
const forceReload = tokens.some((token) => token === "--force");
|
|
@@ -2134,7 +2796,7 @@ const memoryBraidPlugin = {
|
|
|
2134
2796
|
|
|
2135
2797
|
return {
|
|
2136
2798
|
text:
|
|
2137
|
-
"Usage: /memorybraid [status|stats|audit|remediate <audit|quarantine|delete|purge-all-captured> [--apply] [--limit N] [--sample N]|cleanup|warmup [--force]]",
|
|
2799
|
+
"Usage: /memorybraid [status|stats|search <query> [--limit N] [--layer episodic|semantic|procedural|all] [--kind fact|preference|decision|task|heuristic|lesson|strategy|other] [--from YYYY-MM-DD] [--to YYYY-MM-DD] [--include-quarantined]|consolidate|audit|remediate <audit|quarantine|delete|purge-all-captured> [--apply] [--limit N] [--sample N]|cleanup|warmup [--force]]",
|
|
2138
2800
|
};
|
|
2139
2801
|
},
|
|
2140
2802
|
});
|
|
@@ -2575,6 +3237,7 @@ const memoryBraidPlugin = {
|
|
|
2575
3237
|
let mem0AddWithId = 0;
|
|
2576
3238
|
let mem0AddWithoutId = 0;
|
|
2577
3239
|
let remoteQuarantineFiltered = 0;
|
|
3240
|
+
let selectionSkipped = 0;
|
|
2578
3241
|
const remediationState = await readRemediationState(runtimeStatePaths);
|
|
2579
3242
|
const successfulAdds: Array<{
|
|
2580
3243
|
memoryId: string;
|
|
@@ -2644,8 +3307,10 @@ const memoryBraidPlugin = {
|
|
|
2644
3307
|
continue;
|
|
2645
3308
|
}
|
|
2646
3309
|
|
|
3310
|
+
const indexedAt = new Date().toISOString();
|
|
2647
3311
|
const metadata: Record<string, unknown> = {
|
|
2648
3312
|
sourceType: "capture",
|
|
3313
|
+
memoryLayer: "episodic",
|
|
2649
3314
|
memoryOwner: "user",
|
|
2650
3315
|
memoryKind: mapCategoryToMemoryKind(candidate.category),
|
|
2651
3316
|
captureIntent: "observed",
|
|
@@ -2663,7 +3328,11 @@ const memoryBraidPlugin = {
|
|
|
2663
3328
|
capturePath: captureInput.capturePath,
|
|
2664
3329
|
pluginCaptureVersion: PLUGIN_CAPTURE_VERSION,
|
|
2665
3330
|
contentHash: hash,
|
|
2666
|
-
indexedAt
|
|
3331
|
+
indexedAt,
|
|
3332
|
+
eventAt: indexedAt,
|
|
3333
|
+
firstSeenAt: indexedAt,
|
|
3334
|
+
lastSeenAt: indexedAt,
|
|
3335
|
+
supportCount: 1,
|
|
2667
3336
|
};
|
|
2668
3337
|
|
|
2669
3338
|
if (cfg.entityExtraction.enabled) {
|
|
@@ -2678,6 +3347,50 @@ const memoryBraidPlugin = {
|
|
|
2678
3347
|
metadata.entities = entities;
|
|
2679
3348
|
}
|
|
2680
3349
|
}
|
|
3350
|
+
metadata.taxonomy = buildTaxonomy({
|
|
3351
|
+
text: candidate.text,
|
|
3352
|
+
entities: metadata.entities,
|
|
3353
|
+
existingTaxonomy: metadata.taxonomy,
|
|
3354
|
+
});
|
|
3355
|
+
metadata.taxonomySummary = formatTaxonomySummary(normalizeTaxonomy(metadata.taxonomy));
|
|
3356
|
+
const selection = scoreObservedMemory({
|
|
3357
|
+
text: candidate.text,
|
|
3358
|
+
kind: mapCategoryToMemoryKind(candidate.category),
|
|
3359
|
+
extractionScore: candidate.score,
|
|
3360
|
+
taxonomy: normalizeTaxonomy(metadata.taxonomy),
|
|
3361
|
+
source: candidate.source,
|
|
3362
|
+
cfg,
|
|
3363
|
+
});
|
|
3364
|
+
metadata.selectionDecision = selection.decision;
|
|
3365
|
+
metadata.rememberabilityScore = selection.score;
|
|
3366
|
+
metadata.rememberabilityReasons = selection.reasons;
|
|
3367
|
+
log.debug("memory_braid.capture.selection", {
|
|
3368
|
+
runId,
|
|
3369
|
+
target: "episodic",
|
|
3370
|
+
decision: selection.decision,
|
|
3371
|
+
kind: mapCategoryToMemoryKind(candidate.category),
|
|
3372
|
+
source: candidate.source,
|
|
3373
|
+
score: selection.score,
|
|
3374
|
+
reasons: selection.reasons,
|
|
3375
|
+
workspaceHash: scope.workspaceHash,
|
|
3376
|
+
agentId: scope.agentId,
|
|
3377
|
+
sessionKey: scope.sessionKey,
|
|
3378
|
+
contentHashPrefix: hash.slice(0, 12),
|
|
3379
|
+
});
|
|
3380
|
+
if (selection.decision !== "episodic") {
|
|
3381
|
+
selectionSkipped += 1;
|
|
3382
|
+
log.debug("memory_braid.capture.skip", {
|
|
3383
|
+
runId,
|
|
3384
|
+
reason: "selection_rejected",
|
|
3385
|
+
workspaceHash: scope.workspaceHash,
|
|
3386
|
+
agentId: scope.agentId,
|
|
3387
|
+
sessionKey: scope.sessionKey,
|
|
3388
|
+
category: candidate.category,
|
|
3389
|
+
score: selection.score,
|
|
3390
|
+
reasons: selection.reasons,
|
|
3391
|
+
});
|
|
3392
|
+
continue;
|
|
3393
|
+
}
|
|
2681
3394
|
|
|
2682
3395
|
const quarantine = isQuarantinedMemory(
|
|
2683
3396
|
{
|
|
@@ -2723,6 +3436,7 @@ const memoryBraidPlugin = {
|
|
|
2723
3436
|
|
|
2724
3437
|
await withStateLock(runtimeStatePaths.stateLockFile, async () => {
|
|
2725
3438
|
const dedupe = await readCaptureDedupeState(runtimeStatePaths);
|
|
3439
|
+
const consolidation = await readConsolidationState(runtimeStatePaths);
|
|
2726
3440
|
const stats = await readStatsState(runtimeStatePaths);
|
|
2727
3441
|
const lifecycle = cfg.lifecycle.enabled
|
|
2728
3442
|
? await readLifecycleState(runtimeStatePaths)
|
|
@@ -2773,11 +3487,14 @@ const memoryBraidPlugin = {
|
|
|
2773
3487
|
stats.capture.provenanceSkipped += provenanceSkipped;
|
|
2774
3488
|
stats.capture.transcriptShapeSkipped += transcriptShapeSkipped;
|
|
2775
3489
|
stats.capture.quarantinedFiltered += remoteQuarantineFiltered;
|
|
3490
|
+
stats.capture.selectionSkipped += selectionSkipped;
|
|
2776
3491
|
stats.capture.agentLearningAutoCaptured += agentLearningAutoCaptured;
|
|
2777
3492
|
stats.capture.agentLearningAutoRejected += agentLearningAutoRejected;
|
|
2778
3493
|
stats.capture.lastRunAt = new Date(now).toISOString();
|
|
3494
|
+
consolidation.newEpisodicSinceLastRun += persisted;
|
|
2779
3495
|
|
|
2780
3496
|
await writeCaptureDedupeState(runtimeStatePaths, dedupe);
|
|
3497
|
+
await writeConsolidationState(runtimeStatePaths, consolidation);
|
|
2781
3498
|
if (lifecycle) {
|
|
2782
3499
|
await writeLifecycleState(runtimeStatePaths, lifecycle);
|
|
2783
3500
|
}
|
|
@@ -2807,6 +3524,12 @@ const memoryBraidPlugin = {
|
|
|
2807
3524
|
agentLearningAutoRejected,
|
|
2808
3525
|
}, true);
|
|
2809
3526
|
});
|
|
3527
|
+
|
|
3528
|
+
await maybeRunOpportunisticConsolidation({
|
|
3529
|
+
scope: persistentScope,
|
|
3530
|
+
runtimeStatePaths,
|
|
3531
|
+
runId,
|
|
3532
|
+
});
|
|
2810
3533
|
});
|
|
2811
3534
|
|
|
2812
3535
|
api.registerService({
|
|
@@ -2848,6 +3571,12 @@ const memoryBraidPlugin = {
|
|
|
2848
3571
|
lifecycleCaptureTtlDays: cfg.lifecycle.captureTtlDays,
|
|
2849
3572
|
lifecycleCleanupIntervalMinutes: cfg.lifecycle.cleanupIntervalMinutes,
|
|
2850
3573
|
lifecycleReinforceOnRecall: cfg.lifecycle.reinforceOnRecall,
|
|
3574
|
+
consolidationEnabled: cfg.consolidation.enabled,
|
|
3575
|
+
consolidationStartupRun: cfg.consolidation.startupRun,
|
|
3576
|
+
consolidationIntervalMinutes: cfg.consolidation.intervalMinutes,
|
|
3577
|
+
consolidationMinSupportCount: cfg.consolidation.minSupportCount,
|
|
3578
|
+
consolidationMinRecallCount: cfg.consolidation.minRecallCount,
|
|
3579
|
+
consolidationTimeQueryParsing: cfg.consolidation.timeQueryParsing,
|
|
2851
3580
|
entityExtractionEnabled: cfg.entityExtraction.enabled,
|
|
2852
3581
|
entityProvider: cfg.entityExtraction.provider,
|
|
2853
3582
|
entityModel: cfg.entityExtraction.model,
|
|
@@ -2906,12 +3635,48 @@ const memoryBraidPlugin = {
|
|
|
2906
3635
|
});
|
|
2907
3636
|
}, intervalMs);
|
|
2908
3637
|
}
|
|
3638
|
+
|
|
3639
|
+
if (cfg.consolidation.enabled && cfg.consolidation.startupRun) {
|
|
3640
|
+
void runConsolidationOnce({
|
|
3641
|
+
scope: resolveCommandPersistentScope(api.config),
|
|
3642
|
+
reason: "startup",
|
|
3643
|
+
runtimeStatePaths: statePaths,
|
|
3644
|
+
runId,
|
|
3645
|
+
}).catch((err) => {
|
|
3646
|
+
log.warn("memory_braid.consolidation.run", {
|
|
3647
|
+
runId,
|
|
3648
|
+
reason: "startup",
|
|
3649
|
+
error: err instanceof Error ? err.message : String(err),
|
|
3650
|
+
});
|
|
3651
|
+
});
|
|
3652
|
+
}
|
|
3653
|
+
|
|
3654
|
+
if (cfg.consolidation.enabled) {
|
|
3655
|
+
const intervalMs = cfg.consolidation.intervalMinutes * 60 * 1000;
|
|
3656
|
+
consolidationTimer = setInterval(() => {
|
|
3657
|
+
void runConsolidationOnce({
|
|
3658
|
+
scope: resolveCommandPersistentScope(api.config),
|
|
3659
|
+
reason: "interval",
|
|
3660
|
+
runtimeStatePaths: statePaths!,
|
|
3661
|
+
runId: log.newRunId(),
|
|
3662
|
+
}).catch((err) => {
|
|
3663
|
+
log.warn("memory_braid.consolidation.run", {
|
|
3664
|
+
reason: "interval",
|
|
3665
|
+
error: err instanceof Error ? err.message : String(err),
|
|
3666
|
+
});
|
|
3667
|
+
});
|
|
3668
|
+
}, intervalMs);
|
|
3669
|
+
}
|
|
2909
3670
|
},
|
|
2910
3671
|
stop: async () => {
|
|
2911
3672
|
if (lifecycleTimer) {
|
|
2912
3673
|
clearInterval(lifecycleTimer);
|
|
2913
3674
|
lifecycleTimer = null;
|
|
2914
3675
|
}
|
|
3676
|
+
if (consolidationTimer) {
|
|
3677
|
+
clearInterval(consolidationTimer);
|
|
3678
|
+
consolidationTimer = null;
|
|
3679
|
+
}
|
|
2915
3680
|
},
|
|
2916
3681
|
});
|
|
2917
3682
|
},
|