@memoryrelay/plugin-memoryrelay-ai 0.15.7 → 0.16.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.ts +493 -4868
- package/openclaw.plugin.json +41 -3
- package/package.json +1 -1
- package/src/client/memoryrelay-client.ts +816 -0
- package/src/context/namespace-router.ts +19 -0
- package/src/context/request-context.ts +39 -0
- package/src/context/session-resolver.ts +93 -0
- package/src/filters/content-patterns.ts +32 -0
- package/src/filters/noise-patterns.ts +33 -0
- package/src/filters/non-interactive.ts +30 -0
- package/src/hooks/activity.ts +51 -0
- package/src/hooks/agent-end.ts +48 -0
- package/src/hooks/before-agent-start.ts +109 -0
- package/src/hooks/before-prompt-build.ts +46 -0
- package/src/hooks/compaction.ts +51 -0
- package/src/hooks/privacy.ts +44 -0
- package/src/hooks/session-lifecycle.ts +47 -0
- package/src/hooks/subagent.ts +62 -0
- package/src/pipelines/capture/content-strip.ts +14 -0
- package/src/pipelines/capture/dedup.ts +17 -0
- package/src/pipelines/capture/index.ts +13 -0
- package/src/pipelines/capture/message-filter.ts +16 -0
- package/src/pipelines/capture/store.ts +33 -0
- package/src/pipelines/capture/trigger-gate.ts +21 -0
- package/src/pipelines/capture/truncate.ts +16 -0
- package/src/pipelines/recall/format.ts +30 -0
- package/src/pipelines/recall/index.ts +12 -0
- package/src/pipelines/recall/rank.ts +40 -0
- package/src/pipelines/recall/scope-resolver.ts +20 -0
- package/src/pipelines/recall/search.ts +43 -0
- package/src/pipelines/recall/trigger-gate.ts +17 -0
- package/src/pipelines/runner.ts +25 -0
- package/src/pipelines/types.ts +157 -0
- package/src/tools/agent-tools.ts +127 -0
- package/src/tools/decision-tools.ts +309 -0
- package/src/tools/entity-tools.ts +215 -0
- package/src/tools/health-tools.ts +42 -0
- package/src/tools/memory-tools.ts +690 -0
- package/src/tools/pattern-tools.ts +250 -0
- package/src/tools/project-tools.ts +444 -0
- package/src/tools/session-tools.ts +195 -0
- package/src/tools/v2-tools.ts +228 -0
|
@@ -0,0 +1,690 @@
|
|
|
1
|
+
import type { OpenClawPluginApi } from "openclaw/plugin-sdk";
|
|
2
|
+
import type { PluginConfig } from "../pipelines/types.js";
|
|
3
|
+
import type { MemoryRelayClient } from "../client/memoryrelay-client.js";
|
|
4
|
+
import type { SessionResolver } from "../context/session-resolver.js";
|
|
5
|
+
|
|
6
|
+
export function registerMemoryTools(
|
|
7
|
+
api: OpenClawPluginApi,
|
|
8
|
+
config: PluginConfig,
|
|
9
|
+
client: MemoryRelayClient,
|
|
10
|
+
sessionResolver: SessionResolver,
|
|
11
|
+
isToolEnabled: (name: string) => boolean,
|
|
12
|
+
): void {
|
|
13
|
+
const defaultProject = config.defaultProject;
|
|
14
|
+
|
|
15
|
+
// --------------------------------------------------------------------------
|
|
16
|
+
// 1. memory_store
|
|
17
|
+
// --------------------------------------------------------------------------
|
|
18
|
+
if (isToolEnabled("memory_store")) {
|
|
19
|
+
api.registerTool((ctx) => ({
|
|
20
|
+
|
|
21
|
+
name: "memory_store",
|
|
22
|
+
description:
|
|
23
|
+
"Store a new memory in MemoryRelay. Use this to save important information, facts, preferences, or context that should be remembered for future conversations." +
|
|
24
|
+
(defaultProject ? ` Project defaults to '${defaultProject}' if not specified.` : "") +
|
|
25
|
+
" Set deduplicate=true to avoid storing near-duplicate memories.",
|
|
26
|
+
parameters: {
|
|
27
|
+
type: "object",
|
|
28
|
+
properties: {
|
|
29
|
+
content: {
|
|
30
|
+
type: "string",
|
|
31
|
+
description: "The memory content to store. Be specific and include relevant context.",
|
|
32
|
+
},
|
|
33
|
+
metadata: {
|
|
34
|
+
type: "object",
|
|
35
|
+
description: "Optional key-value metadata to attach to the memory",
|
|
36
|
+
additionalProperties: { type: "string" },
|
|
37
|
+
},
|
|
38
|
+
deduplicate: {
|
|
39
|
+
type: "boolean",
|
|
40
|
+
description: "If true, check for duplicate memories before storing. Default false.",
|
|
41
|
+
},
|
|
42
|
+
dedup_threshold: {
|
|
43
|
+
type: "number",
|
|
44
|
+
description: "Similarity threshold for deduplication (0-1). Default 0.95.",
|
|
45
|
+
},
|
|
46
|
+
project: {
|
|
47
|
+
type: "string",
|
|
48
|
+
description: "Project slug to associate with this memory.",
|
|
49
|
+
},
|
|
50
|
+
importance: {
|
|
51
|
+
type: "number",
|
|
52
|
+
description: "Importance score (0-1). Higher values are retained longer.",
|
|
53
|
+
},
|
|
54
|
+
tier: {
|
|
55
|
+
type: "string",
|
|
56
|
+
description: "Memory tier: hot, warm, or cold.",
|
|
57
|
+
enum: ["hot", "warm", "cold"],
|
|
58
|
+
},
|
|
59
|
+
session_id: {
|
|
60
|
+
type: "string",
|
|
61
|
+
description: "Optional MemoryRelay session UUID to associate this memory with. If omitted and project is set, plugin auto-creates session via external_id.",
|
|
62
|
+
},
|
|
63
|
+
scope: {
|
|
64
|
+
type: "string",
|
|
65
|
+
description: "Memory scope: 'session' (current conversation) or 'long-term' (persistent). Default: 'long-term'.",
|
|
66
|
+
enum: ["session", "long-term"],
|
|
67
|
+
},
|
|
68
|
+
},
|
|
69
|
+
required: ["content"],
|
|
70
|
+
},
|
|
71
|
+
execute: async (
|
|
72
|
+
_id,
|
|
73
|
+
args: {
|
|
74
|
+
content: string;
|
|
75
|
+
metadata?: Record<string, string>;
|
|
76
|
+
deduplicate?: boolean;
|
|
77
|
+
dedup_threshold?: number;
|
|
78
|
+
project?: string;
|
|
79
|
+
importance?: number;
|
|
80
|
+
tier?: string;
|
|
81
|
+
session_id?: string;
|
|
82
|
+
scope?: string;
|
|
83
|
+
},
|
|
84
|
+
) => {
|
|
85
|
+
try {
|
|
86
|
+
const { content, metadata: rawMetadata, session_id: explicitSessionId, scope, ...opts } = args;
|
|
87
|
+
|
|
88
|
+
// Auto-tag with sender identity from tool context
|
|
89
|
+
const metadata = rawMetadata || {};
|
|
90
|
+
if (ctx.requesterSenderId && !metadata.sender_id) {
|
|
91
|
+
metadata.sender_id = ctx.requesterSenderId;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// Apply defaultProject fallback before session resolution
|
|
95
|
+
if (!opts.project && defaultProject) opts.project = defaultProject;
|
|
96
|
+
|
|
97
|
+
// Get session_id from SessionResolver if project context available
|
|
98
|
+
// Priority: explicit session_id > context session > no session
|
|
99
|
+
let sessionId: string | undefined = explicitSessionId;
|
|
100
|
+
|
|
101
|
+
if (!sessionId && (opts.project || ctx.workspaceDir)) {
|
|
102
|
+
try {
|
|
103
|
+
const entry = await sessionResolver.resolve({
|
|
104
|
+
sessionKey: opts.project || `workspace-${(ctx.workspaceDir || "").split(/[/\\]/).pop()}`,
|
|
105
|
+
agentId: null,
|
|
106
|
+
channel: null,
|
|
107
|
+
trigger: null,
|
|
108
|
+
prompt: "",
|
|
109
|
+
isSubagent: false,
|
|
110
|
+
parentSessionKey: null,
|
|
111
|
+
namespace: opts.project || "default",
|
|
112
|
+
timestamp: Date.now(),
|
|
113
|
+
});
|
|
114
|
+
sessionId = entry.sessionId;
|
|
115
|
+
} catch {
|
|
116
|
+
// Session resolution failed — continue without session
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
// Build request options with session_id as top-level parameter
|
|
121
|
+
const storeOpts = {
|
|
122
|
+
...opts,
|
|
123
|
+
...(sessionId && { session_id: sessionId }),
|
|
124
|
+
...(scope && { scope }),
|
|
125
|
+
};
|
|
126
|
+
|
|
127
|
+
const memory = await client.store(content, metadata, storeOpts);
|
|
128
|
+
return {
|
|
129
|
+
content: [
|
|
130
|
+
{
|
|
131
|
+
type: "text",
|
|
132
|
+
text: `Memory stored successfully (id: ${memory.id.slice(0, 8)}...)${sessionId ? ` in session ${sessionId.slice(0, 8)}...` : ''}`,
|
|
133
|
+
},
|
|
134
|
+
],
|
|
135
|
+
details: { id: memory.id, stored: true, session_id: sessionId },
|
|
136
|
+
};
|
|
137
|
+
} catch (err) {
|
|
138
|
+
return {
|
|
139
|
+
content: [{ type: "text", text: `Failed to store memory: ${String(err)}` }],
|
|
140
|
+
details: { error: String(err) },
|
|
141
|
+
};
|
|
142
|
+
}
|
|
143
|
+
},
|
|
144
|
+
}),
|
|
145
|
+
{ name: "memory_store" },
|
|
146
|
+
);
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
// --------------------------------------------------------------------------
|
|
150
|
+
// 2. memory_recall
|
|
151
|
+
// --------------------------------------------------------------------------
|
|
152
|
+
if (isToolEnabled("memory_recall")) {
|
|
153
|
+
api.registerTool((ctx) => ({
|
|
154
|
+
|
|
155
|
+
name: "memory_recall",
|
|
156
|
+
description:
|
|
157
|
+
"Search memories using natural language. Returns the most relevant memories based on semantic similarity to the query." +
|
|
158
|
+
(defaultProject ? ` Results scoped to project '${defaultProject}' by default; pass project explicitly to override or omit to search all.` : ""),
|
|
159
|
+
parameters: {
|
|
160
|
+
type: "object",
|
|
161
|
+
properties: {
|
|
162
|
+
query: {
|
|
163
|
+
type: "string",
|
|
164
|
+
description: "Natural language search query",
|
|
165
|
+
},
|
|
166
|
+
limit: {
|
|
167
|
+
type: "number",
|
|
168
|
+
description: "Maximum results (1-50). Default 5.",
|
|
169
|
+
minimum: 1,
|
|
170
|
+
maximum: 50,
|
|
171
|
+
},
|
|
172
|
+
threshold: {
|
|
173
|
+
type: "number",
|
|
174
|
+
description: "Minimum similarity threshold (0-1). Default 0.3.",
|
|
175
|
+
},
|
|
176
|
+
project: {
|
|
177
|
+
type: "string",
|
|
178
|
+
description: "Filter by project slug.",
|
|
179
|
+
},
|
|
180
|
+
tier: {
|
|
181
|
+
type: "string",
|
|
182
|
+
description: "Filter by memory tier: hot, warm, or cold.",
|
|
183
|
+
enum: ["hot", "warm", "cold"],
|
|
184
|
+
},
|
|
185
|
+
min_importance: {
|
|
186
|
+
type: "number",
|
|
187
|
+
description: "Minimum importance score filter (0-1).",
|
|
188
|
+
},
|
|
189
|
+
compress: {
|
|
190
|
+
type: "boolean",
|
|
191
|
+
description: "If true, compress results for token efficiency.",
|
|
192
|
+
},
|
|
193
|
+
scope: {
|
|
194
|
+
type: "string",
|
|
195
|
+
description: "Search scope: 'session', 'long-term', or 'all'. Default: 'all'.",
|
|
196
|
+
enum: ["session", "long-term", "all"],
|
|
197
|
+
},
|
|
198
|
+
},
|
|
199
|
+
required: ["query"],
|
|
200
|
+
},
|
|
201
|
+
execute: async (
|
|
202
|
+
_id,
|
|
203
|
+
args: {
|
|
204
|
+
query: string;
|
|
205
|
+
limit?: number;
|
|
206
|
+
threshold?: number;
|
|
207
|
+
project?: string;
|
|
208
|
+
tier?: string;
|
|
209
|
+
min_importance?: number;
|
|
210
|
+
compress?: boolean;
|
|
211
|
+
scope?: string;
|
|
212
|
+
},
|
|
213
|
+
) => {
|
|
214
|
+
try {
|
|
215
|
+
const {
|
|
216
|
+
query,
|
|
217
|
+
limit = 5,
|
|
218
|
+
threshold,
|
|
219
|
+
project,
|
|
220
|
+
tier,
|
|
221
|
+
min_importance,
|
|
222
|
+
compress,
|
|
223
|
+
scope,
|
|
224
|
+
} = args;
|
|
225
|
+
const searchThreshold = threshold ?? config?.recallThreshold ?? 0.3;
|
|
226
|
+
const searchProject = project ?? defaultProject;
|
|
227
|
+
const results = await client.search(query, limit, searchThreshold, {
|
|
228
|
+
project: searchProject,
|
|
229
|
+
tier,
|
|
230
|
+
min_importance,
|
|
231
|
+
compress,
|
|
232
|
+
...(scope && { scope }),
|
|
233
|
+
});
|
|
234
|
+
|
|
235
|
+
if (results.length === 0) {
|
|
236
|
+
return {
|
|
237
|
+
content: [{ type: "text", text: "No relevant memories found." }],
|
|
238
|
+
details: { count: 0 },
|
|
239
|
+
};
|
|
240
|
+
}
|
|
241
|
+
|
|
242
|
+
const formatted = results
|
|
243
|
+
.map(
|
|
244
|
+
(r) =>
|
|
245
|
+
`- [${r.score.toFixed(2)}] ${r.memory.content.slice(0, 200)}${
|
|
246
|
+
r.memory.content.length > 200 ? "..." : ""
|
|
247
|
+
}`,
|
|
248
|
+
)
|
|
249
|
+
.join("\n");
|
|
250
|
+
|
|
251
|
+
return {
|
|
252
|
+
content: [
|
|
253
|
+
{
|
|
254
|
+
type: "text",
|
|
255
|
+
text: `Found ${results.length} relevant memories:\n${formatted}`,
|
|
256
|
+
},
|
|
257
|
+
],
|
|
258
|
+
details: {
|
|
259
|
+
count: results.length,
|
|
260
|
+
memories: results.map((r) => ({
|
|
261
|
+
id: r.memory.id,
|
|
262
|
+
content: r.memory.content,
|
|
263
|
+
score: r.score,
|
|
264
|
+
})),
|
|
265
|
+
},
|
|
266
|
+
};
|
|
267
|
+
} catch (err) {
|
|
268
|
+
return {
|
|
269
|
+
content: [{ type: "text", text: `Search failed: ${String(err)}` }],
|
|
270
|
+
details: { error: String(err) },
|
|
271
|
+
};
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
}),
|
|
275
|
+
{ name: "memory_recall" },
|
|
276
|
+
);
|
|
277
|
+
}
|
|
278
|
+
|
|
279
|
+
// --------------------------------------------------------------------------
|
|
280
|
+
// 3. memory_forget
|
|
281
|
+
// --------------------------------------------------------------------------
|
|
282
|
+
if (isToolEnabled("memory_forget")) {
|
|
283
|
+
api.registerTool((ctx) => ({
|
|
284
|
+
|
|
285
|
+
name: "memory_forget",
|
|
286
|
+
description: "Delete a memory by ID, or search by query to find candidates. Provide memoryId for direct deletion, or query to search first. A single high-confidence match (>0.9) is auto-deleted; otherwise candidates are listed for you to choose.",
|
|
287
|
+
parameters: {
|
|
288
|
+
type: "object",
|
|
289
|
+
properties: {
|
|
290
|
+
memoryId: {
|
|
291
|
+
type: "string",
|
|
292
|
+
description: "Memory ID to delete",
|
|
293
|
+
},
|
|
294
|
+
query: {
|
|
295
|
+
type: "string",
|
|
296
|
+
description: "Search query to find memory",
|
|
297
|
+
},
|
|
298
|
+
},
|
|
299
|
+
},
|
|
300
|
+
execute: async (_id, { memoryId, query }: { memoryId?: string; query?: string }) => {
|
|
301
|
+
if (memoryId) {
|
|
302
|
+
try {
|
|
303
|
+
await client.delete(memoryId);
|
|
304
|
+
return {
|
|
305
|
+
content: [{ type: "text", text: `Memory ${memoryId.slice(0, 8)}... deleted.` }],
|
|
306
|
+
details: { action: "deleted", id: memoryId },
|
|
307
|
+
};
|
|
308
|
+
} catch (err) {
|
|
309
|
+
return {
|
|
310
|
+
content: [{ type: "text", text: `Delete failed: ${String(err)}` }],
|
|
311
|
+
details: { error: String(err) },
|
|
312
|
+
};
|
|
313
|
+
}
|
|
314
|
+
}
|
|
315
|
+
|
|
316
|
+
if (query) {
|
|
317
|
+
const results = await client.search(query, 5, 0.5, { project: defaultProject });
|
|
318
|
+
|
|
319
|
+
if (results.length === 0) {
|
|
320
|
+
return {
|
|
321
|
+
content: [{ type: "text", text: "No matching memories found." }],
|
|
322
|
+
details: { count: 0 },
|
|
323
|
+
};
|
|
324
|
+
}
|
|
325
|
+
|
|
326
|
+
// If single high-confidence match, delete it
|
|
327
|
+
if (results.length === 1 && results[0].score > 0.9) {
|
|
328
|
+
await client.delete(results[0].memory.id);
|
|
329
|
+
return {
|
|
330
|
+
content: [
|
|
331
|
+
{ type: "text", text: `Forgotten: "${results[0].memory.content.slice(0, 60)}..."` },
|
|
332
|
+
],
|
|
333
|
+
details: { action: "deleted", id: results[0].memory.id },
|
|
334
|
+
};
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
const list = results
|
|
338
|
+
.map((r) => `- [${r.memory.id}] ${r.memory.content.slice(0, 60)}...`)
|
|
339
|
+
.join("\n");
|
|
340
|
+
|
|
341
|
+
return {
|
|
342
|
+
content: [
|
|
343
|
+
{
|
|
344
|
+
type: "text",
|
|
345
|
+
text: `Found ${results.length} candidates. Specify memoryId:\n${list}`,
|
|
346
|
+
},
|
|
347
|
+
],
|
|
348
|
+
details: { action: "candidates", count: results.length },
|
|
349
|
+
};
|
|
350
|
+
}
|
|
351
|
+
|
|
352
|
+
return {
|
|
353
|
+
content: [{ type: "text", text: "Provide query or memoryId." }],
|
|
354
|
+
details: { error: "missing_param" },
|
|
355
|
+
};
|
|
356
|
+
},
|
|
357
|
+
}),
|
|
358
|
+
{ name: "memory_forget" },
|
|
359
|
+
);
|
|
360
|
+
}
|
|
361
|
+
|
|
362
|
+
// --------------------------------------------------------------------------
|
|
363
|
+
// 4. memory_list
|
|
364
|
+
// --------------------------------------------------------------------------
|
|
365
|
+
if (isToolEnabled("memory_list")) {
|
|
366
|
+
api.registerTool((ctx) => ({
|
|
367
|
+
|
|
368
|
+
name: "memory_list",
|
|
369
|
+
description: "List recent memories chronologically for this agent. Use to review what has been stored or to find memory IDs for update/delete operations.",
|
|
370
|
+
parameters: {
|
|
371
|
+
type: "object",
|
|
372
|
+
properties: {
|
|
373
|
+
limit: {
|
|
374
|
+
type: "number",
|
|
375
|
+
description: "Number of memories to return (1-100). Default 20.",
|
|
376
|
+
minimum: 1,
|
|
377
|
+
maximum: 100,
|
|
378
|
+
},
|
|
379
|
+
offset: {
|
|
380
|
+
type: "number",
|
|
381
|
+
description: "Offset for pagination. Default 0.",
|
|
382
|
+
minimum: 0,
|
|
383
|
+
},
|
|
384
|
+
scope: {
|
|
385
|
+
type: "string",
|
|
386
|
+
description: "List scope: 'session', 'long-term', or 'all'. Default: 'all'.",
|
|
387
|
+
enum: ["session", "long-term", "all"],
|
|
388
|
+
},
|
|
389
|
+
},
|
|
390
|
+
},
|
|
391
|
+
execute: async (_id, args: { limit?: number; offset?: number; scope?: string }) => {
|
|
392
|
+
try {
|
|
393
|
+
const memories = await client.list(args.limit ?? 20, args.offset ?? 0, {
|
|
394
|
+
...(args.scope && { scope: args.scope }),
|
|
395
|
+
});
|
|
396
|
+
if (memories.length === 0) {
|
|
397
|
+
return {
|
|
398
|
+
content: [{ type: "text", text: "No memories found." }],
|
|
399
|
+
details: { count: 0 },
|
|
400
|
+
};
|
|
401
|
+
}
|
|
402
|
+
const formatted = memories
|
|
403
|
+
.map((m) => `- [${m.id}] ${m.content.slice(0, 120)}`)
|
|
404
|
+
.join("\n");
|
|
405
|
+
return {
|
|
406
|
+
content: [{ type: "text", text: `${memories.length} memories:\n${formatted}` }],
|
|
407
|
+
details: { count: memories.length, memories },
|
|
408
|
+
};
|
|
409
|
+
} catch (err) {
|
|
410
|
+
return {
|
|
411
|
+
content: [{ type: "text", text: `Failed to list memories: ${String(err)}` }],
|
|
412
|
+
details: { error: String(err) },
|
|
413
|
+
};
|
|
414
|
+
}
|
|
415
|
+
},
|
|
416
|
+
}),
|
|
417
|
+
{ name: "memory_list" },
|
|
418
|
+
);
|
|
419
|
+
}
|
|
420
|
+
|
|
421
|
+
// --------------------------------------------------------------------------
|
|
422
|
+
// 5. memory_get
|
|
423
|
+
// --------------------------------------------------------------------------
|
|
424
|
+
if (isToolEnabled("memory_get")) {
|
|
425
|
+
api.registerTool((ctx) => ({
|
|
426
|
+
|
|
427
|
+
name: "memory_get",
|
|
428
|
+
description: "Retrieve a specific memory by its ID.",
|
|
429
|
+
parameters: {
|
|
430
|
+
type: "object",
|
|
431
|
+
properties: {
|
|
432
|
+
id: {
|
|
433
|
+
type: "string",
|
|
434
|
+
description: "The memory ID (UUID) to retrieve.",
|
|
435
|
+
},
|
|
436
|
+
},
|
|
437
|
+
required: ["id"],
|
|
438
|
+
},
|
|
439
|
+
execute: async (_id, args: { id: string }) => {
|
|
440
|
+
try {
|
|
441
|
+
const memory = await client.get(args.id);
|
|
442
|
+
return {
|
|
443
|
+
content: [{ type: "text", text: JSON.stringify(memory, null, 2) }],
|
|
444
|
+
details: { memory },
|
|
445
|
+
};
|
|
446
|
+
} catch (err) {
|
|
447
|
+
return {
|
|
448
|
+
content: [{ type: "text", text: `Failed to get memory: ${String(err)}` }],
|
|
449
|
+
details: { error: String(err) },
|
|
450
|
+
};
|
|
451
|
+
}
|
|
452
|
+
},
|
|
453
|
+
}),
|
|
454
|
+
{ name: "memory_get" },
|
|
455
|
+
);
|
|
456
|
+
}
|
|
457
|
+
|
|
458
|
+
// --------------------------------------------------------------------------
|
|
459
|
+
// 6. memory_update
|
|
460
|
+
// --------------------------------------------------------------------------
|
|
461
|
+
if (isToolEnabled("memory_update")) {
|
|
462
|
+
api.registerTool((ctx) => ({
|
|
463
|
+
|
|
464
|
+
name: "memory_update",
|
|
465
|
+
description: "Update the content of an existing memory. Use to correct or expand stored information.",
|
|
466
|
+
parameters: {
|
|
467
|
+
type: "object",
|
|
468
|
+
properties: {
|
|
469
|
+
id: {
|
|
470
|
+
type: "string",
|
|
471
|
+
description: "The memory ID (UUID) to update.",
|
|
472
|
+
},
|
|
473
|
+
content: {
|
|
474
|
+
type: "string",
|
|
475
|
+
description: "The new content to replace the existing memory.",
|
|
476
|
+
},
|
|
477
|
+
metadata: {
|
|
478
|
+
type: "object",
|
|
479
|
+
description: "Updated metadata (replaces existing).",
|
|
480
|
+
additionalProperties: { type: "string" },
|
|
481
|
+
},
|
|
482
|
+
},
|
|
483
|
+
required: ["id", "content"],
|
|
484
|
+
},
|
|
485
|
+
execute: async (_id, args: { id: string; content: string; metadata?: Record<string, string> }) => {
|
|
486
|
+
try {
|
|
487
|
+
const memory = await client.update(args.id, args.content, args.metadata);
|
|
488
|
+
return {
|
|
489
|
+
content: [{ type: "text", text: `Memory ${args.id.slice(0, 8)}... updated.` }],
|
|
490
|
+
details: { id: memory.id, updated: true },
|
|
491
|
+
};
|
|
492
|
+
} catch (err) {
|
|
493
|
+
return {
|
|
494
|
+
content: [{ type: "text", text: `Failed to update memory: ${String(err)}` }],
|
|
495
|
+
details: { error: String(err) },
|
|
496
|
+
};
|
|
497
|
+
}
|
|
498
|
+
},
|
|
499
|
+
}),
|
|
500
|
+
{ name: "memory_update" },
|
|
501
|
+
);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// --------------------------------------------------------------------------
|
|
505
|
+
// 7. memory_batch_store
|
|
506
|
+
// --------------------------------------------------------------------------
|
|
507
|
+
if (isToolEnabled("memory_batch_store")) {
|
|
508
|
+
api.registerTool((ctx) => ({
|
|
509
|
+
|
|
510
|
+
name: "memory_batch_store",
|
|
511
|
+
description: "Store multiple memories at once. More efficient than individual calls for bulk storage.",
|
|
512
|
+
parameters: {
|
|
513
|
+
type: "object",
|
|
514
|
+
properties: {
|
|
515
|
+
memories: {
|
|
516
|
+
type: "array",
|
|
517
|
+
description: "Array of memories to store.",
|
|
518
|
+
items: {
|
|
519
|
+
type: "object",
|
|
520
|
+
properties: {
|
|
521
|
+
content: { type: "string", description: "Memory content." },
|
|
522
|
+
metadata: {
|
|
523
|
+
type: "object",
|
|
524
|
+
description: "Optional metadata.",
|
|
525
|
+
additionalProperties: { type: "string" },
|
|
526
|
+
},
|
|
527
|
+
},
|
|
528
|
+
required: ["content"],
|
|
529
|
+
},
|
|
530
|
+
},
|
|
531
|
+
},
|
|
532
|
+
required: ["memories"],
|
|
533
|
+
},
|
|
534
|
+
execute: async (
|
|
535
|
+
_id,
|
|
536
|
+
args: { memories: Array<{ content: string; metadata?: Record<string, string> }> },
|
|
537
|
+
) => {
|
|
538
|
+
try {
|
|
539
|
+
// Auto-tag each memory with sender identity from tool context
|
|
540
|
+
if (ctx.requesterSenderId) {
|
|
541
|
+
for (const mem of args.memories) {
|
|
542
|
+
const metadata = mem.metadata || {};
|
|
543
|
+
if (!metadata.sender_id) {
|
|
544
|
+
metadata.sender_id = ctx.requesterSenderId;
|
|
545
|
+
}
|
|
546
|
+
mem.metadata = metadata;
|
|
547
|
+
}
|
|
548
|
+
}
|
|
549
|
+
|
|
550
|
+
const result = await client.batchStore(args.memories);
|
|
551
|
+
return {
|
|
552
|
+
content: [
|
|
553
|
+
{
|
|
554
|
+
type: "text",
|
|
555
|
+
text: `Batch stored ${args.memories.length} memories successfully.`,
|
|
556
|
+
},
|
|
557
|
+
],
|
|
558
|
+
details: { count: args.memories.length, result },
|
|
559
|
+
};
|
|
560
|
+
} catch (err) {
|
|
561
|
+
return {
|
|
562
|
+
content: [{ type: "text", text: `Batch store failed: ${String(err)}` }],
|
|
563
|
+
details: { error: String(err) },
|
|
564
|
+
};
|
|
565
|
+
}
|
|
566
|
+
},
|
|
567
|
+
}),
|
|
568
|
+
{ name: "memory_batch_store" },
|
|
569
|
+
);
|
|
570
|
+
}
|
|
571
|
+
|
|
572
|
+
// --------------------------------------------------------------------------
|
|
573
|
+
// 8. memory_context
|
|
574
|
+
// --------------------------------------------------------------------------
|
|
575
|
+
if (isToolEnabled("memory_context")) {
|
|
576
|
+
api.registerTool((ctx) => ({
|
|
577
|
+
|
|
578
|
+
name: "memory_context",
|
|
579
|
+
description:
|
|
580
|
+
"Build a context window from relevant memories, optimized for injecting into agent prompts with token budget awareness." +
|
|
581
|
+
(defaultProject ? ` Project defaults to '${defaultProject}' if not specified.` : ""),
|
|
582
|
+
parameters: {
|
|
583
|
+
type: "object",
|
|
584
|
+
properties: {
|
|
585
|
+
query: {
|
|
586
|
+
type: "string",
|
|
587
|
+
description: "The query to build context around.",
|
|
588
|
+
},
|
|
589
|
+
limit: {
|
|
590
|
+
type: "number",
|
|
591
|
+
description: "Maximum number of memories to include.",
|
|
592
|
+
},
|
|
593
|
+
threshold: {
|
|
594
|
+
type: "number",
|
|
595
|
+
description: "Minimum similarity threshold (0-1).",
|
|
596
|
+
},
|
|
597
|
+
max_tokens: {
|
|
598
|
+
type: "number",
|
|
599
|
+
description: "Maximum token budget for the context.",
|
|
600
|
+
},
|
|
601
|
+
project: {
|
|
602
|
+
type: "string",
|
|
603
|
+
description: "Project slug to scope the context.",
|
|
604
|
+
},
|
|
605
|
+
},
|
|
606
|
+
required: ["query"],
|
|
607
|
+
},
|
|
608
|
+
execute: async (
|
|
609
|
+
_id,
|
|
610
|
+
args: { query: string; limit?: number; threshold?: number; max_tokens?: number; project?: string },
|
|
611
|
+
) => {
|
|
612
|
+
try {
|
|
613
|
+
const project = args.project ?? defaultProject;
|
|
614
|
+
const result = await client.buildContext(
|
|
615
|
+
args.query,
|
|
616
|
+
args.limit,
|
|
617
|
+
args.threshold,
|
|
618
|
+
args.max_tokens,
|
|
619
|
+
project,
|
|
620
|
+
);
|
|
621
|
+
return {
|
|
622
|
+
content: [{ type: "text", text: JSON.stringify(result, null, 2) }],
|
|
623
|
+
details: { result },
|
|
624
|
+
};
|
|
625
|
+
} catch (err) {
|
|
626
|
+
return {
|
|
627
|
+
content: [{ type: "text", text: `Context build failed: ${String(err)}` }],
|
|
628
|
+
details: { error: String(err) },
|
|
629
|
+
};
|
|
630
|
+
}
|
|
631
|
+
},
|
|
632
|
+
}),
|
|
633
|
+
{ name: "memory_context" },
|
|
634
|
+
);
|
|
635
|
+
}
|
|
636
|
+
|
|
637
|
+
// --------------------------------------------------------------------------
|
|
638
|
+
// 9. memory_promote
|
|
639
|
+
// --------------------------------------------------------------------------
|
|
640
|
+
if (isToolEnabled("memory_promote")) {
|
|
641
|
+
api.registerTool((ctx) => ({
|
|
642
|
+
|
|
643
|
+
name: "memory_promote",
|
|
644
|
+
description:
|
|
645
|
+
"Promote a memory by updating its importance score and/or tier. Use to ensure critical memories are retained longer.",
|
|
646
|
+
parameters: {
|
|
647
|
+
type: "object",
|
|
648
|
+
properties: {
|
|
649
|
+
memory_id: {
|
|
650
|
+
type: "string",
|
|
651
|
+
description: "The memory ID to promote.",
|
|
652
|
+
},
|
|
653
|
+
importance: {
|
|
654
|
+
type: "number",
|
|
655
|
+
description: "New importance score (0-1).",
|
|
656
|
+
minimum: 0,
|
|
657
|
+
maximum: 1,
|
|
658
|
+
},
|
|
659
|
+
tier: {
|
|
660
|
+
type: "string",
|
|
661
|
+
description: "Target tier: hot, warm, or cold.",
|
|
662
|
+
enum: ["hot", "warm", "cold"],
|
|
663
|
+
},
|
|
664
|
+
},
|
|
665
|
+
required: ["memory_id", "importance"],
|
|
666
|
+
},
|
|
667
|
+
execute: async (_id, args: { memory_id: string; importance: number; tier?: string }) => {
|
|
668
|
+
try {
|
|
669
|
+
const result = await client.promote(args.memory_id, args.importance, args.tier);
|
|
670
|
+
return {
|
|
671
|
+
content: [
|
|
672
|
+
{
|
|
673
|
+
type: "text",
|
|
674
|
+
text: `Memory ${args.memory_id.slice(0, 8)}... promoted (importance: ${args.importance}${args.tier ? `, tier: ${args.tier}` : ""}).`,
|
|
675
|
+
},
|
|
676
|
+
],
|
|
677
|
+
details: { result },
|
|
678
|
+
};
|
|
679
|
+
} catch (err) {
|
|
680
|
+
return {
|
|
681
|
+
content: [{ type: "text", text: `Promote failed: ${String(err)}` }],
|
|
682
|
+
details: { error: String(err) },
|
|
683
|
+
};
|
|
684
|
+
}
|
|
685
|
+
},
|
|
686
|
+
}),
|
|
687
|
+
{ name: "memory_promote" },
|
|
688
|
+
);
|
|
689
|
+
}
|
|
690
|
+
}
|