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/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
- await this.db.run(`
726
- ?[id, created_at, entity_id, text, embedding, metadata] :=
727
- *observation{id, entity_id, text, embedding, metadata, created_at: created_at_raw},
728
- created_at = [created_at_raw, true]
729
- :replace observation {id: String, created_at: Validity => entity_id: String, text: String, embedding: <F32; ${EMBEDDING_DIM}>, metadata: Json}
730
- `);
731
- console.error("[Schema] Observation table migrated (Validity).");
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
- `, { id, entity_id: entityId, text: args.text, embedding, metadata: args.metadata || {} });
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();