shabti 2.6.0 → 2.8.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
@@ -2,7 +2,7 @@
2
2
 
3
3
  Agent Memory OS — semantic memory for AI agents.
4
4
 
5
- A Rust-powered memory engine with Node.js CLI that provides semantic search, deduplication, time-decay scoring, and graph-based memory linking for AI agents.
5
+ A Rust-powered memory engine with Node.js CLI that provides semantic search, deduplication, time-decay scoring, and graph-based memory linking for AI agents. Integrates with Claude Code, Cursor, and other tools via MCP and A2A protocols.
6
6
 
7
7
  ## Install
8
8
 
@@ -30,11 +30,14 @@ shabti config setup --check
30
30
  # Store a memory
31
31
  shabti store "Rust is a systems programming language"
32
32
 
33
+ # Store with namespace and TTL (auto-expires after 1 hour)
34
+ shabti store "temp note" --namespace work --ttl 3600
35
+
33
36
  # Search memories
34
37
  shabti search "systems programming"
35
38
 
36
- # Search with score explanation
37
- shabti search "programming" --explain
39
+ # Search with score explanation and graph expansion
40
+ shabti search "programming" --explain --follow-links 2
38
41
 
39
42
  # Check engine status
40
43
  shabti status
@@ -42,14 +45,21 @@ shabti status
42
45
 
43
46
  ## Commands
44
47
 
45
- | Command | Description |
46
- | ----------------- | ---------------------------- |
47
- | `store <content>` | Store a memory entry |
48
- | `search <query>` | Search memory entries |
49
- | `status` | Show engine status |
50
- | `snapshot` | Manage storage snapshots |
51
- | `config` | Manage configuration |
52
- | `chat` | Interactive chat with OpenAI |
48
+ | Command | Description |
49
+ | ----------------- | ----------------------------------- |
50
+ | `store <content>` | Store a memory entry |
51
+ | `search <query>` | Search memory entries |
52
+ | `delete <id>` | Delete a memory entry by ID |
53
+ | `status` | Show engine status |
54
+ | `export` | Export memories as JSONL |
55
+ | `import <file>` | Import memories from JSONL |
56
+ | `snapshot` | Manage storage snapshots |
57
+ | `config` | Manage configuration |
58
+ | `gc` | Garbage collect expired entries |
59
+ | `health` | Run health checks on engine |
60
+ | `a2a` | Start A2A protocol server |
61
+ | `chat` | Interactive chat with OpenAI |
62
+ | `mcp-config` | Print MCP server configuration JSON |
53
63
 
54
64
  ### store
55
65
 
@@ -57,6 +67,7 @@ shabti status
57
67
  shabti store "Tokyo is the capital of Japan"
58
68
  shabti store "meeting at 3pm" --namespace work
59
69
  shabti store "buy groceries" --tags "todo,personal"
70
+ shabti store "temporary note" --ttl 3600 # expires in 1 hour
60
71
  ```
61
72
 
62
73
  ### search
@@ -70,6 +81,28 @@ shabti search "recent events" --min-score 0.5
70
81
  shabti search "query" --json # JSON output
71
82
  ```
72
83
 
84
+ ### delete
85
+
86
+ ```bash
87
+ shabti delete <uuid>
88
+ ```
89
+
90
+ ### export / import
91
+
92
+ ```bash
93
+ # Export all entries to stdout
94
+ shabti export
95
+
96
+ # Export to file with namespace filter
97
+ shabti export --namespace work --output backup.jsonl
98
+
99
+ # Import from JSONL file
100
+ shabti import backup.jsonl
101
+
102
+ # Import with namespace override and dry-run
103
+ shabti import data.jsonl --namespace imported --dry-run
104
+ ```
105
+
73
106
  ### snapshot
74
107
 
75
108
  ```bash
@@ -88,13 +121,18 @@ shabti config setup # show Qdrant setup instructions
88
121
  shabti config setup --check # test Qdrant connection
89
122
  ```
90
123
 
124
+ ### gc
125
+
126
+ ```bash
127
+ shabti gc # remove entries past their TTL
128
+ ```
129
+
91
130
  ## Interactive Mode (REPL)
92
131
 
93
132
  Run `shabti` with no arguments in a terminal to enter interactive mode:
94
133
 
95
134
  ```
96
135
  $ shabti
97
- shabti v2.0.0
98
136
 
99
137
  [info] Memory engine connected (/remember, /recall available)
100
138
  [info] Interactive mode — model: gpt-4o-mini
@@ -119,14 +157,14 @@ Requires `OPENAI_API_KEY` in `.env` for chat functionality. Memory commands (`/r
119
157
 
120
158
  ## MCP Server
121
159
 
122
- shabti includes an MCP (Model Context Protocol) server for integration with Claude Code, Cursor, and other MCP-compatible tools.
160
+ Shabti includes an MCP (Model Context Protocol) server for integration with Claude Code, Cursor, and other MCP-compatible tools.
123
161
 
124
162
  ```bash
125
163
  # Start the MCP server (stdio transport)
126
164
  npx shabti-mcp
127
165
  ```
128
166
 
129
- ### Claude Code configuration
167
+ ### Claude Code / Cursor configuration
130
168
 
131
169
  Generate the MCP settings JSON:
132
170
 
@@ -147,23 +185,57 @@ Or manually add to your MCP settings:
147
185
  }
148
186
  ```
149
187
 
150
- ### Available tools
188
+ ### MCP Tools
151
189
 
152
- | Tool | Description |
153
- | --------------- | -------------------------------------- |
154
- | `memory_store` | Store a memory entry |
155
- | `memory_search` | Search memories by semantic similarity |
156
- | `memory_delete` | Delete a memory entry by ID |
157
- | `memory_list` | List recent memory entries |
158
- | `memory_status` | Get engine status |
190
+ | Tool | Description |
191
+ | --------------- | ----------------------------------------------------------- |
192
+ | `memory_store` | Store a memory entry with optional namespace, tags, and TTL |
193
+ | `memory_search` | Search memories by semantic similarity |
194
+ | `memory_delete` | Delete a memory entry by ID |
195
+ | `memory_list` | List recent memory entries |
196
+ | `memory_export` | Export entries as JSONL |
197
+ | `memory_gc` | Garbage collect expired entries |
198
+ | `memory_status` | Get engine status |
159
199
 
160
- ### Available resources
200
+ ### MCP Resources
161
201
 
162
202
  | URI | Description |
163
203
  | ----------------- | ---------------------------- |
164
204
  | `shabti://status` | Engine status and statistics |
165
205
  | `shabti://config` | Current configuration |
166
206
 
207
+ ## A2A Protocol Server
208
+
209
+ Shabti supports Google's [Agent-to-Agent (A2A) protocol](https://google.github.io/A2A/) v0.3.0 for inter-agent communication.
210
+
211
+ ```bash
212
+ # Start the A2A server (default port 3000)
213
+ shabti a2a
214
+
215
+ # Start on a custom port
216
+ shabti a2a --port 4000
217
+ ```
218
+
219
+ ### Agent Card
220
+
221
+ Discoverable at `GET /.well-known/agent-card.json`.
222
+
223
+ ### Skills
224
+
225
+ | Skill | Description |
226
+ | --------------- | ------------------------------------ |
227
+ | `memory_store` | Store a memory entry via A2A message |
228
+ | `memory_search` | Semantic search via A2A message |
229
+ | `memory_status` | Engine status via A2A message |
230
+
231
+ ### JSON-RPC Methods
232
+
233
+ | Method | Description |
234
+ | -------------- | -------------------------------- |
235
+ | `message/send` | Send a message to invoke a skill |
236
+ | `tasks/get` | Retrieve task status and results |
237
+ | `tasks/cancel` | Cancel a running task |
238
+
167
239
  ## Node.js API
168
240
 
169
241
  ```javascript
@@ -176,7 +248,11 @@ const engine = new ShabtiEngine({
176
248
  });
177
249
 
178
250
  // Store
179
- await engine.store("Rust is a systems programming language", {});
251
+ await engine.store("Rust is a systems programming language", {
252
+ namespace: "tech",
253
+ tags: ["rust", "programming"],
254
+ ttlSeconds: 86400, // expires in 24 hours
255
+ });
180
256
 
181
257
  // Search
182
258
  const results = await engine.executeQuery({
@@ -187,29 +263,52 @@ const results = await engine.executeQuery({
187
263
 
188
264
  for (const r of results) {
189
265
  console.log(`${r.score.toFixed(4)} ${r.content}`);
190
- if (r.explanation) {
191
- console.log(
192
- ` sim=${r.explanation.semanticSimilarity.toFixed(3)} ` +
193
- `decay=${r.explanation.timeDecayFactor.toFixed(3)} ` +
194
- `boost=${r.explanation.accessBoostFactor.toFixed(3)}`,
195
- );
196
- }
197
266
  }
198
267
 
268
+ // Export
269
+ const entries = engine.listEntries({ namespace: "tech", limit: 100 });
270
+
271
+ // Garbage collect
272
+ const removed = await engine.gc();
273
+
199
274
  await engine.shutdown();
200
275
  ```
201
276
 
277
+ ## Configuration
278
+
279
+ ### Environment Variables
280
+
281
+ | Variable | Description | Default |
282
+ | ------------------- | ----------------------------------------------------- | ----------------------- |
283
+ | `SHABTI_QDRANT_URL` | Override Qdrant URL | `http://localhost:6334` |
284
+ | `SHABTI_LOG_LEVEL` | Log level: `debug`, `info`, `warn`, `error`, `silent` | `info` |
285
+ | `SHABTI_LOG_JSON` | Set to `1` for JSON-formatted log output | — |
286
+ | `SHABTI_A2A_PORT` | A2A server port (standalone mode) | `3000` |
287
+ | `OPENAI_API_KEY` | Required for chat/REPL mode | — |
288
+
289
+ ### Config File
290
+
291
+ Stored at `~/.shabti/config.json`. Manage via `shabti config` commands.
292
+
202
293
  ## Architecture
203
294
 
204
295
  ```
205
296
  shabti (npm CLI + Node.js API)
206
- └── shabti-napi (Rust Node.js FFI via NAPI-RS)
207
- └── shabti-engine (orchestration layer)
208
- ├── shabti-embedding (fastembed-rs, MultilingualE5Small 384-dim)
209
- ├── shabti-index (Qdrant vector DB client)
210
- ├── shabti-storage (append-only log, event store, snapshots)
211
- ├── shabti-graph (k-NN memory link graph)
212
- └── shabti-core (data models, scoring, dedup, query DSL)
297
+ ├── src/commands/ 12 CLI commands (Commander.js)
298
+ ├── src/repl/ Interactive REPL with slash commands
299
+ ├── src/mcp/ MCP server (stdio, 7 tools, 2 resources)
300
+ ├── src/a2a/ A2A server (HTTP, JSON-RPC 2.0)
301
+ ├── src/core/ Engine factory, session, retry logic
302
+ ├── src/utils/ Style, validation, structured logger
303
+ └── native.cjs NAPI-RS bridge
304
+ └── crates/
305
+ ├── shabti-engine Orchestration, store/search/gc
306
+ ├── shabti-core Data models, scoring, dedup, query DSL
307
+ ├── shabti-embedding fastembed-rs (MultilingualE5Small, 384-dim)
308
+ ├── shabti-index Qdrant vector DB client
309
+ ├── shabti-storage Append-only log, event store, snapshots
310
+ ├── shabti-graph k-NN memory link graph
311
+ └── shabti-napi NAPI-RS bindings
213
312
  ```
214
313
 
215
314
  ## Benchmarks
@@ -227,10 +326,10 @@ See [BENCHMARKS.md](BENCHMARKS.md) for detailed results.
227
326
  ```bash
228
327
  npm install # install JS dependencies
229
328
  cargo build # build Rust workspace
230
- npm test # run JS tests (vitest)
231
- cargo test # run Rust tests
329
+ npm test # run JS tests (vitest) — 129 tests
330
+ cargo test # run Rust tests — 26 test modules
232
331
  npm run lint # eslint
233
- cargo clippy # Rust linter
332
+ npm run ci # lint + format check + audit + coverage
234
333
  ```
235
334
 
236
335
  ## License
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "shabti",
3
- "version": "2.6.0",
3
+ "version": "2.8.0",
4
4
  "description": "Agent Memory OS — semantic memory for AI agents",
5
5
  "type": "module",
6
6
  "main": "native.cjs",
package/src/a2a/server.js CHANGED
@@ -325,7 +325,7 @@ export function startA2AServer(port = 3000) {
325
325
  let body;
326
326
  try {
327
327
  body = await readBody(req);
328
- } catch {
328
+ } catch (_) {
329
329
  res.writeHead(400);
330
330
  return res.end();
331
331
  }
@@ -333,7 +333,7 @@ export function startA2AServer(port = 3000) {
333
333
  let rpc;
334
334
  try {
335
335
  rpc = JSON.parse(body);
336
- } catch {
336
+ } catch (_) {
337
337
  res.writeHead(200, { "Content-Type": "application/json" });
338
338
  return res.end(
339
339
  JSON.stringify({
@@ -376,7 +376,7 @@ export function startA2AServer(port = 3000) {
376
376
  if (engine && engine.shutdown) {
377
377
  try {
378
378
  await engine.shutdown();
379
- } catch {
379
+ } catch (_) {
380
380
  // best-effort
381
381
  }
382
382
  }
@@ -0,0 +1,20 @@
1
+ import { createEngine } from "../core/engine.js";
2
+ import { success, error } from "../utils/style.js";
3
+
4
+ export function registerDelete(program) {
5
+ program
6
+ .command("delete")
7
+ .description("Delete a memory entry by ID")
8
+ .argument("<id>", "UUID of the memory entry to delete")
9
+ .action(async (id) => {
10
+ try {
11
+ const engine = createEngine();
12
+ await engine.delete(id);
13
+ success(`Deleted: ${id}`);
14
+ await engine.shutdown();
15
+ } catch (err) {
16
+ error(err.message);
17
+ process.exitCode = 1;
18
+ }
19
+ });
20
+ }
@@ -0,0 +1,104 @@
1
+ import chalk from "chalk";
2
+ import { createEngine, loadConfig } from "../core/engine.js";
3
+ import { heading, success, warn } from "../utils/style.js";
4
+
5
+ export function registerHealth(program) {
6
+ program
7
+ .command("health")
8
+ .description("Run health checks on the shabti engine")
9
+ .option("-j, --json", "Output as JSON")
10
+ .action(async (opts) => {
11
+ const checks = [];
12
+
13
+ // 1. Qdrant connectivity
14
+ const config = loadConfig();
15
+ const qdrantUrl = config.qdrant_url.replace(/:\d+$/, ":6333");
16
+ try {
17
+ const res = await fetch(`${qdrantUrl}/healthz`, {
18
+ signal: AbortSignal.timeout(3000),
19
+ });
20
+ checks.push({
21
+ name: "qdrant",
22
+ status: res.ok ? "ok" : "degraded",
23
+ message: res.ok ? "Reachable" : `HTTP ${res.status}`,
24
+ });
25
+ } catch (err) {
26
+ checks.push({
27
+ name: "qdrant",
28
+ status: "error",
29
+ message: err.message,
30
+ });
31
+ }
32
+
33
+ // 2. Engine initialization
34
+ let engine = null;
35
+ try {
36
+ engine = createEngine();
37
+ const status = engine.status();
38
+ checks.push({
39
+ name: "engine",
40
+ status: "ok",
41
+ message: `${status.entryCount} entries, tier: ${status.tier}`,
42
+ });
43
+ } catch (err) {
44
+ checks.push({
45
+ name: "engine",
46
+ status: "error",
47
+ message: err.message,
48
+ });
49
+ }
50
+
51
+ // 3. Embedding model
52
+ if (engine) {
53
+ try {
54
+ const modelId = engine.modelId();
55
+ checks.push({
56
+ name: "embedding",
57
+ status: "ok",
58
+ message: modelId,
59
+ });
60
+ } catch (err) {
61
+ checks.push({
62
+ name: "embedding",
63
+ status: "error",
64
+ message: err.message,
65
+ });
66
+ }
67
+ }
68
+
69
+ const allOk = checks.every((c) => c.status === "ok");
70
+
71
+ if (opts.json) {
72
+ console.log(JSON.stringify({ healthy: allOk, checks }, null, 2));
73
+ } else {
74
+ heading("Health Check");
75
+ console.log();
76
+ for (const check of checks) {
77
+ const icon =
78
+ check.status === "ok"
79
+ ? chalk.green("✓")
80
+ : check.status === "degraded"
81
+ ? chalk.yellow("⚠")
82
+ : chalk.red("✗");
83
+ console.log(` ${icon} ${chalk.cyan(check.name.padEnd(12))} ${check.message}`);
84
+ }
85
+ console.log();
86
+ if (allOk) {
87
+ success("All checks passed");
88
+ } else {
89
+ warn("Some checks failed");
90
+ }
91
+ console.log();
92
+ }
93
+
94
+ if (engine) {
95
+ try {
96
+ await engine.shutdown();
97
+ } catch (_) {
98
+ // best-effort
99
+ }
100
+ }
101
+
102
+ if (!allOk) process.exitCode = 1;
103
+ });
104
+ }
@@ -32,7 +32,7 @@ export function registerImport(program) {
32
32
  for (let i = 0; i < lines.length; i++) {
33
33
  try {
34
34
  entries.push(JSON.parse(lines[i]));
35
- } catch {
35
+ } catch (_) {
36
36
  warn(`Skipping invalid JSON on line ${i + 1}`);
37
37
  }
38
38
  }
@@ -26,7 +26,7 @@ export function loadConfig() {
26
26
  if (existsSync(CONFIG_PATH)) {
27
27
  try {
28
28
  config = { ...config, ...JSON.parse(readFileSync(CONFIG_PATH, "utf8")) };
29
- } catch {
29
+ } catch (_) {
30
30
  // keep defaults
31
31
  }
32
32
  }
package/src/index.js CHANGED
@@ -15,6 +15,8 @@ import { registerStatus } from "./commands/status.js";
15
15
  import { registerExport } from "./commands/export.js";
16
16
  import { registerImport } from "./commands/import.js";
17
17
  import { registerStore } from "./commands/store.js";
18
+ import { registerDelete } from "./commands/delete.js";
19
+ import { registerHealth } from "./commands/health.js";
18
20
 
19
21
  const { version } = JSON.parse(readFileSync(new URL("../package.json", import.meta.url), "utf8"));
20
22
 
@@ -84,6 +86,8 @@ function buildProgram() {
84
86
  registerExport(program);
85
87
  registerImport(program);
86
88
  registerStore(program);
89
+ registerDelete(program);
90
+ registerHealth(program);
87
91
 
88
92
  program
89
93
  .command("gc")
package/src/mcp/server.js CHANGED
@@ -428,7 +428,7 @@ async function handleRequest(line) {
428
428
  let req;
429
429
  try {
430
430
  req = JSON.parse(line);
431
- } catch {
431
+ } catch (_) {
432
432
  return respondError(null, -32700, "Parse error");
433
433
  }
434
434
 
@@ -465,7 +465,7 @@ async function shutdown() {
465
465
  if (engine && engine.shutdown) {
466
466
  try {
467
467
  await engine.shutdown();
468
- } catch {
468
+ } catch (_) {
469
469
  // best-effort
470
470
  }
471
471
  }
package/src/repl/index.js CHANGED
@@ -26,7 +26,7 @@ export async function launchRepl() {
26
26
  info(
27
27
  `Memory engine connected (${chalk.cyan("/remember")}, ${chalk.cyan("/recall")} available)`,
28
28
  );
29
- } catch {
29
+ } catch (_) {
30
30
  warn("Memory engine not available. Chat-only mode (start Qdrant to enable memory).");
31
31
  }
32
32