cozo-memory 1.0.6 → 1.0.8
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 +77 -10
- package/dist/cli-commands.js +6 -0
- package/dist/cli.js +80 -0
- package/dist/eval-suite.js +136 -0
- package/dist/hybrid-search.js +99 -0
- package/dist/index.js +361 -43
- package/dist/inference-engine.js +7 -0
- package/dist/make-old.js +58 -0
- package/dist/test-agentic-retrieval.js +28 -0
- package/dist/test-discovery.js +53 -0
- package/dist/test-graphrag.js +35 -0
- package/dist/test-user-profile.js +59 -0
- package/dist/tui.js +6 -4
- package/dist/verify-agentic-logic.js +64 -0
- package/package.json +2 -1
- package/dist/test-mcp-search.js +0 -47
- package/dist/tui-blessed.js +0 -789
- package/dist/tui.py +0 -481
package/dist/index.js
CHANGED
|
@@ -361,6 +361,124 @@ class MemoryServer {
|
|
|
361
361
|
}
|
|
362
362
|
return result.rows.map((r) => ({ entity_id: String(r[0]), community_id: String(r[1]) }));
|
|
363
363
|
}
|
|
364
|
+
async summarizeCommunities(args) {
|
|
365
|
+
await this.initPromise;
|
|
366
|
+
const minSize = Math.max(2, Math.floor(args?.min_community_size ?? 3));
|
|
367
|
+
const model = args?.model ?? "demyagent-4b-i1:Q6_K";
|
|
368
|
+
console.error("[GraphRAG] Recomputing communities before summarization...");
|
|
369
|
+
const commResult = await this.recomputeCommunities();
|
|
370
|
+
if (commResult.length === 0) {
|
|
371
|
+
return { status: "no_data", generated_summaries: 0 };
|
|
372
|
+
}
|
|
373
|
+
// Group entities by community
|
|
374
|
+
const byCommunity = new Map();
|
|
375
|
+
for (const row of commResult) {
|
|
376
|
+
const arr = byCommunity.get(row.community_id) ?? [];
|
|
377
|
+
arr.push(row.entity_id);
|
|
378
|
+
byCommunity.set(row.community_id, arr);
|
|
379
|
+
}
|
|
380
|
+
let generatedSummaries = 0;
|
|
381
|
+
const results = [];
|
|
382
|
+
for (const [communityId, entityIds] of byCommunity.entries()) {
|
|
383
|
+
if (entityIds.length < minSize)
|
|
384
|
+
continue;
|
|
385
|
+
console.error(`[GraphRAG] Summarizing community ${communityId} with ${entityIds.length} members...`);
|
|
386
|
+
// Fetch details for all entities in this community
|
|
387
|
+
const entityInfos = [];
|
|
388
|
+
for (const eId of entityIds) {
|
|
389
|
+
try {
|
|
390
|
+
// get entity
|
|
391
|
+
const entRes = await this.db.run('?[name, type] := *entity{id: $id, name, type, @ "NOW"}', { id: eId });
|
|
392
|
+
if (entRes.rows.length > 0) {
|
|
393
|
+
const name = entRes.rows[0][0];
|
|
394
|
+
const type = entRes.rows[0][1];
|
|
395
|
+
// get top observations for this entity
|
|
396
|
+
const obsRes = await this.db.run('?[text, created_at] := *observation{entity_id: $id, text, created_at, @ "NOW"} :limit 3', { id: eId });
|
|
397
|
+
const obs = obsRes.rows.map((r) => r[0]);
|
|
398
|
+
entityInfos.push(`- ${name} (${type})` + (obs.length > 0 ? `\n Notes: ${obs.join("; ")}` : ""));
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
catch (e) {
|
|
402
|
+
console.warn(`[GraphRAG] Error fetching info for ${eId}: ${e.message}`);
|
|
403
|
+
}
|
|
404
|
+
}
|
|
405
|
+
if (entityInfos.length === 0)
|
|
406
|
+
continue;
|
|
407
|
+
const systemPrompt = "You are an expert analyst. Summarize the following community of related entities into a single, cohesive 'Community Report'. Identify the overarching themes, topics, and connections between these entities. Respond ONLY with the finalized summary text.";
|
|
408
|
+
const userPrompt = `Community Size: ${entityIds.length}\n\nMembers & Notes:\n${entityInfos.join("\n")}`;
|
|
409
|
+
let summaryText = "";
|
|
410
|
+
try {
|
|
411
|
+
const ollamaMod = await import("ollama");
|
|
412
|
+
const ollamaClient = ollamaMod?.default ?? ollamaMod;
|
|
413
|
+
const response = await ollamaClient.chat({
|
|
414
|
+
model,
|
|
415
|
+
messages: [
|
|
416
|
+
{ role: "system", content: systemPrompt },
|
|
417
|
+
{ role: "user", content: userPrompt },
|
|
418
|
+
],
|
|
419
|
+
});
|
|
420
|
+
summaryText = response?.message?.content?.trim?.() ?? "";
|
|
421
|
+
}
|
|
422
|
+
catch (e) {
|
|
423
|
+
console.warn(`[GraphRAG] Ollama error for community ${communityId}: ${e.message}`);
|
|
424
|
+
}
|
|
425
|
+
if (!summaryText || summaryText === "") {
|
|
426
|
+
console.warn(`[GraphRAG] Could not generate summary for community ${communityId}, skipping.`);
|
|
427
|
+
continue;
|
|
428
|
+
}
|
|
429
|
+
// Create new CommunitySummary entity
|
|
430
|
+
const nowIso = new Date().toISOString();
|
|
431
|
+
const summaryName = `Community Summary ${communityId.slice(0, 8)} (${nowIso.slice(0, 10)})`;
|
|
432
|
+
const sumEntity = await this.createEntity({
|
|
433
|
+
name: summaryName,
|
|
434
|
+
type: "CommunitySummary",
|
|
435
|
+
metadata: {
|
|
436
|
+
graphrag: {
|
|
437
|
+
kind: "community_summary",
|
|
438
|
+
community_id: communityId,
|
|
439
|
+
member_count: entityIds.length,
|
|
440
|
+
member_ids: entityIds,
|
|
441
|
+
model,
|
|
442
|
+
summarized_at: nowIso
|
|
443
|
+
}
|
|
444
|
+
}
|
|
445
|
+
});
|
|
446
|
+
const summaryEntityId = sumEntity?.id;
|
|
447
|
+
if (summaryEntityId) {
|
|
448
|
+
await this.addObservation({
|
|
449
|
+
entity_id: summaryEntityId,
|
|
450
|
+
text: summaryText,
|
|
451
|
+
metadata: {
|
|
452
|
+
graphrag: {
|
|
453
|
+
kind: "community_summary",
|
|
454
|
+
community_id: communityId,
|
|
455
|
+
}
|
|
456
|
+
}
|
|
457
|
+
});
|
|
458
|
+
// Link the summary to all members
|
|
459
|
+
for (const memberId of entityIds) {
|
|
460
|
+
await this.createRelation({
|
|
461
|
+
from_id: summaryEntityId,
|
|
462
|
+
to_id: memberId,
|
|
463
|
+
relation_type: "summary_of",
|
|
464
|
+
strength: 1.0,
|
|
465
|
+
metadata: { community_id: communityId }
|
|
466
|
+
});
|
|
467
|
+
}
|
|
468
|
+
generatedSummaries++;
|
|
469
|
+
results.push({
|
|
470
|
+
community_id: communityId,
|
|
471
|
+
summary_entity_id: summaryEntityId,
|
|
472
|
+
member_count: entityIds.length
|
|
473
|
+
});
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
return {
|
|
477
|
+
status: "completed",
|
|
478
|
+
generated_summaries: generatedSummaries,
|
|
479
|
+
results
|
|
480
|
+
};
|
|
481
|
+
}
|
|
364
482
|
async recomputeBetweennessCentrality() {
|
|
365
483
|
await this.initPromise;
|
|
366
484
|
const edgeCheckRes = await this.db.run(`?[from_id] := *relationship{from_id, @ "NOW"} :limit 1`);
|
|
@@ -1482,6 +1600,7 @@ ids[id] <- $ids
|
|
|
1482
1600
|
async reflectMemory(args) {
|
|
1483
1601
|
await this.initPromise;
|
|
1484
1602
|
const model = args.model ?? "demyagent-4b-i1:Q6_K";
|
|
1603
|
+
const mode = args.mode ?? "summary";
|
|
1485
1604
|
const targetEntityId = args.entity_id;
|
|
1486
1605
|
let entitiesToReflect = [];
|
|
1487
1606
|
if (targetEntityId) {
|
|
@@ -1504,47 +1623,114 @@ ids[id] <- $ids
|
|
|
1504
1623
|
}
|
|
1505
1624
|
const results = [];
|
|
1506
1625
|
for (const entity of entitiesToReflect) {
|
|
1507
|
-
|
|
1508
|
-
|
|
1509
|
-
|
|
1510
|
-
|
|
1511
|
-
|
|
1512
|
-
|
|
1513
|
-
|
|
1514
|
-
|
|
1515
|
-
|
|
1626
|
+
if (mode === "summary") {
|
|
1627
|
+
const obsRes = await this.db.run('?[text, ts] := *observation{entity_id: $id, text, created_at, @ "NOW"}, ts = to_int(created_at) :order ts', {
|
|
1628
|
+
id: entity.id,
|
|
1629
|
+
});
|
|
1630
|
+
if (obsRes.rows.length < 2) {
|
|
1631
|
+
results.push({ entity_id: entity.id, status: "skipped", reason: "Too few observations for reflection" });
|
|
1632
|
+
continue;
|
|
1633
|
+
}
|
|
1634
|
+
const observations = obsRes.rows.map((r) => `- [${new Date(Number(r[1]) / 1000).toISOString()}] ${r[0]}`);
|
|
1635
|
+
const systemPrompt = `You are an analytical memory module. Analyze the following observations about an entity.
|
|
1516
1636
|
Look for contradictions, temporal developments, behavioral patterns, or deeper insights.
|
|
1517
1637
|
Formulate a concise reflection (max. 3-4 sentences) that helps the user understand the current state or evolution.
|
|
1518
1638
|
If there are contradictory statements, name them explicitly.
|
|
1519
1639
|
If no special patterns are recognizable, answer with "No new insights".`;
|
|
1520
|
-
|
|
1521
|
-
|
|
1522
|
-
|
|
1523
|
-
|
|
1524
|
-
|
|
1525
|
-
|
|
1526
|
-
|
|
1527
|
-
|
|
1528
|
-
|
|
1529
|
-
|
|
1530
|
-
|
|
1531
|
-
|
|
1532
|
-
|
|
1533
|
-
|
|
1534
|
-
|
|
1535
|
-
|
|
1536
|
-
|
|
1537
|
-
|
|
1538
|
-
|
|
1539
|
-
|
|
1540
|
-
|
|
1541
|
-
|
|
1542
|
-
|
|
1543
|
-
|
|
1544
|
-
|
|
1545
|
-
|
|
1546
|
-
|
|
1547
|
-
|
|
1640
|
+
const userPrompt = `Entity: ${entity.name} (${entity.type})\n\nObservations:\n${observations.join("\n")}`;
|
|
1641
|
+
let reflectionText;
|
|
1642
|
+
try {
|
|
1643
|
+
const ollamaMod = await import("ollama");
|
|
1644
|
+
const ollamaClient = ollamaMod?.default ?? ollamaMod;
|
|
1645
|
+
const response = await ollamaClient.chat({
|
|
1646
|
+
model,
|
|
1647
|
+
messages: [
|
|
1648
|
+
{ role: "system", content: systemPrompt },
|
|
1649
|
+
{ role: "user", content: userPrompt },
|
|
1650
|
+
],
|
|
1651
|
+
});
|
|
1652
|
+
reflectionText = response?.message?.content?.trim?.() ?? "";
|
|
1653
|
+
}
|
|
1654
|
+
catch (e) {
|
|
1655
|
+
console.error(`[Reflect] Ollama error for ${entity.name}:`, e);
|
|
1656
|
+
reflectionText = "";
|
|
1657
|
+
}
|
|
1658
|
+
if (reflectionText && reflectionText !== "No new insights" && !reflectionText.includes("No new insights")) {
|
|
1659
|
+
await this.addObservation({
|
|
1660
|
+
entity_id: entity.id,
|
|
1661
|
+
text: `Reflexive insight: ${reflectionText}`,
|
|
1662
|
+
metadata: { kind: "reflection", model, generated_at: Date.now() },
|
|
1663
|
+
});
|
|
1664
|
+
results.push({ entity_id: entity.id, status: "reflected", insight: reflectionText });
|
|
1665
|
+
}
|
|
1666
|
+
else {
|
|
1667
|
+
results.push({ entity_id: entity.id, status: "no_insight_found" });
|
|
1668
|
+
}
|
|
1669
|
+
}
|
|
1670
|
+
else if (mode === "discovery") {
|
|
1671
|
+
// Discovery Mode: Find and validate new relationships
|
|
1672
|
+
const candidates = await this.inferenceEngine.getRefinementCandidates(entity.id);
|
|
1673
|
+
const discoveryResults = [];
|
|
1674
|
+
const suggestions = [];
|
|
1675
|
+
for (const candidate of candidates) {
|
|
1676
|
+
// Check if relationship already exists
|
|
1677
|
+
const existing = await this.db.run(`
|
|
1678
|
+
?[count(from_id)] := *relationship{from_id, to_id, relation_type, @ "NOW"},
|
|
1679
|
+
from_id = $from, to_id = $to, relation_type = $rel
|
|
1680
|
+
`, { from: candidate.from_id, to: candidate.to_id, rel: candidate.relation_type });
|
|
1681
|
+
if (Number(existing.rows[0][0]) > 0)
|
|
1682
|
+
continue;
|
|
1683
|
+
// Load target entity details for context
|
|
1684
|
+
const targetRes = await this.db.run('?[name, type] := *entity{id, name, type, @ "NOW"}, id = $id', { id: candidate.to_id });
|
|
1685
|
+
const targetName = String(targetRes.rows[0][0]);
|
|
1686
|
+
const targetType = String(targetRes.rows[0][1]);
|
|
1687
|
+
const validatePrompt = `You are a Knowledge Graph specialized AI. Evaluate if the following suggested relationship is logically sound based on the reason provided.
|
|
1688
|
+
Source Entity: ${entity.name} (${entity.type})
|
|
1689
|
+
Target Entity: ${targetName} (${targetType})
|
|
1690
|
+
Suggested Relationship: ${candidate.relation_type}
|
|
1691
|
+
Reason: ${candidate.reason}
|
|
1692
|
+
|
|
1693
|
+
Respond with a JSON object:
|
|
1694
|
+
{
|
|
1695
|
+
"confidence": <0.0 to 1.0>,
|
|
1696
|
+
"reasoning": "<short explanation>",
|
|
1697
|
+
"verdict": "create" | "suggest" | "reject"
|
|
1698
|
+
}
|
|
1699
|
+
Assign "create" if confidence > 0.8, "suggest" if confidence > 0.5, else "reject".`;
|
|
1700
|
+
try {
|
|
1701
|
+
const ollamaMod = await import("ollama");
|
|
1702
|
+
const ollamaClient = ollamaMod?.default ?? ollamaMod;
|
|
1703
|
+
const response = await ollamaClient.chat({
|
|
1704
|
+
model,
|
|
1705
|
+
messages: [{ role: "user", content: validatePrompt }],
|
|
1706
|
+
format: "json"
|
|
1707
|
+
});
|
|
1708
|
+
const validation = JSON.parse(response?.message?.content ?? "{}");
|
|
1709
|
+
if (validation.verdict === "create" && validation.confidence > 0.8) {
|
|
1710
|
+
await this.createRelation({
|
|
1711
|
+
from_id: candidate.from_id,
|
|
1712
|
+
to_id: candidate.to_id,
|
|
1713
|
+
relation_type: candidate.relation_type,
|
|
1714
|
+
strength: validation.confidence,
|
|
1715
|
+
metadata: { source: "reflection", reasoning: validation.reasoning, model }
|
|
1716
|
+
});
|
|
1717
|
+
discoveryResults.push({ target: targetName, type: candidate.relation_type, status: "created" });
|
|
1718
|
+
}
|
|
1719
|
+
else if (validation.verdict === "suggest" || validation.confidence > 0.5) {
|
|
1720
|
+
suggestions.push({
|
|
1721
|
+
from_id: candidate.from_id,
|
|
1722
|
+
to_id: candidate.to_id,
|
|
1723
|
+
relation_type: candidate.relation_type,
|
|
1724
|
+
confidence: validation.confidence,
|
|
1725
|
+
reason: validation.reasoning
|
|
1726
|
+
});
|
|
1727
|
+
}
|
|
1728
|
+
}
|
|
1729
|
+
catch (e) {
|
|
1730
|
+
console.error(`[Discovery] Validation failed for ${targetName}:`, e);
|
|
1731
|
+
}
|
|
1732
|
+
}
|
|
1733
|
+
results.push({ entity_id: entity.id, status: "discovery_completed", created: discoveryResults, suggestions });
|
|
1548
1734
|
}
|
|
1549
1735
|
}
|
|
1550
1736
|
return { status: "completed", results };
|
|
@@ -2173,6 +2359,64 @@ ids[id] <- $ids
|
|
|
2173
2359
|
defaultEntityType: args.defaultEntityType
|
|
2174
2360
|
});
|
|
2175
2361
|
}
|
|
2362
|
+
async editUserProfile(args) {
|
|
2363
|
+
try {
|
|
2364
|
+
const current = await this.db.run('?[name, type, metadata] := *entity{id: $id, name, type, metadata, @ "NOW"}', { id: exports.USER_ENTITY_ID });
|
|
2365
|
+
if (current.rows.length === 0) {
|
|
2366
|
+
return { error: "User profile not found. Initialize it first." };
|
|
2367
|
+
}
|
|
2368
|
+
if (args.name || args.type || args.metadata) {
|
|
2369
|
+
const updateResult = await this.updateEntity({
|
|
2370
|
+
id: exports.USER_ENTITY_ID,
|
|
2371
|
+
name: args.name,
|
|
2372
|
+
type: args.type,
|
|
2373
|
+
metadata: args.metadata
|
|
2374
|
+
});
|
|
2375
|
+
if (updateResult.error) {
|
|
2376
|
+
return updateResult;
|
|
2377
|
+
}
|
|
2378
|
+
}
|
|
2379
|
+
if (args.clear_observations) {
|
|
2380
|
+
const existingObs = await this.db.run('?[id] := *observation{id, entity_id: $eid, @ "NOW"}', { eid: exports.USER_ENTITY_ID });
|
|
2381
|
+
for (const row of existingObs.rows) {
|
|
2382
|
+
await this.db.run('?[id, entity_id, text, embedding, metadata, created_at] := *observation{id, entity_id, text, embedding, metadata, created_at, @ "NOW"}, id = $id :delete observation {id, entity_id, text, embedding, metadata, created_at}', { id: row[0] });
|
|
2383
|
+
}
|
|
2384
|
+
}
|
|
2385
|
+
if (args.observations && args.observations.length > 0) {
|
|
2386
|
+
for (const obs of args.observations) {
|
|
2387
|
+
await this.addObservation({
|
|
2388
|
+
entity_id: exports.USER_ENTITY_ID,
|
|
2389
|
+
text: obs.text,
|
|
2390
|
+
metadata: { ...obs.metadata, kind: "user_preference" },
|
|
2391
|
+
deduplicate: true
|
|
2392
|
+
});
|
|
2393
|
+
}
|
|
2394
|
+
}
|
|
2395
|
+
const updated = await this.db.run('?[name, type, metadata] := *entity{id: $id, name, type, metadata, @ "NOW"}', { id: exports.USER_ENTITY_ID });
|
|
2396
|
+
const observations = await this.db.run('?[id, text, metadata] := *observation{id, entity_id: $eid, text, metadata, @ "NOW"}', { eid: exports.USER_ENTITY_ID });
|
|
2397
|
+
return {
|
|
2398
|
+
status: "User profile updated",
|
|
2399
|
+
profile: {
|
|
2400
|
+
id: exports.USER_ENTITY_ID,
|
|
2401
|
+
name: updated.rows[0][0],
|
|
2402
|
+
type: updated.rows[0][1],
|
|
2403
|
+
metadata: updated.rows[0][2],
|
|
2404
|
+
observations: observations.rows.map((r) => ({
|
|
2405
|
+
id: r[0],
|
|
2406
|
+
text: r[1],
|
|
2407
|
+
metadata: r[2]
|
|
2408
|
+
}))
|
|
2409
|
+
}
|
|
2410
|
+
};
|
|
2411
|
+
}
|
|
2412
|
+
catch (error) {
|
|
2413
|
+
console.error("[UserProfile] Error editing user profile:", error);
|
|
2414
|
+
return {
|
|
2415
|
+
error: "Failed to edit user profile",
|
|
2416
|
+
message: error.message || String(error)
|
|
2417
|
+
};
|
|
2418
|
+
}
|
|
2419
|
+
}
|
|
2176
2420
|
registerTools() {
|
|
2177
2421
|
const MetadataSchema = zod_1.z.record(zod_1.z.string(), zod_1.z.any());
|
|
2178
2422
|
const MutateMemorySchema = zod_1.z.discriminatedUnion("action", [
|
|
@@ -2443,12 +2687,17 @@ Validation: Invalid syntax or missing columns in inference rules will result in
|
|
|
2443
2687
|
max_depth: zod_1.z.number().min(1).max(5).optional().default(3).describe("Maximum walking depth"),
|
|
2444
2688
|
limit: zod_1.z.number().optional().default(5).describe("Number of results"),
|
|
2445
2689
|
}),
|
|
2690
|
+
zod_1.z.object({
|
|
2691
|
+
action: zod_1.z.literal("agentic_search"),
|
|
2692
|
+
query: zod_1.z.string().describe("Context query for agentic routing"),
|
|
2693
|
+
limit: zod_1.z.number().optional().default(10).describe("Maximum number of results"),
|
|
2694
|
+
}),
|
|
2446
2695
|
]);
|
|
2447
2696
|
const QueryMemoryParameters = zod_1.z.object({
|
|
2448
2697
|
action: zod_1.z
|
|
2449
|
-
.enum(["search", "advancedSearch", "context", "entity_details", "history", "graph_rag", "graph_walking"])
|
|
2698
|
+
.enum(["search", "advancedSearch", "context", "entity_details", "history", "graph_rag", "graph_walking", "agentic_search"])
|
|
2450
2699
|
.describe("Action (determines which fields are required)"),
|
|
2451
|
-
query: zod_1.z.string().optional().describe("Required for search/advancedSearch/context/graph_rag/graph_walking"),
|
|
2700
|
+
query: zod_1.z.string().optional().describe("Required for search/advancedSearch/context/graph_rag/graph_walking/agentic_search"),
|
|
2452
2701
|
limit: zod_1.z.number().optional().describe("Only for search/advancedSearch/graph_rag/graph_walking"),
|
|
2453
2702
|
filters: zod_1.z.any().optional().describe("Only for advancedSearch"),
|
|
2454
2703
|
graphConstraints: zod_1.z.any().optional().describe("Only for advancedSearch"),
|
|
@@ -2476,8 +2725,9 @@ Supported actions:
|
|
|
2476
2725
|
- 'history': Retrieve historical evolution of an entity. Params: { entity_id: string }.
|
|
2477
2726
|
- '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 }.
|
|
2478
2727
|
- '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 }.
|
|
2728
|
+
- '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 }.
|
|
2479
2729
|
|
|
2480
|
-
Notes: 'context' is ideal for exploratory questions. 'search' and 'advancedSearch' are better for targeted fact retrieval.`,
|
|
2730
|
+
Notes: 'agentic_search' is the most powerful and adaptable, 'context' is ideal for exploratory questions. 'search' and 'advancedSearch' are better for targeted fact retrieval.`,
|
|
2481
2731
|
parameters: QueryMemoryParameters,
|
|
2482
2732
|
execute: async (args) => {
|
|
2483
2733
|
await this.initPromise;
|
|
@@ -2631,6 +2881,16 @@ Notes: 'context' is ideal for exploratory questions. 'search' and 'advancedSearc
|
|
|
2631
2881
|
};
|
|
2632
2882
|
return JSON.stringify(context);
|
|
2633
2883
|
}
|
|
2884
|
+
if (input.action === "agentic_search") {
|
|
2885
|
+
if (!input.query || input.query.trim().length === 0) {
|
|
2886
|
+
return JSON.stringify({ error: "Search query must not be empty." });
|
|
2887
|
+
}
|
|
2888
|
+
const results = await this.hybridSearch.agenticRetrieve({
|
|
2889
|
+
query: input.query,
|
|
2890
|
+
limit: input.limit,
|
|
2891
|
+
});
|
|
2892
|
+
return JSON.stringify(results);
|
|
2893
|
+
}
|
|
2634
2894
|
if (input.action === "graph_rag") {
|
|
2635
2895
|
if (!input.query || input.query.trim().length === 0) {
|
|
2636
2896
|
return JSON.stringify({ error: "Search query must not be empty." });
|
|
@@ -3107,15 +3367,21 @@ Supported actions:
|
|
|
3107
3367
|
action: zod_1.z.literal("reflect"),
|
|
3108
3368
|
entity_id: zod_1.z.string().optional().describe("Optional entity ID for targeted reflection"),
|
|
3109
3369
|
model: zod_1.z.string().optional().default("demyagent-4b-i1:Q6_K"),
|
|
3370
|
+
mode: zod_1.z.enum(["summary", "discovery"]).optional().default("summary").describe("Reflection mode: 'summary' for insights, 'discovery' for new links"),
|
|
3110
3371
|
}),
|
|
3111
3372
|
zod_1.z.object({
|
|
3112
3373
|
action: zod_1.z.literal("clear_memory"),
|
|
3113
3374
|
confirm: zod_1.z.boolean().describe("Must be true to confirm deletion"),
|
|
3114
3375
|
}),
|
|
3376
|
+
zod_1.z.object({
|
|
3377
|
+
action: zod_1.z.literal("summarize_communities"),
|
|
3378
|
+
model: zod_1.z.string().optional().default("demyagent-4b-i1:Q6_K"),
|
|
3379
|
+
min_community_size: zod_1.z.number().min(2).max(100).optional().default(3),
|
|
3380
|
+
}),
|
|
3115
3381
|
]);
|
|
3116
3382
|
const ManageSystemParameters = zod_1.z.object({
|
|
3117
3383
|
action: zod_1.z
|
|
3118
|
-
.enum(["health", "metrics", "export_memory", "import_memory", "snapshot_create", "snapshot_list", "snapshot_diff", "cleanup", "reflect", "clear_memory"])
|
|
3384
|
+
.enum(["health", "metrics", "export_memory", "import_memory", "snapshot_create", "snapshot_list", "snapshot_diff", "cleanup", "reflect", "clear_memory", "summarize_communities"])
|
|
3119
3385
|
.describe("Action (determines which fields are required)"),
|
|
3120
3386
|
format: zod_1.z.enum(["json", "markdown", "obsidian"]).optional().describe("Export format (for export_memory)"),
|
|
3121
3387
|
includeMetadata: zod_1.z.boolean().optional().describe("Include metadata (for export_memory)"),
|
|
@@ -3134,8 +3400,10 @@ Supported actions:
|
|
|
3134
3400
|
older_than_days: zod_1.z.number().optional().describe("Optional for cleanup"),
|
|
3135
3401
|
max_observations: zod_1.z.number().optional().describe("Optional for cleanup"),
|
|
3136
3402
|
min_entity_degree: zod_1.z.number().optional().describe("Optional for cleanup"),
|
|
3137
|
-
model: zod_1.z.string().optional().describe("Optional for cleanup/reflect"),
|
|
3403
|
+
model: zod_1.z.string().optional().describe("Optional for cleanup/reflect/summarize_communities"),
|
|
3138
3404
|
entity_id: zod_1.z.string().optional().describe("Optional for reflect"),
|
|
3405
|
+
min_community_size: zod_1.z.number().optional().describe("Optional for summarize_communities"),
|
|
3406
|
+
mode: zod_1.z.enum(["summary", "discovery"]).optional().describe("Optional for reflect"),
|
|
3139
3407
|
});
|
|
3140
3408
|
this.mcp.addTool({
|
|
3141
3409
|
name: "manage_system",
|
|
@@ -3159,7 +3427,8 @@ Supported actions:
|
|
|
3159
3427
|
* With confirm=false: Dry-Run (shows candidates).
|
|
3160
3428
|
* With confirm=true: Merges old/isolated fragments using LLM (Executive Summary) and removes noise.
|
|
3161
3429
|
- 'reflect': Reflection service. Analyzes memory for contradictions and insights. Params: { entity_id?: string, model?: string }.
|
|
3162
|
-
- 'clear_memory': Resets the entire database. Params: { confirm: boolean (must be true) }
|
|
3430
|
+
- 'clear_memory': Resets the entire database. Params: { confirm: boolean (must be true) }.
|
|
3431
|
+
- 'summarize_communities': Hierarchical GraphRAG. Generates summaries for entity clusters. Params: { model?: string, min_community_size?: number }.`,
|
|
3163
3432
|
parameters: ManageSystemParameters,
|
|
3164
3433
|
execute: async (args) => {
|
|
3165
3434
|
await this.initPromise;
|
|
@@ -3352,6 +3621,7 @@ Supported actions:
|
|
|
3352
3621
|
const result = await this.reflectMemory({
|
|
3353
3622
|
entity_id: input.entity_id,
|
|
3354
3623
|
model: input.model,
|
|
3624
|
+
mode: input.mode,
|
|
3355
3625
|
});
|
|
3356
3626
|
return JSON.stringify(result);
|
|
3357
3627
|
}
|
|
@@ -3359,6 +3629,18 @@ Supported actions:
|
|
|
3359
3629
|
return JSON.stringify({ error: error.message || "Error during reflection" });
|
|
3360
3630
|
}
|
|
3361
3631
|
}
|
|
3632
|
+
if (input.action === "summarize_communities") {
|
|
3633
|
+
try {
|
|
3634
|
+
const result = await this.summarizeCommunities({
|
|
3635
|
+
model: input.model,
|
|
3636
|
+
min_community_size: input.min_community_size,
|
|
3637
|
+
});
|
|
3638
|
+
return JSON.stringify(result);
|
|
3639
|
+
}
|
|
3640
|
+
catch (error) {
|
|
3641
|
+
return JSON.stringify({ error: error.message || "Error during summarize_communities" });
|
|
3642
|
+
}
|
|
3643
|
+
}
|
|
3362
3644
|
if (input.action === "clear_memory") {
|
|
3363
3645
|
if (!input.confirm) {
|
|
3364
3646
|
return JSON.stringify({ error: "Deletion not confirmed. Set 'confirm' to true." });
|
|
@@ -3378,6 +3660,42 @@ Supported actions:
|
|
|
3378
3660
|
return JSON.stringify({ error: "Unknown action" });
|
|
3379
3661
|
},
|
|
3380
3662
|
});
|
|
3663
|
+
// User Profile Management Tool
|
|
3664
|
+
this.mcp.addTool({
|
|
3665
|
+
name: "edit_user_profile",
|
|
3666
|
+
description: `Direct management of the global user profile ('global_user_profile').
|
|
3667
|
+
This tool allows manual editing of user preferences, work style, and profile metadata.
|
|
3668
|
+
|
|
3669
|
+
The user profile is automatically boosted in all searches (50% score boost) and used for personalization.
|
|
3670
|
+
|
|
3671
|
+
Parameters:
|
|
3672
|
+
- name?: string - Update the profile name (default: "The User")
|
|
3673
|
+
- type?: string - Update the profile type (default: "User")
|
|
3674
|
+
- metadata?: object - Update or merge profile metadata
|
|
3675
|
+
- observations?: Array<{ text: string, metadata?: object }> - Add new preference observations
|
|
3676
|
+
- clear_observations?: boolean - Remove all existing observations before adding new ones
|
|
3677
|
+
|
|
3678
|
+
Examples:
|
|
3679
|
+
- Add preferences: { observations: [{ text: "Prefers TypeScript over JavaScript" }] }
|
|
3680
|
+
- Update metadata: { metadata: { timezone: "Europe/Berlin", language: "de" } }
|
|
3681
|
+
- Reset and set new preferences: { clear_observations: true, observations: [{ text: "New preference" }] }
|
|
3682
|
+
|
|
3683
|
+
Note: Use 'mutate_memory' with action='add_observation' and entity_id='global_user_profile' for implicit preference updates.`,
|
|
3684
|
+
parameters: zod_1.z.object({
|
|
3685
|
+
name: zod_1.z.string().optional().describe("New name for the user profile"),
|
|
3686
|
+
type: zod_1.z.string().optional().describe("New type for the user profile"),
|
|
3687
|
+
metadata: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional().describe("Metadata to merge with existing metadata"),
|
|
3688
|
+
observations: zod_1.z.array(zod_1.z.object({
|
|
3689
|
+
text: zod_1.z.string().describe("Preference or work style description"),
|
|
3690
|
+
metadata: zod_1.z.record(zod_1.z.string(), zod_1.z.any()).optional().describe("Optional metadata for this observation")
|
|
3691
|
+
})).optional().describe("New observations to add to the profile"),
|
|
3692
|
+
clear_observations: zod_1.z.boolean().optional().default(false).describe("Clear all existing observations before adding new ones")
|
|
3693
|
+
}),
|
|
3694
|
+
execute: async (args) => {
|
|
3695
|
+
await this.initPromise;
|
|
3696
|
+
return JSON.stringify(await this.editUserProfile(args));
|
|
3697
|
+
}
|
|
3698
|
+
});
|
|
3381
3699
|
}
|
|
3382
3700
|
async start() {
|
|
3383
3701
|
await this.mcp.start({ transportType: "stdio" });
|
package/dist/inference-engine.js
CHANGED
|
@@ -28,6 +28,13 @@ class InferenceEngine {
|
|
|
28
28
|
results.push(...custom);
|
|
29
29
|
return results;
|
|
30
30
|
}
|
|
31
|
+
/**
|
|
32
|
+
* Aggregates potential links from all strategies for the "discovery" mode of reflection.
|
|
33
|
+
*/
|
|
34
|
+
async getRefinementCandidates(entityId) {
|
|
35
|
+
// This is essentially the same as inferRelations but explicitly for refinement
|
|
36
|
+
return this.inferRelations(entityId);
|
|
37
|
+
}
|
|
31
38
|
async inferImplicitRelations(entityId) {
|
|
32
39
|
const expertise = await this.findTransitiveExpertise(entityId);
|
|
33
40
|
const custom = await this.applyCustomRules(entityId);
|
package/dist/make-old.js
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
const uuid_1 = require("uuid");
|
|
5
|
+
async function run() {
|
|
6
|
+
const server = new index_1.MemoryServer();
|
|
7
|
+
await server.initPromise;
|
|
8
|
+
console.log("Directly inserting old entity and observations via CozoDB...");
|
|
9
|
+
const fortyDaysAgo = Math.floor(Date.now() - 40 * 24 * 60 * 60 * 1000) * 1000;
|
|
10
|
+
const entityId = (0, uuid_1.v4)();
|
|
11
|
+
const name = "Very Old Project";
|
|
12
|
+
const type = "Project";
|
|
13
|
+
const metadata = { purpose: "testing janitor" };
|
|
14
|
+
const zeroVec = new Array(1024).fill(0);
|
|
15
|
+
try {
|
|
16
|
+
await server.db.run(`
|
|
17
|
+
?[id, name, type, embedding, name_embedding, metadata, created_at] <- [[$id, $name, $type, $embedding, $name_embedding, $metadata, [$fortyDaysAgo, true]]]
|
|
18
|
+
:insert entity {id, name, type, embedding, name_embedding, metadata, created_at}
|
|
19
|
+
`, {
|
|
20
|
+
id: entityId,
|
|
21
|
+
name,
|
|
22
|
+
type,
|
|
23
|
+
embedding: zeroVec,
|
|
24
|
+
name_embedding: zeroVec,
|
|
25
|
+
metadata,
|
|
26
|
+
fortyDaysAgo
|
|
27
|
+
});
|
|
28
|
+
console.log("Old entity inserted: " + entityId);
|
|
29
|
+
const obsTexts = [
|
|
30
|
+
"This is a really old architecture note.",
|
|
31
|
+
"We decided to use subversion for version control.",
|
|
32
|
+
"The server is a physical machine in the basement.",
|
|
33
|
+
"We wrote our own ORM from scratch.",
|
|
34
|
+
"Deployment takes 3 days and a lot of manual steps."
|
|
35
|
+
];
|
|
36
|
+
for (const text of obsTexts) {
|
|
37
|
+
const obsId = (0, uuid_1.v4)();
|
|
38
|
+
await server.db.run(`
|
|
39
|
+
?[id, entity_id, text, embedding, metadata, created_at] <- [[$id, $entity_id, $text, $embedding, $metadata, [$fortyDaysAgo, true]]]
|
|
40
|
+
:insert observation {id, entity_id, text, embedding, metadata, created_at}
|
|
41
|
+
`, {
|
|
42
|
+
id: obsId,
|
|
43
|
+
entity_id: entityId,
|
|
44
|
+
text,
|
|
45
|
+
embedding: zeroVec,
|
|
46
|
+
metadata: {},
|
|
47
|
+
fortyDaysAgo
|
|
48
|
+
});
|
|
49
|
+
console.log("Old observation inserted: " + obsId);
|
|
50
|
+
}
|
|
51
|
+
console.log("Successfully inserted old data!");
|
|
52
|
+
}
|
|
53
|
+
catch (e) {
|
|
54
|
+
console.error("DB error:", e.message);
|
|
55
|
+
}
|
|
56
|
+
process.exit(0);
|
|
57
|
+
}
|
|
58
|
+
run().catch(console.error);
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const index_1 = require("./index");
|
|
4
|
+
async function run() {
|
|
5
|
+
const server = new index_1.MemoryServer();
|
|
6
|
+
await server.initPromise;
|
|
7
|
+
console.log("Testing Agentic Retrieval Routing Logic...");
|
|
8
|
+
// Expose the protected hybridSearch for testing (TypeScript hack)
|
|
9
|
+
const hybridSearch = server.hybridSearch;
|
|
10
|
+
const queries = [
|
|
11
|
+
{ text: "Welches Datenbank-System nutzt das Backend Project B?", expected: ["vector_search", "hybrid"] },
|
|
12
|
+
{ text: "Wer arbeitet alles mit ReactJS oder was nutzt ReactJS?", expected: ["graph_walk", "hybrid"] },
|
|
13
|
+
{ text: "Wie ist der generelle Status aller Frontend-Projekte?", expected: ["community_summary"] }
|
|
14
|
+
];
|
|
15
|
+
for (const q of queries) {
|
|
16
|
+
console.log(`\n\n--- Query: "${q.text}" ---`);
|
|
17
|
+
console.log(`Expected Route: ${q.expected.join(" or ")}`);
|
|
18
|
+
const results = await hybridSearch.agenticRetrieve({ query: q.text, limit: 1 });
|
|
19
|
+
if (results.length > 0) {
|
|
20
|
+
console.log(`-> LLM Routed to: ${results[0].metadata?.agentic_routing}`);
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
console.log(`\nNo results found. LLM might have routed to an empty strategy.`);
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
process.exit(0);
|
|
27
|
+
}
|
|
28
|
+
run().catch(console.error);
|