audrey 0.16.1 → 0.17.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/LICENSE +21 -21
- package/README.md +310 -643
- package/benchmarks/baselines.js +169 -0
- package/benchmarks/cases.js +421 -0
- package/benchmarks/reference-results.js +70 -0
- package/benchmarks/report.js +255 -0
- package/benchmarks/run.js +514 -0
- package/docs/assets/benchmarks/local-benchmark.svg +45 -0
- package/docs/assets/benchmarks/operations-benchmark.svg +45 -0
- package/docs/assets/benchmarks/published-memory-standards.svg +50 -0
- package/docs/benchmarking.md +151 -0
- package/docs/production-readiness.md +96 -0
- package/examples/fintech-ops-demo.js +67 -0
- package/examples/healthcare-ops-demo.js +67 -0
- package/examples/stripe-demo.js +105 -0
- package/mcp-server/config.js +80 -27
- package/mcp-server/index.js +611 -75
- package/mcp-server/serve.js +482 -0
- package/package.json +24 -5
- package/src/audrey.js +51 -13
- package/src/consolidate.js +70 -54
- package/src/db.js +22 -1
- package/src/embedding.js +16 -12
- package/src/encode.js +8 -2
- package/src/fts.js +134 -0
- package/src/import.js +28 -0
- package/src/llm.js +6 -3
- package/src/migrate.js +2 -2
- package/src/recall.js +253 -32
- package/src/utils.js +25 -0
- package/types/index.d.ts +434 -0
|
@@ -0,0 +1,482 @@
|
|
|
1
|
+
import { createServer } from 'node:http';
|
|
2
|
+
import { timingSafeEqual } from 'node:crypto';
|
|
3
|
+
import { dirname, join } from 'node:path';
|
|
4
|
+
import { unlinkSync } from 'node:fs';
|
|
5
|
+
import { Audrey } from '../src/index.js';
|
|
6
|
+
import { buildAudreyConfig } from './config.js';
|
|
7
|
+
import { VERSION } from './config.js';
|
|
8
|
+
|
|
9
|
+
const DEFAULT_PORT = 3487;
|
|
10
|
+
const MAX_BODY = 10 * 1024 * 1024; // 10 MB
|
|
11
|
+
|
|
12
|
+
function getDashboardHTML() {
|
|
13
|
+
return `<!DOCTYPE html>
|
|
14
|
+
<html lang="en">
|
|
15
|
+
<head>
|
|
16
|
+
<meta charset="utf-8">
|
|
17
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
18
|
+
<title>Audrey Memory Dashboard</title>
|
|
19
|
+
<style>
|
|
20
|
+
:root { --bg: #0f0f0f; --card: #1a1a1a; --border: #2a2a2a; --text: #e0e0e0; --dim: #888; --accent: #6c9; --warn: #e94; --err: #e55; }
|
|
21
|
+
* { margin: 0; padding: 0; box-sizing: border-box; }
|
|
22
|
+
body { font-family: 'SF Mono', 'Cascadia Code', 'Consolas', monospace; background: var(--bg); color: var(--text); padding: 24px; max-width: 1200px; margin: 0 auto; }
|
|
23
|
+
h1 { font-size: 1.4em; margin-bottom: 4px; color: var(--accent); }
|
|
24
|
+
.subtitle { color: var(--dim); font-size: 0.85em; margin-bottom: 24px; }
|
|
25
|
+
.grid { display: grid; grid-template-columns: repeat(auto-fit, minmax(260px, 1fr)); gap: 16px; margin-bottom: 24px; }
|
|
26
|
+
.card { background: var(--card); border: 1px solid var(--border); border-radius: 8px; padding: 16px; }
|
|
27
|
+
.card h3 { font-size: 0.8em; color: var(--dim); text-transform: uppercase; letter-spacing: 0.05em; margin-bottom: 8px; }
|
|
28
|
+
.stat { font-size: 2em; font-weight: bold; color: var(--accent); }
|
|
29
|
+
.stat.warn { color: var(--warn); }
|
|
30
|
+
.stat.err { color: var(--err); }
|
|
31
|
+
.stat-label { font-size: 0.75em; color: var(--dim); }
|
|
32
|
+
table { width: 100%; border-collapse: collapse; font-size: 0.8em; }
|
|
33
|
+
th { text-align: left; color: var(--dim); font-weight: normal; padding: 6px 8px; border-bottom: 1px solid var(--border); }
|
|
34
|
+
td { padding: 6px 8px; border-bottom: 1px solid var(--border); max-width: 400px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
|
|
35
|
+
.badge { display: inline-block; padding: 2px 8px; border-radius: 4px; font-size: 0.75em; }
|
|
36
|
+
.badge-active, .badge-completed { background: #1a3a2a; color: var(--accent); }
|
|
37
|
+
.badge-dormant { background: #3a2a1a; color: var(--warn); }
|
|
38
|
+
.badge-open, .badge-failed { background: #3a1a1a; color: var(--err); }
|
|
39
|
+
.refresh { position: fixed; top: 16px; right: 16px; background: var(--card); border: 1px solid var(--border); color: var(--text); padding: 8px 16px; border-radius: 6px; cursor: pointer; font-family: inherit; }
|
|
40
|
+
.refresh:hover { border-color: var(--accent); }
|
|
41
|
+
</style>
|
|
42
|
+
</head>
|
|
43
|
+
<body>
|
|
44
|
+
<h1>Audrey</h1>
|
|
45
|
+
<p class="subtitle">Memory Health Dashboard</p>
|
|
46
|
+
<button class="refresh" onclick="load()">Refresh</button>
|
|
47
|
+
<div id="content"><p style="color:#888">Loading...</p></div>
|
|
48
|
+
<script>
|
|
49
|
+
function esc(s) {
|
|
50
|
+
const d = document.createElement('div');
|
|
51
|
+
d.textContent = s;
|
|
52
|
+
return d.innerHTML;
|
|
53
|
+
}
|
|
54
|
+
function truncate(s, n) { return s.length > n ? esc(s.slice(0, n)) + '...' : esc(s); }
|
|
55
|
+
|
|
56
|
+
async function load() {
|
|
57
|
+
try {
|
|
58
|
+
const [s, a] = await Promise.all([
|
|
59
|
+
fetch('/status').then(r => r.json()),
|
|
60
|
+
fetch('/analytics').then(r => r.json()),
|
|
61
|
+
]);
|
|
62
|
+
render(s, a);
|
|
63
|
+
} catch (e) {
|
|
64
|
+
document.getElementById('content').textContent = 'Failed to load: ' + e.message;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function render(s, a) {
|
|
69
|
+
const ct = s.contradictions;
|
|
70
|
+
const el = document.getElementById('content');
|
|
71
|
+
el.innerHTML = '';
|
|
72
|
+
|
|
73
|
+
// Stats grid
|
|
74
|
+
const grid = document.createElement('div');
|
|
75
|
+
grid.className = 'grid';
|
|
76
|
+
const cards = [
|
|
77
|
+
['Episodic', s.episodic, 'raw events'],
|
|
78
|
+
['Semantic', s.semantic, 'consolidated principles'],
|
|
79
|
+
['Procedural', s.procedural, 'learned workflows'],
|
|
80
|
+
['Causal Links', s.causalLinks, 'cause-effect pairs'],
|
|
81
|
+
['Dormant', s.dormant, 'below threshold', s.dormant > 0 ? 'warn' : ''],
|
|
82
|
+
['Contradictions', ct.open + ' open', (ct.open+ct.resolved+ct.context_dependent+ct.reopened) + ' total', ct.open > 0 ? 'err' : ''],
|
|
83
|
+
['Consolidations', s.totalConsolidationRuns, s.lastConsolidation ? 'Last: ' + new Date(s.lastConsolidation).toLocaleString() : 'Never'],
|
|
84
|
+
];
|
|
85
|
+
for (const [title, value, label, cls] of cards) {
|
|
86
|
+
const c = document.createElement('div');
|
|
87
|
+
c.className = 'card';
|
|
88
|
+
c.innerHTML = '<h3>' + esc(title) + '</h3><div class="stat ' + (cls||'') + '">' + esc(String(value)) + '</div><div class="stat-label">' + esc(label) + '</div>';
|
|
89
|
+
grid.appendChild(c);
|
|
90
|
+
}
|
|
91
|
+
el.appendChild(grid);
|
|
92
|
+
|
|
93
|
+
// Agent activity
|
|
94
|
+
if (a.agents && a.agents.length > 0) {
|
|
95
|
+
const card = document.createElement('div');
|
|
96
|
+
card.className = 'card';
|
|
97
|
+
card.style.marginBottom = '16px';
|
|
98
|
+
let html = '<h3>Agent Activity</h3><table><tr><th>Agent</th><th>Memories</th></tr>';
|
|
99
|
+
for (const ag of a.agents) html += '<tr><td>' + esc(ag.agent) + '</td><td>' + ag.count + '</td></tr>';
|
|
100
|
+
html += '</table>';
|
|
101
|
+
card.innerHTML = html;
|
|
102
|
+
el.appendChild(card);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Top semantics
|
|
106
|
+
if (a.topSemantics && a.topSemantics.length > 0) {
|
|
107
|
+
const card = document.createElement('div');
|
|
108
|
+
card.className = 'card';
|
|
109
|
+
card.style.marginBottom = '16px';
|
|
110
|
+
let html = '<h3>Top Semantic Principles</h3><table><tr><th>Content</th><th>Retrieved</th><th>Used</th><th>State</th></tr>';
|
|
111
|
+
for (const sem of a.topSemantics) {
|
|
112
|
+
html += '<tr><td title="' + esc(sem.content) + '">' + truncate(sem.content, 80) + '</td><td>' + sem.retrieval_count + '</td><td>' + (sem.usage_count||0) + '</td><td><span class="badge badge-' + esc(sem.state) + '">' + esc(sem.state) + '</span></td></tr>';
|
|
113
|
+
}
|
|
114
|
+
html += '</table>';
|
|
115
|
+
card.innerHTML = html;
|
|
116
|
+
el.appendChild(card);
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
// Recent episodes
|
|
120
|
+
if (a.topEpisodes && a.topEpisodes.length > 0) {
|
|
121
|
+
const card = document.createElement('div');
|
|
122
|
+
card.className = 'card';
|
|
123
|
+
card.style.marginBottom = '16px';
|
|
124
|
+
let html = '<h3>Recent Episodes</h3><table><tr><th>Content</th><th>Used</th><th>Created</th></tr>';
|
|
125
|
+
for (const ep of a.topEpisodes.slice(0, 10)) {
|
|
126
|
+
html += '<tr><td title="' + esc(ep.content) + '">' + truncate(ep.content, 80) + '</td><td>' + (ep.usage_count||0) + '</td><td>' + new Date(ep.created_at).toLocaleDateString() + '</td></tr>';
|
|
127
|
+
}
|
|
128
|
+
html += '</table>';
|
|
129
|
+
card.innerHTML = html;
|
|
130
|
+
el.appendChild(card);
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// Consolidation history
|
|
134
|
+
if (a.recentRuns && a.recentRuns.length > 0) {
|
|
135
|
+
const card = document.createElement('div');
|
|
136
|
+
card.className = 'card';
|
|
137
|
+
card.style.marginBottom = '16px';
|
|
138
|
+
let html = '<h3>Consolidation History</h3><table><tr><th>Started</th><th>Status</th><th>Duration</th></tr>';
|
|
139
|
+
for (const run of a.recentRuns.slice(0, 10)) {
|
|
140
|
+
const dur = run.completed_at && run.started_at ? ((new Date(run.completed_at) - new Date(run.started_at)) / 1000).toFixed(1) + 's' : '-';
|
|
141
|
+
html += '<tr><td>' + new Date(run.started_at).toLocaleString() + '</td><td><span class="badge badge-' + esc(run.status) + '">' + esc(run.status) + '</span></td><td>' + dur + '</td></tr>';
|
|
142
|
+
}
|
|
143
|
+
html += '</table>';
|
|
144
|
+
card.innerHTML = html;
|
|
145
|
+
el.appendChild(card);
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
const footer = document.createElement('p');
|
|
149
|
+
footer.style.cssText = 'color:#888;font-size:0.75em;margin-top:24px';
|
|
150
|
+
footer.textContent = 'Audrey v${VERSION} — refreshed ' + new Date().toLocaleTimeString();
|
|
151
|
+
el.appendChild(footer);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
load();
|
|
155
|
+
setInterval(load, 30000);
|
|
156
|
+
</script>
|
|
157
|
+
</body>
|
|
158
|
+
</html>`;
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
function parseBody(req) {
|
|
162
|
+
return new Promise((resolve, reject) => {
|
|
163
|
+
const chunks = [];
|
|
164
|
+
let size = 0;
|
|
165
|
+
let settled = false;
|
|
166
|
+
const fail = (err) => { if (!settled) { settled = true; reject(err); } };
|
|
167
|
+
const succeed = (val) => { if (!settled) { settled = true; resolve(val); } };
|
|
168
|
+
req.on('data', chunk => {
|
|
169
|
+
if (settled) return;
|
|
170
|
+
size += chunk.length;
|
|
171
|
+
if (size > MAX_BODY) {
|
|
172
|
+
req.destroy();
|
|
173
|
+
fail(new Error('Request body too large'));
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
chunks.push(chunk);
|
|
177
|
+
});
|
|
178
|
+
req.on('end', () => {
|
|
179
|
+
const raw = Buffer.concat(chunks).toString('utf-8');
|
|
180
|
+
if (!raw) return succeed({});
|
|
181
|
+
try {
|
|
182
|
+
succeed(JSON.parse(raw));
|
|
183
|
+
} catch {
|
|
184
|
+
fail(new Error('Invalid JSON'));
|
|
185
|
+
}
|
|
186
|
+
});
|
|
187
|
+
req.on('error', (err) => fail(err));
|
|
188
|
+
});
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
function json(res, status, data) {
|
|
192
|
+
const body = JSON.stringify(data);
|
|
193
|
+
res.writeHead(status, {
|
|
194
|
+
'Content-Type': 'application/json',
|
|
195
|
+
'Content-Length': Buffer.byteLength(body),
|
|
196
|
+
'Access-Control-Allow-Origin': '*',
|
|
197
|
+
'X-Content-Type-Options': 'nosniff',
|
|
198
|
+
'Cache-Control': 'no-store',
|
|
199
|
+
});
|
|
200
|
+
res.end(body);
|
|
201
|
+
}
|
|
202
|
+
|
|
203
|
+
function cors(res) {
|
|
204
|
+
res.writeHead(204, {
|
|
205
|
+
'Access-Control-Allow-Origin': '*',
|
|
206
|
+
'Access-Control-Allow-Methods': 'GET, POST, OPTIONS',
|
|
207
|
+
'Access-Control-Allow-Headers': 'Content-Type, Authorization',
|
|
208
|
+
'Access-Control-Max-Age': '86400',
|
|
209
|
+
});
|
|
210
|
+
res.end();
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
function route(method, pathname) {
|
|
214
|
+
return `${method} ${pathname}`;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
async function drainAndCloseAudrey(audrey) {
|
|
218
|
+
if (audrey && typeof audrey.waitForIdle === 'function') {
|
|
219
|
+
await audrey.waitForIdle();
|
|
220
|
+
}
|
|
221
|
+
audrey?.close();
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
/**
|
|
225
|
+
* Creates an HTTP server wrapping an Audrey instance.
|
|
226
|
+
* @param {Audrey} audrey - The Audrey instance to serve
|
|
227
|
+
* @param {{ apiKey?: string, audreyFactory?: () => Audrey }} options
|
|
228
|
+
*/
|
|
229
|
+
export function createAudreyServer(audrey, options = {}) {
|
|
230
|
+
const apiKey = options.apiKey || null;
|
|
231
|
+
const audreyFactory = options.audreyFactory || null;
|
|
232
|
+
|
|
233
|
+
// Mutable holder so restore can swap the instance
|
|
234
|
+
const ctx = { audrey };
|
|
235
|
+
|
|
236
|
+
function authenticate(req, res) {
|
|
237
|
+
if (!apiKey) return true;
|
|
238
|
+
const auth = req.headers.authorization || '';
|
|
239
|
+
const expected = `Bearer ${apiKey}`;
|
|
240
|
+
if (auth.length === expected.length &&
|
|
241
|
+
timingSafeEqual(Buffer.from(auth), Buffer.from(expected))) {
|
|
242
|
+
return true;
|
|
243
|
+
}
|
|
244
|
+
json(res, 401, { error: 'Unauthorized' });
|
|
245
|
+
return false;
|
|
246
|
+
}
|
|
247
|
+
|
|
248
|
+
const server = createServer(async (req, res) => {
|
|
249
|
+
const url = new URL(req.url, `http://${req.headers.host}`);
|
|
250
|
+
const key = route(req.method, url.pathname);
|
|
251
|
+
|
|
252
|
+
if (req.method === 'OPTIONS') {
|
|
253
|
+
cors(res);
|
|
254
|
+
return;
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
if (!authenticate(req, res)) return;
|
|
258
|
+
|
|
259
|
+
const requestAgent = req.headers['x-audrey-agent'] || null;
|
|
260
|
+
|
|
261
|
+
try {
|
|
262
|
+
switch (key) {
|
|
263
|
+
case 'GET /health': {
|
|
264
|
+
json(res, 200, { ok: true, version: VERSION });
|
|
265
|
+
break;
|
|
266
|
+
}
|
|
267
|
+
|
|
268
|
+
case 'GET /status': {
|
|
269
|
+
const stats = ctx.audrey.introspect();
|
|
270
|
+
json(res, 200, stats);
|
|
271
|
+
break;
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
case 'GET /analytics': {
|
|
275
|
+
const db = ctx.audrey.db;
|
|
276
|
+
const topEpisodes = db.prepare(
|
|
277
|
+
'SELECT id, content, usage_count, created_at FROM episodes ORDER BY usage_count DESC LIMIT 10'
|
|
278
|
+
).all();
|
|
279
|
+
const topSemantics = db.prepare(
|
|
280
|
+
"SELECT id, content, retrieval_count, usage_count, state FROM semantics WHERE state != 'rolled_back' ORDER BY retrieval_count DESC LIMIT 10"
|
|
281
|
+
).all();
|
|
282
|
+
const recentRuns = db.prepare(
|
|
283
|
+
'SELECT * FROM consolidation_runs ORDER BY started_at DESC LIMIT 20'
|
|
284
|
+
).all();
|
|
285
|
+
const metrics = db.prepare(
|
|
286
|
+
'SELECT * FROM consolidation_metrics ORDER BY completed_at DESC LIMIT 20'
|
|
287
|
+
).all();
|
|
288
|
+
const agents = db.prepare(
|
|
289
|
+
"SELECT agent, COUNT(*) as count FROM episodes GROUP BY agent ORDER BY count DESC"
|
|
290
|
+
).all();
|
|
291
|
+
json(res, 200, { topEpisodes, topSemantics, recentRuns, metrics, agents });
|
|
292
|
+
break;
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
case 'GET /dashboard': {
|
|
296
|
+
res.writeHead(200, { 'Content-Type': 'text/html', 'Cache-Control': 'no-store' });
|
|
297
|
+
res.end(getDashboardHTML());
|
|
298
|
+
break;
|
|
299
|
+
}
|
|
300
|
+
|
|
301
|
+
case 'POST /encode': {
|
|
302
|
+
const body = await parseBody(req);
|
|
303
|
+
if (!body.content) {
|
|
304
|
+
json(res, 400, { error: 'content is required' });
|
|
305
|
+
return;
|
|
306
|
+
}
|
|
307
|
+
if (requestAgent) body.agent = requestAgent;
|
|
308
|
+
const id = await ctx.audrey.encode(body);
|
|
309
|
+
json(res, 201, { id });
|
|
310
|
+
break;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
case 'POST /recall': {
|
|
314
|
+
const body = await parseBody(req);
|
|
315
|
+
if (!body.query) {
|
|
316
|
+
json(res, 400, { error: 'query is required' });
|
|
317
|
+
return;
|
|
318
|
+
}
|
|
319
|
+
const { query, ...opts } = body;
|
|
320
|
+
if (requestAgent) opts.agent = requestAgent;
|
|
321
|
+
const results = await ctx.audrey.recall(query, opts);
|
|
322
|
+
json(res, 200, {
|
|
323
|
+
results,
|
|
324
|
+
partialFailure: Boolean(results.partialFailure),
|
|
325
|
+
errors: results.errors ?? [],
|
|
326
|
+
});
|
|
327
|
+
break;
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
case 'POST /dream': {
|
|
331
|
+
const body = await parseBody(req);
|
|
332
|
+
const result = await ctx.audrey.dream(body);
|
|
333
|
+
json(res, 200, result);
|
|
334
|
+
break;
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
case 'POST /consolidate': {
|
|
338
|
+
const body = await parseBody(req);
|
|
339
|
+
const result = await ctx.audrey.consolidate(body);
|
|
340
|
+
json(res, 200, result);
|
|
341
|
+
break;
|
|
342
|
+
}
|
|
343
|
+
|
|
344
|
+
case 'POST /mark-used': {
|
|
345
|
+
const body = await parseBody(req);
|
|
346
|
+
if (!body.id) {
|
|
347
|
+
json(res, 400, { error: 'id is required' });
|
|
348
|
+
return;
|
|
349
|
+
}
|
|
350
|
+
ctx.audrey.markUsed(body.id);
|
|
351
|
+
json(res, 200, { ok: true });
|
|
352
|
+
break;
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
case 'POST /forget': {
|
|
356
|
+
const body = await parseBody(req);
|
|
357
|
+
if (body.query) {
|
|
358
|
+
const result = await ctx.audrey.forgetByQuery(body.query, body);
|
|
359
|
+
json(res, 200, result);
|
|
360
|
+
} else if (body.id) {
|
|
361
|
+
const result = ctx.audrey.forget(body.id, body);
|
|
362
|
+
json(res, 200, result);
|
|
363
|
+
} else {
|
|
364
|
+
json(res, 400, { error: 'id or query is required' });
|
|
365
|
+
}
|
|
366
|
+
break;
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
case 'POST /snapshot': {
|
|
370
|
+
const data = ctx.audrey.export();
|
|
371
|
+
json(res, 200, data);
|
|
372
|
+
break;
|
|
373
|
+
}
|
|
374
|
+
|
|
375
|
+
case 'POST /restore': {
|
|
376
|
+
const body = await parseBody(req);
|
|
377
|
+
if (!body.version) {
|
|
378
|
+
json(res, 400, { error: 'Invalid snapshot: missing version field' });
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
if (!audreyFactory) {
|
|
382
|
+
json(res, 501, { error: 'Restore not available: no audreyFactory configured' });
|
|
383
|
+
return;
|
|
384
|
+
}
|
|
385
|
+
const dbPath = ctx.audrey.db?.name;
|
|
386
|
+
await drainAndCloseAudrey(ctx.audrey);
|
|
387
|
+
if (dbPath) {
|
|
388
|
+
const dir = dirname(dbPath);
|
|
389
|
+
for (const f of ['audrey.db', 'audrey.db-wal', 'audrey.db-shm']) {
|
|
390
|
+
try { unlinkSync(join(dir, f)); } catch {}
|
|
391
|
+
}
|
|
392
|
+
}
|
|
393
|
+
ctx.audrey = audreyFactory();
|
|
394
|
+
await ctx.audrey.import(body);
|
|
395
|
+
const stats = ctx.audrey.introspect();
|
|
396
|
+
json(res, 200, { ok: true, ...stats });
|
|
397
|
+
break;
|
|
398
|
+
}
|
|
399
|
+
|
|
400
|
+
default: {
|
|
401
|
+
json(res, 404, { error: 'Not found', endpoints: [
|
|
402
|
+
'GET /health',
|
|
403
|
+
'GET /status',
|
|
404
|
+
'POST /encode',
|
|
405
|
+
'POST /recall',
|
|
406
|
+
'POST /dream',
|
|
407
|
+
'POST /consolidate',
|
|
408
|
+
'POST /mark-used',
|
|
409
|
+
'POST /forget',
|
|
410
|
+
'POST /snapshot',
|
|
411
|
+
'POST /restore',
|
|
412
|
+
]});
|
|
413
|
+
}
|
|
414
|
+
}
|
|
415
|
+
} catch (err) {
|
|
416
|
+
if (err.message.includes('too large')) {
|
|
417
|
+
json(res, 413, { error: 'Request body too large' });
|
|
418
|
+
} else if (err.message.includes('Invalid JSON')) {
|
|
419
|
+
json(res, 400, { error: 'Invalid JSON in request body' });
|
|
420
|
+
} else if (err.message.includes('source type')) {
|
|
421
|
+
json(res, 400, { error: err.message });
|
|
422
|
+
} else {
|
|
423
|
+
console.error('[audrey] Internal error:', err.message);
|
|
424
|
+
json(res, 500, { error: 'Internal server error' });
|
|
425
|
+
}
|
|
426
|
+
}
|
|
427
|
+
});
|
|
428
|
+
|
|
429
|
+
server._ctx = ctx;
|
|
430
|
+
return server;
|
|
431
|
+
}
|
|
432
|
+
|
|
433
|
+
export async function startServer(options = {}) {
|
|
434
|
+
const port = options.port || parseInt(process.env.AUDREY_PORT, 10) || DEFAULT_PORT;
|
|
435
|
+
const host = options.host || process.env.AUDREY_HOST || '127.0.0.1';
|
|
436
|
+
const apiKey = options.apiKey || process.env.AUDREY_API_KEY || null;
|
|
437
|
+
|
|
438
|
+
const config = buildAudreyConfig();
|
|
439
|
+
const audrey = new Audrey(config);
|
|
440
|
+
const audreyFactory = () => new Audrey(config);
|
|
441
|
+
|
|
442
|
+
const server = createAudreyServer(audrey, { apiKey, audreyFactory });
|
|
443
|
+
|
|
444
|
+
server.listen(port, host, () => {
|
|
445
|
+
console.log(`[audrey] REST API server listening on http://${host}:${port}`);
|
|
446
|
+
console.log(`[audrey] Data: ${config.dataDir}`);
|
|
447
|
+
console.log(`[audrey] Embedding: ${config.embedding.provider}`);
|
|
448
|
+
if (apiKey) {
|
|
449
|
+
console.log('[audrey] Auth: Bearer token required');
|
|
450
|
+
} else {
|
|
451
|
+
console.warn('[audrey] WARNING: No API key configured. Set AUDREY_API_KEY for production use.');
|
|
452
|
+
}
|
|
453
|
+
console.log('');
|
|
454
|
+
console.log('Endpoints:');
|
|
455
|
+
console.log(' GET /health - Liveness probe');
|
|
456
|
+
console.log(' GET /status - Memory stats (introspect)');
|
|
457
|
+
console.log(' POST /encode - Store a memory');
|
|
458
|
+
console.log(' POST /recall - Semantic search');
|
|
459
|
+
console.log(' POST /dream - Consolidation + decay cycle');
|
|
460
|
+
console.log(' POST /consolidate - Run consolidation only');
|
|
461
|
+
console.log(' POST /forget - Forget by id or query');
|
|
462
|
+
console.log(' POST /snapshot - Export all memories as JSON');
|
|
463
|
+
console.log(' POST /restore - Import snapshot (wipes + reimports)');
|
|
464
|
+
console.log('');
|
|
465
|
+
console.log('Press Ctrl+C to stop.');
|
|
466
|
+
});
|
|
467
|
+
|
|
468
|
+
const shutdown = () => {
|
|
469
|
+
console.log('\n[audrey] Shutting down...');
|
|
470
|
+
void drainAndCloseAudrey(server._ctx.audrey)
|
|
471
|
+
.catch(err => {
|
|
472
|
+
console.error('[audrey] Shutdown drain failed:', err.message);
|
|
473
|
+
})
|
|
474
|
+
.finally(() => {
|
|
475
|
+
server.close(() => process.exit(0));
|
|
476
|
+
});
|
|
477
|
+
};
|
|
478
|
+
process.on('SIGINT', shutdown);
|
|
479
|
+
process.on('SIGTERM', shutdown);
|
|
480
|
+
|
|
481
|
+
return { server, audrey };
|
|
482
|
+
}
|
package/package.json
CHANGED
|
@@ -1,9 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "audrey",
|
|
3
|
-
"version": "0.
|
|
4
|
-
"description": "Biological memory architecture for AI agents
|
|
3
|
+
"version": "0.17.0",
|
|
4
|
+
"description": "Biological memory architecture for AI agents - encode, consolidate, and recall memories with confidence decay, contradiction detection, and causal graphs",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "src/index.js",
|
|
7
|
+
"types": "types/index.d.ts",
|
|
7
8
|
"exports": {
|
|
8
9
|
".": "./src/index.js",
|
|
9
10
|
"./mcp": "./mcp-server/index.js"
|
|
@@ -15,12 +16,26 @@
|
|
|
15
16
|
"files": [
|
|
16
17
|
"src/",
|
|
17
18
|
"mcp-server/",
|
|
19
|
+
"benchmarks/*.js",
|
|
20
|
+
"docs/production-readiness.md",
|
|
21
|
+
"docs/benchmarking.md",
|
|
22
|
+
"docs/assets/benchmarks/",
|
|
23
|
+
"examples/",
|
|
24
|
+
"types/",
|
|
18
25
|
"README.md",
|
|
19
26
|
"LICENSE"
|
|
20
27
|
],
|
|
21
28
|
"scripts": {
|
|
22
29
|
"test": "vitest run",
|
|
23
|
-
"test:watch": "vitest"
|
|
30
|
+
"test:watch": "vitest",
|
|
31
|
+
"pack:check": "npm pack --dry-run",
|
|
32
|
+
"bench:memory": "node benchmarks/run.js",
|
|
33
|
+
"bench:memory:retrieval": "node benchmarks/run.js --suite retrieval",
|
|
34
|
+
"bench:memory:operations": "node benchmarks/run.js --suite operations",
|
|
35
|
+
"bench:memory:json": "node benchmarks/run.js --json",
|
|
36
|
+
"bench:memory:check": "node benchmarks/run.js --check",
|
|
37
|
+
"bench:memory:readme-assets": "node benchmarks/run.js --readme-assets-dir docs/assets/benchmarks",
|
|
38
|
+
"serve": "node mcp-server/index.js serve"
|
|
24
39
|
},
|
|
25
40
|
"keywords": [
|
|
26
41
|
"ai",
|
|
@@ -47,7 +62,11 @@
|
|
|
47
62
|
"persistent-memory",
|
|
48
63
|
"rag",
|
|
49
64
|
"claude",
|
|
50
|
-
"agent-framework"
|
|
65
|
+
"agent-framework",
|
|
66
|
+
"rest-api",
|
|
67
|
+
"http-server",
|
|
68
|
+
"fintech",
|
|
69
|
+
"healthcare"
|
|
51
70
|
],
|
|
52
71
|
"repository": {
|
|
53
72
|
"type": "git",
|
|
@@ -57,7 +76,7 @@
|
|
|
57
76
|
"bugs": {
|
|
58
77
|
"url": "https://github.com/Evilander/Audrey/issues"
|
|
59
78
|
},
|
|
60
|
-
"author": "
|
|
79
|
+
"author": "evilander",
|
|
61
80
|
"engines": {
|
|
62
81
|
"node": ">=18"
|
|
63
82
|
},
|