cozo-memory 1.0.9 → 1.1.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/README.md +36 -7
- package/dist/cli-commands.js +32 -0
- package/dist/cli.js +125 -0
- package/dist/compare-embeddings.js +402 -0
- package/dist/download-pplx-embed.js +151 -0
- package/dist/embedding-service.js +79 -7
- package/dist/eval-suite.js +7 -1
- package/dist/hybrid-search.js +87 -8
- package/dist/index.js +259 -18
- package/dist/reranker-service.js +125 -0
- package/dist/test-multi-level-memory.js +80 -0
- package/package.json +5 -3
package/dist/index.js
CHANGED
|
@@ -88,6 +88,8 @@ class MemoryServer {
|
|
|
88
88
|
}
|
|
89
89
|
async janitorCleanup(args) {
|
|
90
90
|
await this.initPromise;
|
|
91
|
+
console.error(`[Janitor] Starting cleanup (auto-compressing sessions first)...`);
|
|
92
|
+
await this.compressSessions({ model: args.model });
|
|
91
93
|
const olderThanDays = Math.max(1, Math.floor(args.older_than_days ?? 30));
|
|
92
94
|
const maxObservations = Math.max(1, Math.floor(args.max_observations ?? 20));
|
|
93
95
|
const minEntityDegree = Math.max(0, Math.floor(args.min_entity_degree ?? 2));
|
|
@@ -333,6 +335,69 @@ class MemoryServer {
|
|
|
333
335
|
results,
|
|
334
336
|
};
|
|
335
337
|
}
|
|
338
|
+
async compressSessions(args) {
|
|
339
|
+
const model = args.model ?? "demyagent-4b-i1:Q6_K";
|
|
340
|
+
const inactiveThreshold = Date.now() - 30 * 60 * 1000; // 30 minutes
|
|
341
|
+
try {
|
|
342
|
+
// 1. Find inactive open sessions
|
|
343
|
+
const inactiveRes = await this.db.run(`?[session_id, last_active, metadata] := *session_state{session_id, last_active, status, metadata}, status == 'open', last_active < $threshold`, { threshold: inactiveThreshold });
|
|
344
|
+
if (inactiveRes.rows.length === 0) {
|
|
345
|
+
console.error(`[Janitor] No inactive sessions found for compression.`);
|
|
346
|
+
return;
|
|
347
|
+
}
|
|
348
|
+
console.error(`[Janitor] Found ${inactiveRes.rows.length} inactive sessions to compress.`);
|
|
349
|
+
for (const row of inactiveRes.rows) {
|
|
350
|
+
const sessionId = row[0];
|
|
351
|
+
const lastActive = row[1];
|
|
352
|
+
const metadata = row[2];
|
|
353
|
+
// 2. Fetch observations for this session
|
|
354
|
+
const obsRes = await this.db.run(`?[text] := *observation{session_id: $session_id, text, @ "NOW"}`, { session_id: sessionId });
|
|
355
|
+
if (obsRes.rows.length > 0) {
|
|
356
|
+
const sessionLogs = obsRes.rows.map((r) => r[0]);
|
|
357
|
+
// 3. Summarize using LLM
|
|
358
|
+
const systemPrompt = "You are a memory consolidation service. Summarize the following session logs into exactly 2-3 concise bullet points for long-term storage. Respond ONLY with the bullet points.";
|
|
359
|
+
const userPrompt = `Session Logs for ${sessionId}:\n\n` + sessionLogs.join('\n');
|
|
360
|
+
let summaryText;
|
|
361
|
+
try {
|
|
362
|
+
const ollamaMod = await import("ollama");
|
|
363
|
+
const ollamaClient = ollamaMod?.default ?? ollamaMod;
|
|
364
|
+
const response = await ollamaClient.chat({
|
|
365
|
+
model,
|
|
366
|
+
messages: [
|
|
367
|
+
{ role: "system", content: systemPrompt },
|
|
368
|
+
{ role: "user", content: userPrompt },
|
|
369
|
+
],
|
|
370
|
+
});
|
|
371
|
+
summaryText = response?.message?.content?.trim?.() ?? "";
|
|
372
|
+
}
|
|
373
|
+
catch (e) {
|
|
374
|
+
console.warn(`[Janitor] LLM failed for session ${sessionId}: ${e.message}. Using basic concatenation.`);
|
|
375
|
+
summaryText = sessionLogs.join('\n').slice(0, 500);
|
|
376
|
+
}
|
|
377
|
+
if (summaryText) {
|
|
378
|
+
// 4. Write compression summary to global user profile
|
|
379
|
+
await this.addObservation({
|
|
380
|
+
entity_id: "global_user_profile",
|
|
381
|
+
text: `Session Summary (${sessionId}):\n${summaryText}`,
|
|
382
|
+
metadata: {
|
|
383
|
+
kind: "session_compression",
|
|
384
|
+
original_session_id: sessionId,
|
|
385
|
+
summarized_at: new Date().toISOString()
|
|
386
|
+
}
|
|
387
|
+
});
|
|
388
|
+
}
|
|
389
|
+
}
|
|
390
|
+
// 5. Mark session as compressed
|
|
391
|
+
await this.db.run(`?[session_id, last_active, status, metadata] <- $data
|
|
392
|
+
:put session_state {session_id, last_active, status, metadata}`, {
|
|
393
|
+
data: [[sessionId, lastActive, "compressed", metadata]]
|
|
394
|
+
});
|
|
395
|
+
}
|
|
396
|
+
}
|
|
397
|
+
catch (e) {
|
|
398
|
+
console.error(`[Janitor] Session compression error:`, e.message);
|
|
399
|
+
}
|
|
400
|
+
}
|
|
336
401
|
async advancedSearch(args) {
|
|
337
402
|
await this.initPromise;
|
|
338
403
|
return this.hybridSearch.advancedSearch(args);
|
|
@@ -699,7 +764,7 @@ class MemoryServer {
|
|
|
699
764
|
// Observation Table
|
|
700
765
|
if (!relations.includes("observation")) {
|
|
701
766
|
try {
|
|
702
|
-
await this.db.run(`{:create observation {id: String, created_at: Validity => entity_id: String, text: String, embedding: <F32; ${EMBEDDING_DIM}>, metadata: Json}}`);
|
|
767
|
+
await this.db.run(`{:create observation {id: String, created_at: Validity => entity_id: String, session_id: String, task_id: String, text: String, embedding: <F32; ${EMBEDDING_DIM}>, metadata: Json}}`);
|
|
703
768
|
console.error("[Schema] Observation table created.");
|
|
704
769
|
}
|
|
705
770
|
catch (e) {
|
|
@@ -707,8 +772,11 @@ class MemoryServer {
|
|
|
707
772
|
}
|
|
708
773
|
}
|
|
709
774
|
else {
|
|
775
|
+
const columnsRes = await this.db.run(`::columns observation`);
|
|
776
|
+
const columns = columnsRes.rows.map((r) => r[0]);
|
|
710
777
|
const timeTravelReady = await this.isTimeTravelReady("observation");
|
|
711
|
-
if (!timeTravelReady) {
|
|
778
|
+
if (!columns.includes("session_id") || !columns.includes("task_id") || !timeTravelReady) {
|
|
779
|
+
console.error("[Schema] Migrating observation table for Session/Task support...");
|
|
712
780
|
// Drop indices before migration
|
|
713
781
|
try {
|
|
714
782
|
await this.db.run("::hnsw drop observation:semantic");
|
|
@@ -722,13 +790,26 @@ class MemoryServer {
|
|
|
722
790
|
await this.db.run("::lsh drop observation:lsh");
|
|
723
791
|
}
|
|
724
792
|
catch (e) { }
|
|
725
|
-
|
|
726
|
-
|
|
727
|
-
|
|
728
|
-
|
|
729
|
-
|
|
730
|
-
|
|
731
|
-
|
|
793
|
+
if (!timeTravelReady) {
|
|
794
|
+
await this.db.run(`
|
|
795
|
+
?[id, created_at, entity_id, session_id, task_id, text, embedding, metadata] :=
|
|
796
|
+
*observation{id, entity_id, text, embedding, metadata, created_at: created_at_raw},
|
|
797
|
+
created_at = [created_at_raw, true],
|
|
798
|
+
session_id = "",
|
|
799
|
+
task_id = ""
|
|
800
|
+
:replace observation {id: String, created_at: Validity => entity_id: String, session_id: String, task_id: String, text: String, embedding: <F32; ${EMBEDDING_DIM}>, metadata: Json}
|
|
801
|
+
`);
|
|
802
|
+
}
|
|
803
|
+
else {
|
|
804
|
+
await this.db.run(`
|
|
805
|
+
?[id, created_at, entity_id, session_id, task_id, text, embedding, metadata] :=
|
|
806
|
+
*observation{id, entity_id, text, embedding, metadata, created_at},
|
|
807
|
+
session_id = "",
|
|
808
|
+
task_id = ""
|
|
809
|
+
:replace observation {id: String, created_at: Validity => entity_id: String, session_id: String, task_id: String, text: String, embedding: <F32; ${EMBEDDING_DIM}>, metadata: Json}
|
|
810
|
+
`);
|
|
811
|
+
}
|
|
812
|
+
console.error("[Schema] Observation table migrated (Session/Task/Validity).");
|
|
732
813
|
}
|
|
733
814
|
}
|
|
734
815
|
try {
|
|
@@ -867,6 +948,26 @@ class MemoryServer {
|
|
|
867
948
|
console.error("[Schema] Snapshot table error:", e.message);
|
|
868
949
|
}
|
|
869
950
|
}
|
|
951
|
+
// Session State Table
|
|
952
|
+
if (!relations.includes("session_state")) {
|
|
953
|
+
try {
|
|
954
|
+
await this.db.run('{:create session_state {session_id: String => last_active: Int, status: String, metadata: Json}}');
|
|
955
|
+
console.error("[Schema] Session State table created.");
|
|
956
|
+
}
|
|
957
|
+
catch (e) {
|
|
958
|
+
console.error("[Schema] Session State table error:", e.message);
|
|
959
|
+
}
|
|
960
|
+
}
|
|
961
|
+
// Task State Table
|
|
962
|
+
if (!relations.includes("task_state")) {
|
|
963
|
+
try {
|
|
964
|
+
await this.db.run('{:create task_state {task_id: String => status: String, session_id: String, metadata: Json}}');
|
|
965
|
+
console.error("[Schema] Task State table created.");
|
|
966
|
+
}
|
|
967
|
+
catch (e) {
|
|
968
|
+
console.error("[Schema] Task State table error:", e.message);
|
|
969
|
+
}
|
|
970
|
+
}
|
|
870
971
|
if (!relations.includes("inference_rule")) {
|
|
871
972
|
try {
|
|
872
973
|
await this.db.run('{:create inference_rule {id: String => name: String, datalog: String, created_at: Int}}');
|
|
@@ -1302,11 +1403,37 @@ class MemoryServer {
|
|
|
1302
1403
|
}
|
|
1303
1404
|
}
|
|
1304
1405
|
const now = Date.now() * 1000;
|
|
1406
|
+
const session_id = args.session_id ?? "";
|
|
1407
|
+
const task_id = args.task_id ?? "";
|
|
1305
1408
|
await this.db.run(`
|
|
1306
|
-
?[id, created_at, entity_id, text, embedding, metadata] <- [
|
|
1307
|
-
[$id, [${now}, true], $entity_id, $text, $embedding, $metadata]
|
|
1308
|
-
] :insert observation {id, created_at => entity_id, text, embedding, metadata}
|
|
1309
|
-
`, {
|
|
1409
|
+
?[id, created_at, entity_id, session_id, task_id, text, embedding, metadata] <- [
|
|
1410
|
+
[$id, [${now}, true], $entity_id, $session, $task, $text, $embedding, $metadata]
|
|
1411
|
+
] :insert observation {id, created_at => entity_id, session_id, task_id, text, embedding, metadata}
|
|
1412
|
+
`, {
|
|
1413
|
+
id,
|
|
1414
|
+
entity_id: entityId,
|
|
1415
|
+
session: session_id,
|
|
1416
|
+
task: task_id,
|
|
1417
|
+
text: args.text,
|
|
1418
|
+
embedding,
|
|
1419
|
+
metadata: args.metadata || {}
|
|
1420
|
+
});
|
|
1421
|
+
// Update session activity if session_id is provided
|
|
1422
|
+
if (session_id) {
|
|
1423
|
+
try {
|
|
1424
|
+
await this.db.run(`
|
|
1425
|
+
?[id, last_active, status, metadata] :=
|
|
1426
|
+
*session_state{id, metadata},
|
|
1427
|
+
id = $id,
|
|
1428
|
+
last_active = $now,
|
|
1429
|
+
status = "active"
|
|
1430
|
+
:update session_state {id => last_active, status, metadata}
|
|
1431
|
+
`, { id: session_id, now: Math.floor(now / 1000000) });
|
|
1432
|
+
}
|
|
1433
|
+
catch (e) {
|
|
1434
|
+
console.warn(`[AddObservation] Session activity update failed for ${session_id}:`, e.message);
|
|
1435
|
+
}
|
|
1436
|
+
}
|
|
1310
1437
|
// Optional: Automatic inference after new observation (in background)
|
|
1311
1438
|
const suggestionsRaw = await this.inferenceEngine.inferRelations(entityId);
|
|
1312
1439
|
const suggestions = await this.formatInferredRelationsForContext(suggestionsRaw);
|
|
@@ -1314,6 +1441,8 @@ class MemoryServer {
|
|
|
1314
1441
|
return {
|
|
1315
1442
|
id,
|
|
1316
1443
|
entity_id: entityId,
|
|
1444
|
+
session_id,
|
|
1445
|
+
task_id,
|
|
1317
1446
|
created_at: now,
|
|
1318
1447
|
created_at_iso,
|
|
1319
1448
|
status: "Observation saved",
|
|
@@ -1324,6 +1453,61 @@ class MemoryServer {
|
|
|
1324
1453
|
return { error: error.message || "Unknown error" };
|
|
1325
1454
|
}
|
|
1326
1455
|
}
|
|
1456
|
+
async startSession(args) {
|
|
1457
|
+
await this.initPromise;
|
|
1458
|
+
const id = (0, uuid_1.v4)();
|
|
1459
|
+
const now = Math.floor(Date.now() / 1000);
|
|
1460
|
+
const name = args.name || `Session ${new Date().toISOString()}`;
|
|
1461
|
+
// Create a Session entity first
|
|
1462
|
+
await this.createEntity({
|
|
1463
|
+
name,
|
|
1464
|
+
type: "Session",
|
|
1465
|
+
metadata: { session_id: id, ...args.metadata }
|
|
1466
|
+
});
|
|
1467
|
+
await this.db.run(`
|
|
1468
|
+
?[session_id, last_active, status, metadata] <- [[$id, $now, "active", $metadata]]
|
|
1469
|
+
:insert session_state {session_id => last_active, status, metadata}
|
|
1470
|
+
`, { id, now, metadata: args.metadata || {} });
|
|
1471
|
+
return { id, name, status: "Session started" };
|
|
1472
|
+
}
|
|
1473
|
+
async stopSession(args) {
|
|
1474
|
+
await this.initPromise;
|
|
1475
|
+
await this.db.run(`
|
|
1476
|
+
?[session_id, last_active, status, metadata] :=
|
|
1477
|
+
*session_state{session_id, last_active, metadata},
|
|
1478
|
+
session_id = $id,
|
|
1479
|
+
status = "closed"
|
|
1480
|
+
:update session_state {session_id => last_active, status, metadata}
|
|
1481
|
+
`, { id: args.id });
|
|
1482
|
+
return { id: args.id, status: "Session closed" };
|
|
1483
|
+
}
|
|
1484
|
+
async startTask(args) {
|
|
1485
|
+
await this.initPromise;
|
|
1486
|
+
const id = (0, uuid_1.v4)();
|
|
1487
|
+
const sessionId = args.session_id || "";
|
|
1488
|
+
// Create a Task entity
|
|
1489
|
+
await this.createEntity({
|
|
1490
|
+
name: args.name,
|
|
1491
|
+
type: "Task",
|
|
1492
|
+
metadata: { task_id: id, session_id: sessionId, ...args.metadata }
|
|
1493
|
+
});
|
|
1494
|
+
await this.db.run(`
|
|
1495
|
+
?[task_id, status, session_id, metadata] <- [[$id, "active", $session, $metadata]]
|
|
1496
|
+
:insert task_state {task_id => status, session_id, metadata}
|
|
1497
|
+
`, { id, session: sessionId, metadata: args.metadata || {} });
|
|
1498
|
+
return { id, name: args.name, session_id: sessionId, status: "Task started" };
|
|
1499
|
+
}
|
|
1500
|
+
async stopTask(args) {
|
|
1501
|
+
await this.initPromise;
|
|
1502
|
+
await this.db.run(`
|
|
1503
|
+
?[task_id, status, session_id, metadata] :=
|
|
1504
|
+
*task_state{task_id, session_id, metadata},
|
|
1505
|
+
task_id = $id,
|
|
1506
|
+
status = "completed"
|
|
1507
|
+
:update task_state {task_id => status, session_id, metadata}
|
|
1508
|
+
`, { id: args.id });
|
|
1509
|
+
return { id: args.id, status: "Task stopped" };
|
|
1510
|
+
}
|
|
1327
1511
|
async createRelation(args) {
|
|
1328
1512
|
if (args.from_id === args.to_id) {
|
|
1329
1513
|
return { error: "Self-references in relationships are not allowed" };
|
|
@@ -2162,14 +2346,16 @@ ids[id] <- $ids
|
|
|
2162
2346
|
const now = Date.now() * 1000;
|
|
2163
2347
|
allParams[`obs_id${suffix}`] = id;
|
|
2164
2348
|
allParams[`obs_entity_id${suffix}`] = entity_id;
|
|
2349
|
+
allParams[`obs_session_id${suffix}`] = params.session_id || "";
|
|
2350
|
+
allParams[`obs_task_id${suffix}`] = params.task_id || "";
|
|
2165
2351
|
allParams[`obs_text${suffix}`] = text || "";
|
|
2166
2352
|
allParams[`obs_embedding${suffix}`] = embedding;
|
|
2167
2353
|
allParams[`obs_metadata${suffix}`] = metadata || {};
|
|
2168
2354
|
statements.push(`
|
|
2169
2355
|
{
|
|
2170
|
-
?[id, created_at, entity_id, text, embedding, metadata] <- [
|
|
2171
|
-
[$obs_id${suffix}, [${now}, true], $obs_entity_id${suffix}, $obs_text${suffix}, $obs_embedding${suffix}, $obs_metadata${suffix}]
|
|
2172
|
-
] :insert observation {id, created_at => entity_id, text, embedding, metadata}
|
|
2356
|
+
?[id, created_at, entity_id, session_id, task_id, text, embedding, metadata] <- [
|
|
2357
|
+
[$obs_id${suffix}, [${now}, true], $obs_entity_id${suffix}, $obs_session_id${suffix}, $obs_task_id${suffix}, $obs_text${suffix}, $obs_embedding${suffix}, $obs_metadata${suffix}]
|
|
2358
|
+
] :insert observation {id, created_at => entity_id, session_id, task_id, text, embedding, metadata}
|
|
2173
2359
|
}
|
|
2174
2360
|
`);
|
|
2175
2361
|
results.push({ action: "add_observation", id, entity_id });
|
|
@@ -2445,6 +2631,8 @@ ids[id] <- $ids
|
|
|
2445
2631
|
text: zod_1.z.string().describe("The fact or observation"),
|
|
2446
2632
|
metadata: MetadataSchema.optional().describe("Additional metadata"),
|
|
2447
2633
|
deduplicate: zod_1.z.boolean().optional().default(true).describe("Skip exact duplicates"),
|
|
2634
|
+
session_id: zod_1.z.string().optional().describe("Associated session ID"),
|
|
2635
|
+
task_id: zod_1.z.string().optional().describe("Associated task ID"),
|
|
2448
2636
|
}).passthrough().refine((v) => Boolean(v.entity_id) || Boolean(v.entity_name), {
|
|
2449
2637
|
message: "entity_id or entity_name is required",
|
|
2450
2638
|
path: ["entity_id"],
|
|
@@ -2536,10 +2724,29 @@ ids[id] <- $ids
|
|
|
2536
2724
|
message: "file_path or content is required for ingest_file",
|
|
2537
2725
|
path: ["file_path"],
|
|
2538
2726
|
}),
|
|
2727
|
+
zod_1.z.object({
|
|
2728
|
+
action: zod_1.z.literal("start_session"),
|
|
2729
|
+
name: zod_1.z.string().optional().describe("Optional name for the session"),
|
|
2730
|
+
metadata: MetadataSchema.optional().describe("Additional metadata"),
|
|
2731
|
+
}).passthrough(),
|
|
2732
|
+
zod_1.z.object({
|
|
2733
|
+
action: zod_1.z.literal("stop_session"),
|
|
2734
|
+
id: zod_1.z.string().describe("ID of the session to stop"),
|
|
2735
|
+
}).passthrough(),
|
|
2736
|
+
zod_1.z.object({
|
|
2737
|
+
action: zod_1.z.literal("start_task"),
|
|
2738
|
+
name: zod_1.z.string().describe("Name of the task"),
|
|
2739
|
+
session_id: zod_1.z.string().optional().describe("Optional session ID this task belongs to"),
|
|
2740
|
+
metadata: MetadataSchema.optional().describe("Additional metadata"),
|
|
2741
|
+
}).passthrough(),
|
|
2742
|
+
zod_1.z.object({
|
|
2743
|
+
action: zod_1.z.literal("stop_task"),
|
|
2744
|
+
id: zod_1.z.string().describe("ID of the task to stop"),
|
|
2745
|
+
}).passthrough(),
|
|
2539
2746
|
]);
|
|
2540
2747
|
const MutateMemoryParameters = zod_1.z.object({
|
|
2541
2748
|
action: zod_1.z
|
|
2542
|
-
.enum(["create_entity", "update_entity", "delete_entity", "add_observation", "create_relation", "run_transaction", "add_inference_rule", "ingest_file"])
|
|
2749
|
+
.enum(["create_entity", "update_entity", "delete_entity", "add_observation", "create_relation", "run_transaction", "add_inference_rule", "ingest_file", "start_session", "stop_session", "start_task", "stop_task"])
|
|
2543
2750
|
.describe("Action (determines which fields are required)"),
|
|
2544
2751
|
name: zod_1.z.string().optional().describe("For create_entity (required) or add_inference_rule (required)"),
|
|
2545
2752
|
type: zod_1.z.string().optional().describe("For create_entity (required)"),
|
|
@@ -2587,6 +2794,10 @@ Supported actions:
|
|
|
2587
2794
|
Example (Manager Transitivity):
|
|
2588
2795
|
'?[from_id, to_id, relation_type, confidence, reason] := *relationship{from_id: $id, to_id: mid, relation_type: "manager_of", @ "NOW"}, *relationship{from_id: mid, to_id: target, relation_type: "manager_of", @ "NOW"}, from_id = $id, to_id = target, relation_type = "ober_manager_von", confidence = 0.6, reason = "Transitive Manager Path"'
|
|
2589
2796
|
- 'ingest_file': Bulk import of documents (Markdown/JSON). Supports chunking (paragraphs) and automatic entity creation. Params: { entity_id | entity_name (required), format, content, ... }. Ideal for quickly populating memory from existing notes.
|
|
2797
|
+
- 'start_session': Initializes a new session for context tracking. Params: { name?: string, metadata?: object }.
|
|
2798
|
+
- 'stop_session': Closes a session. Params: { id: string }.
|
|
2799
|
+
- 'start_task': Initializes a new task within a session. Params: { name: string, session_id?: string, metadata?: object }.
|
|
2800
|
+
- 'stop_task': Marks a task as completed. Params: { id: string }.
|
|
2590
2801
|
|
|
2591
2802
|
Validation: Invalid syntax or missing columns in inference rules will result in errors.`,
|
|
2592
2803
|
parameters: MutateMemoryParameters,
|
|
@@ -2620,6 +2831,14 @@ Validation: Invalid syntax or missing columns in inference rules will result in
|
|
|
2620
2831
|
return JSON.stringify(await this.addInferenceRule(rest));
|
|
2621
2832
|
if (action === "ingest_file")
|
|
2622
2833
|
return JSON.stringify(await this.ingestFile(rest));
|
|
2834
|
+
if (action === "start_session")
|
|
2835
|
+
return JSON.stringify(await this.startSession(rest));
|
|
2836
|
+
if (action === "stop_session")
|
|
2837
|
+
return JSON.stringify(await this.stopSession(rest));
|
|
2838
|
+
if (action === "start_task")
|
|
2839
|
+
return JSON.stringify(await this.startTask(rest));
|
|
2840
|
+
if (action === "stop_task")
|
|
2841
|
+
return JSON.stringify(await this.stopTask(rest));
|
|
2623
2842
|
return JSON.stringify({ error: "Unknown action" });
|
|
2624
2843
|
},
|
|
2625
2844
|
});
|
|
@@ -2631,6 +2850,9 @@ Validation: Invalid syntax or missing columns in inference rules will result in
|
|
|
2631
2850
|
entity_types: zod_1.z.array(zod_1.z.string()).optional().describe("Filter by entity types"),
|
|
2632
2851
|
include_entities: zod_1.z.boolean().optional().default(true).describe("Include entities in search"),
|
|
2633
2852
|
include_observations: zod_1.z.boolean().optional().default(true).describe("Include observations in search"),
|
|
2853
|
+
rerank: zod_1.z.boolean().optional().default(false).describe("Use Cross-Encoder reranking for higher precision"),
|
|
2854
|
+
session_id: zod_1.z.string().optional().describe("Prioritize results from this session"),
|
|
2855
|
+
task_id: zod_1.z.string().optional().describe("Prioritize results from this task"),
|
|
2634
2856
|
}),
|
|
2635
2857
|
zod_1.z.object({
|
|
2636
2858
|
action: zod_1.z.literal("advancedSearch"),
|
|
@@ -2658,6 +2880,9 @@ Validation: Invalid syntax or missing columns in inference rules will result in
|
|
|
2658
2880
|
vectorParams: zod_1.z.object({
|
|
2659
2881
|
efSearch: zod_1.z.number().optional().describe("HNSW search precision"),
|
|
2660
2882
|
}).optional().describe("Vector parameters"),
|
|
2883
|
+
rerank: zod_1.z.boolean().optional().default(false).describe("Use Cross-Encoder reranking for higher precision"),
|
|
2884
|
+
session_id: zod_1.z.string().optional().describe("Prioritize results from this session"),
|
|
2885
|
+
task_id: zod_1.z.string().optional().describe("Prioritize results from this task"),
|
|
2661
2886
|
}),
|
|
2662
2887
|
zod_1.z.object({
|
|
2663
2888
|
action: zod_1.z.literal("context"),
|
|
@@ -2679,6 +2904,7 @@ Validation: Invalid syntax or missing columns in inference rules will result in
|
|
|
2679
2904
|
query: zod_1.z.string().describe("Search query for initial vector seeds"),
|
|
2680
2905
|
max_depth: zod_1.z.number().min(1).max(3).optional().default(2).describe("Maximum depth of graph expansion (Default: 2)"),
|
|
2681
2906
|
limit: zod_1.z.number().optional().default(10).describe("Number of initial vector seeds"),
|
|
2907
|
+
rerank: zod_1.z.boolean().optional().default(false).describe("Use Cross-Encoder reranking for higher precision"),
|
|
2682
2908
|
}),
|
|
2683
2909
|
zod_1.z.object({
|
|
2684
2910
|
action: zod_1.z.literal("graph_walking"),
|
|
@@ -2691,6 +2917,9 @@ Validation: Invalid syntax or missing columns in inference rules will result in
|
|
|
2691
2917
|
action: zod_1.z.literal("agentic_search"),
|
|
2692
2918
|
query: zod_1.z.string().describe("Context query for agentic routing"),
|
|
2693
2919
|
limit: zod_1.z.number().optional().default(10).describe("Maximum number of results"),
|
|
2920
|
+
rerank: zod_1.z.boolean().optional().default(false).describe("Use Cross-Encoder reranking for higher precision"),
|
|
2921
|
+
session_id: zod_1.z.string().optional().describe("Prioritize results from this session"),
|
|
2922
|
+
task_id: zod_1.z.string().optional().describe("Prioritize results from this task"),
|
|
2694
2923
|
}),
|
|
2695
2924
|
]);
|
|
2696
2925
|
const QueryMemoryParameters = zod_1.z.object({
|
|
@@ -2699,6 +2928,8 @@ Validation: Invalid syntax or missing columns in inference rules will result in
|
|
|
2699
2928
|
.describe("Action (determines which fields are required)"),
|
|
2700
2929
|
query: zod_1.z.string().optional().describe("Required for search/advancedSearch/context/graph_rag/graph_walking/agentic_search"),
|
|
2701
2930
|
limit: zod_1.z.number().optional().describe("Only for search/advancedSearch/graph_rag/graph_walking"),
|
|
2931
|
+
session_id: zod_1.z.string().optional().describe("Optional session ID for context boosting"),
|
|
2932
|
+
task_id: zod_1.z.string().optional().describe("Optional task ID for context boosting"),
|
|
2702
2933
|
filters: zod_1.z.any().optional().describe("Only for advancedSearch"),
|
|
2703
2934
|
graphConstraints: zod_1.z.any().optional().describe("Only for advancedSearch"),
|
|
2704
2935
|
vectorOptions: zod_1.z.any().optional().describe("Only for advancedSearch"),
|
|
@@ -2711,6 +2942,7 @@ Validation: Invalid syntax or missing columns in inference rules will result in
|
|
|
2711
2942
|
as_of: zod_1.z.string().optional().describe("Only for entity_details: ISO string or 'NOW'"),
|
|
2712
2943
|
max_depth: zod_1.z.number().optional().describe("Only for graph_rag/graph_walking: Maximum expansion depth"),
|
|
2713
2944
|
start_entity_id: zod_1.z.string().optional().describe("Only for graph_walking: Start entity"),
|
|
2945
|
+
rerank: zod_1.z.boolean().optional().describe("Only for search/advancedSearch/agentic_search: Enable Cross-Encoder reranking"),
|
|
2714
2946
|
});
|
|
2715
2947
|
this.mcp.addTool({
|
|
2716
2948
|
name: "query_memory",
|
|
@@ -2745,6 +2977,9 @@ Notes: 'agentic_search' is the most powerful and adaptable, 'context' is ideal f
|
|
|
2745
2977
|
entityTypes: input.entity_types,
|
|
2746
2978
|
includeEntities: input.include_entities,
|
|
2747
2979
|
includeObservations: input.include_observations,
|
|
2980
|
+
rerank: input.rerank,
|
|
2981
|
+
session_id: input.session_id,
|
|
2982
|
+
task_id: input.task_id,
|
|
2748
2983
|
});
|
|
2749
2984
|
const conflictEntityIds = Array.from(new Set(results
|
|
2750
2985
|
.map((r) => (r.name ? r.id : r.entity_id))
|
|
@@ -2776,6 +3011,9 @@ Notes: 'agentic_search' is the most powerful and adaptable, 'context' is ideal f
|
|
|
2776
3011
|
filters: input.filters,
|
|
2777
3012
|
graphConstraints: input.graphConstraints,
|
|
2778
3013
|
vectorParams: input.vectorParams,
|
|
3014
|
+
rerank: input.rerank,
|
|
3015
|
+
session_id: input.session_id,
|
|
3016
|
+
task_id: input.task_id,
|
|
2779
3017
|
});
|
|
2780
3018
|
const conflictEntityIds = Array.from(new Set(results
|
|
2781
3019
|
.map((r) => (r.name ? r.id : r.entity_id))
|
|
@@ -2888,6 +3126,7 @@ Notes: 'agentic_search' is the most powerful and adaptable, 'context' is ideal f
|
|
|
2888
3126
|
const results = await this.hybridSearch.agenticRetrieve({
|
|
2889
3127
|
query: input.query,
|
|
2890
3128
|
limit: input.limit,
|
|
3129
|
+
rerank: input.rerank,
|
|
2891
3130
|
});
|
|
2892
3131
|
return JSON.stringify(results);
|
|
2893
3132
|
}
|
|
@@ -2900,7 +3139,8 @@ Notes: 'agentic_search' is the most powerful and adaptable, 'context' is ideal f
|
|
|
2900
3139
|
limit: input.limit,
|
|
2901
3140
|
graphConstraints: {
|
|
2902
3141
|
maxDepth: input.max_depth
|
|
2903
|
-
}
|
|
3142
|
+
},
|
|
3143
|
+
rerank: input.rerank,
|
|
2904
3144
|
});
|
|
2905
3145
|
return JSON.stringify(results);
|
|
2906
3146
|
}
|
|
@@ -3698,6 +3938,7 @@ Note: Use 'mutate_memory' with action='add_observation' and entity_id='global_us
|
|
|
3698
3938
|
});
|
|
3699
3939
|
}
|
|
3700
3940
|
async start() {
|
|
3941
|
+
await this.initPromise;
|
|
3701
3942
|
await this.mcp.start({ transportType: "stdio" });
|
|
3702
3943
|
console.error("Cozo Memory MCP Server running on stdio");
|
|
3703
3944
|
}
|
|
@@ -0,0 +1,125 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
|
|
3
|
+
if (k2 === undefined) k2 = k;
|
|
4
|
+
var desc = Object.getOwnPropertyDescriptor(m, k);
|
|
5
|
+
if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
|
|
6
|
+
desc = { enumerable: true, get: function() { return m[k]; } };
|
|
7
|
+
}
|
|
8
|
+
Object.defineProperty(o, k2, desc);
|
|
9
|
+
}) : (function(o, m, k, k2) {
|
|
10
|
+
if (k2 === undefined) k2 = k;
|
|
11
|
+
o[k2] = m[k];
|
|
12
|
+
}));
|
|
13
|
+
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
|
|
14
|
+
Object.defineProperty(o, "default", { enumerable: true, value: v });
|
|
15
|
+
}) : function(o, v) {
|
|
16
|
+
o["default"] = v;
|
|
17
|
+
});
|
|
18
|
+
var __importStar = (this && this.__importStar) || (function () {
|
|
19
|
+
var ownKeys = function(o) {
|
|
20
|
+
ownKeys = Object.getOwnPropertyNames || function (o) {
|
|
21
|
+
var ar = [];
|
|
22
|
+
for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
|
|
23
|
+
return ar;
|
|
24
|
+
};
|
|
25
|
+
return ownKeys(o);
|
|
26
|
+
};
|
|
27
|
+
return function (mod) {
|
|
28
|
+
if (mod && mod.__esModule) return mod;
|
|
29
|
+
var result = {};
|
|
30
|
+
if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
|
|
31
|
+
__setModuleDefault(result, mod);
|
|
32
|
+
return result;
|
|
33
|
+
};
|
|
34
|
+
})();
|
|
35
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
36
|
+
exports.RerankerService = void 0;
|
|
37
|
+
const transformers_1 = require("@xenova/transformers");
|
|
38
|
+
const path = __importStar(require("path"));
|
|
39
|
+
const fs = __importStar(require("fs"));
|
|
40
|
+
// Robust path to project root
|
|
41
|
+
const PROJECT_ROOT = path.resolve(__dirname, '..');
|
|
42
|
+
const CACHE_DIR = path.resolve(PROJECT_ROOT, '.cache');
|
|
43
|
+
transformers_1.env.cacheDir = CACHE_DIR;
|
|
44
|
+
transformers_1.env.allowLocalModels = true;
|
|
45
|
+
class RerankerService {
|
|
46
|
+
pipe = null;
|
|
47
|
+
modelId;
|
|
48
|
+
initialized = false;
|
|
49
|
+
constructor() {
|
|
50
|
+
// Using a tiny but effective cross-encoder
|
|
51
|
+
this.modelId = process.env.RERANKER_MODEL || "Xenova/ms-marco-MiniLM-L-6-v2";
|
|
52
|
+
console.error(`[RerankerService] Using model: ${this.modelId}`);
|
|
53
|
+
}
|
|
54
|
+
async init() {
|
|
55
|
+
if (this.initialized)
|
|
56
|
+
return;
|
|
57
|
+
try {
|
|
58
|
+
// Check if model exists locally in cache
|
|
59
|
+
const parts = this.modelId.split('/');
|
|
60
|
+
const namespace = parts[0];
|
|
61
|
+
const modelName = parts[1];
|
|
62
|
+
const modelDir = path.join(CACHE_DIR, namespace, modelName);
|
|
63
|
+
if (!fs.existsSync(modelDir)) {
|
|
64
|
+
console.log(`[RerankerService] Model not found, downloading ${this.modelId}...`);
|
|
65
|
+
}
|
|
66
|
+
// We use the sequence-classification task for cross-encoders
|
|
67
|
+
this.pipe = await (0, transformers_1.pipeline)('sequence-classification', this.modelId, {
|
|
68
|
+
quantized: true,
|
|
69
|
+
// @ts-ignore
|
|
70
|
+
progress_callback: (info) => {
|
|
71
|
+
if (info.status === 'done') {
|
|
72
|
+
console.error(`[RerankerService] Loaded shard: ${info.file}`);
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
});
|
|
76
|
+
this.initialized = true;
|
|
77
|
+
console.error(`[RerankerService] Initialization complete.`);
|
|
78
|
+
}
|
|
79
|
+
catch (error) {
|
|
80
|
+
console.error(`[RerankerService] Initialization failed:`, error);
|
|
81
|
+
throw error;
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Reranks a list of documents based on a query.
|
|
86
|
+
* @param query The search query
|
|
87
|
+
* @param documents Array of document strings to rank
|
|
88
|
+
* @returns Array of { index, score } sorted by score descending
|
|
89
|
+
*/
|
|
90
|
+
async rerank(query, documents) {
|
|
91
|
+
if (documents.length === 0)
|
|
92
|
+
return [];
|
|
93
|
+
await this.init();
|
|
94
|
+
try {
|
|
95
|
+
const results = [];
|
|
96
|
+
// Cross-encoders take pairs of [query, document]
|
|
97
|
+
// We can process them in a single batch
|
|
98
|
+
const inputs = documents.map(doc => [query, doc]);
|
|
99
|
+
// @ts-ignore
|
|
100
|
+
const outputs = await this.pipe(inputs, {
|
|
101
|
+
topk: 1 // We want the score for the "relevant" class (usually index 1 or the only output)
|
|
102
|
+
});
|
|
103
|
+
// Handle both array of results and single result (if only 1 doc)
|
|
104
|
+
const outputArray = Array.isArray(outputs) ? outputs : [outputs];
|
|
105
|
+
for (let i = 0; i < outputArray.length; i++) {
|
|
106
|
+
// Cross-encoders for ms-marco typically output a single logit/score or a 2-class distribution
|
|
107
|
+
// transformers.js sequence-classification returns { label: string, score: number }[]
|
|
108
|
+
// For ms-marco, label 'LABEL_1' is usually the relevance score
|
|
109
|
+
const out = outputArray[i];
|
|
110
|
+
results.push({
|
|
111
|
+
index: i,
|
|
112
|
+
score: out.score || 0
|
|
113
|
+
});
|
|
114
|
+
}
|
|
115
|
+
// Sort by score descending
|
|
116
|
+
return results.sort((a, b) => b.score - a.score);
|
|
117
|
+
}
|
|
118
|
+
catch (error) {
|
|
119
|
+
console.error(`[RerankerService] Reranking failed:`, error);
|
|
120
|
+
// Fallback: return original order with neutral scores
|
|
121
|
+
return documents.map((_, i) => ({ index: i, score: 0 }));
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
exports.RerankerService = RerankerService;
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
async function testMultiLevelMemory() {
|
|
5
|
+
console.log("=== Testing Multi-Level Memory (v2.0) ===\n");
|
|
6
|
+
const server = new index_1.MemoryServer();
|
|
7
|
+
await server.start();
|
|
8
|
+
try {
|
|
9
|
+
// 1. Session Management
|
|
10
|
+
console.log("--- 1. Testing Session Management ---");
|
|
11
|
+
const sessionRes = await server.startSession({ name: "Research Session", metadata: { user: "test_user" } });
|
|
12
|
+
const sessionId = sessionRes.id;
|
|
13
|
+
console.log(`Started session: ${sessionId}`);
|
|
14
|
+
// 2. Task Management
|
|
15
|
+
console.log("\n--- 2. Testing Task Management ---");
|
|
16
|
+
const taskRes = await server.startTask({ name: "Analyze Multi-Level Memory", session_id: sessionId });
|
|
17
|
+
const taskId = taskRes.id;
|
|
18
|
+
console.log(`Started task: ${taskId}`);
|
|
19
|
+
// 3. Observations with Context
|
|
20
|
+
console.log("\n--- 3. Adding observations with session and task IDs ---");
|
|
21
|
+
await server.addObservation({
|
|
22
|
+
entity_name: "Multi-Level Memory",
|
|
23
|
+
entity_type: "Concept",
|
|
24
|
+
text: "Multi-Level Memory is implemented in v2.0.",
|
|
25
|
+
session_id: sessionId,
|
|
26
|
+
task_id: taskId,
|
|
27
|
+
metadata: { importance: "high" }
|
|
28
|
+
});
|
|
29
|
+
console.log("Added observation for Multi-Level Memory with session and task context.");
|
|
30
|
+
await server.addObservation({
|
|
31
|
+
entity_name: "Janitor",
|
|
32
|
+
entity_type: "Service",
|
|
33
|
+
text: "Janitor now handles session compression.",
|
|
34
|
+
session_id: sessionId,
|
|
35
|
+
metadata: { importance: "medium" }
|
|
36
|
+
});
|
|
37
|
+
console.log("Added observation for Janitor with session context.");
|
|
38
|
+
// 4. Search with Context Boost
|
|
39
|
+
console.log("\n--- 4. Testing Context-Aware Search (Boosting) ---");
|
|
40
|
+
console.log("Case A: Search with session and task boost");
|
|
41
|
+
const searchResA = await server.advancedSearch({
|
|
42
|
+
query: "Multi-Level Memory",
|
|
43
|
+
session_id: sessionId,
|
|
44
|
+
task_id: taskId,
|
|
45
|
+
limit: 5
|
|
46
|
+
});
|
|
47
|
+
console.log("Search results (A):");
|
|
48
|
+
searchResA.forEach((r, i) => {
|
|
49
|
+
console.log(` [${i + 1}] Score: ${r.score.toFixed(4)}, Text: ${r.text || r.name}, Explanation: ${r.explanation}`);
|
|
50
|
+
});
|
|
51
|
+
// 5. Session Compression
|
|
52
|
+
console.log("\n--- 5. Testing Session Compression (Janitor) ---");
|
|
53
|
+
console.log("Stopping session to prepare for compression...");
|
|
54
|
+
await server.stopSession({ id: sessionId });
|
|
55
|
+
console.log("Manually aging session activity in session_state table...");
|
|
56
|
+
const oldTs = Date.now() - 40 * 60 * 1000; // 40 minutes ago
|
|
57
|
+
await server['db'].run(`?[session_id, last_active, status, metadata] <- [[$id, $ts, 'open', $meta]]
|
|
58
|
+
:put session_state {session_id, last_active, status, metadata}`, { id: sessionId, ts: oldTs, meta: { user: "test_user" } });
|
|
59
|
+
console.log("Running Janitor cleanup with confirm=true...");
|
|
60
|
+
const janitorRes = await server.janitorCleanup({ confirm: true });
|
|
61
|
+
console.log("Janitor result:", JSON.stringify(janitorRes, null, 2));
|
|
62
|
+
// 6. Verify Compression Result
|
|
63
|
+
console.log("\n--- 6. Verifying compression summary in User Profile ---");
|
|
64
|
+
const profileRes = await server.advancedSearch({ query: "Session Summary", entityTypes: ["Observation"] });
|
|
65
|
+
console.log("Profile search results:");
|
|
66
|
+
profileRes.forEach((r, i) => {
|
|
67
|
+
if (r.text?.includes(sessionId)) {
|
|
68
|
+
console.log(` [MATCH] ${r.text}`);
|
|
69
|
+
}
|
|
70
|
+
});
|
|
71
|
+
console.log("\n=== Multi-Level Memory Test completed successfully ===");
|
|
72
|
+
}
|
|
73
|
+
catch (error) {
|
|
74
|
+
console.error("Test failed:", error);
|
|
75
|
+
}
|
|
76
|
+
finally {
|
|
77
|
+
process.exit(0);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
testMultiLevelMemory();
|