shabti 1.13.0 → 1.14.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 +148 -40
- package/package.json +1 -1
- package/src/commands/config.js +18 -1
- package/src/repl/index.js +13 -1
- package/src/repl/slashCommands.js +69 -3
package/README.md
CHANGED
|
@@ -1,6 +1,8 @@
|
|
|
1
1
|
# Shabti
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
Agent Memory OS — semantic memory for AI agents.
|
|
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.
|
|
4
6
|
|
|
5
7
|
## Install
|
|
6
8
|
|
|
@@ -8,75 +10,181 @@ Demo CLI tool — showcasing npm-publishable CLI structure.
|
|
|
8
10
|
npm install -g shabti
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
### Prerequisites
|
|
14
|
+
|
|
15
|
+
Shabti requires a running Qdrant instance for vector storage:
|
|
12
16
|
|
|
13
17
|
```bash
|
|
14
|
-
|
|
18
|
+
docker run -d --name qdrant -p 6333:6333 -p 6334:6334 qdrant/qdrant
|
|
15
19
|
```
|
|
16
20
|
|
|
17
|
-
|
|
21
|
+
Verify the connection:
|
|
18
22
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
| `list` | Show sample task list |
|
|
23
|
-
| `spin` | Demo async operation with spinner |
|
|
23
|
+
```bash
|
|
24
|
+
shabti config setup --check
|
|
25
|
+
```
|
|
24
26
|
|
|
25
|
-
|
|
27
|
+
## Quick Start
|
|
26
28
|
|
|
27
29
|
```bash
|
|
28
|
-
|
|
29
|
-
|
|
30
|
+
# Store a memory
|
|
31
|
+
shabti store "Rust is a systems programming language"
|
|
32
|
+
|
|
33
|
+
# Search memories
|
|
34
|
+
shabti search "systems programming"
|
|
35
|
+
|
|
36
|
+
# Search with score explanation
|
|
37
|
+
shabti search "programming" --explain
|
|
30
38
|
|
|
31
|
-
|
|
32
|
-
|
|
39
|
+
# Check engine status
|
|
40
|
+
shabti status
|
|
33
41
|
```
|
|
34
42
|
|
|
35
|
-
|
|
43
|
+
## Commands
|
|
36
44
|
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
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 |
|
|
40
53
|
|
|
41
|
-
|
|
42
|
-
shabti list --json
|
|
54
|
+
### store
|
|
43
55
|
|
|
44
|
-
|
|
45
|
-
shabti
|
|
56
|
+
```bash
|
|
57
|
+
shabti store "Tokyo is the capital of Japan"
|
|
58
|
+
shabti store "meeting at 3pm" --namespace work
|
|
59
|
+
shabti store "buy groceries" --tags "todo,personal"
|
|
46
60
|
```
|
|
47
61
|
|
|
48
|
-
###
|
|
62
|
+
### search
|
|
49
63
|
|
|
50
64
|
```bash
|
|
51
|
-
|
|
52
|
-
shabti
|
|
65
|
+
shabti search "capital of Japan"
|
|
66
|
+
shabti search "meeting" --namespace work --limit 5
|
|
67
|
+
shabti search "programming" --explain # show score breakdown
|
|
68
|
+
shabti search "AI" --follow-links 2 # expand via graph links
|
|
69
|
+
shabti search "recent events" --min-score 0.5
|
|
70
|
+
shabti search "query" --json # JSON output
|
|
71
|
+
```
|
|
53
72
|
|
|
54
|
-
|
|
55
|
-
|
|
73
|
+
### snapshot
|
|
74
|
+
|
|
75
|
+
```bash
|
|
76
|
+
shabti snapshot create
|
|
77
|
+
shabti snapshot list
|
|
78
|
+
shabti snapshot restore <id>
|
|
56
79
|
```
|
|
57
80
|
|
|
58
|
-
|
|
81
|
+
### config
|
|
59
82
|
|
|
60
83
|
```bash
|
|
61
|
-
|
|
62
|
-
|
|
84
|
+
shabti config show
|
|
85
|
+
shabti config show --json
|
|
86
|
+
shabti config set qdrant_url http://localhost:6334
|
|
87
|
+
shabti config setup # show Qdrant setup instructions
|
|
88
|
+
shabti config setup --check # test Qdrant connection
|
|
89
|
+
```
|
|
63
90
|
|
|
64
|
-
|
|
65
|
-
npm test
|
|
91
|
+
## Interactive Mode (REPL)
|
|
66
92
|
|
|
67
|
-
|
|
68
|
-
npm run lint
|
|
93
|
+
Run `shabti` with no arguments in a terminal to enter interactive mode:
|
|
69
94
|
|
|
70
|
-
|
|
71
|
-
|
|
95
|
+
```
|
|
96
|
+
$ shabti
|
|
97
|
+
shabti v2.0.0
|
|
98
|
+
|
|
99
|
+
[info] Memory engine connected (/remember, /recall available)
|
|
100
|
+
[info] Interactive mode — model: gpt-4o-mini
|
|
101
|
+
|
|
102
|
+
you> /remember Tokyo is the capital of Japan
|
|
103
|
+
[ok] Remembered: a1b2c3d4-...
|
|
104
|
+
|
|
105
|
+
you> /recall capital
|
|
106
|
+
0.9234 Tokyo is the capital of Japan
|
|
107
|
+
|
|
108
|
+
you> /help
|
|
109
|
+
/help Show this help message
|
|
110
|
+
/exit Exit the REPL
|
|
111
|
+
/clear Clear conversation history
|
|
112
|
+
/model Show or switch the model
|
|
113
|
+
/history Show conversation history
|
|
114
|
+
/remember Store a memory
|
|
115
|
+
/recall Search memories
|
|
116
|
+
```
|
|
72
117
|
|
|
73
|
-
|
|
74
|
-
|
|
118
|
+
Requires `OPENAI_API_KEY` in `.env` for chat functionality. Memory commands (`/remember`, `/recall`) work with the local Qdrant engine.
|
|
119
|
+
|
|
120
|
+
## Node.js API
|
|
121
|
+
|
|
122
|
+
```javascript
|
|
123
|
+
import { ShabtiEngine } from "shabti";
|
|
124
|
+
|
|
125
|
+
const engine = new ShabtiEngine({
|
|
126
|
+
qdrantUrl: "http://localhost:6334",
|
|
127
|
+
collectionName: "my-app",
|
|
128
|
+
dataDir: "./data",
|
|
129
|
+
});
|
|
130
|
+
|
|
131
|
+
// Store
|
|
132
|
+
await engine.store("Rust is a systems programming language", {});
|
|
133
|
+
|
|
134
|
+
// Search
|
|
135
|
+
const results = await engine.executeQuery({
|
|
136
|
+
text: "systems programming",
|
|
137
|
+
limit: 10,
|
|
138
|
+
withExplanation: true,
|
|
139
|
+
});
|
|
140
|
+
|
|
141
|
+
for (const r of results) {
|
|
142
|
+
console.log(`${r.score.toFixed(4)} ${r.content}`);
|
|
143
|
+
if (r.explanation) {
|
|
144
|
+
console.log(
|
|
145
|
+
` sim=${r.explanation.semanticSimilarity.toFixed(3)} ` +
|
|
146
|
+
`decay=${r.explanation.timeDecayFactor.toFixed(3)} ` +
|
|
147
|
+
`boost=${r.explanation.accessBoostFactor.toFixed(3)}`,
|
|
148
|
+
);
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
await engine.shutdown();
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
## Architecture
|
|
156
|
+
|
|
157
|
+
```
|
|
158
|
+
shabti (npm CLI + Node.js API)
|
|
159
|
+
└── shabti-napi (Rust ↔ Node.js FFI via NAPI-RS)
|
|
160
|
+
└── shabti-engine (orchestration layer)
|
|
161
|
+
├── shabti-embedding (fastembed-rs, MultilingualE5Small 384-dim)
|
|
162
|
+
├── shabti-index (Qdrant vector DB client)
|
|
163
|
+
├── shabti-storage (append-only log, event store, snapshots)
|
|
164
|
+
├── shabti-graph (k-NN memory link graph)
|
|
165
|
+
└── shabti-core (data models, scoring, dedup, query DSL)
|
|
75
166
|
```
|
|
76
167
|
|
|
77
|
-
##
|
|
168
|
+
## Benchmarks
|
|
78
169
|
|
|
79
|
-
See [
|
|
170
|
+
See [BENCHMARKS.md](BENCHMARKS.md) for detailed results.
|
|
171
|
+
|
|
172
|
+
| Metric | Target | Measured |
|
|
173
|
+
| ---------- | -------- | ----------- |
|
|
174
|
+
| Recall@10 | >= 0.85 | **1.000** |
|
|
175
|
+
| Insert p99 | <= 100ms | **60.22ms** |
|
|
176
|
+
| Search p99 | <= 100ms | **58.71ms** |
|
|
177
|
+
|
|
178
|
+
## Development
|
|
179
|
+
|
|
180
|
+
```bash
|
|
181
|
+
npm install # install JS dependencies
|
|
182
|
+
cargo build # build Rust workspace
|
|
183
|
+
npm test # run JS tests (vitest)
|
|
184
|
+
cargo test # run Rust tests
|
|
185
|
+
npm run lint # eslint
|
|
186
|
+
cargo clippy # Rust linter
|
|
187
|
+
```
|
|
80
188
|
|
|
81
189
|
## License
|
|
82
190
|
|
package/package.json
CHANGED
package/src/commands/config.js
CHANGED
|
@@ -48,7 +48,8 @@ export function registerConfig(program) {
|
|
|
48
48
|
cmd
|
|
49
49
|
.command("setup")
|
|
50
50
|
.description("Show Qdrant setup instructions")
|
|
51
|
-
.
|
|
51
|
+
.option("--check", "Test connection to Qdrant")
|
|
52
|
+
.action(async (opts) => {
|
|
52
53
|
const config = loadConfig();
|
|
53
54
|
heading("Qdrant Setup");
|
|
54
55
|
console.log();
|
|
@@ -63,5 +64,21 @@ export function registerConfig(program) {
|
|
|
63
64
|
console.log(" To change the URL:");
|
|
64
65
|
console.log(` shabti config set qdrant_url ${chalk.dim("<url>")}`);
|
|
65
66
|
console.log();
|
|
67
|
+
|
|
68
|
+
if (opts.check) {
|
|
69
|
+
const restUrl = config.qdrant_url.replace(":6334", ":6333");
|
|
70
|
+
try {
|
|
71
|
+
const res = await fetch(`${restUrl}/healthz`);
|
|
72
|
+
if (res.ok) {
|
|
73
|
+
success("Qdrant is reachable and healthy.");
|
|
74
|
+
} else {
|
|
75
|
+
error(`Qdrant responded with status ${res.status}`);
|
|
76
|
+
process.exitCode = 1;
|
|
77
|
+
}
|
|
78
|
+
} catch (err) {
|
|
79
|
+
error(`Cannot reach Qdrant at ${restUrl}: ${err.message}`);
|
|
80
|
+
process.exitCode = 1;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
66
83
|
});
|
|
67
84
|
}
|
package/src/repl/index.js
CHANGED
|
@@ -18,6 +18,18 @@ export async function launchRepl() {
|
|
|
18
18
|
|
|
19
19
|
const model = "gpt-4o-mini";
|
|
20
20
|
|
|
21
|
+
// Try to connect memory engine (non-blocking)
|
|
22
|
+
let engine = null;
|
|
23
|
+
try {
|
|
24
|
+
const { createEngine } = await import("../core/engine.js");
|
|
25
|
+
engine = createEngine();
|
|
26
|
+
info(
|
|
27
|
+
`Memory engine connected (${chalk.cyan("/remember")}, ${chalk.cyan("/recall")} available)`,
|
|
28
|
+
);
|
|
29
|
+
} catch {
|
|
30
|
+
warn("Memory engine not available. Chat-only mode (start Qdrant to enable memory).");
|
|
31
|
+
}
|
|
32
|
+
|
|
21
33
|
console.log();
|
|
22
34
|
info(`Interactive mode — model: ${chalk.cyan(model)}`);
|
|
23
35
|
console.log(chalk.dim(" Type a message, or /help for commands. Ctrl+C or /exit to quit.\n"));
|
|
@@ -26,7 +38,7 @@ export async function launchRepl() {
|
|
|
26
38
|
apiKey,
|
|
27
39
|
model,
|
|
28
40
|
promptPrefix: "you> ",
|
|
29
|
-
onSlashCommand: handleSlashCommand,
|
|
41
|
+
onSlashCommand: (cmd, args, sess, rl) => handleSlashCommand(cmd, args, sess, rl, engine),
|
|
30
42
|
legacyExitWord: false,
|
|
31
43
|
});
|
|
32
44
|
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import chalk from "chalk";
|
|
2
|
-
import { success, info } from "../utils/style.js";
|
|
2
|
+
import { success, info, warn, error } from "../utils/style.js";
|
|
3
3
|
|
|
4
4
|
const COMMANDS = {
|
|
5
5
|
"/help": "Show this help message",
|
|
@@ -7,6 +7,8 @@ const COMMANDS = {
|
|
|
7
7
|
"/clear": "Clear conversation history",
|
|
8
8
|
"/model": "Show or switch the model (e.g. /model gpt-4o)",
|
|
9
9
|
"/history": "Show conversation history",
|
|
10
|
+
"/remember": "Store a memory (e.g. /remember Tokyo is the capital of Japan)",
|
|
11
|
+
"/recall": "Search memories (e.g. /recall capital of Japan)",
|
|
10
12
|
};
|
|
11
13
|
|
|
12
14
|
/**
|
|
@@ -17,9 +19,10 @@ const COMMANDS = {
|
|
|
17
19
|
* @param {string} args - Arguments after the command
|
|
18
20
|
* @param {import("../core/session.js").ChatSession} session
|
|
19
21
|
* @param {import("readline").Interface} rl
|
|
20
|
-
* @
|
|
22
|
+
* @param {object|null} [engine] - ShabtiEngine instance (nullable)
|
|
23
|
+
* @returns {boolean|Promise<boolean>}
|
|
21
24
|
*/
|
|
22
|
-
export function handleSlashCommand(cmd, args, session, rl) {
|
|
25
|
+
export function handleSlashCommand(cmd, args, session, rl, engine = null) {
|
|
23
26
|
switch (cmd) {
|
|
24
27
|
case "/help":
|
|
25
28
|
console.log();
|
|
@@ -66,7 +69,70 @@ export function handleSlashCommand(cmd, args, session, rl) {
|
|
|
66
69
|
return true;
|
|
67
70
|
}
|
|
68
71
|
|
|
72
|
+
case "/remember":
|
|
73
|
+
return handleRemember(args, engine);
|
|
74
|
+
|
|
75
|
+
case "/recall":
|
|
76
|
+
return handleRecall(args, engine);
|
|
77
|
+
|
|
69
78
|
default:
|
|
70
79
|
return false;
|
|
71
80
|
}
|
|
72
81
|
}
|
|
82
|
+
|
|
83
|
+
async function handleRemember(text, engine) {
|
|
84
|
+
if (!engine) {
|
|
85
|
+
console.log();
|
|
86
|
+
warn("Memory engine not available. Start Qdrant to enable memory features.");
|
|
87
|
+
console.log();
|
|
88
|
+
return true;
|
|
89
|
+
}
|
|
90
|
+
if (!text) {
|
|
91
|
+
console.log();
|
|
92
|
+
info("Usage: /remember <text to store>");
|
|
93
|
+
console.log();
|
|
94
|
+
return true;
|
|
95
|
+
}
|
|
96
|
+
try {
|
|
97
|
+
const result = await engine.store(text, {});
|
|
98
|
+
if (result.status === "stored") {
|
|
99
|
+
success(`Remembered: ${result.id}`);
|
|
100
|
+
} else {
|
|
101
|
+
info(`Already remembered (duplicate)`);
|
|
102
|
+
}
|
|
103
|
+
} catch (err) {
|
|
104
|
+
error(`Failed to store: ${err.message}`);
|
|
105
|
+
}
|
|
106
|
+
console.log();
|
|
107
|
+
return true;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
async function handleRecall(query, engine) {
|
|
111
|
+
if (!engine) {
|
|
112
|
+
console.log();
|
|
113
|
+
warn("Memory engine not available. Start Qdrant to enable memory features.");
|
|
114
|
+
console.log();
|
|
115
|
+
return true;
|
|
116
|
+
}
|
|
117
|
+
if (!query) {
|
|
118
|
+
console.log();
|
|
119
|
+
info("Usage: /recall <search query>");
|
|
120
|
+
console.log();
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
try {
|
|
124
|
+
const results = await engine.executeQuery({ text: query, limit: 5 });
|
|
125
|
+
console.log();
|
|
126
|
+
if (results.length === 0) {
|
|
127
|
+
console.log(chalk.dim(" No memories found."));
|
|
128
|
+
} else {
|
|
129
|
+
for (const r of results) {
|
|
130
|
+
console.log(` ${chalk.yellow(r.score.toFixed(4))} ${r.content}`);
|
|
131
|
+
}
|
|
132
|
+
}
|
|
133
|
+
} catch (err) {
|
|
134
|
+
error(`Failed to search: ${err.message}`);
|
|
135
|
+
}
|
|
136
|
+
console.log();
|
|
137
|
+
return true;
|
|
138
|
+
}
|