mr-memory 3.7.6 → 3.7.7
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/index.js +64 -10
- package/index.ts +93 -10
- package/memoryrouter-api.js +11 -0
- package/memoryrouter-api.ts +11 -0
- package/openclaw.plugin.json +8 -0
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -8,7 +8,7 @@
|
|
|
8
8
|
* BYOK — provider API keys never touch MemoryRouter.
|
|
9
9
|
*/
|
|
10
10
|
import { readFile, lstat, writeFile, copyFile, mkdir } from "node:fs/promises";
|
|
11
|
-
import { deleteMemories, getMemoryStats, ingestMemory, prepareMemory, searchMemory } from "./memoryrouter-api.js";
|
|
11
|
+
import { deleteMemories, getMemoryStats, ingestMemory, prepareMemory, searchMemory, warmupMemory } from "./memoryrouter-api.js";
|
|
12
12
|
import { join, resolve, relative, isAbsolute, sep, dirname } from "node:path";
|
|
13
13
|
import os from "node:os";
|
|
14
14
|
const DEFAULT_ENDPOINT = "https://api.memoryrouter.ai";
|
|
@@ -267,7 +267,7 @@ const WORKSPACE_FILES = [
|
|
|
267
267
|
"TOOLS.md", "AGENTS.md", "SOUL.md", "BOOTSTRAP.md",
|
|
268
268
|
];
|
|
269
269
|
function openClawConfigPath() {
|
|
270
|
-
return process.env.OPENCLAW_CONFIG || join(os.homedir(), ".openclaw", "openclaw.json");
|
|
270
|
+
return process.env.OPENCLAW_CONFIG_PATH || process.env.OPENCLAW_CONFIG || join(os.homedir(), ".openclaw", "openclaw.json");
|
|
271
271
|
}
|
|
272
272
|
async function readOpenClawConfigFile() {
|
|
273
273
|
try {
|
|
@@ -357,6 +357,47 @@ async function setPluginEnabled(api, enabled) {
|
|
|
357
357
|
}
|
|
358
358
|
await updateOpenClawPluginEntry(api, { enabled });
|
|
359
359
|
}
|
|
360
|
+
async function mergePluginConfig(api, patch) {
|
|
361
|
+
const current = await readOpenClawConfigFile();
|
|
362
|
+
const existing = current.plugins?.entries?.[api.id]?.config;
|
|
363
|
+
await setPluginConfig(api, {
|
|
364
|
+
...(existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {}),
|
|
365
|
+
...patch,
|
|
366
|
+
});
|
|
367
|
+
}
|
|
368
|
+
// Keep mr-memory as a first-class plugin config entry without clobbering user settings.
|
|
369
|
+
// OpenClaw doctor validates plugin entries against discoverable manifests, so the entry
|
|
370
|
+
// must be present, enabled, and allowlisted when an allowlist exists.
|
|
371
|
+
async function ensureDoctorSafePluginEntry(api) {
|
|
372
|
+
try {
|
|
373
|
+
const config = await readOpenClawConfigFile();
|
|
374
|
+
const plugins = config.plugins ?? {};
|
|
375
|
+
const entries = plugins.entries ?? {};
|
|
376
|
+
const existingEntry = entries[api.id] && typeof entries[api.id] === "object" && !Array.isArray(entries[api.id])
|
|
377
|
+
? entries[api.id]
|
|
378
|
+
: {};
|
|
379
|
+
const nextPlugins = {
|
|
380
|
+
...plugins,
|
|
381
|
+
entries: {
|
|
382
|
+
...entries,
|
|
383
|
+
[api.id]: {
|
|
384
|
+
...existingEntry,
|
|
385
|
+
enabled: true,
|
|
386
|
+
},
|
|
387
|
+
},
|
|
388
|
+
};
|
|
389
|
+
if (Array.isArray(plugins.allow) && !plugins.allow.includes(api.id)) {
|
|
390
|
+
nextPlugins.allow = [...plugins.allow, api.id];
|
|
391
|
+
}
|
|
392
|
+
const next = { ...config, plugins: nextPlugins };
|
|
393
|
+
if (JSON.stringify(next.plugins) === JSON.stringify(config.plugins))
|
|
394
|
+
return;
|
|
395
|
+
await writeOpenClawConfigFile(next);
|
|
396
|
+
}
|
|
397
|
+
catch (err) {
|
|
398
|
+
api.logger.info?.(`memoryrouter: doctor-safe registration skipped — ${err instanceof Error ? err.message : String(err)}`);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
360
401
|
/**
|
|
361
402
|
* Read all workspace files and return as a single text blob for token counting.
|
|
362
403
|
*/
|
|
@@ -434,13 +475,16 @@ const memoryRouterPlugin = {
|
|
|
434
475
|
return memoryKey;
|
|
435
476
|
}
|
|
436
477
|
const hasAnyKey = !!(memoryKey || (agentKeys && Object.keys(agentKeys).length > 0));
|
|
478
|
+
void (async () => {
|
|
479
|
+
await ensureDoctorSafePluginEntry(api);
|
|
480
|
+
if (hasAnyKey)
|
|
481
|
+
await selfHealNativeMemorySearch(api);
|
|
482
|
+
})();
|
|
437
483
|
if (hasAnyKey) {
|
|
438
484
|
if (memoryKey)
|
|
439
485
|
api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
|
|
440
486
|
if (agentKeys)
|
|
441
487
|
api.logger.info?.(`memoryrouter: ${Object.keys(agentKeys).length} agent key(s) configured`);
|
|
442
|
-
// Self-heal already-configured installs: disable native search if still on.
|
|
443
|
-
void selfHealNativeMemorySearch(api);
|
|
444
488
|
}
|
|
445
489
|
else {
|
|
446
490
|
api.logger.info?.("memoryrouter: no key configured — run: openclaw mr <key>");
|
|
@@ -603,6 +647,8 @@ If setting up MemoryRouter for another agent:
|
|
|
603
647
|
if (data.context) {
|
|
604
648
|
const elapsed = Date.now() - startMs;
|
|
605
649
|
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms) [llm_input]`);
|
|
650
|
+
if (elapsed > 2000 && data.metrics)
|
|
651
|
+
log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
|
|
606
652
|
const wrapped = wrapForInjection(data.context);
|
|
607
653
|
return { appendSystemContext: wrapped };
|
|
608
654
|
}
|
|
@@ -704,6 +750,8 @@ If setting up MemoryRouter for another agent:
|
|
|
704
750
|
if (data.context) {
|
|
705
751
|
const elapsed = Date.now() - startMs;
|
|
706
752
|
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms)`);
|
|
753
|
+
if (elapsed > 2000 && data.metrics)
|
|
754
|
+
log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
|
|
707
755
|
const wrapped = wrapForInjection(data.context);
|
|
708
756
|
return { appendSystemContext: wrapped };
|
|
709
757
|
}
|
|
@@ -919,7 +967,7 @@ If setting up MemoryRouter for another agent:
|
|
|
919
967
|
return;
|
|
920
968
|
}
|
|
921
969
|
try {
|
|
922
|
-
await
|
|
970
|
+
await mergePluginConfig(api, { key });
|
|
923
971
|
await setPluginEnabled(api, true);
|
|
924
972
|
try {
|
|
925
973
|
const changed = await setNativeMemorySearchEnabled(false);
|
|
@@ -995,7 +1043,7 @@ If setting up MemoryRouter for another agent:
|
|
|
995
1043
|
return;
|
|
996
1044
|
}
|
|
997
1045
|
try {
|
|
998
|
-
await
|
|
1046
|
+
await mergePluginConfig(api, { key: memoryKey, density: name });
|
|
999
1047
|
console.log(`✓ Memory density set to ${name}`);
|
|
1000
1048
|
}
|
|
1001
1049
|
catch (err) {
|
|
@@ -1013,11 +1061,8 @@ If setting up MemoryRouter for another agent:
|
|
|
1013
1061
|
}
|
|
1014
1062
|
const newLogging = !logging;
|
|
1015
1063
|
try {
|
|
1016
|
-
await
|
|
1064
|
+
await mergePluginConfig(api, {
|
|
1017
1065
|
key: memoryKey,
|
|
1018
|
-
endpoint: cfg?.endpoint,
|
|
1019
|
-
density,
|
|
1020
|
-
mode,
|
|
1021
1066
|
logging: newLogging,
|
|
1022
1067
|
});
|
|
1023
1068
|
console.log(`✓ Logging ${newLogging ? "ON" : "OFF"} (restart gateway to apply)`);
|
|
@@ -1170,8 +1215,17 @@ If setting up MemoryRouter for another agent:
|
|
|
1170
1215
|
start: () => {
|
|
1171
1216
|
if (memoryKey)
|
|
1172
1217
|
api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
|
|
1218
|
+
if (hasAnyKey && !warmupTimer) {
|
|
1219
|
+
pingWarmup("startup");
|
|
1220
|
+
warmupTimer = setInterval(() => pingWarmup("keepalive"), 4 * 60 * 1000);
|
|
1221
|
+
warmupTimer.unref?.();
|
|
1222
|
+
}
|
|
1173
1223
|
},
|
|
1174
1224
|
stop: () => {
|
|
1225
|
+
if (warmupTimer) {
|
|
1226
|
+
clearInterval(warmupTimer);
|
|
1227
|
+
warmupTimer = undefined;
|
|
1228
|
+
}
|
|
1175
1229
|
api.logger.info?.("memoryrouter: stopped");
|
|
1176
1230
|
},
|
|
1177
1231
|
});
|
package/index.ts
CHANGED
|
@@ -9,7 +9,7 @@
|
|
|
9
9
|
*/
|
|
10
10
|
|
|
11
11
|
import { readFile, readdir, stat, lstat, writeFile, copyFile, mkdir } from "node:fs/promises";
|
|
12
|
-
import { deleteMemories, getMemoryStats, ingestMemory, prepareMemory, searchMemory } from "./memoryrouter-api.js";
|
|
12
|
+
import { deleteMemories, getMemoryStats, ingestMemory, prepareMemory, searchMemory, warmupMemory } from "./memoryrouter-api.js";
|
|
13
13
|
import { join, resolve, relative, isAbsolute, sep, dirname } from "node:path";
|
|
14
14
|
import path from "node:path";
|
|
15
15
|
import os from "node:os";
|
|
@@ -296,7 +296,7 @@ type CompatApi = OpenClawPluginApi & {
|
|
|
296
296
|
type JsonObject = Record<string, any>;
|
|
297
297
|
|
|
298
298
|
function openClawConfigPath(): string {
|
|
299
|
-
return process.env.OPENCLAW_CONFIG || join(os.homedir(), ".openclaw", "openclaw.json");
|
|
299
|
+
return process.env.OPENCLAW_CONFIG_PATH || process.env.OPENCLAW_CONFIG || join(os.homedir(), ".openclaw", "openclaw.json");
|
|
300
300
|
}
|
|
301
301
|
|
|
302
302
|
async function readOpenClawConfigFile(): Promise<JsonObject> {
|
|
@@ -388,6 +388,47 @@ async function setPluginEnabled(api: OpenClawPluginApi, enabled: boolean): Promi
|
|
|
388
388
|
await updateOpenClawPluginEntry(api, { enabled });
|
|
389
389
|
}
|
|
390
390
|
|
|
391
|
+
async function mergePluginConfig(api: OpenClawPluginApi, patch: Record<string, unknown>): Promise<void> {
|
|
392
|
+
const current = await readOpenClawConfigFile();
|
|
393
|
+
const existing = current.plugins?.entries?.[api.id]?.config;
|
|
394
|
+
await setPluginConfig(api, {
|
|
395
|
+
...(existing && typeof existing === "object" && !Array.isArray(existing) ? existing : {}),
|
|
396
|
+
...patch,
|
|
397
|
+
});
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
// Keep mr-memory as a first-class plugin config entry without clobbering user settings.
|
|
401
|
+
// OpenClaw doctor validates plugin entries against discoverable manifests, so the entry
|
|
402
|
+
// must be present, enabled, and allowlisted when an allowlist exists.
|
|
403
|
+
async function ensureDoctorSafePluginEntry(api: OpenClawPluginApi): Promise<void> {
|
|
404
|
+
try {
|
|
405
|
+
const config = await readOpenClawConfigFile();
|
|
406
|
+
const plugins = config.plugins ?? {};
|
|
407
|
+
const entries = plugins.entries ?? {};
|
|
408
|
+
const existingEntry = entries[api.id] && typeof entries[api.id] === "object" && !Array.isArray(entries[api.id])
|
|
409
|
+
? entries[api.id]
|
|
410
|
+
: {};
|
|
411
|
+
const nextPlugins: JsonObject = {
|
|
412
|
+
...plugins,
|
|
413
|
+
entries: {
|
|
414
|
+
...entries,
|
|
415
|
+
[api.id]: {
|
|
416
|
+
...existingEntry,
|
|
417
|
+
enabled: true,
|
|
418
|
+
},
|
|
419
|
+
},
|
|
420
|
+
};
|
|
421
|
+
if (Array.isArray(plugins.allow) && !plugins.allow.includes(api.id)) {
|
|
422
|
+
nextPlugins.allow = [...plugins.allow, api.id];
|
|
423
|
+
}
|
|
424
|
+
const next = { ...config, plugins: nextPlugins };
|
|
425
|
+
if (JSON.stringify(next.plugins) === JSON.stringify(config.plugins)) return;
|
|
426
|
+
await writeOpenClawConfigFile(next);
|
|
427
|
+
} catch (err) {
|
|
428
|
+
api.logger.info?.(`memoryrouter: doctor-safe registration skipped — ${err instanceof Error ? err.message : String(err)}`);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
|
|
391
432
|
/**
|
|
392
433
|
* Read all workspace files and return as a single text blob for token counting.
|
|
393
434
|
*/
|
|
@@ -460,12 +501,44 @@ const memoryRouterPlugin = {
|
|
|
460
501
|
}
|
|
461
502
|
|
|
462
503
|
const hasAnyKey = !!(memoryKey || (agentKeys && Object.keys(agentKeys).length > 0));
|
|
504
|
+
let warmupTimer: ReturnType<typeof setInterval> | undefined;
|
|
505
|
+
|
|
506
|
+
function configuredKeys(): string[] {
|
|
507
|
+
const keys = new Set<string>();
|
|
508
|
+
if (memoryKey) keys.add(memoryKey);
|
|
509
|
+
if (agentKeys) {
|
|
510
|
+
for (const key of Object.values(agentKeys)) {
|
|
511
|
+
if (key) keys.add(key);
|
|
512
|
+
}
|
|
513
|
+
}
|
|
514
|
+
return Array.from(keys);
|
|
515
|
+
}
|
|
516
|
+
|
|
517
|
+
function pingWarmup(reason: string): void {
|
|
518
|
+
for (const key of configuredKeys()) {
|
|
519
|
+
warmupMemory(endpoint, key, embeddings)
|
|
520
|
+
.then(async (res) => {
|
|
521
|
+
if (!res.ok) {
|
|
522
|
+
log(`memoryrouter: warmup ${reason} failed (${res.status})`);
|
|
523
|
+
return;
|
|
524
|
+
}
|
|
525
|
+
const data = await res.json().catch(() => null) as any;
|
|
526
|
+
log(`memoryrouter: warmup ${reason} ok (${data?.totalVectors ?? 0} vectors, ${data?.warmupTimeMs ?? "?"}ms)`);
|
|
527
|
+
})
|
|
528
|
+
.catch((err) => {
|
|
529
|
+
log(`memoryrouter: warmup ${reason} error — ${err instanceof Error ? err.message : String(err)}`);
|
|
530
|
+
});
|
|
531
|
+
}
|
|
532
|
+
}
|
|
533
|
+
|
|
534
|
+
void (async () => {
|
|
535
|
+
await ensureDoctorSafePluginEntry(api);
|
|
536
|
+
if (hasAnyKey) await selfHealNativeMemorySearch(api);
|
|
537
|
+
})();
|
|
463
538
|
|
|
464
539
|
if (hasAnyKey) {
|
|
465
540
|
if (memoryKey) api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
|
|
466
541
|
if (agentKeys) api.logger.info?.(`memoryrouter: ${Object.keys(agentKeys).length} agent key(s) configured`);
|
|
467
|
-
// Self-heal already-configured installs: disable native search if still on.
|
|
468
|
-
void selfHealNativeMemorySearch(api);
|
|
469
542
|
} else {
|
|
470
543
|
api.logger.info?.("memoryrouter: no key configured — run: openclaw mr <key>");
|
|
471
544
|
api.logger.info?.("memoryrouter: get your free API key at https://memoryrouter.ai");
|
|
@@ -632,11 +705,13 @@ If setting up MemoryRouter for another agent:
|
|
|
632
705
|
memories_found?: number;
|
|
633
706
|
tokens_billed?: number;
|
|
634
707
|
memory_tokens?: number;
|
|
708
|
+
metrics?: Record<string, unknown>;
|
|
635
709
|
};
|
|
636
710
|
|
|
637
711
|
if (data.context) {
|
|
638
712
|
const elapsed = Date.now() - startMs;
|
|
639
713
|
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms) [llm_input]`);
|
|
714
|
+
if (elapsed > 2000 && data.metrics) log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
|
|
640
715
|
const wrapped = wrapForInjection(data.context);
|
|
641
716
|
return { appendSystemContext: wrapped };
|
|
642
717
|
}
|
|
@@ -744,11 +819,13 @@ If setting up MemoryRouter for another agent:
|
|
|
744
819
|
memories_found?: number;
|
|
745
820
|
tokens_billed?: number;
|
|
746
821
|
memory_tokens?: number;
|
|
822
|
+
metrics?: Record<string, unknown>;
|
|
747
823
|
};
|
|
748
824
|
|
|
749
825
|
if (data.context) {
|
|
750
826
|
const elapsed = Date.now() - startMs;
|
|
751
827
|
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms)`);
|
|
828
|
+
if (elapsed > 2000 && data.metrics) log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
|
|
752
829
|
const wrapped = wrapForInjection(data.context);
|
|
753
830
|
return { appendSystemContext: wrapped };
|
|
754
831
|
}
|
|
@@ -973,7 +1050,7 @@ If setting up MemoryRouter for another agent:
|
|
|
973
1050
|
return;
|
|
974
1051
|
}
|
|
975
1052
|
try {
|
|
976
|
-
await
|
|
1053
|
+
await mergePluginConfig(api, { key });
|
|
977
1054
|
await setPluginEnabled(api, true);
|
|
978
1055
|
try {
|
|
979
1056
|
const changed = await setNativeMemorySearchEnabled(false);
|
|
@@ -1046,7 +1123,7 @@ If setting up MemoryRouter for another agent:
|
|
|
1046
1123
|
.action(async () => {
|
|
1047
1124
|
if (!memoryKey) { console.error("Not configured. Run: openclaw mr <key>"); return; }
|
|
1048
1125
|
try {
|
|
1049
|
-
await
|
|
1126
|
+
await mergePluginConfig(api, { key: memoryKey, density: name });
|
|
1050
1127
|
console.log(`✓ Memory density set to ${name}`);
|
|
1051
1128
|
} catch (err) {
|
|
1052
1129
|
console.error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
|
|
@@ -1064,11 +1141,8 @@ If setting up MemoryRouter for another agent:
|
|
|
1064
1141
|
}
|
|
1065
1142
|
const newLogging = !logging;
|
|
1066
1143
|
try {
|
|
1067
|
-
await
|
|
1144
|
+
await mergePluginConfig(api, {
|
|
1068
1145
|
key: memoryKey,
|
|
1069
|
-
endpoint: cfg?.endpoint,
|
|
1070
|
-
density,
|
|
1071
|
-
mode,
|
|
1072
1146
|
logging: newLogging,
|
|
1073
1147
|
});
|
|
1074
1148
|
console.log(`✓ Logging ${newLogging ? "ON" : "OFF"} (restart gateway to apply)`);
|
|
@@ -1237,8 +1311,17 @@ If setting up MemoryRouter for another agent:
|
|
|
1237
1311
|
id: "mr-memory",
|
|
1238
1312
|
start: () => {
|
|
1239
1313
|
if (memoryKey) api.logger.info?.(`memoryrouter: active (key: ${memoryKey.slice(0, 6)}...)`);
|
|
1314
|
+
if (hasAnyKey && !warmupTimer) {
|
|
1315
|
+
pingWarmup("startup");
|
|
1316
|
+
warmupTimer = setInterval(() => pingWarmup("keepalive"), 4 * 60 * 1000);
|
|
1317
|
+
warmupTimer.unref?.();
|
|
1318
|
+
}
|
|
1240
1319
|
},
|
|
1241
1320
|
stop: () => {
|
|
1321
|
+
if (warmupTimer) {
|
|
1322
|
+
clearInterval(warmupTimer);
|
|
1323
|
+
warmupTimer = undefined;
|
|
1324
|
+
}
|
|
1242
1325
|
api.logger.info?.("memoryrouter: stopped");
|
|
1243
1326
|
},
|
|
1244
1327
|
});
|
package/memoryrouter-api.js
CHANGED
|
@@ -47,3 +47,14 @@ export async function deleteMemories(endpoint, key, embeddings) {
|
|
|
47
47
|
headers: { Authorization: `Bearer ${key}` },
|
|
48
48
|
});
|
|
49
49
|
}
|
|
50
|
+
|
|
51
|
+
export async function warmupMemory(endpoint, key, embeddings) {
|
|
52
|
+
return fetch(`${endpoint}/v1/memory/warmup`, {
|
|
53
|
+
method: "POST",
|
|
54
|
+
headers: {
|
|
55
|
+
Authorization: `Bearer ${key}`,
|
|
56
|
+
...(embeddings && { "X-Embedding-Model": embeddings }),
|
|
57
|
+
},
|
|
58
|
+
signal: AbortSignal.timeout(15000),
|
|
59
|
+
});
|
|
60
|
+
}
|
package/memoryrouter-api.ts
CHANGED
|
@@ -58,3 +58,14 @@ export async function deleteMemories(endpoint: string, key: string, embeddings?:
|
|
|
58
58
|
headers: { Authorization: `Bearer ${key}` },
|
|
59
59
|
});
|
|
60
60
|
}
|
|
61
|
+
|
|
62
|
+
export async function warmupMemory(endpoint: string, key: string, embeddings?: string): Promise<Response> {
|
|
63
|
+
return fetch(`${endpoint}/v1/memory/warmup`, {
|
|
64
|
+
method: "POST",
|
|
65
|
+
headers: {
|
|
66
|
+
Authorization: `Bearer ${key}`,
|
|
67
|
+
...(embeddings && { "X-Embedding-Model": embeddings }),
|
|
68
|
+
},
|
|
69
|
+
signal: AbortSignal.timeout(15000),
|
|
70
|
+
});
|
|
71
|
+
}
|
package/openclaw.plugin.json
CHANGED
|
@@ -46,6 +46,14 @@
|
|
|
46
46
|
"type": "string",
|
|
47
47
|
"enum": ["bge", "qwen"],
|
|
48
48
|
"description": "Embedding model: bge (1024d, default) or qwen (4096d)"
|
|
49
|
+
},
|
|
50
|
+
"agentKeys": {
|
|
51
|
+
"type": "object",
|
|
52
|
+
"additionalProperties": {
|
|
53
|
+
"type": "string",
|
|
54
|
+
"pattern": "^mk[_-]"
|
|
55
|
+
},
|
|
56
|
+
"description": "Optional per-agent MemoryRouter keys, keyed by OpenClaw agent id"
|
|
49
57
|
}
|
|
50
58
|
},
|
|
51
59
|
"required": []
|