mr-memory 3.8.0 → 3.9.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/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, warmupMemory } from "./memoryrouter-api.js";
11
+ import { commitReflections, consolidateCheckout, dateSearch, deleteMemories, deleteMemoryIds, getMemoryStats, ingestMemory, inspectMemory, 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";
@@ -55,6 +55,15 @@ function stripOldMemory(text) {
55
55
  function jsonToolResult(payload) {
56
56
  return { content: [{ type: "text", text: JSON.stringify(payload, null, 2) }], details: payload };
57
57
  }
58
+ function buildDebtNudgeLine(reflection) {
59
+ if (!reflection || !reflection.suggestion)
60
+ return null;
61
+ const rawTokens = reflection.unconsolidated?.raw?.est_tokens ?? 0;
62
+ const tier2Tokens = reflection.unconsolidated?.tier2?.est_tokens ?? 0;
63
+ const totalTokens = rawTokens + tier2Tokens;
64
+ const kTokens = Math.max(1, Math.round(totalTokens / 1000));
65
+ return `[MemoryRouter] Consolidation suggested: ${kTokens}k unconsolidated tokens. Use the memory_reflect tool to consolidate them now.`;
66
+ }
58
67
  // ── memory_get helpers (copied from OpenClaw source — pure filesystem, no embeddings)
59
68
  function isFileMissingError(err) {
60
69
  return Boolean(err && typeof err === "object" && "code" in err && err.code === "ENOENT");
@@ -731,7 +740,11 @@ If setting up MemoryRouter for another agent:
731
740
  api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms) [llm_input]`);
732
741
  if (elapsed > 2000 && data.metrics)
733
742
  log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
734
- const wrapped = wrapForInjection(data.context);
743
+ let contextText = data.context;
744
+ const nudge = buildDebtNudgeLine(data.reflection);
745
+ if (nudge)
746
+ contextText = `${contextText}\n\n${nudge}`;
747
+ const wrapped = wrapForInjection(contextText);
735
748
  return { appendSystemContext: wrapped };
736
749
  }
737
750
  }
@@ -836,7 +849,11 @@ If setting up MemoryRouter for another agent:
836
849
  api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms)`);
837
850
  if (elapsed > 2000 && data.metrics)
838
851
  log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
839
- const wrapped = wrapForInjection(data.context);
852
+ let contextText = data.context;
853
+ const nudge = buildDebtNudgeLine(data.reflection);
854
+ if (nudge)
855
+ contextText = `${contextText}\n\n${nudge}`;
856
+ const wrapped = wrapForInjection(contextText);
840
857
  return { appendSystemContext: wrapped };
841
858
  }
842
859
  }
@@ -963,6 +980,17 @@ If setting up MemoryRouter for another agent:
963
980
  query: { type: "string" },
964
981
  maxResults: { type: "number" },
965
982
  minScore: { type: "number" },
983
+ tiers: {
984
+ type: "array",
985
+ items: { type: "integer", enum: [1, 2, 3] },
986
+ description: "Restrict results to these memory tiers. 1 raw memories, 2 reflections, 3 high-level reflections. Omit to search all tiers blended.",
987
+ },
988
+ importance: {
989
+ type: "integer",
990
+ minimum: 1,
991
+ maximum: 10,
992
+ 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.",
993
+ },
966
994
  },
967
995
  required: ["query"],
968
996
  },
@@ -973,8 +1001,20 @@ If setting up MemoryRouter for another agent:
973
1001
  if (!query)
974
1002
  return jsonToolResult({ results: [], error: "query required" });
975
1003
  const limit = typeof params.maxResults === "number" ? params.maxResults : 50;
1004
+ const tiers = Array.isArray(params.tiers)
1005
+ ? params.tiers.filter((t) => typeof t === "number" && [1, 2, 3].includes(t))
1006
+ : undefined;
1007
+ const importance = typeof params.importance === "number" && params.importance >= 1 && params.importance <= 10
1008
+ ? Math.floor(params.importance)
1009
+ : undefined;
976
1010
  try {
977
- const res = await searchMemory(endpoint, toolKey, { query, limit, embeddings });
1011
+ const res = await searchMemory(endpoint, toolKey, {
1012
+ query,
1013
+ limit,
1014
+ ...(tiers && tiers.length > 0 && { tiers }),
1015
+ ...(importance !== undefined && { importance }),
1016
+ embeddings,
1017
+ });
978
1018
  if (!res.ok) {
979
1019
  const errBody = await res.text().catch(() => "");
980
1020
  return jsonToolResult({ results: [], error: `Search failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
@@ -988,6 +1028,8 @@ If setting up MemoryRouter for another agent:
988
1028
  role: m.role,
989
1029
  window: m.window,
990
1030
  date: m.timestampHuman,
1031
+ ...(m.tier !== undefined && { tier: m.tier }),
1032
+ ...(m.importance !== undefined && m.importance !== null && { importance: m.importance }),
991
1033
  }));
992
1034
  return jsonToolResult({
993
1035
  results,
@@ -1003,6 +1045,203 @@ If setting up MemoryRouter for another agent:
1003
1045
  },
1004
1046
  };
1005
1047
  });
1048
+ // ==================================================================
1049
+ // Tools: memory_reflect + memory_commit (consolidation, two-tool shape)
1050
+ // Mirrors the MCP consolidate_memories / commit_reflections pair.
1051
+ // Invariants: source_ids are stripped, the model never sees row IDs,
1052
+ // and contract instructions are transmitted verbatim.
1053
+ // ==================================================================
1054
+ // memory_reflect: checkout wrapper (nothing is modified until memory_commit)
1055
+ api.registerTool((ctx) => {
1056
+ const toolKey = resolveKey(ctx);
1057
+ return {
1058
+ label: "Memory Reflect",
1059
+ name: "memory_reflect",
1060
+ description: "Consolidate unconsolidated memories into reflections. Use when memory responses indicate consolidation is available, or when the user asks to organize or reflect on memory. Checks out a batch of memories under a 15 minute lease and returns their texts plus server instructions for writing reflection entries. Follow the instructions, then call memory_commit with the batch_id. Nothing is modified until commit.",
1061
+ parameters: {
1062
+ type: "object",
1063
+ properties: {
1064
+ tier: {
1065
+ type: "integer",
1066
+ enum: [1, 2],
1067
+ description: "1 consolidates raw memories into reflections; 2 consolidates reflections into high-level reflections. Default 1.",
1068
+ },
1069
+ max_tokens: {
1070
+ type: "integer",
1071
+ minimum: 1000,
1072
+ description: "Token budget for the checked-out batch. Defaults to 50000. No connector-side cap.",
1073
+ },
1074
+ },
1075
+ required: [],
1076
+ },
1077
+ execute: async (_toolCallId, params) => {
1078
+ if (!toolKey)
1079
+ return jsonToolResult({ error: "No memory key configured for this agent" });
1080
+ const tier = params.tier === 2 ? 2 : 1;
1081
+ const maxTokens = typeof params.max_tokens === "number" && params.max_tokens >= 1000
1082
+ ? Math.floor(params.max_tokens)
1083
+ : 50000;
1084
+ try {
1085
+ const res = await consolidateCheckout(endpoint, toolKey, {
1086
+ tier: tier,
1087
+ max_tokens: maxTokens,
1088
+ ...(embeddings && { embeddings }),
1089
+ });
1090
+ if (!res.ok) {
1091
+ const errBody = await res.text().catch(() => "");
1092
+ return jsonToolResult({ error: `Checkout failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
1093
+ }
1094
+ const data = await res.json();
1095
+ if (!data.batch_id) {
1096
+ return jsonToolResult({ batch_id: null, count: 0, message: "Nothing to consolidate at this tier right now." });
1097
+ }
1098
+ // Contract instructions are server-authored and passed through verbatim.
1099
+ // If neither contract form is present, refuse; never author a local prompt.
1100
+ const instructions = data.reflection_contract?.instructions ?? data.tier_definitions?.instruction;
1101
+ if (!instructions) {
1102
+ return jsonToolResult({ error: "Checkout response carried no reflection contract. Cannot consolidate without server instructions." });
1103
+ }
1104
+ // source_ids stripped: the model never sees memory row IDs.
1105
+ return jsonToolResult({
1106
+ batch_id: data.batch_id,
1107
+ lease_expires_at: data.lease_expires_at ?? null,
1108
+ count: data.count ?? 0,
1109
+ est_tokens: data.est_tokens ?? 0,
1110
+ instructions,
1111
+ memories: (data.memories || []).map((m) => ({ text: m.text, date: m.date })),
1112
+ message: `When your entries are ready, call memory_commit with batch_id ${data.batch_id} before ${data.lease_expires_at ?? "the lease expires"}.`,
1113
+ });
1114
+ }
1115
+ catch (err) {
1116
+ return jsonToolResult({ error: err instanceof Error ? err.message : String(err) });
1117
+ }
1118
+ },
1119
+ };
1120
+ });
1121
+ // memory_commit: commit wrapper (idempotent; 409 means the lease expired)
1122
+ api.registerTool((ctx) => {
1123
+ const toolKey = resolveKey(ctx);
1124
+ return {
1125
+ label: "Memory Commit",
1126
+ name: "memory_commit",
1127
+ description: "Commit reflection entries for a batch checked out with memory_reflect. Each entry is standalone text plus an importance rating from 1 to 10. Safe to retry: committing the same batch twice reports success without duplicating.",
1128
+ parameters: {
1129
+ type: "object",
1130
+ properties: {
1131
+ batch_id: { type: "string", description: "The batch_id returned by memory_reflect." },
1132
+ entries: {
1133
+ type: "array",
1134
+ items: {
1135
+ type: "object",
1136
+ properties: {
1137
+ text: { type: "string", description: "Standalone reflection prose, 1 to 8000 chars. No IDs or scores inline." },
1138
+ importance: { type: "integer", minimum: 1, maximum: 10 },
1139
+ },
1140
+ required: ["text", "importance"],
1141
+ additionalProperties: false,
1142
+ },
1143
+ minItems: 1,
1144
+ maxItems: 100,
1145
+ },
1146
+ },
1147
+ required: ["batch_id", "entries"],
1148
+ },
1149
+ execute: async (_toolCallId, params) => {
1150
+ if (!toolKey)
1151
+ return jsonToolResult({ error: "No memory key configured for this agent" });
1152
+ const batchId = typeof params.batch_id === "string" ? params.batch_id.trim() : "";
1153
+ if (!batchId)
1154
+ return jsonToolResult({ error: "batch_id required" });
1155
+ const rawEntries = Array.isArray(params.entries) ? params.entries : [];
1156
+ if (rawEntries.length < 1 || rawEntries.length > 100) {
1157
+ return jsonToolResult({ error: "entries must contain between 1 and 100 items" });
1158
+ }
1159
+ const entries = [];
1160
+ for (let i = 0; i < rawEntries.length; i++) {
1161
+ const e = rawEntries[i];
1162
+ if (!e || typeof e !== "object")
1163
+ return jsonToolResult({ error: `entry ${i + 1} is not an object` });
1164
+ const extraKeys = Object.keys(e).filter((k) => k !== "text" && k !== "importance");
1165
+ if (extraKeys.length > 0) {
1166
+ // Position-reference impossibility: entries are text plus importance only.
1167
+ return jsonToolResult({ error: `entry ${i + 1} has unsupported fields: ${extraKeys.join(", ")}. Entries are text plus importance only; coverage is always the whole batch.` });
1168
+ }
1169
+ const text = typeof e.text === "string" ? e.text : "";
1170
+ if (!text || text.length > 8000)
1171
+ return jsonToolResult({ error: `entry ${i + 1} text must be 1 to 8000 chars` });
1172
+ const importance = typeof e.importance === "number" && Number.isInteger(e.importance) && e.importance >= 1 && e.importance <= 10
1173
+ ? e.importance
1174
+ : null;
1175
+ if (importance === null)
1176
+ return jsonToolResult({ error: `entry ${i + 1} importance must be an integer 1 to 10` });
1177
+ entries.push({ text, importance });
1178
+ }
1179
+ try {
1180
+ const res = await commitReflections(endpoint, toolKey, {
1181
+ batch_id: batchId,
1182
+ entries,
1183
+ ...(embeddings && { embeddings }),
1184
+ });
1185
+ if (res.status === 409) {
1186
+ return jsonToolResult({ error: "lease expired, call memory_reflect again" });
1187
+ }
1188
+ if (!res.ok) {
1189
+ const errBody = await res.text().catch(() => "");
1190
+ return jsonToolResult({ error: `Commit failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
1191
+ }
1192
+ const data = await res.json();
1193
+ // Created UUIDs dropped: the model never sees memory row IDs.
1194
+ return jsonToolResult({
1195
+ committed: data.committed ?? false,
1196
+ already_committed: data.already_committed ?? false,
1197
+ entries_created: data.created?.length ?? 0,
1198
+ sources_consolidated: data.sources_consolidated ?? 0,
1199
+ debt: data.reflection?.suggestion ?? null,
1200
+ });
1201
+ }
1202
+ catch (err) {
1203
+ return jsonToolResult({ error: err instanceof Error ? err.message : String(err) });
1204
+ }
1205
+ },
1206
+ };
1207
+ });
1208
+ // memory_inspect: read-only lineage drill-down (curation surface, ids allowed)
1209
+ api.registerTool((ctx) => {
1210
+ const toolKey = resolveKey(ctx);
1211
+ return {
1212
+ label: "Memory Inspect",
1213
+ name: "memory_inspect",
1214
+ description: "Inspect a memory's consolidation lineage by memory_id: its tier, the batch it came from, the sources it consolidated, and what it was consolidated into. Read-only.",
1215
+ parameters: {
1216
+ type: "object",
1217
+ properties: {
1218
+ memory_id: { type: "string", description: "The memory id to inspect." },
1219
+ },
1220
+ required: ["memory_id"],
1221
+ },
1222
+ execute: async (_toolCallId, params) => {
1223
+ if (!toolKey)
1224
+ return jsonToolResult({ error: "No memory key configured for this agent" });
1225
+ const memoryId = typeof params.memory_id === "string" ? params.memory_id.trim() : "";
1226
+ if (!memoryId)
1227
+ return jsonToolResult({ error: "memory_id required" });
1228
+ try {
1229
+ const res = await inspectMemory(endpoint, toolKey, {
1230
+ memory_id: memoryId,
1231
+ ...(embeddings && { embeddings }),
1232
+ });
1233
+ if (!res.ok) {
1234
+ const errBody = await res.text().catch(() => "");
1235
+ return jsonToolResult({ error: `Inspect failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
1236
+ }
1237
+ return jsonToolResult(await res.json());
1238
+ }
1239
+ catch (err) {
1240
+ return jsonToolResult({ error: err instanceof Error ? err.message : String(err) });
1241
+ }
1242
+ },
1243
+ };
1244
+ });
1006
1245
  // memory_get — exact copy of OpenClaw's implementation (pure local file read)
1007
1246
  api.registerTool((ctx) => {
1008
1247
  const workspaceDir = ctx.workspaceDir || "";
@@ -1278,7 +1517,9 @@ If setting up MemoryRouter for another agent:
1278
1517
  }
1279
1518
  });
1280
1519
  mr.command("delete")
1281
- .description("Clear all memories from vault")
1520
+ .description("Clear all memories from vault, or delete specific memories with --ids")
1521
+ .option("--ids <ids>", "Comma-separated memory ids to delete (instead of the whole vault)")
1522
+ .option("--yes", "Confirm deletion (required with --ids)")
1282
1523
  .option("--key <key>", "Override memory key (for subagent vaults)")
1283
1524
  .action(async (opts) => {
1284
1525
  const effectiveKey = opts.key || memoryKey;
@@ -1286,6 +1527,34 @@ If setting up MemoryRouter for another agent:
1286
1527
  console.error("Not configured. Run: openclaw mr <key> or pass --key <key>");
1287
1528
  return;
1288
1529
  }
1530
+ if (opts.ids) {
1531
+ const ids = opts.ids.split(",").map((s) => s.trim()).filter(Boolean);
1532
+ if (ids.length === 0) {
1533
+ console.error("No ids provided.");
1534
+ return;
1535
+ }
1536
+ console.log(`The following ${ids.length} memor${ids.length === 1 ? "y" : "ies"} will be PERMANENTLY deleted:`);
1537
+ for (const id of ids)
1538
+ console.log(` • ${id}`);
1539
+ if (!opts.yes) {
1540
+ console.error("\nRefusing to delete without --yes. Re-run with --yes to confirm.");
1541
+ return;
1542
+ }
1543
+ try {
1544
+ const res = await deleteMemoryIds(endpoint, effectiveKey, { ids, ...(embeddings && { embeddings }) });
1545
+ if (!res.ok) {
1546
+ const errBody = await res.text().catch(() => "");
1547
+ console.error(`Delete failed: HTTP ${res.status}${errBody ? `: ${errBody.slice(0, 200)}` : ""}`);
1548
+ return;
1549
+ }
1550
+ const data = (await res.json());
1551
+ console.log(`✓ Deleted ${data.deleted ?? 0} of ${data.requested ?? ids.length} (not found: ${data.not_found ?? 0})`);
1552
+ }
1553
+ catch (err) {
1554
+ console.error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
1555
+ }
1556
+ return;
1557
+ }
1289
1558
  try {
1290
1559
  const res = await deleteMemories(endpoint, effectiveKey, embeddings);
1291
1560
  const data = (await res.json());
@@ -1296,6 +1565,46 @@ If setting up MemoryRouter for another agent:
1296
1565
  console.error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
1297
1566
  }
1298
1567
  });
1568
+ mr.command("reflect")
1569
+ .description("Show reflection debt status (consolidation runs via the agent's memory_reflect tool)")
1570
+ .option("--key <key>", "Override memory key (for subagent vaults)")
1571
+ .action(async (opts) => {
1572
+ const effectiveKey = opts.key || memoryKey;
1573
+ if (!effectiveKey) {
1574
+ console.error("Not configured. Run: openclaw mr <key> or pass --key <key>");
1575
+ return;
1576
+ }
1577
+ try {
1578
+ // Debt probe: an empty-window date-search returns the reflection
1579
+ // debt block with zero results and zero retrieval billing.
1580
+ const from = new Date(Date.now() - 1000).toISOString();
1581
+ const res = await dateSearch(endpoint, effectiveKey, { from, max_tokens: 1000, ...(embeddings && { embeddings }) });
1582
+ if (!res.ok) {
1583
+ const errBody = await res.text().catch(() => "");
1584
+ console.error(`Debt check failed: HTTP ${res.status}${errBody ? `: ${errBody.slice(0, 200)}` : ""}`);
1585
+ return;
1586
+ }
1587
+ const data = (await res.json());
1588
+ const debt = data.reflection;
1589
+ console.log("Reflection Debt");
1590
+ console.log("───────────────────────────");
1591
+ const raw = debt?.unconsolidated?.raw;
1592
+ const tier2 = debt?.unconsolidated?.tier2;
1593
+ console.log(`Raw unconsolidated: ${raw?.units ?? 0} memories (~${raw?.est_tokens ?? 0} tokens)`);
1594
+ console.log(`Tier 2 unconsolidated: ${tier2?.count ?? 0} reflections (~${tier2?.est_tokens ?? 0} tokens)`);
1595
+ console.log(`Threshold: ${debt?.threshold_tokens ?? 0} tokens`);
1596
+ if (debt?.suggestion) {
1597
+ console.log(`\nStatus: ${debt.suggestion}`);
1598
+ console.log("Consolidation runs through the agent. Ask your agent to consolidate memories (it uses the memory_reflect and memory_commit tools).");
1599
+ }
1600
+ else {
1601
+ console.log("\nStatus: nothing to consolidate right now.");
1602
+ }
1603
+ }
1604
+ catch (err) {
1605
+ console.error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
1606
+ }
1607
+ });
1299
1608
  }, { commands: ["mr"] });
1300
1609
  // ==================================================================
1301
1610
  // Service
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, warmupMemory } from "./memoryrouter-api.js";
12
+ import { commitReflections, consolidateCheckout, dateSearch, deleteMemories, deleteMemoryIds, getMemoryStats, ingestMemory, inspectMemory, 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";
@@ -65,6 +65,25 @@ function jsonToolResult(payload: unknown) {
65
65
  return { content: [{ type: "text" as const, text: JSON.stringify(payload, null, 2) }], details: payload };
66
66
  }
67
67
 
68
+ // ── Reflection debt nudge (one line, appended to prepare injections whenever the server suggests it)
69
+ type ReflectionDebt = {
70
+ suggestion?: string | null;
71
+ unconsolidated?: {
72
+ raw?: { units?: number; est_tokens?: number };
73
+ tier2?: { count?: number; importance?: number; est_tokens?: number };
74
+ };
75
+ threshold_tokens?: number;
76
+ };
77
+
78
+ function buildDebtNudgeLine(reflection: ReflectionDebt | undefined): string | null {
79
+ if (!reflection || !reflection.suggestion) return null;
80
+ const rawTokens = reflection.unconsolidated?.raw?.est_tokens ?? 0;
81
+ const tier2Tokens = reflection.unconsolidated?.tier2?.est_tokens ?? 0;
82
+ const totalTokens = rawTokens + tier2Tokens;
83
+ const kTokens = Math.max(1, Math.round(totalTokens / 1000));
84
+ return `[MemoryRouter] Consolidation suggested: ${kTokens}k unconsolidated tokens. Use the memory_reflect tool to consolidate them now.`;
85
+ }
86
+
68
87
  // ── memory_get helpers (copied from OpenClaw source — pure filesystem, no embeddings)
69
88
  function isFileMissingError(err: unknown): boolean {
70
89
  return Boolean(err && typeof err === "object" && "code" in err && (err as any).code === "ENOENT");
@@ -768,13 +787,17 @@ If setting up MemoryRouter for another agent:
768
787
  tokens_billed?: number;
769
788
  memory_tokens?: number;
770
789
  metrics?: Record<string, unknown>;
790
+ reflection?: ReflectionDebt;
771
791
  };
772
792
 
773
793
  if (data.context) {
774
794
  const elapsed = Date.now() - startMs;
775
795
  api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms) [llm_input]`);
776
796
  if (elapsed > 2000 && data.metrics) log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
777
- const wrapped = wrapForInjection(data.context);
797
+ let contextText = data.context;
798
+ const nudge = buildDebtNudgeLine(data.reflection);
799
+ if (nudge) contextText = `${contextText}\n\n${nudge}`;
800
+ const wrapped = wrapForInjection(contextText);
778
801
  return { appendSystemContext: wrapped };
779
802
  }
780
803
  } catch {
@@ -885,13 +908,17 @@ If setting up MemoryRouter for another agent:
885
908
  tokens_billed?: number;
886
909
  memory_tokens?: number;
887
910
  metrics?: Record<string, unknown>;
911
+ reflection?: ReflectionDebt;
888
912
  };
889
913
 
890
914
  if (data.context) {
891
915
  const elapsed = Date.now() - startMs;
892
916
  api.logger.info?.(`memoryrouter: injected ${data.memories_found || 0} memories (${data.memory_tokens || 0} tokens, ${elapsed}ms)`);
893
917
  if (elapsed > 2000 && data.metrics) log(`memoryrouter: prepare metrics ${JSON.stringify(data.metrics)}`);
894
- const wrapped = wrapForInjection(data.context);
918
+ let contextText = data.context;
919
+ const nudge = buildDebtNudgeLine(data.reflection);
920
+ if (nudge) contextText = `${contextText}\n\n${nudge}`;
921
+ const wrapped = wrapForInjection(contextText);
895
922
  return { appendSystemContext: wrapped };
896
923
  }
897
924
  } catch (err) {
@@ -1025,6 +1052,17 @@ If setting up MemoryRouter for another agent:
1025
1052
  query: { type: "string" },
1026
1053
  maxResults: { type: "number" },
1027
1054
  minScore: { type: "number" },
1055
+ tiers: {
1056
+ type: "array",
1057
+ items: { type: "integer", enum: [1, 2, 3] },
1058
+ description: "Restrict results to these memory tiers. 1 raw memories, 2 reflections, 3 high-level reflections. Omit to search all tiers blended.",
1059
+ },
1060
+ importance: {
1061
+ type: "integer",
1062
+ minimum: 1,
1063
+ maximum: 10,
1064
+ 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.",
1065
+ },
1028
1066
  },
1029
1067
  required: ["query"],
1030
1068
  } as any,
@@ -1033,14 +1071,26 @@ If setting up MemoryRouter for another agent:
1033
1071
  const query = typeof params.query === "string" ? params.query.trim() : "";
1034
1072
  if (!query) return jsonToolResult({ results: [], error: "query required" });
1035
1073
  const limit = typeof params.maxResults === "number" ? params.maxResults : 50;
1074
+ const tiers = Array.isArray(params.tiers)
1075
+ ? (params.tiers as unknown[]).filter((t): t is number => typeof t === "number" && [1, 2, 3].includes(t))
1076
+ : undefined;
1077
+ const importance = typeof params.importance === "number" && params.importance >= 1 && params.importance <= 10
1078
+ ? Math.floor(params.importance)
1079
+ : undefined;
1036
1080
  try {
1037
- const res = await searchMemory(endpoint, toolKey, { query, limit, embeddings });
1081
+ const res = await searchMemory(endpoint, toolKey, {
1082
+ query,
1083
+ limit,
1084
+ ...(tiers && tiers.length > 0 && { tiers }),
1085
+ ...(importance !== undefined && { importance }),
1086
+ embeddings,
1087
+ });
1038
1088
  if (!res.ok) {
1039
1089
  const errBody = await res.text().catch(() => "");
1040
1090
  return jsonToolResult({ results: [], error: `Search failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
1041
1091
  }
1042
1092
  const data = await res.json() as {
1043
- memories?: Array<{ content: string; score: number; role: string; window: string; timestampHuman: string }>;
1093
+ memories?: Array<{ content: string; score: number; role: string; window: string; timestampHuman: string; tier?: number; importance?: number }>;
1044
1094
  totalMemories?: number;
1045
1095
  tokenCount?: number;
1046
1096
  };
@@ -1052,6 +1102,8 @@ If setting up MemoryRouter for another agent:
1052
1102
  role: m.role,
1053
1103
  window: m.window,
1054
1104
  date: m.timestampHuman,
1105
+ ...(m.tier !== undefined && { tier: m.tier }),
1106
+ ...(m.importance !== undefined && m.importance !== null && { importance: m.importance }),
1055
1107
  }));
1056
1108
  return jsonToolResult({
1057
1109
  results,
@@ -1067,6 +1119,211 @@ If setting up MemoryRouter for another agent:
1067
1119
  };
1068
1120
  });
1069
1121
 
1122
+ // ==================================================================
1123
+ // Tools: memory_reflect + memory_commit (consolidation, two-tool shape)
1124
+ // Mirrors the MCP consolidate_memories / commit_reflections pair.
1125
+ // Invariants: source_ids are stripped, the model never sees row IDs,
1126
+ // and contract instructions are transmitted verbatim.
1127
+ // ==================================================================
1128
+
1129
+ // memory_reflect: checkout wrapper (nothing is modified until memory_commit)
1130
+ api.registerTool((ctx) => {
1131
+ const toolKey = resolveKey(ctx);
1132
+ return {
1133
+ label: "Memory Reflect",
1134
+ name: "memory_reflect",
1135
+ description: "Consolidate unconsolidated memories into reflections. Use when memory responses indicate consolidation is available, or when the user asks to organize or reflect on memory. Checks out a batch of memories under a 15 minute lease and returns their texts plus server instructions for writing reflection entries. Follow the instructions, then call memory_commit with the batch_id. Nothing is modified until commit.",
1136
+ parameters: {
1137
+ type: "object",
1138
+ properties: {
1139
+ tier: {
1140
+ type: "integer",
1141
+ enum: [1, 2],
1142
+ description: "1 consolidates raw memories into reflections; 2 consolidates reflections into high-level reflections. Default 1.",
1143
+ },
1144
+ max_tokens: {
1145
+ type: "integer",
1146
+ minimum: 1000,
1147
+ description: "Token budget for the checked-out batch. Defaults to 50000. No connector-side cap.",
1148
+ },
1149
+ },
1150
+ required: [],
1151
+ } as any,
1152
+ execute: async (_toolCallId: string, params: Record<string, unknown>) => {
1153
+ if (!toolKey) return jsonToolResult({ error: "No memory key configured for this agent" });
1154
+ const tier = params.tier === 2 ? 2 : 1;
1155
+ const maxTokens = typeof params.max_tokens === "number" && params.max_tokens >= 1000
1156
+ ? Math.floor(params.max_tokens)
1157
+ : 50000;
1158
+ try {
1159
+ const res = await consolidateCheckout(endpoint, toolKey, {
1160
+ tier: tier as 1 | 2,
1161
+ max_tokens: maxTokens,
1162
+ ...(embeddings && { embeddings }),
1163
+ });
1164
+ if (!res.ok) {
1165
+ const errBody = await res.text().catch(() => "");
1166
+ return jsonToolResult({ error: `Checkout failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
1167
+ }
1168
+ const data = await res.json() as {
1169
+ batch_id?: string | null;
1170
+ tier?: number;
1171
+ count?: number;
1172
+ est_tokens?: number;
1173
+ lease_expires_at?: string | null;
1174
+ memories?: Array<{ text: string; date: string }>;
1175
+ reflection_contract?: { version?: number; instructions?: string };
1176
+ tier_definitions?: { target_tier?: number; instruction?: string };
1177
+ };
1178
+ if (!data.batch_id) {
1179
+ return jsonToolResult({ batch_id: null, count: 0, message: "Nothing to consolidate at this tier right now." });
1180
+ }
1181
+ // Contract instructions are server-authored and passed through verbatim.
1182
+ // If neither contract form is present, refuse; never author a local prompt.
1183
+ const instructions = data.reflection_contract?.instructions ?? data.tier_definitions?.instruction;
1184
+ if (!instructions) {
1185
+ return jsonToolResult({ error: "Checkout response carried no reflection contract. Cannot consolidate without server instructions." });
1186
+ }
1187
+ // source_ids stripped: the model never sees memory row IDs.
1188
+ return jsonToolResult({
1189
+ batch_id: data.batch_id,
1190
+ lease_expires_at: data.lease_expires_at ?? null,
1191
+ count: data.count ?? 0,
1192
+ est_tokens: data.est_tokens ?? 0,
1193
+ instructions,
1194
+ memories: (data.memories || []).map((m) => ({ text: m.text, date: m.date })),
1195
+ message: `When your entries are ready, call memory_commit with batch_id ${data.batch_id} before ${data.lease_expires_at ?? "the lease expires"}.`,
1196
+ });
1197
+ } catch (err) {
1198
+ return jsonToolResult({ error: err instanceof Error ? err.message : String(err) });
1199
+ }
1200
+ },
1201
+ };
1202
+ });
1203
+
1204
+ // memory_commit: commit wrapper (idempotent; 409 means the lease expired)
1205
+ api.registerTool((ctx) => {
1206
+ const toolKey = resolveKey(ctx);
1207
+ return {
1208
+ label: "Memory Commit",
1209
+ name: "memory_commit",
1210
+ description: "Commit reflection entries for a batch checked out with memory_reflect. Each entry is standalone text plus an importance rating from 1 to 10. Safe to retry: committing the same batch twice reports success without duplicating.",
1211
+ parameters: {
1212
+ type: "object",
1213
+ properties: {
1214
+ batch_id: { type: "string", description: "The batch_id returned by memory_reflect." },
1215
+ entries: {
1216
+ type: "array",
1217
+ items: {
1218
+ type: "object",
1219
+ properties: {
1220
+ text: { type: "string", description: "Standalone reflection prose, 1 to 8000 chars. No IDs or scores inline." },
1221
+ importance: { type: "integer", minimum: 1, maximum: 10 },
1222
+ },
1223
+ required: ["text", "importance"],
1224
+ additionalProperties: false,
1225
+ },
1226
+ minItems: 1,
1227
+ maxItems: 100,
1228
+ },
1229
+ },
1230
+ required: ["batch_id", "entries"],
1231
+ } as any,
1232
+ execute: async (_toolCallId: string, params: Record<string, unknown>) => {
1233
+ if (!toolKey) return jsonToolResult({ error: "No memory key configured for this agent" });
1234
+ const batchId = typeof params.batch_id === "string" ? params.batch_id.trim() : "";
1235
+ if (!batchId) return jsonToolResult({ error: "batch_id required" });
1236
+ const rawEntries = Array.isArray(params.entries) ? params.entries as Array<Record<string, unknown>> : [];
1237
+ if (rawEntries.length < 1 || rawEntries.length > 100) {
1238
+ return jsonToolResult({ error: "entries must contain between 1 and 100 items" });
1239
+ }
1240
+ const entries: Array<{ text: string; importance: number }> = [];
1241
+ for (let i = 0; i < rawEntries.length; i++) {
1242
+ const e = rawEntries[i];
1243
+ if (!e || typeof e !== "object") return jsonToolResult({ error: `entry ${i + 1} is not an object` });
1244
+ const extraKeys = Object.keys(e).filter((k) => k !== "text" && k !== "importance");
1245
+ if (extraKeys.length > 0) {
1246
+ // Position-reference impossibility: entries are text plus importance only.
1247
+ return jsonToolResult({ error: `entry ${i + 1} has unsupported fields: ${extraKeys.join(", ")}. Entries are text plus importance only; coverage is always the whole batch.` });
1248
+ }
1249
+ const text = typeof e.text === "string" ? e.text : "";
1250
+ if (!text || text.length > 8000) return jsonToolResult({ error: `entry ${i + 1} text must be 1 to 8000 chars` });
1251
+ const importance = typeof e.importance === "number" && Number.isInteger(e.importance) && e.importance >= 1 && e.importance <= 10
1252
+ ? e.importance
1253
+ : null;
1254
+ if (importance === null) return jsonToolResult({ error: `entry ${i + 1} importance must be an integer 1 to 10` });
1255
+ entries.push({ text, importance });
1256
+ }
1257
+ try {
1258
+ const res = await commitReflections(endpoint, toolKey, {
1259
+ batch_id: batchId,
1260
+ entries,
1261
+ ...(embeddings && { embeddings }),
1262
+ });
1263
+ if (res.status === 409) {
1264
+ return jsonToolResult({ error: "lease expired, call memory_reflect again" });
1265
+ }
1266
+ if (!res.ok) {
1267
+ const errBody = await res.text().catch(() => "");
1268
+ return jsonToolResult({ error: `Commit failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
1269
+ }
1270
+ const data = await res.json() as {
1271
+ committed?: boolean;
1272
+ already_committed?: boolean;
1273
+ created?: Array<{ id: string; importance: number }>;
1274
+ sources_consolidated?: number;
1275
+ reflection?: ReflectionDebt;
1276
+ };
1277
+ // Created UUIDs dropped: the model never sees memory row IDs.
1278
+ return jsonToolResult({
1279
+ committed: data.committed ?? false,
1280
+ already_committed: data.already_committed ?? false,
1281
+ entries_created: data.created?.length ?? 0,
1282
+ sources_consolidated: data.sources_consolidated ?? 0,
1283
+ debt: data.reflection?.suggestion ?? null,
1284
+ });
1285
+ } catch (err) {
1286
+ return jsonToolResult({ error: err instanceof Error ? err.message : String(err) });
1287
+ }
1288
+ },
1289
+ };
1290
+ });
1291
+
1292
+ // memory_inspect: read-only lineage drill-down (curation surface, ids allowed)
1293
+ api.registerTool((ctx) => {
1294
+ const toolKey = resolveKey(ctx);
1295
+ return {
1296
+ label: "Memory Inspect",
1297
+ name: "memory_inspect",
1298
+ description: "Inspect a memory's consolidation lineage by memory_id: its tier, the batch it came from, the sources it consolidated, and what it was consolidated into. Read-only.",
1299
+ parameters: {
1300
+ type: "object",
1301
+ properties: {
1302
+ memory_id: { type: "string", description: "The memory id to inspect." },
1303
+ },
1304
+ required: ["memory_id"],
1305
+ } as any,
1306
+ execute: async (_toolCallId: string, params: Record<string, unknown>) => {
1307
+ if (!toolKey) return jsonToolResult({ error: "No memory key configured for this agent" });
1308
+ const memoryId = typeof params.memory_id === "string" ? params.memory_id.trim() : "";
1309
+ if (!memoryId) return jsonToolResult({ error: "memory_id required" });
1310
+ try {
1311
+ const res = await inspectMemory(endpoint, toolKey, {
1312
+ memory_id: memoryId,
1313
+ ...(embeddings && { embeddings }),
1314
+ });
1315
+ if (!res.ok) {
1316
+ const errBody = await res.text().catch(() => "");
1317
+ return jsonToolResult({ error: `Inspect failed: HTTP ${res.status}`, details: errBody.slice(0, 200) });
1318
+ }
1319
+ return jsonToolResult(await res.json());
1320
+ } catch (err) {
1321
+ return jsonToolResult({ error: err instanceof Error ? err.message : String(err) });
1322
+ }
1323
+ },
1324
+ };
1325
+ });
1326
+
1070
1327
  // memory_get — exact copy of OpenClaw's implementation (pure local file read)
1071
1328
  api.registerTool((ctx) => {
1072
1329
  const workspaceDir = ctx.workspaceDir || "";
@@ -1353,11 +1610,36 @@ If setting up MemoryRouter for another agent:
1353
1610
  });
1354
1611
 
1355
1612
  mr.command("delete")
1356
- .description("Clear all memories from vault")
1613
+ .description("Clear all memories from vault, or delete specific memories with --ids")
1614
+ .option("--ids <ids>", "Comma-separated memory ids to delete (instead of the whole vault)")
1615
+ .option("--yes", "Confirm deletion (required with --ids)")
1357
1616
  .option("--key <key>", "Override memory key (for subagent vaults)")
1358
- .action(async (opts: { key?: string }) => {
1617
+ .action(async (opts: { ids?: string; yes?: boolean; key?: string }) => {
1359
1618
  const effectiveKey = opts.key || memoryKey;
1360
1619
  if (!effectiveKey) { console.error("Not configured. Run: openclaw mr <key> or pass --key <key>"); return; }
1620
+ if (opts.ids) {
1621
+ const ids = opts.ids.split(",").map((s) => s.trim()).filter(Boolean);
1622
+ if (ids.length === 0) { console.error("No ids provided."); return; }
1623
+ console.log(`The following ${ids.length} memor${ids.length === 1 ? "y" : "ies"} will be PERMANENTLY deleted:`);
1624
+ for (const id of ids) console.log(` • ${id}`);
1625
+ if (!opts.yes) {
1626
+ console.error("\nRefusing to delete without --yes. Re-run with --yes to confirm.");
1627
+ return;
1628
+ }
1629
+ try {
1630
+ const res = await deleteMemoryIds(endpoint, effectiveKey, { ids, ...(embeddings && { embeddings }) });
1631
+ if (!res.ok) {
1632
+ const errBody = await res.text().catch(() => "");
1633
+ console.error(`Delete failed: HTTP ${res.status}${errBody ? `: ${errBody.slice(0, 200)}` : ""}`);
1634
+ return;
1635
+ }
1636
+ const data = (await res.json()) as { requested?: number; deleted?: number; not_found?: number };
1637
+ console.log(`✓ Deleted ${data.deleted ?? 0} of ${data.requested ?? ids.length} (not found: ${data.not_found ?? 0})`);
1638
+ } catch (err) {
1639
+ console.error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
1640
+ }
1641
+ return;
1642
+ }
1361
1643
  try {
1362
1644
  const res = await deleteMemories(endpoint, effectiveKey, embeddings);
1363
1645
  const data = (await res.json()) as { message?: string };
@@ -1367,6 +1649,42 @@ If setting up MemoryRouter for another agent:
1367
1649
  console.error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
1368
1650
  }
1369
1651
  });
1652
+
1653
+ mr.command("reflect")
1654
+ .description("Show reflection debt status (consolidation runs via the agent's memory_reflect tool)")
1655
+ .option("--key <key>", "Override memory key (for subagent vaults)")
1656
+ .action(async (opts: { key?: string }) => {
1657
+ const effectiveKey = opts.key || memoryKey;
1658
+ if (!effectiveKey) { console.error("Not configured. Run: openclaw mr <key> or pass --key <key>"); return; }
1659
+ try {
1660
+ // Debt probe: an empty-window date-search returns the reflection
1661
+ // debt block with zero results and zero retrieval billing.
1662
+ const from = new Date(Date.now() - 1000).toISOString();
1663
+ const res = await dateSearch(endpoint, effectiveKey, { from, max_tokens: 1000, ...(embeddings && { embeddings }) });
1664
+ if (!res.ok) {
1665
+ const errBody = await res.text().catch(() => "");
1666
+ console.error(`Debt check failed: HTTP ${res.status}${errBody ? `: ${errBody.slice(0, 200)}` : ""}`);
1667
+ return;
1668
+ }
1669
+ const data = (await res.json()) as { reflection?: ReflectionDebt };
1670
+ const debt = data.reflection;
1671
+ console.log("Reflection Debt");
1672
+ console.log("───────────────────────────");
1673
+ const raw = debt?.unconsolidated?.raw;
1674
+ const tier2 = debt?.unconsolidated?.tier2;
1675
+ console.log(`Raw unconsolidated: ${raw?.units ?? 0} memories (~${raw?.est_tokens ?? 0} tokens)`);
1676
+ console.log(`Tier 2 unconsolidated: ${tier2?.count ?? 0} reflections (~${tier2?.est_tokens ?? 0} tokens)`);
1677
+ console.log(`Threshold: ${debt?.threshold_tokens ?? 0} tokens`);
1678
+ if (debt?.suggestion) {
1679
+ console.log(`\nStatus: ${debt.suggestion}`);
1680
+ console.log("Consolidation runs through the agent. Ask your agent to consolidate memories (it uses the memory_reflect and memory_commit tools).");
1681
+ } else {
1682
+ console.log("\nStatus: nothing to consolidate right now.");
1683
+ }
1684
+ } catch (err) {
1685
+ console.error(`Failed: ${err instanceof Error ? err.message : String(err)}`);
1686
+ }
1687
+ });
1370
1688
  },
1371
1689
  { commands: ["mr"] },
1372
1690
  );
@@ -27,7 +27,75 @@ export async function searchMemory(endpoint, key, payload) {
27
27
  "Content-Type": "application/json",
28
28
  ...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
29
29
  },
30
- body: JSON.stringify({ query: payload.query, limit: payload.limit }),
30
+ body: JSON.stringify({
31
+ query: payload.query,
32
+ limit: payload.limit,
33
+ ...(payload.tiers !== undefined && { tiers: payload.tiers }),
34
+ ...(payload.importance !== undefined && { importance: payload.importance }),
35
+ }),
36
+ });
37
+ }
38
+ export async function consolidateCheckout(endpoint, key, payload) {
39
+ return fetch(`${endpoint}/v1/memory/consolidate/checkout`, {
40
+ method: "POST",
41
+ headers: {
42
+ ...JSON_HEADERS,
43
+ Authorization: `Bearer ${key}`,
44
+ ...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
45
+ },
46
+ body: JSON.stringify({
47
+ tier: payload.tier,
48
+ max_tokens: payload.max_tokens ?? 50000,
49
+ }),
50
+ });
51
+ }
52
+ export async function commitReflections(endpoint, key, payload) {
53
+ return fetch(`${endpoint}/v1/memory/consolidate/commit`, {
54
+ method: "POST",
55
+ headers: {
56
+ ...JSON_HEADERS,
57
+ Authorization: `Bearer ${key}`,
58
+ ...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
59
+ },
60
+ body: JSON.stringify({ batch_id: payload.batch_id, entries: payload.entries }),
61
+ });
62
+ }
63
+ export async function inspectMemory(endpoint, key, payload) {
64
+ return fetch(`${endpoint}/v1/memory/inspect`, {
65
+ method: "POST",
66
+ headers: {
67
+ ...JSON_HEADERS,
68
+ Authorization: `Bearer ${key}`,
69
+ ...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
70
+ },
71
+ body: JSON.stringify({ memory_id: payload.memory_id }),
72
+ });
73
+ }
74
+ export async function deleteMemoryIds(endpoint, key, payload) {
75
+ return fetch(`${endpoint}/v1/memory/delete-ids`, {
76
+ method: "POST",
77
+ headers: {
78
+ ...JSON_HEADERS,
79
+ Authorization: `Bearer ${key}`,
80
+ ...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
81
+ },
82
+ body: JSON.stringify({ ids: payload.ids }),
83
+ });
84
+ }
85
+ export async function dateSearch(endpoint, key, payload) {
86
+ return fetch(`${endpoint}/v1/memory/date-search`, {
87
+ method: "POST",
88
+ headers: {
89
+ ...JSON_HEADERS,
90
+ Authorization: `Bearer ${key}`,
91
+ ...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
92
+ },
93
+ body: JSON.stringify({
94
+ from: payload.from,
95
+ ...(payload.to !== undefined && { to: payload.to }),
96
+ ...(payload.query !== undefined && { query: payload.query }),
97
+ ...(payload.max_tokens !== undefined && { max_tokens: payload.max_tokens }),
98
+ }),
31
99
  });
32
100
  }
33
101
  export async function getMemoryStats(endpoint, key, embeddings) {
@@ -47,7 +115,6 @@ export async function deleteMemories(endpoint, key, embeddings) {
47
115
  headers: { Authorization: `Bearer ${key}` },
48
116
  });
49
117
  }
50
-
51
118
  export async function warmupMemory(endpoint, key, embeddings) {
52
119
  return fetch(`${endpoint}/v1/memory/warmup`, {
53
120
  method: "POST",
@@ -28,7 +28,7 @@ export function ingestMemory(endpoint: string, key: string, payload: { messages:
28
28
  });
29
29
  }
30
30
 
31
- export async function searchMemory(endpoint: string, key: string, payload: { query: string; limit: number; embeddings?: string }): Promise<Response> {
31
+ export async function searchMemory(endpoint: string, key: string, payload: { query: string; limit: number; tiers?: number[]; importance?: number; embeddings?: string }): Promise<Response> {
32
32
  return fetch(`${endpoint}/v1/memory/search`, {
33
33
  method: "POST",
34
34
  headers: {
@@ -36,7 +36,80 @@ export async function searchMemory(endpoint: string, key: string, payload: { que
36
36
  "Content-Type": "application/json",
37
37
  ...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
38
38
  },
39
- body: JSON.stringify({ query: payload.query, limit: payload.limit }),
39
+ body: JSON.stringify({
40
+ query: payload.query,
41
+ limit: payload.limit,
42
+ ...(payload.tiers !== undefined && { tiers: payload.tiers }),
43
+ ...(payload.importance !== undefined && { importance: payload.importance }),
44
+ }),
45
+ });
46
+ }
47
+
48
+ export async function consolidateCheckout(endpoint: string, key: string, payload: { tier: 1 | 2; max_tokens?: number; embeddings?: string }): Promise<Response> {
49
+ return fetch(`${endpoint}/v1/memory/consolidate/checkout`, {
50
+ method: "POST",
51
+ headers: {
52
+ ...JSON_HEADERS,
53
+ Authorization: `Bearer ${key}`,
54
+ ...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
55
+ },
56
+ body: JSON.stringify({
57
+ tier: payload.tier,
58
+ max_tokens: payload.max_tokens ?? 50000,
59
+ }),
60
+ });
61
+ }
62
+
63
+ export async function commitReflections(endpoint: string, key: string, payload: { batch_id: string; entries: Array<{ text: string; importance: number }>; embeddings?: string }): Promise<Response> {
64
+ return fetch(`${endpoint}/v1/memory/consolidate/commit`, {
65
+ method: "POST",
66
+ headers: {
67
+ ...JSON_HEADERS,
68
+ Authorization: `Bearer ${key}`,
69
+ ...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
70
+ },
71
+ body: JSON.stringify({ batch_id: payload.batch_id, entries: payload.entries }),
72
+ });
73
+ }
74
+
75
+ export async function inspectMemory(endpoint: string, key: string, payload: { memory_id: string; embeddings?: string }): Promise<Response> {
76
+ return fetch(`${endpoint}/v1/memory/inspect`, {
77
+ method: "POST",
78
+ headers: {
79
+ ...JSON_HEADERS,
80
+ Authorization: `Bearer ${key}`,
81
+ ...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
82
+ },
83
+ body: JSON.stringify({ memory_id: payload.memory_id }),
84
+ });
85
+ }
86
+
87
+ export async function deleteMemoryIds(endpoint: string, key: string, payload: { ids: string[]; embeddings?: string }): Promise<Response> {
88
+ return fetch(`${endpoint}/v1/memory/delete-ids`, {
89
+ method: "POST",
90
+ headers: {
91
+ ...JSON_HEADERS,
92
+ Authorization: `Bearer ${key}`,
93
+ ...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
94
+ },
95
+ body: JSON.stringify({ ids: payload.ids }),
96
+ });
97
+ }
98
+
99
+ export async function dateSearch(endpoint: string, key: string, payload: { from: string; to?: string; query?: string; max_tokens?: number; embeddings?: string }): Promise<Response> {
100
+ return fetch(`${endpoint}/v1/memory/date-search`, {
101
+ method: "POST",
102
+ headers: {
103
+ ...JSON_HEADERS,
104
+ Authorization: `Bearer ${key}`,
105
+ ...(payload.embeddings && { "X-Embedding-Model": payload.embeddings }),
106
+ },
107
+ body: JSON.stringify({
108
+ from: payload.from,
109
+ ...(payload.to !== undefined && { to: payload.to }),
110
+ ...(payload.query !== undefined && { query: payload.query }),
111
+ ...(payload.max_tokens !== undefined && { max_tokens: payload.max_tokens }),
112
+ }),
40
113
  });
41
114
  }
42
115
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "mr-memory",
3
- "version": "3.8.0",
3
+ "version": "3.9.1",
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": [