shabti 2.11.0 → 2.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
@@ -210,11 +210,13 @@ Or manually add to your MCP settings:
210
210
  | --------------- | ----------------------------------------------------------- |
211
211
  | `memory_store` | Store a memory entry with optional namespace, tags, and TTL |
212
212
  | `memory_search` | Search memories by semantic similarity |
213
+ | `memory_get` | Retrieve a specific memory entry by UUID |
213
214
  | `memory_delete` | Delete a memory entry by ID |
214
215
  | `memory_list` | List recent memory entries |
215
216
  | `memory_export` | Export entries as JSONL |
216
217
  | `memory_gc` | Garbage collect expired entries |
217
218
  | `memory_status` | Get engine status |
219
+ | `memory_health` | Run health checks on engine components |
218
220
 
219
221
  ### MCP Resources
220
222
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shabti",
3
- "version": "2.11.0",
3
+ "version": "2.12.0",
4
4
  "description": "Agent Memory OS — semantic memory for AI agents",
5
5
  "type": "module",
6
6
  "main": "native.cjs",
@@ -43,6 +43,54 @@ export function buildAgentCard(baseUrl) {
43
43
  inputModes: ["text"],
44
44
  outputModes: ["application/json"],
45
45
  },
46
+ {
47
+ id: "memory_get",
48
+ name: "Get Memory",
49
+ description: "Retrieve a specific memory entry by its ID",
50
+ tags: ["memory", "get", "retrieve", "fetch"],
51
+ inputModes: ["application/json"],
52
+ outputModes: ["application/json"],
53
+ },
54
+ {
55
+ id: "memory_delete",
56
+ name: "Delete Memory",
57
+ description: "Delete a specific memory entry by its ID",
58
+ tags: ["memory", "delete", "remove"],
59
+ inputModes: ["application/json"],
60
+ outputModes: ["application/json"],
61
+ },
62
+ {
63
+ id: "memory_list",
64
+ name: "List Memories",
65
+ description: "List stored memory entries with optional filters",
66
+ tags: ["memory", "list", "entries"],
67
+ inputModes: ["text", "application/json"],
68
+ outputModes: ["application/json"],
69
+ },
70
+ {
71
+ id: "memory_export",
72
+ name: "Export Memories",
73
+ description: "Export all memory entries as JSONL data",
74
+ tags: ["memory", "export", "dump", "backup"],
75
+ inputModes: ["text", "application/json"],
76
+ outputModes: ["application/json"],
77
+ },
78
+ {
79
+ id: "memory_gc",
80
+ name: "Garbage Collect",
81
+ description: "Remove expired memory entries and reclaim storage",
82
+ tags: ["memory", "gc", "garbage", "cleanup"],
83
+ inputModes: ["text"],
84
+ outputModes: ["application/json"],
85
+ },
86
+ {
87
+ id: "memory_health",
88
+ name: "Health Check",
89
+ description: "Check the health of the memory engine and its dependencies",
90
+ tags: ["memory", "health", "diagnostics"],
91
+ inputModes: ["text"],
92
+ outputModes: ["application/json"],
93
+ },
46
94
  ],
47
95
  };
48
96
  }
package/src/a2a/server.js CHANGED
@@ -122,6 +122,112 @@ function handleMemoryStatus(engine) {
122
122
  };
123
123
  }
124
124
 
125
+ async function handleMemoryDelete(engine, parts) {
126
+ let id = null;
127
+
128
+ for (const part of parts) {
129
+ if (part.kind === "data" && part.data?.id) {
130
+ id = part.data.id;
131
+ }
132
+ }
133
+
134
+ if (!id) {
135
+ throw Object.assign(new Error("Missing id in message parts"), { code: -32602 });
136
+ }
137
+
138
+ await engine.delete(id);
139
+ return {
140
+ artifactId: randomUUID(),
141
+ name: "delete_result",
142
+ parts: [{ kind: "data", data: { deleted: true, id } }],
143
+ };
144
+ }
145
+
146
+ async function handleMemoryGet(engine, parts) {
147
+ let id = null;
148
+
149
+ for (const part of parts) {
150
+ if (part.kind === "data" && part.data?.id) {
151
+ id = part.data.id;
152
+ }
153
+ }
154
+
155
+ if (!id) {
156
+ throw Object.assign(new Error("Missing id in message parts"), { code: -32602 });
157
+ }
158
+
159
+ const entry = await engine.get(id);
160
+ return {
161
+ artifactId: randomUUID(),
162
+ name: "get_result",
163
+ parts: [{ kind: "data", data: entry }],
164
+ };
165
+ }
166
+
167
+ function handleMemoryList(engine, parts) {
168
+ let opts = {};
169
+
170
+ for (const part of parts) {
171
+ if (part.kind === "data" && part.data) {
172
+ if (part.data.limit) opts.limit = part.data.limit;
173
+ if (part.data.namespace) opts.namespace = part.data.namespace;
174
+ }
175
+ }
176
+
177
+ const entries = engine.listEntries(opts);
178
+ return {
179
+ artifactId: randomUUID(),
180
+ name: "list_result",
181
+ parts: [{ kind: "data", data: { entries, count: entries.length } }],
182
+ };
183
+ }
184
+
185
+ function handleMemoryExport(engine) {
186
+ const entries = engine.listEntries();
187
+ const lines = entries.map((e) => JSON.stringify(e));
188
+ return {
189
+ artifactId: randomUUID(),
190
+ name: "export_result",
191
+ parts: [{ kind: "data", data: { entries: entries.length, data: lines.join("\n") } }],
192
+ };
193
+ }
194
+
195
+ async function handleMemoryGc(engine) {
196
+ const removed = await engine.gc();
197
+ return {
198
+ artifactId: randomUUID(),
199
+ name: "gc_result",
200
+ parts: [{ kind: "data", data: { removed } }],
201
+ };
202
+ }
203
+
204
+ async function handleMemoryHealth(engine) {
205
+ const checks = [];
206
+ const status = engine.status();
207
+ const qdrantUrl = status.qdrantUrl.replace(/:\d+$/, ":6333");
208
+
209
+ try {
210
+ const res = await fetch(`${qdrantUrl}/healthz`, { signal: AbortSignal.timeout(3000) });
211
+ checks.push({
212
+ name: "qdrant",
213
+ status: res.ok ? "ok" : "degraded",
214
+ message: res.ok ? "Reachable" : `HTTP ${res.status}`,
215
+ });
216
+ } catch (err) {
217
+ checks.push({ name: "qdrant", status: "error", message: err.message });
218
+ }
219
+
220
+ checks.push({ name: "engine", status: "ok", message: `${status.entryCount} entries` });
221
+ checks.push({ name: "embedding", status: "ok", message: `Model: ${engine.modelId()}` });
222
+
223
+ const healthy = checks.every((c) => c.status === "ok");
224
+ return {
225
+ artifactId: randomUUID(),
226
+ name: "health_result",
227
+ parts: [{ kind: "data", data: { healthy, checks } }],
228
+ };
229
+ }
230
+
125
231
  // ============================================================
126
232
  // Skill Router
127
233
  // ============================================================
@@ -140,6 +246,11 @@ export function resolveSkill(parts) {
140
246
  const lower = part.text.toLowerCase();
141
247
  if (/\b(store|save|remember)\b/.test(lower)) return "memory_store";
142
248
  if (/\b(search|find|recall|query)\b/.test(lower)) return "memory_search";
249
+ if (/\b(delete|remove)\b/.test(lower)) return "memory_delete";
250
+ if (/\b(get|retrieve)\b/.test(lower)) return "memory_get";
251
+ if (/\b(list)\b/.test(lower)) return "memory_list";
252
+ if (/\b(export|dump)\b/.test(lower)) return "memory_export";
253
+ if (/\b(garbage|cleanup)\b/.test(lower)) return "memory_gc";
143
254
  if (/\b(status|health|stats|info)\b/.test(lower)) return "memory_status";
144
255
  }
145
256
  }
@@ -149,6 +260,7 @@ export function resolveSkill(parts) {
149
260
  if (part.kind === "data" && part.data) {
150
261
  if (part.data.content) return "memory_store";
151
262
  if (part.data.query) return "memory_search";
263
+ if (part.data.id) return "memory_get";
152
264
  }
153
265
  }
154
266
 
@@ -204,6 +316,24 @@ async function handleMessageSend(engine, taskStore, params) {
204
316
  case "memory_status":
205
317
  artifact = handleMemoryStatus(engine);
206
318
  break;
319
+ case "memory_delete":
320
+ artifact = await handleMemoryDelete(engine, message.parts);
321
+ break;
322
+ case "memory_get":
323
+ artifact = await handleMemoryGet(engine, message.parts);
324
+ break;
325
+ case "memory_list":
326
+ artifact = handleMemoryList(engine, message.parts);
327
+ break;
328
+ case "memory_export":
329
+ artifact = handleMemoryExport(engine);
330
+ break;
331
+ case "memory_gc":
332
+ artifact = await handleMemoryGc(engine);
333
+ break;
334
+ case "memory_health":
335
+ artifact = await handleMemoryHealth(engine);
336
+ break;
207
337
  default:
208
338
  task.status = { state: "failed", timestamp: new Date().toISOString() };
209
339
  taskStore.set(task.id, task);
package/src/mcp/server.js CHANGED
@@ -103,6 +103,26 @@ const TOOLS = [
103
103
  properties: {},
104
104
  },
105
105
  },
106
+ {
107
+ name: "memory_health",
108
+ description:
109
+ "Run health checks on the memory engine (Qdrant connectivity, engine status, embedding model)",
110
+ inputSchema: {
111
+ type: "object",
112
+ properties: {},
113
+ },
114
+ },
115
+ {
116
+ name: "memory_get",
117
+ description: "Retrieve a specific memory entry by its UUID",
118
+ inputSchema: {
119
+ type: "object",
120
+ properties: {
121
+ id: { type: "string", description: "UUID of the memory entry to retrieve" },
122
+ },
123
+ required: ["id"],
124
+ },
125
+ },
106
126
  ];
107
127
 
108
128
  const RESOURCES = [
@@ -373,6 +393,78 @@ async function handleToolsCall(id, params) {
373
393
  }
374
394
  }
375
395
 
396
+ if (name === "memory_health") {
397
+ const checks = [];
398
+ const config = loadConfig();
399
+ const qdrantUrl = config.qdrant_url.replace(/:\d+$/, ":6333");
400
+ try {
401
+ const res = await fetch(`${qdrantUrl}/healthz`, {
402
+ signal: AbortSignal.timeout(3000),
403
+ });
404
+ checks.push({
405
+ name: "qdrant",
406
+ status: res.ok ? "ok" : "degraded",
407
+ message: res.ok ? "Reachable" : `HTTP ${res.status}`,
408
+ });
409
+ } catch (err) {
410
+ checks.push({ name: "qdrant", status: "error", message: err.message });
411
+ }
412
+
413
+ if (eng) {
414
+ try {
415
+ const status = eng.status();
416
+ checks.push({
417
+ name: "engine",
418
+ status: "ok",
419
+ message: `${status.entryCount} entries, tier: ${status.tier}`,
420
+ });
421
+ } catch (err) {
422
+ checks.push({ name: "engine", status: "error", message: err.message });
423
+ }
424
+ try {
425
+ const modelId = eng.modelId();
426
+ checks.push({ name: "embedding", status: "ok", message: modelId });
427
+ } catch (err) {
428
+ checks.push({ name: "embedding", status: "error", message: err.message });
429
+ }
430
+ } else {
431
+ checks.push({ name: "engine", status: "error", message: "Engine not available" });
432
+ }
433
+
434
+ const healthy = checks.every((c) => c.status === "ok");
435
+ return respond(id, {
436
+ content: [
437
+ {
438
+ type: "text",
439
+ text: JSON.stringify({ healthy, checks }, null, 2),
440
+ },
441
+ ],
442
+ });
443
+ }
444
+
445
+ if (name === "memory_get") {
446
+ if (!eng) {
447
+ return respondError(id, -32603, "Engine not available");
448
+ }
449
+ const entryId = args?.id;
450
+ if (!entryId) {
451
+ return respondError(id, -32602, "Missing required parameter: id");
452
+ }
453
+ try {
454
+ const entry = await eng.get(entryId);
455
+ return respond(id, {
456
+ content: [
457
+ {
458
+ type: "text",
459
+ text: JSON.stringify(entry, null, 2),
460
+ },
461
+ ],
462
+ });
463
+ } catch (err) {
464
+ return respondError(id, -32603, err.message);
465
+ }
466
+ }
467
+
376
468
  respondError(id, -32601, `Unknown tool: ${name}`);
377
469
  }
378
470