mr-memory 3.11.0 → 3.12.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -9,10 +9,10 @@ Use it when you want OpenClaw memory that does not restart from zero every sessi
9
9
  ## Install
10
10
 
11
11
  ```bash
12
- openclaw plugins install npm:mr-memory@3.8.0
12
+ openclaw plugins install npm:mr-memory@3.12.0
13
13
  ```
14
14
 
15
- The explicit `npm:` source and version make the audited install deterministic. `mr-memory@3.8.0` is published on npm. A ClawHub listing has not been verified and is not required for this path.
15
+ The explicit `npm:` source and version make the audited install deterministic. `mr-memory@3.12.0` is published on npm. A ClawHub listing has not been verified and is not required for this path.
16
16
 
17
17
  ## Setup
18
18
 
@@ -53,6 +53,16 @@ openclaw mr import --dismiss # permanently silence the import nudge
53
53
 
54
54
  Guarantees: the extract is validated locally (four fields per record, `openclaw:` prefixed ids, epoch-ms timestamps); the CLI shows record count, date range, estimated tokens, and the server price quote and requires explicit approval before any write; the import id is content-derived and every batch carries an Idempotency-Key, so re-running after any interruption replays committed batches for free and resumes. Duplicates are never re-billed. The memory key never appears in the prompt or the extract file. When a vault is new, injected context includes a one-line import suggestion until an import completes or you `--dismiss`.
55
55
 
56
+ ## Agent Tools
57
+
58
+ The plugin registers these tools for the agent:
59
+
60
+ - `memory_search`: semantic search across the vault. Supports `tiers` and `importance` filters; only reflections (tiers 2 and 3) carry importance ratings.
61
+ - `memory_date_search`: time-window retrieval for questions like "what happened last week" or "most important things this month". Takes concrete ISO `from`/`to` dates, optional `query` for in-window relevance ranking, plus `tiers` and `importance` filters.
62
+ - `memory_get`: safe snippet read from local memory files.
63
+ - `memory_reflect` / `memory_commit`: consolidate raw memories into reflections (checkout and commit pair).
64
+ - `memory_inspect`: read-only consolidation lineage for a memory id.
65
+
56
66
  ## How It Works
57
67
 
58
68
  When enabled, relay hooks retrieve memory before agent/model input and capture the direct user-to-assistant conversation at `agent_end`. Inference and provider API keys remain inside OpenClaw; the plugin does not route provider calls through MemoryRouter and is not a provider-key proxy.
@@ -212,6 +212,10 @@ export async function runAutoConsolidate(options) {
212
212
  sessionKey,
213
213
  message: consolidationMessage(tier),
214
214
  deliver: false,
215
+ // The gateway's `agent` method requires idempotencyKey on some
216
+ // deployments (and auto-generates it on others); always send a
217
+ // fresh one per run attempt so both validate.
218
+ idempotencyKey: randomUUID(),
215
219
  });
216
220
  const waited = await options.runtime.waitForRun({ runId, timeoutMs: RUN_TIMEOUT_MS });
217
221
  status = waited.status;
@@ -49,7 +49,7 @@ export type ReflectionDebtBlock = {
49
49
  };
50
50
 
51
51
  type SubagentRuntime = {
52
- run: (params: { sessionKey: string; message: string; deliver?: boolean; lane?: string }) => Promise<{ runId: string }>;
52
+ run: (params: { sessionKey: string; message: string; deliver?: boolean; lane?: string; idempotencyKey?: string }) => Promise<{ runId: string }>;
53
53
  waitForRun: (params: { runId: string; timeoutMs?: number }) => Promise<{ status: string; error?: string }>;
54
54
  deleteSession: (params: { sessionKey: string; deleteTranscript?: boolean }) => Promise<void>;
55
55
  };
@@ -215,6 +215,10 @@ export async function runAutoConsolidate(options: AutoConsolidateOptions): Promi
215
215
  sessionKey,
216
216
  message: consolidationMessage(tier),
217
217
  deliver: false,
218
+ // The gateway's `agent` method requires idempotencyKey on some
219
+ // deployments (and auto-generates it on others); always send a
220
+ // fresh one per run attempt so both validate.
221
+ idempotencyKey: randomUUID(),
218
222
  });
219
223
  const waited = await options.runtime.waitForRun({ runId, timeoutMs: RUN_TIMEOUT_MS });
220
224
  status = waited.status;
package/index.js CHANGED
@@ -1016,7 +1016,7 @@ If setting up MemoryRouter for another agent:
1016
1016
  return {
1017
1017
  label: "Memory Search",
1018
1018
  name: "memory_search",
1019
- description: "Search memories from past conversations and your workspace. Use when relevant.",
1019
+ description: "Search memories from past conversations and your workspace by meaning. Use when relevant. For questions like 'what are the most important things' set importance to 7 or higher with tiers [2, 3], since only reflections carry importance ratings. For time-window questions like 'what happened last week' or 'lately' use memory_date_search instead.",
1020
1020
  parameters: {
1021
1021
  type: "object",
1022
1022
  properties: {
@@ -1026,13 +1026,13 @@ If setting up MemoryRouter for another agent:
1026
1026
  tiers: {
1027
1027
  type: "array",
1028
1028
  items: { type: "integer", enum: [1, 2, 3] },
1029
- description: "Restrict results to these memory tiers. 1 raw memories, 2 reflections, 3 high-level reflections. Omit to search all tiers blended.",
1029
+ description: "Restrict results to these memory tiers. 1 raw memories, 2 reflections, 3 high-level reflections. Only tiers 2 and 3 carry importance ratings. Omit to search all tiers blended.",
1030
1030
  },
1031
1031
  importance: {
1032
1032
  type: "integer",
1033
1033
  minimum: 1,
1034
1034
  maximum: 10,
1035
- description: "Only return memories rated at least this important (1 to 10). Only reflections carry ratings, so this filters to tier 2 and 3 content.",
1035
+ description: "Minimum importance threshold, 1 to 10. Only reflections (tiers 2 and 3) carry ratings, so raw memories are excluded whenever this is set. For 'most important' questions use 7 or higher with tiers [2, 3].",
1036
1036
  },
1037
1037
  },
1038
1038
  required: ["query"],
@@ -1088,6 +1088,104 @@ If setting up MemoryRouter for another agent:
1088
1088
  },
1089
1089
  };
1090
1090
  });
1091
+ // memory_date_search: calls MR /v1/memory/date-search (time-window retrieval)
1092
+ api.registerTool((ctx) => {
1093
+ const toolKey = resolveKey(ctx);
1094
+ return {
1095
+ label: "Memory Date Search",
1096
+ name: "memory_date_search",
1097
+ description: "Retrieve memories from a specific time window. Use for questions like 'what happened last week' or 'most important things this month or lately'. For importance questions set importance to 7 or higher with tiers [2, 3], since only reflections carry importance ratings. Omit query for a chronological review; include query to rank by relevance within the window. Resolve relative phrases like 'lately' to concrete ISO dates before calling.",
1098
+ parameters: {
1099
+ type: "object",
1100
+ properties: {
1101
+ from: {
1102
+ type: "string",
1103
+ description: "Window start, ISO 8601 date or datetime (e.g. 2026-08-10 or 2026-08-10T00:00:00Z). Required. Must be a concrete date, not a relative phrase.",
1104
+ },
1105
+ to: {
1106
+ type: "string",
1107
+ description: "Window end, ISO 8601 date or datetime. Optional, defaults to now.",
1108
+ },
1109
+ query: {
1110
+ type: "string",
1111
+ description: "Optional relevance query. Omit for a pure chronological browse; include to rank results within the window.",
1112
+ },
1113
+ tiers: {
1114
+ type: "array",
1115
+ items: { type: "integer", enum: [1, 2, 3] },
1116
+ description: "Restrict results to these memory tiers. 1 raw memories, 2 reflections, 3 high-level reflections. Only tiers 2 and 3 carry importance ratings. Omit to include all tiers.",
1117
+ },
1118
+ importance: {
1119
+ type: "integer",
1120
+ minimum: 1,
1121
+ maximum: 10,
1122
+ description: "Minimum importance threshold, 1 to 10. Only reflections (tiers 2 and 3) carry ratings, so raw memories are excluded whenever this is set. For 'most important' questions use 7 or higher with tiers [2, 3].",
1123
+ },
1124
+ maxTokens: {
1125
+ type: "integer",
1126
+ minimum: 1000,
1127
+ maximum: 200000,
1128
+ description: "Token budget for returned results. Server default 50000; use a smaller value like 5000 for a quick look.",
1129
+ },
1130
+ },
1131
+ required: ["from"],
1132
+ },
1133
+ execute: async (_toolCallId, params) => {
1134
+ if (!toolKey)
1135
+ return jsonToolResult({ results: [], error: "No memory key configured for this agent" });
1136
+ const from = typeof params.from === "string" ? params.from.trim() : "";
1137
+ if (!from)
1138
+ return jsonToolResult({ results: [], error: "from required (ISO 8601 date or datetime)" });
1139
+ const to = typeof params.to === "string" && params.to.trim() ? params.to.trim() : undefined;
1140
+ const query = typeof params.query === "string" && params.query.trim() ? params.query.trim() : undefined;
1141
+ const tiers = Array.isArray(params.tiers)
1142
+ ? params.tiers.filter((t) => typeof t === "number" && [1, 2, 3].includes(t))
1143
+ : undefined;
1144
+ const importance = typeof params.importance === "number" && params.importance >= 1 && params.importance <= 10
1145
+ ? Math.floor(params.importance)
1146
+ : undefined;
1147
+ const maxTokens = typeof params.maxTokens === "number" && Number.isFinite(params.maxTokens)
1148
+ ? Math.max(1000, Math.min(200000, Math.floor(params.maxTokens)))
1149
+ : undefined;
1150
+ try {
1151
+ const res = await dateSearch(endpoint, toolKey, {
1152
+ from,
1153
+ ...(to !== undefined && { to }),
1154
+ ...(query !== undefined && { query }),
1155
+ ...(tiers && tiers.length > 0 && { tiers }),
1156
+ ...(importance !== undefined && { importance }),
1157
+ ...(maxTokens !== undefined && { max_tokens: maxTokens }),
1158
+ ...(embeddings && { embeddings }),
1159
+ });
1160
+ if (!res.ok) {
1161
+ const errBody = await res.text().catch(() => "");
1162
+ return jsonToolResult({ results: [], error: `Date search failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
1163
+ }
1164
+ const data = await res.json();
1165
+ const results = (data.results || []).map((m) => ({
1166
+ snippet: m.content,
1167
+ date: m.date,
1168
+ role: m.role,
1169
+ tier: m.tier,
1170
+ ...(m.score !== undefined && { score: m.score }),
1171
+ ...(m.importance !== undefined && m.importance !== null && { importance: m.importance }),
1172
+ }));
1173
+ return jsonToolResult({
1174
+ results,
1175
+ totalFound: results.length,
1176
+ window: data.returned ?? null,
1177
+ truncated: data.truncated ?? false,
1178
+ ...(data.next_to !== undefined && { next_to: data.next_to }),
1179
+ ...(data.next_from !== undefined && { next_from: data.next_from }),
1180
+ provider: "memoryrouter",
1181
+ });
1182
+ }
1183
+ catch (err) {
1184
+ return jsonToolResult({ results: [], error: err instanceof Error ? err.message : String(err) });
1185
+ }
1186
+ },
1187
+ };
1188
+ });
1091
1189
  // ==================================================================
1092
1190
  // Tools: memory_reflect + memory_commit (consolidation, two-tool shape)
1093
1191
  // Mirrors the MCP consolidate_memories / commit_reflections pair.
@@ -1346,7 +1444,7 @@ If setting up MemoryRouter for another agent:
1346
1444
  console.error(` ⚠ Could not disable native memory search: ${err instanceof Error ? err.message : String(err)}`);
1347
1445
  }
1348
1446
  console.log(`✓ MemoryRouter enabled. Key: ${key.slice(0, 6)}...${key.slice(-3)}`);
1349
- console.log(` • memory_search + memory_get tools registered via plugin`);
1447
+ console.log(` • memory_search + memory_date_search + memory_get tools registered via plugin`);
1350
1448
  console.log(`\nRun: openclaw mr upload to upload your memories`);
1351
1449
  }
1352
1450
  catch (err) {
package/index.ts CHANGED
@@ -1095,7 +1095,7 @@ If setting up MemoryRouter for another agent:
1095
1095
  return {
1096
1096
  label: "Memory Search",
1097
1097
  name: "memory_search",
1098
- description: "Search memories from past conversations and your workspace. Use when relevant.",
1098
+ description: "Search memories from past conversations and your workspace by meaning. Use when relevant. For questions like 'what are the most important things' set importance to 7 or higher with tiers [2, 3], since only reflections carry importance ratings. For time-window questions like 'what happened last week' or 'lately' use memory_date_search instead.",
1099
1099
  parameters: {
1100
1100
  type: "object",
1101
1101
  properties: {
@@ -1105,13 +1105,13 @@ If setting up MemoryRouter for another agent:
1105
1105
  tiers: {
1106
1106
  type: "array",
1107
1107
  items: { type: "integer", enum: [1, 2, 3] },
1108
- description: "Restrict results to these memory tiers. 1 raw memories, 2 reflections, 3 high-level reflections. Omit to search all tiers blended.",
1108
+ description: "Restrict results to these memory tiers. 1 raw memories, 2 reflections, 3 high-level reflections. Only tiers 2 and 3 carry importance ratings. Omit to search all tiers blended.",
1109
1109
  },
1110
1110
  importance: {
1111
1111
  type: "integer",
1112
1112
  minimum: 1,
1113
1113
  maximum: 10,
1114
- description: "Only return memories rated at least this important (1 to 10). Only reflections carry ratings, so this filters to tier 2 and 3 content.",
1114
+ description: "Minimum importance threshold, 1 to 10. Only reflections (tiers 2 and 3) carry ratings, so raw memories are excluded whenever this is set. For 'most important' questions use 7 or higher with tiers [2, 3].",
1115
1115
  },
1116
1116
  },
1117
1117
  required: ["query"],
@@ -1169,6 +1169,109 @@ If setting up MemoryRouter for another agent:
1169
1169
  };
1170
1170
  });
1171
1171
 
1172
+ // memory_date_search: calls MR /v1/memory/date-search (time-window retrieval)
1173
+ api.registerTool((ctx) => {
1174
+ const toolKey = resolveKey(ctx);
1175
+ return {
1176
+ label: "Memory Date Search",
1177
+ name: "memory_date_search",
1178
+ description: "Retrieve memories from a specific time window. Use for questions like 'what happened last week' or 'most important things this month or lately'. For importance questions set importance to 7 or higher with tiers [2, 3], since only reflections carry importance ratings. Omit query for a chronological review; include query to rank by relevance within the window. Resolve relative phrases like 'lately' to concrete ISO dates before calling.",
1179
+ parameters: {
1180
+ type: "object",
1181
+ properties: {
1182
+ from: {
1183
+ type: "string",
1184
+ description: "Window start, ISO 8601 date or datetime (e.g. 2026-08-10 or 2026-08-10T00:00:00Z). Required. Must be a concrete date, not a relative phrase.",
1185
+ },
1186
+ to: {
1187
+ type: "string",
1188
+ description: "Window end, ISO 8601 date or datetime. Optional, defaults to now.",
1189
+ },
1190
+ query: {
1191
+ type: "string",
1192
+ description: "Optional relevance query. Omit for a pure chronological browse; include to rank results within the window.",
1193
+ },
1194
+ tiers: {
1195
+ type: "array",
1196
+ items: { type: "integer", enum: [1, 2, 3] },
1197
+ description: "Restrict results to these memory tiers. 1 raw memories, 2 reflections, 3 high-level reflections. Only tiers 2 and 3 carry importance ratings. Omit to include all tiers.",
1198
+ },
1199
+ importance: {
1200
+ type: "integer",
1201
+ minimum: 1,
1202
+ maximum: 10,
1203
+ description: "Minimum importance threshold, 1 to 10. Only reflections (tiers 2 and 3) carry ratings, so raw memories are excluded whenever this is set. For 'most important' questions use 7 or higher with tiers [2, 3].",
1204
+ },
1205
+ maxTokens: {
1206
+ type: "integer",
1207
+ minimum: 1000,
1208
+ maximum: 200000,
1209
+ description: "Token budget for returned results. Server default 50000; use a smaller value like 5000 for a quick look.",
1210
+ },
1211
+ },
1212
+ required: ["from"],
1213
+ } as any,
1214
+ execute: async (_toolCallId: string, params: Record<string, unknown>) => {
1215
+ if (!toolKey) return jsonToolResult({ results: [], error: "No memory key configured for this agent" });
1216
+ const from = typeof params.from === "string" ? params.from.trim() : "";
1217
+ if (!from) return jsonToolResult({ results: [], error: "from required (ISO 8601 date or datetime)" });
1218
+ const to = typeof params.to === "string" && params.to.trim() ? params.to.trim() : undefined;
1219
+ const query = typeof params.query === "string" && params.query.trim() ? params.query.trim() : undefined;
1220
+ const tiers = Array.isArray(params.tiers)
1221
+ ? (params.tiers as unknown[]).filter((t): t is number => typeof t === "number" && [1, 2, 3].includes(t))
1222
+ : undefined;
1223
+ const importance = typeof params.importance === "number" && params.importance >= 1 && params.importance <= 10
1224
+ ? Math.floor(params.importance)
1225
+ : undefined;
1226
+ const maxTokens = typeof params.maxTokens === "number" && Number.isFinite(params.maxTokens)
1227
+ ? Math.max(1000, Math.min(200000, Math.floor(params.maxTokens)))
1228
+ : undefined;
1229
+ try {
1230
+ const res = await dateSearch(endpoint, toolKey, {
1231
+ from,
1232
+ ...(to !== undefined && { to }),
1233
+ ...(query !== undefined && { query }),
1234
+ ...(tiers && tiers.length > 0 && { tiers }),
1235
+ ...(importance !== undefined && { importance }),
1236
+ ...(maxTokens !== undefined && { max_tokens: maxTokens }),
1237
+ ...(embeddings && { embeddings }),
1238
+ });
1239
+ if (!res.ok) {
1240
+ const errBody = await res.text().catch(() => "");
1241
+ return jsonToolResult({ results: [], error: `Date search failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
1242
+ }
1243
+ const data = await res.json() as {
1244
+ results?: Array<{ tier: number; content: string; date: string; role: string; score?: number; importance?: number }>;
1245
+ returned?: { from: string | null; to: string | null };
1246
+ truncated?: boolean;
1247
+ est_tokens_in_window?: number;
1248
+ next_to?: string;
1249
+ next_from?: string;
1250
+ };
1251
+ const results = (data.results || []).map((m) => ({
1252
+ snippet: m.content,
1253
+ date: m.date,
1254
+ role: m.role,
1255
+ tier: m.tier,
1256
+ ...(m.score !== undefined && { score: m.score }),
1257
+ ...(m.importance !== undefined && m.importance !== null && { importance: m.importance }),
1258
+ }));
1259
+ return jsonToolResult({
1260
+ results,
1261
+ totalFound: results.length,
1262
+ window: data.returned ?? null,
1263
+ truncated: data.truncated ?? false,
1264
+ ...(data.next_to !== undefined && { next_to: data.next_to }),
1265
+ ...(data.next_from !== undefined && { next_from: data.next_from }),
1266
+ provider: "memoryrouter",
1267
+ });
1268
+ } catch (err) {
1269
+ return jsonToolResult({ results: [], error: err instanceof Error ? err.message : String(err) });
1270
+ }
1271
+ },
1272
+ };
1273
+ });
1274
+
1172
1275
  // ==================================================================
1173
1276
  // Tools: memory_reflect + memory_commit (consolidation, two-tool shape)
1174
1277
  // Mirrors the MCP consolidate_memories / commit_reflections pair.
@@ -1434,7 +1537,7 @@ If setting up MemoryRouter for another agent:
1434
1537
  console.error(` ⚠ Could not disable native memory search: ${err instanceof Error ? err.message : String(err)}`);
1435
1538
  }
1436
1539
  console.log(`✓ MemoryRouter enabled. Key: ${key.slice(0, 6)}...${key.slice(-3)}`);
1437
- console.log(` • memory_search + memory_get tools registered via plugin`);
1540
+ console.log(` • memory_search + memory_date_search + memory_get tools registered via plugin`);
1438
1541
  console.log(`\nRun: openclaw mr upload to upload your memories`);
1439
1542
  } catch (err) {
1440
1543
  const message = err instanceof Error ? err.message : String(err);
@@ -94,6 +94,8 @@ export async function dateSearch(endpoint, key, payload) {
94
94
  from: payload.from,
95
95
  ...(payload.to !== undefined && { to: payload.to }),
96
96
  ...(payload.query !== undefined && { query: payload.query }),
97
+ ...(payload.tiers !== undefined && { tiers: payload.tiers }),
98
+ ...(payload.importance !== undefined && { importance: payload.importance }),
97
99
  ...(payload.max_tokens !== undefined && { max_tokens: payload.max_tokens }),
98
100
  }),
99
101
  });
@@ -96,7 +96,7 @@ export async function deleteMemoryIds(endpoint: string, key: string, payload: {
96
96
  });
97
97
  }
98
98
 
99
- export async function dateSearch(endpoint: string, key: string, payload: { from: string; to?: string; query?: string; max_tokens?: number; embeddings?: string }): Promise<Response> {
99
+ export async function dateSearch(endpoint: string, key: string, payload: { from: string; to?: string; query?: string; tiers?: number[]; importance?: number; max_tokens?: number; embeddings?: string }): Promise<Response> {
100
100
  return fetch(`${endpoint}/v1/memory/date-search`, {
101
101
  method: "POST",
102
102
  headers: {
@@ -108,6 +108,8 @@ export async function dateSearch(endpoint: string, key: string, payload: { from:
108
108
  from: payload.from,
109
109
  ...(payload.to !== undefined && { to: payload.to }),
110
110
  ...(payload.query !== undefined && { query: payload.query }),
111
+ ...(payload.tiers !== undefined && { tiers: payload.tiers }),
112
+ ...(payload.importance !== undefined && { importance: payload.importance }),
111
113
  ...(payload.max_tokens !== undefined && { max_tokens: payload.max_tokens }),
112
114
  }),
113
115
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-memory",
3
- "version": "3.11.0",
3
+ "version": "3.12.0",
4
4
  "description": "Persistent memory for OpenClaw, powered by MemoryRouter. Relay-based AI agent memory across sessions, compaction, and model changes.",
5
5
  "type": "module",
6
6
  "files": [