mr-memory 3.15.0 → 3.16.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/index.js +21 -5
- package/index.ts +21 -5
- package/memory-instructions.js +1 -1
- package/memory-instructions.ts +1 -1
- package/openclaw.plugin.json +8 -1
- package/package.json +1 -1
package/index.js
CHANGED
|
@@ -12,6 +12,7 @@ import { commitReflections, consolidateCheckout, dateSearch, deleteMemories, del
|
|
|
12
12
|
import { isAutoConsolidationSession, maybeTriggerAutoConsolidate } from "./auto-consolidate.js";
|
|
13
13
|
import { withMemoryInstructions } from "./memory-instructions.js";
|
|
14
14
|
import { join, resolve, relative, isAbsolute, sep, dirname } from "node:path";
|
|
15
|
+
import path from "node:path";
|
|
15
16
|
import os from "node:os";
|
|
16
17
|
const DEFAULT_ENDPOINT = "https://api.memoryrouter.ai";
|
|
17
18
|
/** Keep-warm defaults.
|
|
@@ -291,6 +292,14 @@ const WORKSPACE_FILES = [
|
|
|
291
292
|
"IDENTITY.md", "USER.md", "MEMORY.md", "HEARTBEAT.md",
|
|
292
293
|
"TOOLS.md", "AGENTS.md", "SOUL.md", "BOOTSTRAP.md",
|
|
293
294
|
];
|
|
295
|
+
/** Explicit host agent id wins; older hosts fall back to workspace basename. */
|
|
296
|
+
export function isReadOnlyAgent(readOnlyAgents, ctx) {
|
|
297
|
+
const explicit = typeof ctx?.agentId === "string" ? ctx.agentId.trim() : "";
|
|
298
|
+
const dir = typeof ctx?.workspaceDir === "string"
|
|
299
|
+
? path.basename(ctx.workspaceDir.replace(/[\\/]+$/, "")) : "";
|
|
300
|
+
const agentId = explicit || (dir === "workspace" ? "main" : dir.replace(/^workspace-/, ""));
|
|
301
|
+
return !!agentId && (readOnlyAgents ?? []).includes(agentId);
|
|
302
|
+
}
|
|
294
303
|
// ──────────────────────────────────────────────────────
|
|
295
304
|
// Helpers
|
|
296
305
|
// ──────────────────────────────────────────────────────
|
|
@@ -574,6 +583,7 @@ const memoryRouterPlugin = {
|
|
|
574
583
|
const logging = cfg?.logging ?? false;
|
|
575
584
|
const embeddings = cfg?.embeddings; // undefined = bge (default), "qwen" = qwen3-8b
|
|
576
585
|
const agentKeys = cfg?.agentKeys;
|
|
586
|
+
const readOnlyAgents = cfg?.readOnlyAgents;
|
|
577
587
|
const autoConsolidate = cfg?.autoConsolidate !== false; // default true
|
|
578
588
|
const log = (msg) => { if (logging)
|
|
579
589
|
api.logger.info?.(msg); };
|
|
@@ -582,6 +592,8 @@ const memoryRouterPlugin = {
|
|
|
582
592
|
// Fire-and-forget; all rails (lock, cooldown, 3-batch bound) live in
|
|
583
593
|
// auto-consolidate.ts. Failures never propagate into the hook path.
|
|
584
594
|
function triggerAutoConsolidation(reflection, ctx, activeKey) {
|
|
595
|
+
if (isReadOnlyAgent(readOnlyAgents, ctx))
|
|
596
|
+
return;
|
|
585
597
|
try {
|
|
586
598
|
maybeTriggerAutoConsolidate(reflection ?? null, {
|
|
587
599
|
autoConsolidate,
|
|
@@ -879,10 +891,10 @@ If setting up MemoryRouter for another agent:
|
|
|
879
891
|
if (data.context) {
|
|
880
892
|
const elapsed = Date.now() - startMs;
|
|
881
893
|
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms) [llm_input]`);
|
|
882
|
-
if (
|
|
894
|
+
if (data.metrics)
|
|
883
895
|
log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
|
|
884
896
|
let contextText = data.context;
|
|
885
|
-
const nudge = buildDebtNudgeLine(data.reflection);
|
|
897
|
+
const nudge = isReadOnlyAgent(readOnlyAgents, ctx) ? null : buildDebtNudgeLine(data.reflection);
|
|
886
898
|
if (nudge)
|
|
887
899
|
contextText = `${contextText}\n\n${nudge}`;
|
|
888
900
|
const importNudge = buildImportNudgeLine(data.import);
|
|
@@ -993,11 +1005,11 @@ If setting up MemoryRouter for another agent:
|
|
|
993
1005
|
triggerAutoConsolidation(data.reflection, ctx, activeKey);
|
|
994
1006
|
if (data.context) {
|
|
995
1007
|
const elapsed = Date.now() - startMs;
|
|
996
|
-
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms)`);
|
|
997
|
-
if (
|
|
1008
|
+
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms) agent=${ctx?.agentId || "unknown"} session=${ctx?.sessionKey || "unknown"}`);
|
|
1009
|
+
if (data.metrics)
|
|
998
1010
|
log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
|
|
999
1011
|
let contextText = data.context;
|
|
1000
|
-
const nudge = buildDebtNudgeLine(data.reflection);
|
|
1012
|
+
const nudge = isReadOnlyAgent(readOnlyAgents, ctx) ? null : buildDebtNudgeLine(data.reflection);
|
|
1001
1013
|
if (nudge)
|
|
1002
1014
|
contextText = `${contextText}\n\n${nudge}`;
|
|
1003
1015
|
const importNudge = buildImportNudgeLine(data.import);
|
|
@@ -1026,6 +1038,10 @@ If setting up MemoryRouter for another agent:
|
|
|
1026
1038
|
// Core: agent_end — store this turn's conversation
|
|
1027
1039
|
// ==================================================================
|
|
1028
1040
|
api.on("agent_end", async (event, ctx) => {
|
|
1041
|
+
if (isReadOnlyAgent(readOnlyAgents, ctx)) {
|
|
1042
|
+
api.logger.info?.(`memoryrouter: ingest skipped read-only agent=${ctx?.agentId || path.basename(ctx?.workspaceDir || "")} session=${ctx?.sessionKey || "unknown"}`);
|
|
1043
|
+
return;
|
|
1044
|
+
}
|
|
1029
1045
|
// Never store the auto-consolidation session's own chatter as memory.
|
|
1030
1046
|
if (isAutoConsolidationSession(ctx?.sessionKey))
|
|
1031
1047
|
return;
|
package/index.ts
CHANGED
|
@@ -314,7 +314,17 @@ const WORKSPACE_FILES = [
|
|
|
314
314
|
"TOOLS.md", "AGENTS.md", "SOUL.md", "BOOTSTRAP.md",
|
|
315
315
|
];
|
|
316
316
|
|
|
317
|
+
/** Explicit host agent id wins; older hosts fall back to workspace basename. */
|
|
318
|
+
export function isReadOnlyAgent(readOnlyAgents: string[] | undefined, ctx: any): boolean {
|
|
319
|
+
const explicit = typeof ctx?.agentId === "string" ? ctx.agentId.trim() : "";
|
|
320
|
+
const dir = typeof ctx?.workspaceDir === "string"
|
|
321
|
+
? path.basename(ctx.workspaceDir.replace(/[\\/]+$/, "")) : "";
|
|
322
|
+
const agentId = explicit || (dir === "workspace" ? "main" : dir.replace(/^workspace-/, ""));
|
|
323
|
+
return !!agentId && (readOnlyAgents ?? []).includes(agentId);
|
|
324
|
+
}
|
|
325
|
+
|
|
317
326
|
type MemoryRouterConfig = {
|
|
327
|
+
readOnlyAgents?: string[];
|
|
318
328
|
key: string;
|
|
319
329
|
endpoint?: string;
|
|
320
330
|
/** xhigh is accepted only for backward compatibility and normalizes to high. */
|
|
@@ -632,6 +642,7 @@ const memoryRouterPlugin = {
|
|
|
632
642
|
const logging = cfg?.logging ?? false;
|
|
633
643
|
const embeddings = cfg?.embeddings; // undefined = bge (default), "qwen" = qwen3-8b
|
|
634
644
|
const agentKeys = cfg?.agentKeys;
|
|
645
|
+
const readOnlyAgents = cfg?.readOnlyAgents;
|
|
635
646
|
const autoConsolidate = cfg?.autoConsolidate !== false; // default true
|
|
636
647
|
const log = (msg: string) => { if (logging) api.logger.info?.(msg); };
|
|
637
648
|
|
|
@@ -640,6 +651,7 @@ const memoryRouterPlugin = {
|
|
|
640
651
|
// Fire-and-forget; all rails (lock, cooldown, 3-batch bound) live in
|
|
641
652
|
// auto-consolidate.ts. Failures never propagate into the hook path.
|
|
642
653
|
function triggerAutoConsolidation(reflection: ReflectionDebt | undefined, ctx: any, activeKey: string): void {
|
|
654
|
+
if (isReadOnlyAgent(readOnlyAgents, ctx)) return;
|
|
643
655
|
try {
|
|
644
656
|
maybeTriggerAutoConsolidate(reflection ?? null, {
|
|
645
657
|
autoConsolidate,
|
|
@@ -946,9 +958,9 @@ If setting up MemoryRouter for another agent:
|
|
|
946
958
|
if (data.context) {
|
|
947
959
|
const elapsed = Date.now() - startMs;
|
|
948
960
|
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms) [llm_input]`);
|
|
949
|
-
if (
|
|
961
|
+
if (data.metrics) log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
|
|
950
962
|
let contextText = data.context;
|
|
951
|
-
const nudge = buildDebtNudgeLine(data.reflection);
|
|
963
|
+
const nudge = isReadOnlyAgent(readOnlyAgents, ctx) ? null : buildDebtNudgeLine(data.reflection);
|
|
952
964
|
if (nudge) contextText = `${contextText}\n\n${nudge}`;
|
|
953
965
|
const importNudge = buildImportNudgeLine(data.import);
|
|
954
966
|
if (importNudge) contextText = `${contextText}\n\n${importNudge}`;
|
|
@@ -1072,10 +1084,10 @@ If setting up MemoryRouter for another agent:
|
|
|
1072
1084
|
|
|
1073
1085
|
if (data.context) {
|
|
1074
1086
|
const elapsed = Date.now() - startMs;
|
|
1075
|
-
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms)`);
|
|
1076
|
-
if (
|
|
1087
|
+
api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms) agent=${ctx?.agentId || "unknown"} session=${ctx?.sessionKey || "unknown"}`);
|
|
1088
|
+
if (data.metrics) log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
|
|
1077
1089
|
let contextText = data.context;
|
|
1078
|
-
const nudge = buildDebtNudgeLine(data.reflection);
|
|
1090
|
+
const nudge = isReadOnlyAgent(readOnlyAgents, ctx) ? null : buildDebtNudgeLine(data.reflection);
|
|
1079
1091
|
if (nudge) contextText = `${contextText}\n\n${nudge}`;
|
|
1080
1092
|
const importNudge = buildImportNudgeLine(data.import);
|
|
1081
1093
|
if (importNudge) contextText = `${contextText}\n\n${importNudge}`;
|
|
@@ -1104,6 +1116,10 @@ If setting up MemoryRouter for another agent:
|
|
|
1104
1116
|
// ==================================================================
|
|
1105
1117
|
|
|
1106
1118
|
api.on("agent_end", async (event, ctx) => {
|
|
1119
|
+
if (isReadOnlyAgent(readOnlyAgents, ctx)) {
|
|
1120
|
+
api.logger.info?.(`memoryrouter: ingest skipped read-only agent=${ctx?.agentId || path.basename(ctx?.workspaceDir || "")} session=${ctx?.sessionKey || "unknown"}`);
|
|
1121
|
+
return;
|
|
1122
|
+
}
|
|
1107
1123
|
// Never store the auto-consolidation session's own chatter as memory.
|
|
1108
1124
|
if (isAutoConsolidationSession(ctx?.sessionKey)) return;
|
|
1109
1125
|
const activeKey = resolveKey(ctx);
|
package/memory-instructions.js
CHANGED
|
@@ -13,7 +13,7 @@ export const MEMORY_INSTRUCTIONS = "MemoryRouter is the user’s external, persi
|
|
|
13
13
|
*/
|
|
14
14
|
export const OPENCLAW_MAPPING = [
|
|
15
15
|
"How this maps to OpenClaw: before each prompt the MemoryRouter plugin already runs the RETRIEVE search and adds the results as <memory_context>, so that search already ran; call memory_search only for more detail (tiers and importance are its filters), memory_inspect for the sources behind a reflection, and memory_date_search for time windows.",
|
|
16
|
-
"After each turn the plugin saves the user's message and your final reply automatically. There is no separate save tool, so useful details
|
|
16
|
+
"After each turn the plugin saves the user's message and your final reply automatically. There is no separate save tool, so the STORE rule is met by stating useful details plainly in your reply: when the user makes a decision, states a preference, gives a fact about a person, project, or tool, sets a plan or commitment, makes a correction, or reports an outcome, restate it with the exact wording, numbers, and identifiers so the saved turn records it. To supersede outdated information, state the correction and what it replaces.",
|
|
17
17
|
"If the user asks what was searched or saved, answer truthfully.",
|
|
18
18
|
].join(" ");
|
|
19
19
|
export const OPENCLAW_MEMORY_INSTRUCTIONS = `MemoryRouter instructions\n${MEMORY_INSTRUCTIONS}\n\n${OPENCLAW_MAPPING}`;
|
package/memory-instructions.ts
CHANGED
|
@@ -14,7 +14,7 @@ export const MEMORY_INSTRUCTIONS = "MemoryRouter is the user’s external, persi
|
|
|
14
14
|
*/
|
|
15
15
|
export const OPENCLAW_MAPPING = [
|
|
16
16
|
"How this maps to OpenClaw: before each prompt the MemoryRouter plugin already runs the RETRIEVE search and adds the results as <memory_context>, so that search already ran; call memory_search only for more detail (tiers and importance are its filters), memory_inspect for the sources behind a reflection, and memory_date_search for time windows.",
|
|
17
|
-
"After each turn the plugin saves the user's message and your final reply automatically. There is no separate save tool, so useful details
|
|
17
|
+
"After each turn the plugin saves the user's message and your final reply automatically. There is no separate save tool, so the STORE rule is met by stating useful details plainly in your reply: when the user makes a decision, states a preference, gives a fact about a person, project, or tool, sets a plan or commitment, makes a correction, or reports an outcome, restate it with the exact wording, numbers, and identifiers so the saved turn records it. To supersede outdated information, state the correction and what it replaces.",
|
|
18
18
|
"If the user asks what was searched or saved, answer truthfully.",
|
|
19
19
|
].join(" ");
|
|
20
20
|
|
package/openclaw.plugin.json
CHANGED
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"id": "mr-memory",
|
|
3
3
|
"name": "MemoryRouter Memory",
|
|
4
4
|
"description": "Persistent memory for OpenClaw, powered by MemoryRouter. Memory that survives sessions, compaction, and model changes.",
|
|
5
|
-
"version": "3.
|
|
5
|
+
"version": "3.16.0",
|
|
6
6
|
"activation": {
|
|
7
7
|
"onStartup": true,
|
|
8
8
|
"onCommands": [
|
|
@@ -92,6 +92,13 @@
|
|
|
92
92
|
"pattern": "^mk[_-]"
|
|
93
93
|
},
|
|
94
94
|
"description": "Optional per-agent MemoryRouter keys, keyed by OpenClaw agent id"
|
|
95
|
+
},
|
|
96
|
+
"readOnlyAgents": {
|
|
97
|
+
"type": "array",
|
|
98
|
+
"items": {
|
|
99
|
+
"type": "string"
|
|
100
|
+
},
|
|
101
|
+
"description": "Agent IDs that receive memory retrieval but skip automatic ingest and automatic consolidation. Explicit tool writes must be separately restricted."
|
|
95
102
|
}
|
|
96
103
|
},
|
|
97
104
|
"required": []
|
package/package.json
CHANGED