cozo-memory 1.1.3 → 1.1.4
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 +330 -4
- package/dist/adaptive-retrieval.js +520 -0
- package/dist/dynamic-fusion.js +602 -0
- package/dist/index.js +386 -6
- package/dist/logical-edges-service.js +316 -0
- package/dist/multi-hop-vector-pivot.js +390 -0
- package/dist/temporal-embedding-service.js +313 -0
- package/dist/test-adaptive-integration.js +84 -0
- package/dist/test-adaptive-retrieval.js +135 -0
- package/dist/test-dynamic-fusion.js +231 -0
- package/dist/test-logical-edges.js +282 -0
- package/dist/test-multi-hop-vector-pivot-v2.js +239 -0
- package/dist/test-multi-hop-vector-pivot.js +240 -0
- package/dist/test-temporal-embeddings.js +123 -0
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -16,6 +16,8 @@ const fs_1 = __importDefault(require("fs"));
|
|
|
16
16
|
const pdf_mjs_1 = require("pdfjs-dist/legacy/build/pdf.mjs");
|
|
17
17
|
const hybrid_search_1 = require("./hybrid-search");
|
|
18
18
|
const inference_engine_1 = require("./inference-engine");
|
|
19
|
+
const dynamic_fusion_1 = require("./dynamic-fusion");
|
|
20
|
+
const adaptive_retrieval_1 = require("./adaptive-retrieval");
|
|
19
21
|
exports.DB_PATH = path_1.default.resolve(__dirname, "..", "memory_db.cozo");
|
|
20
22
|
const DB_ENGINE = process.env.DB_ENGINE || "sqlite"; // "sqlite" or "rocksdb"
|
|
21
23
|
exports.USER_ENTITY_ID = "global_user_profile";
|
|
@@ -26,6 +28,8 @@ class MemoryServer {
|
|
|
26
28
|
mcp;
|
|
27
29
|
embeddingService;
|
|
28
30
|
hybridSearch;
|
|
31
|
+
dynamicFusion;
|
|
32
|
+
adaptiveRetrieval;
|
|
29
33
|
inferenceEngine;
|
|
30
34
|
initPromise;
|
|
31
35
|
compactionLocks = new Set();
|
|
@@ -76,6 +80,8 @@ class MemoryServer {
|
|
|
76
80
|
console.error(`[DB] Using backend: ${DB_ENGINE}, path: ${fullDbPath}`);
|
|
77
81
|
this.embeddingService = new embedding_service_1.EmbeddingService();
|
|
78
82
|
this.hybridSearch = new hybrid_search_1.HybridSearch(this.db, this.embeddingService);
|
|
83
|
+
this.dynamicFusion = new dynamic_fusion_1.DynamicFusionSearch(this.db, this.embeddingService);
|
|
84
|
+
this.adaptiveRetrieval = new adaptive_retrieval_1.AdaptiveGraphRetrieval(this.db, this.embeddingService);
|
|
79
85
|
this.inferenceEngine = new inference_engine_1.InferenceEngine(this.db, this.embeddingService);
|
|
80
86
|
this.mcp = new fastmcp_1.FastMCP({
|
|
81
87
|
name: "cozo-memory-server",
|
|
@@ -2383,6 +2389,215 @@ Format MUST start with "ExecutiveSummary: " followed by the consolidated content
|
|
|
2383
2389
|
return { error: "Deletion failed", message: error.message };
|
|
2384
2390
|
}
|
|
2385
2391
|
}
|
|
2392
|
+
/**
|
|
2393
|
+
* Memory Defragmentation
|
|
2394
|
+
* Reorganizes memory structure by:
|
|
2395
|
+
* 1. Detecting and merging duplicate/near-duplicate observations using LSH MinHash
|
|
2396
|
+
* 2. Connecting fragmented knowledge islands (small connected components) to main graph
|
|
2397
|
+
* 3. Removing orphaned entities (no observations or relations)
|
|
2398
|
+
*
|
|
2399
|
+
* Inspired by Letta MemFS defragmentation
|
|
2400
|
+
*/
|
|
2401
|
+
async defragMemory(args) {
|
|
2402
|
+
await this.initPromise;
|
|
2403
|
+
const startTime = Date.now();
|
|
2404
|
+
try {
|
|
2405
|
+
console.error(`[Defrag] Starting memory defragmentation (confirm=${args.confirm})`);
|
|
2406
|
+
const similarity_threshold = args.similarity_threshold || 0.95; // High threshold for near-duplicates
|
|
2407
|
+
const min_island_size = args.min_island_size || 3; // Islands with <= 3 nodes
|
|
2408
|
+
const stats = {
|
|
2409
|
+
duplicates_found: 0,
|
|
2410
|
+
duplicates_merged: 0,
|
|
2411
|
+
islands_found: 0,
|
|
2412
|
+
islands_connected: 0,
|
|
2413
|
+
orphans_found: 0,
|
|
2414
|
+
orphans_removed: 0,
|
|
2415
|
+
};
|
|
2416
|
+
// Step 1: Find duplicate observations using semantic similarity
|
|
2417
|
+
console.error(`[Defrag] Step 1: Detecting duplicate observations (threshold=${similarity_threshold})`);
|
|
2418
|
+
const allObs = await this.db.run(`
|
|
2419
|
+
?[id, entity_id, text, embedding] := *observation{id, entity_id, text, embedding, @ "NOW"}
|
|
2420
|
+
`);
|
|
2421
|
+
const duplicatePairs = [];
|
|
2422
|
+
// Compare embeddings for similarity (using existing embeddings)
|
|
2423
|
+
for (let i = 0; i < allObs.rows.length; i++) {
|
|
2424
|
+
for (let j = i + 1; j < allObs.rows.length; j++) {
|
|
2425
|
+
const emb1 = allObs.rows[i][3];
|
|
2426
|
+
const emb2 = allObs.rows[j][3];
|
|
2427
|
+
if (!emb1 || !emb2 || emb1.length === 0 || emb2.length === 0)
|
|
2428
|
+
continue;
|
|
2429
|
+
// Cosine similarity
|
|
2430
|
+
const similarity = this.cosineSimilarity(emb1, emb2);
|
|
2431
|
+
if (similarity >= similarity_threshold) {
|
|
2432
|
+
duplicatePairs.push([
|
|
2433
|
+
String(allObs.rows[i][0]), // id1
|
|
2434
|
+
String(allObs.rows[j][0]), // id2
|
|
2435
|
+
similarity
|
|
2436
|
+
]);
|
|
2437
|
+
}
|
|
2438
|
+
}
|
|
2439
|
+
}
|
|
2440
|
+
stats.duplicates_found = duplicatePairs.length;
|
|
2441
|
+
console.error(`[Defrag] Found ${stats.duplicates_found} duplicate observation pairs`);
|
|
2442
|
+
// Step 2: Find fragmented knowledge islands (small connected components)
|
|
2443
|
+
console.error(`[Defrag] Step 2: Detecting fragmented knowledge islands`);
|
|
2444
|
+
const components = await this.recomputeConnectedComponents();
|
|
2445
|
+
const smallIslands = Object.entries(components.components || {})
|
|
2446
|
+
.filter(([_, entities]) => entities.length > 0 && entities.length <= min_island_size)
|
|
2447
|
+
.map(([compId, entities]) => ({ compId, entities: entities, size: entities.length }));
|
|
2448
|
+
stats.islands_found = smallIslands.length;
|
|
2449
|
+
console.error(`[Defrag] Found ${stats.islands_found} small knowledge islands (size <= ${min_island_size})`);
|
|
2450
|
+
// Step 3: Find orphaned entities (no observations, no relations)
|
|
2451
|
+
console.error(`[Defrag] Step 3: Detecting orphaned entities`);
|
|
2452
|
+
// Simplified approach: Get all entities, then filter in JavaScript
|
|
2453
|
+
const allEntities = await this.db.run(`
|
|
2454
|
+
?[id, name, type] := *entity{id, name, type, @ "NOW"}, id != "global_user_profile"
|
|
2455
|
+
`);
|
|
2456
|
+
const orphanedEntities = { rows: [] };
|
|
2457
|
+
for (const row of allEntities.rows) {
|
|
2458
|
+
const entityId = String(row[0]);
|
|
2459
|
+
// Check if entity has observations
|
|
2460
|
+
const obsCheck = await this.db.run(`
|
|
2461
|
+
?[count(id)] := *observation{id, entity_id, @ "NOW"}, entity_id = $eid
|
|
2462
|
+
`, { eid: entityId });
|
|
2463
|
+
const hasObs = Number(obsCheck.rows[0]?.[0] || 0) > 0;
|
|
2464
|
+
if (hasObs)
|
|
2465
|
+
continue;
|
|
2466
|
+
// Check if entity has relationships
|
|
2467
|
+
const relCheck = await this.db.run(`
|
|
2468
|
+
out[count(from_id)] := *relationship{from_id, @ "NOW"}, from_id = $eid
|
|
2469
|
+
in[count(to_id)] := *relationship{to_id, @ "NOW"}, to_id = $eid
|
|
2470
|
+
?[total] := out[out_count], in[in_count], total = out_count + in_count
|
|
2471
|
+
`, { eid: entityId });
|
|
2472
|
+
const hasRel = Number(relCheck.rows[0]?.[0] || 0) > 0;
|
|
2473
|
+
if (hasRel)
|
|
2474
|
+
continue;
|
|
2475
|
+
// This entity is orphaned
|
|
2476
|
+
orphanedEntities.rows.push(row);
|
|
2477
|
+
}
|
|
2478
|
+
stats.orphans_found = orphanedEntities.rows.length;
|
|
2479
|
+
console.error(`[Defrag] Found ${stats.orphans_found} orphaned entities`);
|
|
2480
|
+
// If not confirmed, return dry-run results
|
|
2481
|
+
if (!args.confirm) {
|
|
2482
|
+
return {
|
|
2483
|
+
status: "dry_run",
|
|
2484
|
+
message: "Defragmentation analysis complete. Set confirm=true to execute.",
|
|
2485
|
+
statistics: stats,
|
|
2486
|
+
preview: {
|
|
2487
|
+
duplicate_samples: duplicatePairs.slice(0, 5).map(([id1, id2, sim]) => ({
|
|
2488
|
+
observation_id_1: id1,
|
|
2489
|
+
observation_id_2: id2,
|
|
2490
|
+
similarity: sim
|
|
2491
|
+
})),
|
|
2492
|
+
island_samples: smallIslands.slice(0, 5),
|
|
2493
|
+
orphan_samples: orphanedEntities.rows.slice(0, 5).map((r) => ({
|
|
2494
|
+
id: String(r[0]),
|
|
2495
|
+
name: String(r[1]),
|
|
2496
|
+
type: String(r[2])
|
|
2497
|
+
}))
|
|
2498
|
+
}
|
|
2499
|
+
};
|
|
2500
|
+
}
|
|
2501
|
+
// Execute defragmentation
|
|
2502
|
+
console.error(`[Defrag] Executing defragmentation...`);
|
|
2503
|
+
// Merge duplicates: Keep first, delete second
|
|
2504
|
+
for (const [id1, id2, similarity] of duplicatePairs) {
|
|
2505
|
+
try {
|
|
2506
|
+
// Delete the duplicate observation
|
|
2507
|
+
await this.db.run(`
|
|
2508
|
+
?[id, created_at] := *observation{id, created_at}, id = $id2
|
|
2509
|
+
:rm observation {id, created_at}
|
|
2510
|
+
`, { id2 });
|
|
2511
|
+
stats.duplicates_merged++;
|
|
2512
|
+
}
|
|
2513
|
+
catch (e) {
|
|
2514
|
+
console.error(`[Defrag] Failed to merge duplicate ${id2}:`, e.message);
|
|
2515
|
+
}
|
|
2516
|
+
}
|
|
2517
|
+
// Connect islands: Create semantic relations to main graph
|
|
2518
|
+
for (const island of smallIslands) {
|
|
2519
|
+
try {
|
|
2520
|
+
// Find the most central entity in the island
|
|
2521
|
+
const islandEntityIds = island.entities.map((e) => e.id);
|
|
2522
|
+
// Find semantically similar entities in the main graph (not in this island)
|
|
2523
|
+
for (const entityId of islandEntityIds) {
|
|
2524
|
+
const entityData = await this.db.run(`
|
|
2525
|
+
?[embedding] := *entity{id: $id, embedding, @ "NOW"}
|
|
2526
|
+
`, { id: entityId });
|
|
2527
|
+
if (entityData.rows.length === 0)
|
|
2528
|
+
continue;
|
|
2529
|
+
const embedding = entityData.rows[0][0];
|
|
2530
|
+
if (!embedding || embedding.length === 0)
|
|
2531
|
+
continue;
|
|
2532
|
+
// Find similar entity in main graph
|
|
2533
|
+
const similarEntities = await this.db.run(`
|
|
2534
|
+
~entity:semantic { id: target_id | query: $emb, k: 1, bind_distance: dist }
|
|
2535
|
+
?[target_id, dist] := ~entity:semantic { id: target_id | query: $emb, k: 1, bind_distance: dist },
|
|
2536
|
+
target_id != $id
|
|
2537
|
+
`, { emb: embedding, id: entityId });
|
|
2538
|
+
if (similarEntities.rows.length > 0) {
|
|
2539
|
+
const targetId = String(similarEntities.rows[0][0]);
|
|
2540
|
+
const distance = Number(similarEntities.rows[0][1]);
|
|
2541
|
+
const similarity = 1 - distance;
|
|
2542
|
+
// Create bridge relation
|
|
2543
|
+
if (similarity >= 0.7) {
|
|
2544
|
+
await this.createRelation({
|
|
2545
|
+
from_id: entityId,
|
|
2546
|
+
to_id: targetId,
|
|
2547
|
+
relation_type: "semantically_related",
|
|
2548
|
+
strength: similarity,
|
|
2549
|
+
metadata: { created_by: "defrag", reason: "island_connection" }
|
|
2550
|
+
});
|
|
2551
|
+
stats.islands_connected++;
|
|
2552
|
+
break; // One connection per island is enough
|
|
2553
|
+
}
|
|
2554
|
+
}
|
|
2555
|
+
}
|
|
2556
|
+
}
|
|
2557
|
+
catch (e) {
|
|
2558
|
+
console.error(`[Defrag] Failed to connect island ${island.compId}:`, e.message);
|
|
2559
|
+
}
|
|
2560
|
+
}
|
|
2561
|
+
// Remove orphaned entities
|
|
2562
|
+
for (const row of orphanedEntities.rows) {
|
|
2563
|
+
try {
|
|
2564
|
+
const entityId = String(row[0]);
|
|
2565
|
+
await this.deleteEntity({ entity_id: entityId });
|
|
2566
|
+
stats.orphans_removed++;
|
|
2567
|
+
}
|
|
2568
|
+
catch (e) {
|
|
2569
|
+
console.error(`[Defrag] Failed to remove orphan ${row[0]}:`, e.message);
|
|
2570
|
+
}
|
|
2571
|
+
}
|
|
2572
|
+
this.trackOperation('defrag', startTime);
|
|
2573
|
+
return {
|
|
2574
|
+
status: "completed",
|
|
2575
|
+
message: `Defragmentation complete: ${stats.duplicates_merged} duplicates merged, ${stats.islands_connected} islands connected, ${stats.orphans_removed} orphans removed`,
|
|
2576
|
+
statistics: stats,
|
|
2577
|
+
duration_ms: Date.now() - startTime
|
|
2578
|
+
};
|
|
2579
|
+
}
|
|
2580
|
+
catch (error) {
|
|
2581
|
+
console.error("[Defrag] Error during defragmentation:", error);
|
|
2582
|
+
this.trackError('defrag');
|
|
2583
|
+
return { error: "Defragmentation failed", message: error.message };
|
|
2584
|
+
}
|
|
2585
|
+
}
|
|
2586
|
+
cosineSimilarity(a, b) {
|
|
2587
|
+
if (a.length !== b.length)
|
|
2588
|
+
return 0;
|
|
2589
|
+
let dotProduct = 0;
|
|
2590
|
+
let normA = 0;
|
|
2591
|
+
let normB = 0;
|
|
2592
|
+
for (let i = 0; i < a.length; i++) {
|
|
2593
|
+
dotProduct += a[i] * b[i];
|
|
2594
|
+
normA += a[i] * a[i];
|
|
2595
|
+
normB += b[i] * b[i];
|
|
2596
|
+
}
|
|
2597
|
+
if (normA === 0 || normB === 0)
|
|
2598
|
+
return 0;
|
|
2599
|
+
return dotProduct / (Math.sqrt(normA) * Math.sqrt(normB));
|
|
2600
|
+
}
|
|
2386
2601
|
async invalidateObservation(args) {
|
|
2387
2602
|
await this.initPromise;
|
|
2388
2603
|
const startTime = Date.now();
|
|
@@ -3183,13 +3398,56 @@ Validation: Invalid syntax or missing columns in inference rules will result in
|
|
|
3183
3398
|
session_id: zod_1.z.string().optional().describe("Prioritize results from this session"),
|
|
3184
3399
|
task_id: zod_1.z.string().optional().describe("Prioritize results from this task"),
|
|
3185
3400
|
}),
|
|
3401
|
+
zod_1.z.object({
|
|
3402
|
+
action: zod_1.z.literal("dynamic_fusion"),
|
|
3403
|
+
query: zod_1.z.string().describe("Search query"),
|
|
3404
|
+
config: zod_1.z.object({
|
|
3405
|
+
vector: zod_1.z.object({
|
|
3406
|
+
enabled: zod_1.z.boolean().optional().default(true),
|
|
3407
|
+
weight: zod_1.z.number().min(0).max(1).optional().default(0.4),
|
|
3408
|
+
topK: zod_1.z.number().optional().default(20),
|
|
3409
|
+
efSearch: zod_1.z.number().optional().default(100),
|
|
3410
|
+
}).optional(),
|
|
3411
|
+
sparse: zod_1.z.object({
|
|
3412
|
+
enabled: zod_1.z.boolean().optional().default(true),
|
|
3413
|
+
weight: zod_1.z.number().min(0).max(1).optional().default(0.3),
|
|
3414
|
+
topK: zod_1.z.number().optional().default(20),
|
|
3415
|
+
minScore: zod_1.z.number().optional().default(0.1),
|
|
3416
|
+
}).optional(),
|
|
3417
|
+
fts: zod_1.z.object({
|
|
3418
|
+
enabled: zod_1.z.boolean().optional().default(true),
|
|
3419
|
+
weight: zod_1.z.number().min(0).max(1).optional().default(0.2),
|
|
3420
|
+
topK: zod_1.z.number().optional().default(20),
|
|
3421
|
+
fuzzy: zod_1.z.boolean().optional().default(true),
|
|
3422
|
+
}).optional(),
|
|
3423
|
+
graph: zod_1.z.object({
|
|
3424
|
+
enabled: zod_1.z.boolean().optional().default(true),
|
|
3425
|
+
weight: zod_1.z.number().min(0).max(1).optional().default(0.1),
|
|
3426
|
+
maxDepth: zod_1.z.number().min(1).max(3).optional().default(2),
|
|
3427
|
+
maxResults: zod_1.z.number().optional().default(20),
|
|
3428
|
+
relationTypes: zod_1.z.array(zod_1.z.string()).optional(),
|
|
3429
|
+
}).optional(),
|
|
3430
|
+
fusion: zod_1.z.object({
|
|
3431
|
+
strategy: zod_1.z.enum(['rrf', 'weighted_sum', 'max', 'adaptive']).optional().default('rrf'),
|
|
3432
|
+
rrfK: zod_1.z.number().optional().default(60),
|
|
3433
|
+
minScore: zod_1.z.number().optional().default(0.0),
|
|
3434
|
+
deduplication: zod_1.z.boolean().optional().default(true),
|
|
3435
|
+
}).optional(),
|
|
3436
|
+
}).optional().describe("Dynamic fusion configuration (all paths optional)"),
|
|
3437
|
+
limit: zod_1.z.number().optional().default(10).describe("Maximum number of results to return"),
|
|
3438
|
+
}),
|
|
3439
|
+
zod_1.z.object({
|
|
3440
|
+
action: zod_1.z.literal("adaptive_retrieval"),
|
|
3441
|
+
query: zod_1.z.string().describe("Search query for adaptive retrieval"),
|
|
3442
|
+
limit: zod_1.z.number().optional().default(10).describe("Maximum number of results"),
|
|
3443
|
+
}),
|
|
3186
3444
|
]);
|
|
3187
3445
|
const QueryMemoryParameters = zod_1.z.object({
|
|
3188
3446
|
action: zod_1.z
|
|
3189
|
-
.enum(["search", "advancedSearch", "context", "entity_details", "history", "graph_rag", "graph_walking", "agentic_search"])
|
|
3447
|
+
.enum(["search", "advancedSearch", "context", "entity_details", "history", "graph_rag", "graph_walking", "agentic_search", "dynamic_fusion", "adaptive_retrieval"])
|
|
3190
3448
|
.describe("Action (determines which fields are required)"),
|
|
3191
|
-
query: zod_1.z.string().optional().describe("Required for search/advancedSearch/context/graph_rag/graph_walking/agentic_search"),
|
|
3192
|
-
limit: zod_1.z.number().optional().describe("Only for search/advancedSearch/graph_rag/graph_walking"),
|
|
3449
|
+
query: zod_1.z.string().optional().describe("Required for search/advancedSearch/context/graph_rag/graph_walking/agentic_search/dynamic_fusion/adaptive_retrieval"),
|
|
3450
|
+
limit: zod_1.z.number().optional().describe("Only for search/advancedSearch/graph_rag/graph_walking/dynamic_fusion/adaptive_retrieval"),
|
|
3193
3451
|
session_id: zod_1.z.string().optional().describe("Optional session ID for context boosting"),
|
|
3194
3452
|
task_id: zod_1.z.string().optional().describe("Optional task ID for context boosting"),
|
|
3195
3453
|
filters: zod_1.z.any().optional().describe("Only for advancedSearch"),
|
|
@@ -3205,6 +3463,7 @@ Validation: Invalid syntax or missing columns in inference rules will result in
|
|
|
3205
3463
|
max_depth: zod_1.z.number().optional().describe("Only for graph_rag/graph_walking: Maximum expansion depth"),
|
|
3206
3464
|
start_entity_id: zod_1.z.string().optional().describe("Only for graph_walking: Start entity"),
|
|
3207
3465
|
rerank: zod_1.z.boolean().optional().describe("Only for search/advancedSearch/agentic_search: Enable Cross-Encoder reranking"),
|
|
3466
|
+
config: zod_1.z.any().optional().describe("Only for dynamic_fusion: Fusion configuration object"),
|
|
3208
3467
|
});
|
|
3209
3468
|
this.mcp.addTool({
|
|
3210
3469
|
name: "query_memory",
|
|
@@ -3220,8 +3479,10 @@ Supported actions:
|
|
|
3220
3479
|
- 'graph_rag': Graph-based reasoning (Hybrid RAG). Finds semantic vector seeds first, then expands via graph traversals. Ideal for multi-hop reasoning. Params: { query: string, max_depth?: number, limit?: number }.
|
|
3221
3480
|
- 'graph_walking': Recursive semantic graph search. Starts at vector seeds or an entity and follows relationships to other semantically relevant entities. Params: { query: string, start_entity_id?: string, max_depth?: number, limit?: number }.
|
|
3222
3481
|
- 'agentic_search': Auto-Routing Search. Uses local LLM to analyze intent and routes the query automatically to the best strategy (Vector, Graph, or Community Summaries). Params: { query: string, limit?: number }.
|
|
3482
|
+
- 'adaptive_retrieval': GraphRAG-R1 inspired adaptive retrieval with Progressive Retrieval Attenuation (PRA) and Cost-Aware F1 (CAF) scoring. Automatically selects optimal strategy based on query complexity and historical performance. Params: { query: string, limit?: number }.
|
|
3483
|
+
- 'dynamic_fusion': Advanced multi-path fusion search combining Vector (HNSW), Sparse (keyword), FTS (full-text), and Graph traversal with configurable weights and strategies. Params: { query: string, config?: { vector?, sparse?, fts?, graph?, fusion? }, limit?: number }. Each path can be enabled/disabled and weighted independently. Fusion strategies: 'rrf' (Reciprocal Rank Fusion), 'weighted_sum', 'max', 'adaptive'. Returns results with path contribution details and performance stats.
|
|
3223
3484
|
|
|
3224
|
-
Notes: '
|
|
3485
|
+
Notes: 'adaptive_retrieval' learns from usage and optimizes over time. 'dynamic_fusion' provides the most control and transparency over retrieval paths. 'agentic_search' is the most adaptive. 'context' is ideal for exploratory questions. 'search' and 'advancedSearch' are better for targeted fact retrieval.`,
|
|
3225
3486
|
parameters: QueryMemoryParameters,
|
|
3226
3487
|
execute: async (args) => {
|
|
3227
3488
|
await this.initPromise;
|
|
@@ -3392,6 +3653,101 @@ Notes: 'agentic_search' is the most powerful and adaptable, 'context' is ideal f
|
|
|
3392
3653
|
});
|
|
3393
3654
|
return JSON.stringify(results);
|
|
3394
3655
|
}
|
|
3656
|
+
if (input.action === "dynamic_fusion") {
|
|
3657
|
+
const startTime = Date.now();
|
|
3658
|
+
if (!input.query || input.query.trim().length === 0) {
|
|
3659
|
+
return JSON.stringify({ error: "Search query must not be empty." });
|
|
3660
|
+
}
|
|
3661
|
+
try {
|
|
3662
|
+
console.log('[query_memory] Dynamic Fusion search:', {
|
|
3663
|
+
query: input.query,
|
|
3664
|
+
config: input.config ? 'custom' : 'default',
|
|
3665
|
+
limit: input.limit
|
|
3666
|
+
});
|
|
3667
|
+
// Execute dynamic fusion search
|
|
3668
|
+
const { results, stats } = await this.dynamicFusion.search(input.query, input.config || {});
|
|
3669
|
+
// Apply limit
|
|
3670
|
+
const limitedResults = results.slice(0, input.limit || 10);
|
|
3671
|
+
const response = {
|
|
3672
|
+
results: limitedResults,
|
|
3673
|
+
stats: {
|
|
3674
|
+
...stats,
|
|
3675
|
+
totalTime: Date.now() - startTime,
|
|
3676
|
+
resultsReturned: limitedResults.length,
|
|
3677
|
+
resultsTotal: results.length
|
|
3678
|
+
},
|
|
3679
|
+
metadata: {
|
|
3680
|
+
query: input.query,
|
|
3681
|
+
fusionStrategy: input.config?.fusion?.strategy || 'rrf',
|
|
3682
|
+
enabledPaths: {
|
|
3683
|
+
vector: input.config?.vector?.enabled !== false,
|
|
3684
|
+
sparse: input.config?.sparse?.enabled !== false,
|
|
3685
|
+
fts: input.config?.fts?.enabled !== false,
|
|
3686
|
+
graph: input.config?.graph?.enabled !== false
|
|
3687
|
+
}
|
|
3688
|
+
}
|
|
3689
|
+
};
|
|
3690
|
+
console.log('[query_memory] Dynamic Fusion completed:', {
|
|
3691
|
+
resultsReturned: limitedResults.length,
|
|
3692
|
+
totalTime: response.stats.totalTime,
|
|
3693
|
+
pathContributions: stats.pathContributions
|
|
3694
|
+
});
|
|
3695
|
+
return JSON.stringify(response);
|
|
3696
|
+
}
|
|
3697
|
+
catch (error) {
|
|
3698
|
+
console.error('[query_memory] Dynamic Fusion error:', error);
|
|
3699
|
+
return JSON.stringify({
|
|
3700
|
+
error: "Dynamic fusion search failed",
|
|
3701
|
+
details: error.message
|
|
3702
|
+
});
|
|
3703
|
+
}
|
|
3704
|
+
}
|
|
3705
|
+
if (input.action === "adaptive_retrieval") {
|
|
3706
|
+
const startTime = Date.now();
|
|
3707
|
+
if (!input.query || input.query.trim().length === 0) {
|
|
3708
|
+
return JSON.stringify({ error: "Search query must not be empty." });
|
|
3709
|
+
}
|
|
3710
|
+
try {
|
|
3711
|
+
console.log('[query_memory] Adaptive Retrieval search:', {
|
|
3712
|
+
query: input.query,
|
|
3713
|
+
limit: input.limit
|
|
3714
|
+
});
|
|
3715
|
+
// Execute adaptive retrieval
|
|
3716
|
+
const result = await this.adaptiveRetrieval.retrieve(input.query, input.limit || 10);
|
|
3717
|
+
const response = {
|
|
3718
|
+
results: result.results,
|
|
3719
|
+
metadata: {
|
|
3720
|
+
query: input.query,
|
|
3721
|
+
strategy: result.strategy,
|
|
3722
|
+
retrievalCount: result.retrievalCount,
|
|
3723
|
+
latency: result.latency,
|
|
3724
|
+
cafScore: result.cafScore,
|
|
3725
|
+
totalTime: Date.now() - startTime
|
|
3726
|
+
},
|
|
3727
|
+
performance: {
|
|
3728
|
+
strategyUsed: result.strategy,
|
|
3729
|
+
retrievalCalls: result.retrievalCount,
|
|
3730
|
+
costAwareF1: result.cafScore,
|
|
3731
|
+
latencyMs: result.latency
|
|
3732
|
+
}
|
|
3733
|
+
};
|
|
3734
|
+
console.log('[query_memory] Adaptive Retrieval completed:', {
|
|
3735
|
+
strategy: result.strategy,
|
|
3736
|
+
resultsReturned: result.results.length,
|
|
3737
|
+
retrievalCount: result.retrievalCount,
|
|
3738
|
+
cafScore: result.cafScore?.toFixed(3),
|
|
3739
|
+
totalTime: response.metadata.totalTime
|
|
3740
|
+
});
|
|
3741
|
+
return JSON.stringify(response);
|
|
3742
|
+
}
|
|
3743
|
+
catch (error) {
|
|
3744
|
+
console.error('[query_memory] Adaptive Retrieval error:', error);
|
|
3745
|
+
return JSON.stringify({
|
|
3746
|
+
error: "Adaptive retrieval search failed",
|
|
3747
|
+
details: error.message
|
|
3748
|
+
});
|
|
3749
|
+
}
|
|
3750
|
+
}
|
|
3395
3751
|
if (input.action === "graph_rag") {
|
|
3396
3752
|
if (!input.query || input.query.trim().length === 0) {
|
|
3397
3753
|
return JSON.stringify({ error: "Search query must not be empty." });
|
|
@@ -3865,6 +4221,12 @@ Supported actions:
|
|
|
3865
4221
|
min_entity_degree: zod_1.z.number().min(0).max(100).optional().default(2),
|
|
3866
4222
|
model: zod_1.z.string().optional().default("demyagent-4b-i1:Q6_K"),
|
|
3867
4223
|
}),
|
|
4224
|
+
zod_1.z.object({
|
|
4225
|
+
action: zod_1.z.literal("defrag"),
|
|
4226
|
+
confirm: zod_1.z.boolean().describe("Must be true to confirm defragmentation"),
|
|
4227
|
+
similarity_threshold: zod_1.z.number().min(0.8).max(1.0).optional().default(0.95).describe("Similarity threshold for duplicate detection (0.8-1.0)"),
|
|
4228
|
+
min_island_size: zod_1.z.number().min(1).max(10).optional().default(3).describe("Maximum size of knowledge islands to connect"),
|
|
4229
|
+
}),
|
|
3868
4230
|
zod_1.z.object({
|
|
3869
4231
|
action: zod_1.z.literal("reflect"),
|
|
3870
4232
|
entity_id: zod_1.z.string().optional().describe("Optional entity ID for targeted reflection"),
|
|
@@ -3889,7 +4251,7 @@ Supported actions:
|
|
|
3889
4251
|
]);
|
|
3890
4252
|
const ManageSystemParameters = zod_1.z.object({
|
|
3891
4253
|
action: zod_1.z
|
|
3892
|
-
.enum(["health", "metrics", "export_memory", "import_memory", "snapshot_create", "snapshot_list", "snapshot_diff", "cleanup", "reflect", "clear_memory", "summarize_communities", "compact"])
|
|
4254
|
+
.enum(["health", "metrics", "export_memory", "import_memory", "snapshot_create", "snapshot_list", "snapshot_diff", "cleanup", "defrag", "reflect", "clear_memory", "summarize_communities", "compact"])
|
|
3893
4255
|
.describe("Action (determines which fields are required)"),
|
|
3894
4256
|
format: zod_1.z.enum(["json", "markdown", "obsidian"]).optional().describe("Export format (for export_memory)"),
|
|
3895
4257
|
includeMetadata: zod_1.z.boolean().optional().describe("Include metadata (for export_memory)"),
|
|
@@ -3904,10 +4266,12 @@ Supported actions:
|
|
|
3904
4266
|
snapshot_id_a: zod_1.z.string().optional().describe("Required for snapshot_diff"),
|
|
3905
4267
|
snapshot_id_b: zod_1.z.string().optional().describe("Required for snapshot_diff"),
|
|
3906
4268
|
metadata: MetadataSchema.optional().describe("Optional for snapshot_create"),
|
|
3907
|
-
confirm: zod_1.z.boolean().optional().describe("Required for cleanup/clear_memory and must be true"),
|
|
4269
|
+
confirm: zod_1.z.boolean().optional().describe("Required for cleanup/defrag/clear_memory and must be true"),
|
|
3908
4270
|
older_than_days: zod_1.z.number().optional().describe("Optional for cleanup"),
|
|
3909
4271
|
max_observations: zod_1.z.number().optional().describe("Optional for cleanup"),
|
|
3910
4272
|
min_entity_degree: zod_1.z.number().optional().describe("Optional for cleanup"),
|
|
4273
|
+
similarity_threshold: zod_1.z.number().optional().describe("Optional for defrag (0.8-1.0, default 0.95)"),
|
|
4274
|
+
min_island_size: zod_1.z.number().optional().describe("Optional for defrag (1-10, default 3)"),
|
|
3911
4275
|
model: zod_1.z.string().optional().describe("Optional for cleanup/reflect/summarize_communities"),
|
|
3912
4276
|
entity_id: zod_1.z.string().optional().describe("Optional for reflect"),
|
|
3913
4277
|
min_community_size: zod_1.z.number().optional().describe("Optional for summarize_communities"),
|
|
@@ -3934,6 +4298,9 @@ Supported actions:
|
|
|
3934
4298
|
- 'cleanup': Janitor service for consolidation. Params: { confirm: boolean, older_than_days?: number, max_observations?: number, min_entity_degree?: number, model?: string }.
|
|
3935
4299
|
* With confirm=false: Dry-Run (shows candidates).
|
|
3936
4300
|
* With confirm=true: Merges old/isolated fragments using LLM (Executive Summary) and removes noise.
|
|
4301
|
+
- 'defrag': Memory defragmentation. Reorganizes memory structure by detecting/merging duplicates, connecting fragmented knowledge islands, and removing orphaned entities. Params: { confirm: boolean, similarity_threshold?: number (0.8-1.0, default 0.95), min_island_size?: number (1-10, default 3) }.
|
|
4302
|
+
* With confirm=false: Dry-Run (shows analysis and candidates).
|
|
4303
|
+
* With confirm=true: Executes defragmentation and returns statistics.
|
|
3937
4304
|
- 'reflect': Reflection service. Analyzes memory for contradictions and insights. Params: { entity_id?: string, model?: string }.
|
|
3938
4305
|
- 'clear_memory': Resets the entire database. Params: { confirm: boolean (must be true) }.
|
|
3939
4306
|
- 'summarize_communities': Hierarchical GraphRAG. Generates summaries for entity clusters. Params: { model?: string, min_community_size?: number }.`,
|
|
@@ -4124,6 +4491,19 @@ Supported actions:
|
|
|
4124
4491
|
return JSON.stringify({ error: error.message || "Error during cleanup" });
|
|
4125
4492
|
}
|
|
4126
4493
|
}
|
|
4494
|
+
if (input.action === "defrag") {
|
|
4495
|
+
try {
|
|
4496
|
+
const result = await this.defragMemory({
|
|
4497
|
+
confirm: Boolean(input.confirm),
|
|
4498
|
+
similarity_threshold: input.similarity_threshold,
|
|
4499
|
+
min_island_size: input.min_island_size,
|
|
4500
|
+
});
|
|
4501
|
+
return JSON.stringify(result);
|
|
4502
|
+
}
|
|
4503
|
+
catch (error) {
|
|
4504
|
+
return JSON.stringify({ error: error.message || "Error during defragmentation" });
|
|
4505
|
+
}
|
|
4506
|
+
}
|
|
4127
4507
|
if (input.action === "reflect") {
|
|
4128
4508
|
try {
|
|
4129
4509
|
const result = await this.reflectMemory({
|