@agentmemory/agentmemory 0.7.3 → 0.7.5
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/AGENTS.md +3 -3
- package/README.md +10 -10
- package/dist/cli.mjs +5 -4
- package/dist/cli.mjs.map +1 -1
- package/dist/docker-compose.yml +1 -0
- package/dist/iii-config.yaml +2 -2
- package/dist/index.mjs +488 -6
- package/dist/index.mjs.map +1 -1
- package/dist/{src-sYZDDbiA.mjs → src-Dflj3k63.mjs} +489 -7
- package/dist/src-Dflj3k63.mjs.map +1 -0
- package/dist/standalone.mjs +42 -3
- package/dist/standalone.mjs.map +1 -1
- package/docker-compose.yml +1 -0
- package/iii-config.yaml +2 -2
- package/package.json +3 -3
- package/plugin/.claude-plugin/plugin.json +2 -2
- package/dist/src-sYZDDbiA.mjs.map +0 -1
package/dist/index.mjs
CHANGED
|
@@ -826,6 +826,7 @@ const KV = {
|
|
|
826
826
|
sentinels: "mem:sentinels",
|
|
827
827
|
crystals: "mem:crystals",
|
|
828
828
|
lessons: "mem:lessons",
|
|
829
|
+
insights: "mem:insights",
|
|
829
830
|
graphEdgeHistory: "mem:graph:edge-history",
|
|
830
831
|
enrichedChunks: (sessionId) => `mem:enriched:${sessionId}`,
|
|
831
832
|
latentEmbeddings: (obsId) => `mem:latent:${obsId}`,
|
|
@@ -3739,7 +3740,7 @@ function registerAutoForgetFunction(sdk, kv) {
|
|
|
3739
3740
|
|
|
3740
3741
|
//#endregion
|
|
3741
3742
|
//#region src/version.ts
|
|
3742
|
-
const VERSION = "0.7.
|
|
3743
|
+
const VERSION = "0.7.5";
|
|
3743
3744
|
|
|
3744
3745
|
//#endregion
|
|
3745
3746
|
//#region src/functions/export-import.ts
|
|
@@ -3767,7 +3768,7 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
3767
3768
|
const uniqueProjects = [...new Set(paginatedSessions.map((s) => s.project))];
|
|
3768
3769
|
const profileResults = await Promise.all(uniqueProjects.map((project) => kv.get(KV.profiles, project).catch(() => null)));
|
|
3769
3770
|
for (const profile of profileResults) if (profile) profiles.push(profile);
|
|
3770
|
-
const [graphNodes, graphEdges, semanticMemories, proceduralMemories, actions, actionEdges, sentinels, sketches, crystals, facets, lessons, routines, signals, checkpoints] = await Promise.all([
|
|
3771
|
+
const [graphNodes, graphEdges, semanticMemories, proceduralMemories, actions, actionEdges, sentinels, sketches, crystals, facets, lessons, insights, routines, signals, checkpoints] = await Promise.all([
|
|
3771
3772
|
kv.list(KV.graphNodes).catch(() => []),
|
|
3772
3773
|
kv.list(KV.graphEdges).catch(() => []),
|
|
3773
3774
|
kv.list(KV.semantic).catch(() => []),
|
|
@@ -3779,6 +3780,7 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
3779
3780
|
kv.list(KV.crystals).catch(() => []),
|
|
3780
3781
|
kv.list(KV.facets).catch(() => []),
|
|
3781
3782
|
kv.list(KV.lessons).catch(() => []),
|
|
3783
|
+
kv.list(KV.insights).catch(() => []),
|
|
3782
3784
|
kv.list(KV.routines).catch(() => []),
|
|
3783
3785
|
kv.list(KV.signals).catch(() => []),
|
|
3784
3786
|
kv.list(KV.checkpoints).catch(() => [])
|
|
@@ -3802,6 +3804,7 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
3802
3804
|
crystals: crystals.length > 0 ? crystals : void 0,
|
|
3803
3805
|
facets: facets.length > 0 ? facets : void 0,
|
|
3804
3806
|
lessons: lessons.length > 0 ? lessons : void 0,
|
|
3807
|
+
insights: insights.length > 0 ? insights : void 0,
|
|
3805
3808
|
routines: routines.length > 0 ? routines : void 0,
|
|
3806
3809
|
signals: signals.length > 0 ? signals : void 0,
|
|
3807
3810
|
checkpoints: checkpoints.length > 0 ? checkpoints : void 0
|
|
@@ -3837,7 +3840,9 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
3837
3840
|
"0.6.1",
|
|
3838
3841
|
"0.7.0",
|
|
3839
3842
|
"0.7.2",
|
|
3840
|
-
"0.7.3"
|
|
3843
|
+
"0.7.3",
|
|
3844
|
+
"0.7.4",
|
|
3845
|
+
"0.7.5"
|
|
3841
3846
|
]).has(importData.version)) return {
|
|
3842
3847
|
success: false,
|
|
3843
3848
|
error: `Unsupported export version: ${importData.version}`
|
|
@@ -3924,6 +3929,7 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
3924
3929
|
for (const c of await kv.list(KV.crystals).catch(() => [])) await kv.delete(KV.crystals, c.id);
|
|
3925
3930
|
for (const f of await kv.list(KV.facets).catch(() => [])) await kv.delete(KV.facets, f.id);
|
|
3926
3931
|
for (const l of await kv.list(KV.lessons).catch(() => [])) await kv.delete(KV.lessons, l.id);
|
|
3932
|
+
for (const i of await kv.list(KV.insights).catch(() => [])) await kv.delete(KV.insights, i.id);
|
|
3927
3933
|
for (const n of await kv.list(KV.graphNodes).catch(() => [])) await kv.delete(KV.graphNodes, n.id);
|
|
3928
3934
|
for (const e of await kv.list(KV.graphEdges).catch(() => [])) await kv.delete(KV.graphEdges, e.id);
|
|
3929
3935
|
for (const s of await kv.list(KV.semantic).catch(() => [])) await kv.delete(KV.semantic, s.id);
|
|
@@ -4095,6 +4101,15 @@ function registerExportImportFunction(sdk, kv) {
|
|
|
4095
4101
|
}
|
|
4096
4102
|
await kv.set(KV.lessons, lesson.id, lesson);
|
|
4097
4103
|
}
|
|
4104
|
+
if (importData.insights) for (const insight of importData.insights) {
|
|
4105
|
+
if (strategy === "skip") {
|
|
4106
|
+
if (await kv.get(KV.insights, insight.id).catch(() => null)) {
|
|
4107
|
+
stats.skipped++;
|
|
4108
|
+
continue;
|
|
4109
|
+
}
|
|
4110
|
+
}
|
|
4111
|
+
await kv.set(KV.insights, insight.id, insight);
|
|
4112
|
+
}
|
|
4098
4113
|
ctx.logger.info("Import complete", {
|
|
4099
4114
|
strategy,
|
|
4100
4115
|
...stats
|
|
@@ -4649,6 +4664,16 @@ function registerConsolidationPipelineFunction(sdk, kv, provider) {
|
|
|
4649
4664
|
reason: "fewer than 5 summaries"
|
|
4650
4665
|
};
|
|
4651
4666
|
}
|
|
4667
|
+
if (tier === "all" || tier === "reflect") try {
|
|
4668
|
+
results.reflect = await sdk.trigger("mem::reflect", {
|
|
4669
|
+
maxClusters: 10,
|
|
4670
|
+
project: data?.project
|
|
4671
|
+
});
|
|
4672
|
+
} catch (err) {
|
|
4673
|
+
const msg = err instanceof Error ? err.message : String(err);
|
|
4674
|
+
ctx.logger.warn("Reflect tier failed", { error: msg });
|
|
4675
|
+
results.reflect = { error: msg };
|
|
4676
|
+
}
|
|
4652
4677
|
if (tier === "all" || tier === "procedural") {
|
|
4653
4678
|
const patterns = (await kv.list(KV.memories)).filter((m) => m.isLatest && m.type === "pattern").map((m) => ({
|
|
4654
4679
|
content: m.content,
|
|
@@ -8405,6 +8430,320 @@ function registerObsidianExportFunction(sdk, kv) {
|
|
|
8405
8430
|
});
|
|
8406
8431
|
}
|
|
8407
8432
|
|
|
8433
|
+
//#endregion
|
|
8434
|
+
//#region src/prompts/reflect.ts
|
|
8435
|
+
const REFLECT_SYSTEM = `You are a higher-order reasoning engine. Given a cluster of related concepts, facts, lessons, and action outcomes, synthesize cross-cutting insights that span multiple individual memories.
|
|
8436
|
+
|
|
8437
|
+
Output format (XML):
|
|
8438
|
+
<insights>
|
|
8439
|
+
<insight confidence="0.0-1.0" title="Short descriptive title">
|
|
8440
|
+
The higher-order observation or principle. Should be actionable and non-obvious — something that only becomes visible when viewing multiple memories together.
|
|
8441
|
+
</insight>
|
|
8442
|
+
</insights>
|
|
8443
|
+
|
|
8444
|
+
Rules:
|
|
8445
|
+
- Identify patterns, principles, or strategies that span 2+ source items
|
|
8446
|
+
- Confidence reflects how well-supported the insight is across sources
|
|
8447
|
+
- Title should be a concise label (under 60 chars)
|
|
8448
|
+
- Content should be the actual observation (1-3 sentences)
|
|
8449
|
+
- Prefer actionable insights over abstract summaries
|
|
8450
|
+
- Skip insights that merely restate a single source item
|
|
8451
|
+
- Always emit confidence attribute before title attribute`;
|
|
8452
|
+
function buildReflectPrompt(cluster) {
|
|
8453
|
+
const sections = [];
|
|
8454
|
+
sections.push(`## Concept Cluster: ${cluster.concepts.join(", ")}`);
|
|
8455
|
+
if (cluster.facts.length > 0) sections.push("\n## Known Facts", ...cluster.facts.map((f) => `- [confidence=${f.confidence}] ${f.fact}`));
|
|
8456
|
+
if (cluster.lessons.length > 0) sections.push("\n## Lessons Learned", ...cluster.lessons.map((l) => `- [confidence=${l.confidence}] ${l.content}`));
|
|
8457
|
+
if (cluster.crystalNarratives.length > 0) sections.push("\n## Completed Work Summaries", ...cluster.crystalNarratives.map((n) => `- ${n}`));
|
|
8458
|
+
return `Synthesize higher-order insights from this cluster of related memories:\n\n${sections.join("\n")}`;
|
|
8459
|
+
}
|
|
8460
|
+
|
|
8461
|
+
//#endregion
|
|
8462
|
+
//#region src/functions/reflect.ts
|
|
8463
|
+
function reinforceInsight(insight) {
|
|
8464
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8465
|
+
insight.reinforcements++;
|
|
8466
|
+
insight.confidence = Math.min(1, insight.confidence + .1 * (1 - insight.confidence));
|
|
8467
|
+
insight.lastReinforcedAt = now;
|
|
8468
|
+
insight.updatedAt = now;
|
|
8469
|
+
}
|
|
8470
|
+
function buildGraphClusters(nodes, edges, maxClusters) {
|
|
8471
|
+
const conceptNodes = nodes.filter((n) => n.type === "concept" && !n.stale);
|
|
8472
|
+
if (conceptNodes.length === 0) return [];
|
|
8473
|
+
const edgeMap = /* @__PURE__ */ new Map();
|
|
8474
|
+
for (const edge of edges) {
|
|
8475
|
+
if (edge.stale) continue;
|
|
8476
|
+
if (!edgeMap.has(edge.sourceNodeId)) edgeMap.set(edge.sourceNodeId, /* @__PURE__ */ new Set());
|
|
8477
|
+
if (!edgeMap.has(edge.targetNodeId)) edgeMap.set(edge.targetNodeId, /* @__PURE__ */ new Set());
|
|
8478
|
+
edgeMap.get(edge.sourceNodeId).add(edge.targetNodeId);
|
|
8479
|
+
edgeMap.get(edge.targetNodeId).add(edge.sourceNodeId);
|
|
8480
|
+
}
|
|
8481
|
+
const degree = /* @__PURE__ */ new Map();
|
|
8482
|
+
for (const node of conceptNodes) degree.set(node.id, edgeMap.get(node.id)?.size || 0);
|
|
8483
|
+
const sorted = [...conceptNodes].sort((a, b) => (degree.get(b.id) || 0) - (degree.get(a.id) || 0));
|
|
8484
|
+
const visited = /* @__PURE__ */ new Set();
|
|
8485
|
+
const clusters = [];
|
|
8486
|
+
const conceptNodeIds = new Set(conceptNodes.map((n) => n.id));
|
|
8487
|
+
for (const seed of sorted) {
|
|
8488
|
+
if (visited.has(seed.id) || clusters.length >= maxClusters) break;
|
|
8489
|
+
const cluster = [];
|
|
8490
|
+
const queue = [seed.id];
|
|
8491
|
+
const seen = /* @__PURE__ */ new Set();
|
|
8492
|
+
let depth = 0;
|
|
8493
|
+
while (queue.length > 0 && depth <= 2) {
|
|
8494
|
+
const levelCount = queue.length;
|
|
8495
|
+
for (let i = 0; i < levelCount; i++) {
|
|
8496
|
+
const current = queue.shift();
|
|
8497
|
+
if (seen.has(current)) continue;
|
|
8498
|
+
seen.add(current);
|
|
8499
|
+
if (conceptNodeIds.has(current)) {
|
|
8500
|
+
const node = conceptNodes.find((n) => n.id === current);
|
|
8501
|
+
if (node) cluster.push(node.name);
|
|
8502
|
+
visited.add(current);
|
|
8503
|
+
}
|
|
8504
|
+
const neighbors = edgeMap.get(current) || /* @__PURE__ */ new Set();
|
|
8505
|
+
for (const neighbor of neighbors) if (!seen.has(neighbor)) queue.push(neighbor);
|
|
8506
|
+
}
|
|
8507
|
+
depth++;
|
|
8508
|
+
}
|
|
8509
|
+
if (cluster.length >= 2) clusters.push(cluster);
|
|
8510
|
+
}
|
|
8511
|
+
return clusters;
|
|
8512
|
+
}
|
|
8513
|
+
function buildJaccardClusters(semanticMemories, lessons, maxClusters) {
|
|
8514
|
+
const allConcepts = /* @__PURE__ */ new Map();
|
|
8515
|
+
for (const sem of semanticMemories) {
|
|
8516
|
+
const terms = sem.fact.toLowerCase().split(/\s+/).filter((t) => t.length > 3);
|
|
8517
|
+
for (const term of terms) {
|
|
8518
|
+
if (!allConcepts.has(term)) allConcepts.set(term, /* @__PURE__ */ new Set());
|
|
8519
|
+
allConcepts.get(term).add(sem.id);
|
|
8520
|
+
}
|
|
8521
|
+
}
|
|
8522
|
+
for (const lesson of lessons) for (const tag of lesson.tags) {
|
|
8523
|
+
const key = tag.toLowerCase();
|
|
8524
|
+
if (!allConcepts.has(key)) allConcepts.set(key, /* @__PURE__ */ new Set());
|
|
8525
|
+
allConcepts.get(key).add(lesson.id);
|
|
8526
|
+
}
|
|
8527
|
+
const conceptList = [...allConcepts.keys()].filter((k) => (allConcepts.get(k)?.size || 0) >= 2);
|
|
8528
|
+
const visited = /* @__PURE__ */ new Set();
|
|
8529
|
+
const clusters = [];
|
|
8530
|
+
for (const concept of conceptList) {
|
|
8531
|
+
if (visited.has(concept) || clusters.length >= maxClusters) break;
|
|
8532
|
+
const cluster = [concept];
|
|
8533
|
+
visited.add(concept);
|
|
8534
|
+
const docsA = allConcepts.get(concept) || /* @__PURE__ */ new Set();
|
|
8535
|
+
for (const other of conceptList) {
|
|
8536
|
+
if (visited.has(other)) continue;
|
|
8537
|
+
const docsB = allConcepts.get(other) || /* @__PURE__ */ new Set();
|
|
8538
|
+
let intersection = 0;
|
|
8539
|
+
for (const d of docsA) if (docsB.has(d)) intersection++;
|
|
8540
|
+
const union = docsA.size + docsB.size - intersection;
|
|
8541
|
+
if ((union > 0 ? intersection / union : 0) > .3) {
|
|
8542
|
+
cluster.push(other);
|
|
8543
|
+
visited.add(other);
|
|
8544
|
+
}
|
|
8545
|
+
}
|
|
8546
|
+
if (cluster.length >= 2) clusters.push(cluster);
|
|
8547
|
+
}
|
|
8548
|
+
return clusters;
|
|
8549
|
+
}
|
|
8550
|
+
function registerReflectFunctions(sdk, kv, provider) {
|
|
8551
|
+
sdk.registerFunction({ id: "mem::reflect" }, async (data) => {
|
|
8552
|
+
const maxClusters = Math.min(data?.maxClusters ?? 10, 20);
|
|
8553
|
+
const maxInsightsPerCluster = 5;
|
|
8554
|
+
const maxTotal = 50;
|
|
8555
|
+
const [graphNodes, graphEdges, semanticMemories, lessons, crystals] = await Promise.all([
|
|
8556
|
+
kv.list(KV.graphNodes).catch(() => []),
|
|
8557
|
+
kv.list(KV.graphEdges).catch(() => []),
|
|
8558
|
+
kv.list(KV.semantic).catch(() => []),
|
|
8559
|
+
kv.list(KV.lessons).catch(() => []),
|
|
8560
|
+
kv.list(KV.crystals).catch(() => [])
|
|
8561
|
+
]);
|
|
8562
|
+
let activeLessons = lessons.filter((l) => !l.deleted);
|
|
8563
|
+
if (data?.project) activeLessons = activeLessons.filter((l) => l.project === data.project);
|
|
8564
|
+
let conceptClusters = buildGraphClusters(graphNodes, graphEdges, maxClusters);
|
|
8565
|
+
const usedFallback = conceptClusters.length === 0;
|
|
8566
|
+
if (usedFallback) conceptClusters = buildJaccardClusters(semanticMemories, activeLessons, maxClusters);
|
|
8567
|
+
let newInsights = 0;
|
|
8568
|
+
let reinforced = 0;
|
|
8569
|
+
let clustersSkipped = 0;
|
|
8570
|
+
let totalInsights = 0;
|
|
8571
|
+
for (const conceptNames of conceptClusters) {
|
|
8572
|
+
if (totalInsights >= maxTotal) break;
|
|
8573
|
+
const conceptSet = new Set(conceptNames.map((c) => c.toLowerCase()));
|
|
8574
|
+
const clusterFacts = semanticMemories.filter((s) => {
|
|
8575
|
+
return s.fact.toLowerCase().split(/\s+/).some((t) => conceptSet.has(t));
|
|
8576
|
+
});
|
|
8577
|
+
const clusterLessons = activeLessons.filter((l) => l.tags.some((t) => conceptSet.has(t.toLowerCase())) || conceptNames.some((c) => l.content.toLowerCase().includes(c.toLowerCase())));
|
|
8578
|
+
const clusterCrystals = crystals.filter((c) => (c.lessons || []).some((l) => conceptNames.some((cn) => l.toLowerCase().includes(cn.toLowerCase()))));
|
|
8579
|
+
if (clusterFacts.length + clusterLessons.length + clusterCrystals.length < 3) {
|
|
8580
|
+
clustersSkipped++;
|
|
8581
|
+
continue;
|
|
8582
|
+
}
|
|
8583
|
+
const cluster = {
|
|
8584
|
+
concepts: conceptNames,
|
|
8585
|
+
facts: clusterFacts.map((f) => ({
|
|
8586
|
+
fact: f.fact,
|
|
8587
|
+
confidence: f.confidence
|
|
8588
|
+
})),
|
|
8589
|
+
lessons: clusterLessons.map((l) => ({
|
|
8590
|
+
content: l.content,
|
|
8591
|
+
confidence: l.confidence
|
|
8592
|
+
})),
|
|
8593
|
+
crystalNarratives: clusterCrystals.map((c) => c.narrative),
|
|
8594
|
+
factIds: clusterFacts.map((f) => f.id),
|
|
8595
|
+
lessonIds: clusterLessons.map((l) => l.id),
|
|
8596
|
+
crystalIds: clusterCrystals.map((c) => c.id)
|
|
8597
|
+
};
|
|
8598
|
+
try {
|
|
8599
|
+
const prompt = buildReflectPrompt(cluster);
|
|
8600
|
+
const response = await provider.summarize(REFLECT_SYSTEM, prompt);
|
|
8601
|
+
const insightRegex = /<insight\s+confidence="([^"]+)"\s+title="([^"]+)">([\s\S]*?)<\/insight>/g;
|
|
8602
|
+
let match;
|
|
8603
|
+
let clusterCount = 0;
|
|
8604
|
+
while ((match = insightRegex.exec(response)) !== null && clusterCount < maxInsightsPerCluster && totalInsights < maxTotal) {
|
|
8605
|
+
const parsedConf = parseFloat(match[1]);
|
|
8606
|
+
const confidence = Number.isNaN(parsedConf) ? .5 : Math.max(0, Math.min(1, parsedConf));
|
|
8607
|
+
const title = match[2].trim();
|
|
8608
|
+
const content = match[3].trim();
|
|
8609
|
+
if (!content) continue;
|
|
8610
|
+
const fp = fingerprintId("ins", content.trim().toLowerCase());
|
|
8611
|
+
const existing = await kv.get(KV.insights, fp);
|
|
8612
|
+
if (existing && !existing.deleted) {
|
|
8613
|
+
reinforceInsight(existing);
|
|
8614
|
+
await kv.set(KV.insights, existing.id, existing);
|
|
8615
|
+
reinforced++;
|
|
8616
|
+
} else {
|
|
8617
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
8618
|
+
const insight = {
|
|
8619
|
+
id: fp,
|
|
8620
|
+
title,
|
|
8621
|
+
content,
|
|
8622
|
+
confidence,
|
|
8623
|
+
reinforcements: 0,
|
|
8624
|
+
sourceConceptCluster: conceptNames,
|
|
8625
|
+
sourceMemoryIds: cluster.factIds,
|
|
8626
|
+
sourceLessonIds: cluster.lessonIds,
|
|
8627
|
+
sourceCrystalIds: cluster.crystalIds,
|
|
8628
|
+
project: data?.project,
|
|
8629
|
+
tags: conceptNames,
|
|
8630
|
+
createdAt: now,
|
|
8631
|
+
updatedAt: now,
|
|
8632
|
+
decayRate: .05
|
|
8633
|
+
};
|
|
8634
|
+
await kv.set(KV.insights, insight.id, insight);
|
|
8635
|
+
newInsights++;
|
|
8636
|
+
}
|
|
8637
|
+
clusterCount++;
|
|
8638
|
+
totalInsights++;
|
|
8639
|
+
}
|
|
8640
|
+
} catch {
|
|
8641
|
+
continue;
|
|
8642
|
+
}
|
|
8643
|
+
}
|
|
8644
|
+
try {
|
|
8645
|
+
await recordAudit(kv, "reflect", "mem::reflect", [], {
|
|
8646
|
+
newInsights,
|
|
8647
|
+
reinforced,
|
|
8648
|
+
clustersProcessed: conceptClusters.length - clustersSkipped,
|
|
8649
|
+
clustersSkipped,
|
|
8650
|
+
usedFallback
|
|
8651
|
+
});
|
|
8652
|
+
} catch {}
|
|
8653
|
+
return {
|
|
8654
|
+
success: true,
|
|
8655
|
+
newInsights,
|
|
8656
|
+
reinforced,
|
|
8657
|
+
clustersProcessed: conceptClusters.length - clustersSkipped,
|
|
8658
|
+
clustersSkipped,
|
|
8659
|
+
usedFallback
|
|
8660
|
+
};
|
|
8661
|
+
});
|
|
8662
|
+
sdk.registerFunction({ id: "mem::insight-list" }, async (data) => {
|
|
8663
|
+
const limit = data?.limit ?? 50;
|
|
8664
|
+
const minConfidence = data?.minConfidence ?? 0;
|
|
8665
|
+
let items = await kv.list(KV.insights);
|
|
8666
|
+
items = items.filter((i) => !i.deleted && i.confidence >= minConfidence);
|
|
8667
|
+
if (data?.project) items = items.filter((i) => i.project === data.project);
|
|
8668
|
+
items.sort((a, b) => b.confidence - a.confidence);
|
|
8669
|
+
return {
|
|
8670
|
+
success: true,
|
|
8671
|
+
insights: items.slice(0, limit)
|
|
8672
|
+
};
|
|
8673
|
+
});
|
|
8674
|
+
sdk.registerFunction({ id: "mem::insight-search" }, async (data) => {
|
|
8675
|
+
if (!data?.query?.trim()) return {
|
|
8676
|
+
success: false,
|
|
8677
|
+
error: "query is required"
|
|
8678
|
+
};
|
|
8679
|
+
const query = data.query.toLowerCase();
|
|
8680
|
+
const minConfidence = data.minConfidence ?? .1;
|
|
8681
|
+
const limit = data.limit ?? 10;
|
|
8682
|
+
let items = await kv.list(KV.insights);
|
|
8683
|
+
items = items.filter((i) => !i.deleted && i.confidence >= minConfidence);
|
|
8684
|
+
if (data.project) items = items.filter((i) => i.project === data.project);
|
|
8685
|
+
const terms = query.split(/\s+/).filter((t) => t.length > 1);
|
|
8686
|
+
const scored = items.map((i) => {
|
|
8687
|
+
const text = `${i.title} ${i.content} ${i.tags.join(" ")}`.toLowerCase();
|
|
8688
|
+
const matchCount = terms.filter((t) => text.includes(t)).length;
|
|
8689
|
+
if (matchCount === 0) return null;
|
|
8690
|
+
const relevance = matchCount / terms.length;
|
|
8691
|
+
const recencyBoost = 1 / (1 + (i.lastReinforcedAt ? (Date.now() - new Date(i.lastReinforcedAt).getTime()) / (1e3 * 60 * 60 * 24) : (Date.now() - new Date(i.createdAt).getTime()) / (1e3 * 60 * 60 * 24)) * .01);
|
|
8692
|
+
return {
|
|
8693
|
+
insight: i,
|
|
8694
|
+
score: i.confidence * relevance * recencyBoost
|
|
8695
|
+
};
|
|
8696
|
+
}).filter(Boolean);
|
|
8697
|
+
scored.sort((a, b) => b.score - a.score);
|
|
8698
|
+
try {
|
|
8699
|
+
await recordAudit(kv, "insight_search", "mem::insight-search", [], {
|
|
8700
|
+
query: data.query,
|
|
8701
|
+
resultCount: scored.length
|
|
8702
|
+
});
|
|
8703
|
+
} catch {}
|
|
8704
|
+
return {
|
|
8705
|
+
success: true,
|
|
8706
|
+
insights: scored.slice(0, limit).map((s) => ({
|
|
8707
|
+
...s.insight,
|
|
8708
|
+
score: Math.round(s.score * 1e3) / 1e3
|
|
8709
|
+
}))
|
|
8710
|
+
};
|
|
8711
|
+
});
|
|
8712
|
+
sdk.registerFunction({ id: "mem::insight-decay-sweep" }, async () => {
|
|
8713
|
+
const items = await kv.list(KV.insights);
|
|
8714
|
+
let decayed = 0;
|
|
8715
|
+
let softDeleted = 0;
|
|
8716
|
+
const now = Date.now();
|
|
8717
|
+
const timestamp = (/* @__PURE__ */ new Date()).toISOString();
|
|
8718
|
+
const dirty = [];
|
|
8719
|
+
for (const insight of items) {
|
|
8720
|
+
if (insight.deleted) continue;
|
|
8721
|
+
const baseline = insight.lastDecayedAt || insight.lastReinforcedAt || insight.createdAt;
|
|
8722
|
+
const weeksSince = (now - new Date(baseline).getTime()) / (1e3 * 60 * 60 * 24 * 7);
|
|
8723
|
+
if (weeksSince < 1) continue;
|
|
8724
|
+
const decay = insight.decayRate * weeksSince;
|
|
8725
|
+
const newConfidence = Math.max(.05, insight.confidence - decay);
|
|
8726
|
+
if (newConfidence !== insight.confidence) {
|
|
8727
|
+
insight.confidence = Math.round(newConfidence * 1e3) / 1e3;
|
|
8728
|
+
insight.lastDecayedAt = timestamp;
|
|
8729
|
+
insight.updatedAt = timestamp;
|
|
8730
|
+
if (insight.confidence <= .1 && insight.reinforcements === 0) {
|
|
8731
|
+
insight.deleted = true;
|
|
8732
|
+
softDeleted++;
|
|
8733
|
+
} else decayed++;
|
|
8734
|
+
dirty.push(insight);
|
|
8735
|
+
}
|
|
8736
|
+
}
|
|
8737
|
+
await Promise.all(dirty.map((i) => kv.set(KV.insights, i.id, i)));
|
|
8738
|
+
return {
|
|
8739
|
+
success: true,
|
|
8740
|
+
decayed,
|
|
8741
|
+
softDeleted,
|
|
8742
|
+
total: items.length
|
|
8743
|
+
};
|
|
8744
|
+
});
|
|
8745
|
+
}
|
|
8746
|
+
|
|
8408
8747
|
//#endregion
|
|
8409
8748
|
//#region src/functions/sliding-window.ts
|
|
8410
8749
|
const SLIDING_WINDOW_SYSTEM = `You are a contextual enrichment engine. Given a primary observation and its surrounding context window (previous and next observations from the same session), produce an enriched version.
|
|
@@ -11193,6 +11532,73 @@ function registerApiTriggers(sdk, kv, secret, metricsStore, provider) {
|
|
|
11193
11532
|
http_method: "POST"
|
|
11194
11533
|
}
|
|
11195
11534
|
});
|
|
11535
|
+
sdk.registerFunction({ id: "api::reflect" }, async (req) => {
|
|
11536
|
+
const denied = checkAuth$1(req, secret);
|
|
11537
|
+
if (denied) return denied;
|
|
11538
|
+
const body = req.body || {};
|
|
11539
|
+
return {
|
|
11540
|
+
status_code: 200,
|
|
11541
|
+
body: await sdk.trigger("mem::reflect", {
|
|
11542
|
+
project: typeof body.project === "string" ? body.project : void 0,
|
|
11543
|
+
maxClusters: typeof body.maxClusters === "number" ? body.maxClusters : void 0
|
|
11544
|
+
})
|
|
11545
|
+
};
|
|
11546
|
+
});
|
|
11547
|
+
sdk.registerTrigger({
|
|
11548
|
+
type: "http",
|
|
11549
|
+
function_id: "api::reflect",
|
|
11550
|
+
config: {
|
|
11551
|
+
api_path: "/agentmemory/reflect",
|
|
11552
|
+
http_method: "POST"
|
|
11553
|
+
}
|
|
11554
|
+
});
|
|
11555
|
+
sdk.registerFunction({ id: "api::insight-list" }, async (req) => {
|
|
11556
|
+
const denied = checkAuth$1(req, secret);
|
|
11557
|
+
if (denied) return denied;
|
|
11558
|
+
const params = req.query_params || {};
|
|
11559
|
+
return {
|
|
11560
|
+
status_code: 200,
|
|
11561
|
+
body: await sdk.trigger("mem::insight-list", {
|
|
11562
|
+
project: params.project,
|
|
11563
|
+
minConfidence: params.minConfidence ? parseFloat(params.minConfidence) : void 0,
|
|
11564
|
+
limit: params.limit ? parseInt(params.limit, 10) : void 0
|
|
11565
|
+
})
|
|
11566
|
+
};
|
|
11567
|
+
});
|
|
11568
|
+
sdk.registerTrigger({
|
|
11569
|
+
type: "http",
|
|
11570
|
+
function_id: "api::insight-list",
|
|
11571
|
+
config: {
|
|
11572
|
+
api_path: "/agentmemory/insights",
|
|
11573
|
+
http_method: "GET"
|
|
11574
|
+
}
|
|
11575
|
+
});
|
|
11576
|
+
sdk.registerFunction({ id: "api::insight-search" }, async (req) => {
|
|
11577
|
+
const denied = checkAuth$1(req, secret);
|
|
11578
|
+
if (denied) return denied;
|
|
11579
|
+
const body = req.body;
|
|
11580
|
+
if (!body?.query || typeof body.query !== "string") return {
|
|
11581
|
+
status_code: 400,
|
|
11582
|
+
body: { error: "query is required" }
|
|
11583
|
+
};
|
|
11584
|
+
return {
|
|
11585
|
+
status_code: 200,
|
|
11586
|
+
body: await sdk.trigger("mem::insight-search", {
|
|
11587
|
+
query: body.query,
|
|
11588
|
+
project: typeof body.project === "string" ? body.project : void 0,
|
|
11589
|
+
minConfidence: typeof body.minConfidence === "number" ? body.minConfidence : void 0,
|
|
11590
|
+
limit: typeof body.limit === "number" ? body.limit : void 0
|
|
11591
|
+
})
|
|
11592
|
+
};
|
|
11593
|
+
});
|
|
11594
|
+
sdk.registerTrigger({
|
|
11595
|
+
type: "http",
|
|
11596
|
+
function_id: "api::insight-search",
|
|
11597
|
+
config: {
|
|
11598
|
+
api_path: "/agentmemory/insights/search",
|
|
11599
|
+
http_method: "POST"
|
|
11600
|
+
}
|
|
11601
|
+
});
|
|
11196
11602
|
}
|
|
11197
11603
|
|
|
11198
11604
|
//#endregion
|
|
@@ -12105,6 +12511,43 @@ const V070_TOOLS = [
|
|
|
12105
12511
|
}
|
|
12106
12512
|
}
|
|
12107
12513
|
];
|
|
12514
|
+
const V073_TOOLS = [{
|
|
12515
|
+
name: "memory_reflect",
|
|
12516
|
+
description: "Traverse the knowledge graph, group related memories by concept clusters, and synthesize higher-order insights via LLM. Returns new and reinforced insights.",
|
|
12517
|
+
inputSchema: {
|
|
12518
|
+
type: "object",
|
|
12519
|
+
properties: {
|
|
12520
|
+
project: {
|
|
12521
|
+
type: "string",
|
|
12522
|
+
description: "Filter by project"
|
|
12523
|
+
},
|
|
12524
|
+
maxClusters: {
|
|
12525
|
+
type: "number",
|
|
12526
|
+
description: "Max concept clusters to process (default 10, max 20)"
|
|
12527
|
+
}
|
|
12528
|
+
}
|
|
12529
|
+
}
|
|
12530
|
+
}, {
|
|
12531
|
+
name: "memory_insight_list",
|
|
12532
|
+
description: "List synthesized insights — higher-order observations derived from patterns across memories, lessons, and crystals.",
|
|
12533
|
+
inputSchema: {
|
|
12534
|
+
type: "object",
|
|
12535
|
+
properties: {
|
|
12536
|
+
project: {
|
|
12537
|
+
type: "string",
|
|
12538
|
+
description: "Filter by project"
|
|
12539
|
+
},
|
|
12540
|
+
minConfidence: {
|
|
12541
|
+
type: "number",
|
|
12542
|
+
description: "Minimum confidence threshold (default 0)"
|
|
12543
|
+
},
|
|
12544
|
+
limit: {
|
|
12545
|
+
type: "number",
|
|
12546
|
+
description: "Max results (default 50)"
|
|
12547
|
+
}
|
|
12548
|
+
}
|
|
12549
|
+
}
|
|
12550
|
+
}];
|
|
12108
12551
|
const ESSENTIAL_TOOLS = new Set([
|
|
12109
12552
|
"memory_save",
|
|
12110
12553
|
"memory_recall",
|
|
@@ -12112,7 +12555,8 @@ const ESSENTIAL_TOOLS = new Set([
|
|
|
12112
12555
|
"memory_smart_search",
|
|
12113
12556
|
"memory_sessions",
|
|
12114
12557
|
"memory_diagnose",
|
|
12115
|
-
"memory_lesson_save"
|
|
12558
|
+
"memory_lesson_save",
|
|
12559
|
+
"memory_reflect"
|
|
12116
12560
|
]);
|
|
12117
12561
|
function getAllTools() {
|
|
12118
12562
|
return [
|
|
@@ -12121,7 +12565,8 @@ function getAllTools() {
|
|
|
12121
12565
|
...V050_TOOLS,
|
|
12122
12566
|
...V051_TOOLS,
|
|
12123
12567
|
...V061_TOOLS,
|
|
12124
|
-
...V070_TOOLS
|
|
12568
|
+
...V070_TOOLS,
|
|
12569
|
+
...V073_TOOLS
|
|
12125
12570
|
];
|
|
12126
12571
|
}
|
|
12127
12572
|
function getVisibleTools() {
|
|
@@ -12941,6 +13386,33 @@ function registerMcpEndpoints(sdk, kv, secret) {
|
|
|
12941
13386
|
}] }
|
|
12942
13387
|
};
|
|
12943
13388
|
}
|
|
13389
|
+
case "memory_reflect": {
|
|
13390
|
+
const reflectResult = await sdk.trigger("mem::reflect", {
|
|
13391
|
+
project: args.project,
|
|
13392
|
+
maxClusters: args.maxClusters
|
|
13393
|
+
});
|
|
13394
|
+
return {
|
|
13395
|
+
status_code: 200,
|
|
13396
|
+
body: { content: [{
|
|
13397
|
+
type: "text",
|
|
13398
|
+
text: JSON.stringify(reflectResult, null, 2)
|
|
13399
|
+
}] }
|
|
13400
|
+
};
|
|
13401
|
+
}
|
|
13402
|
+
case "memory_insight_list": {
|
|
13403
|
+
const insightListResult = await sdk.trigger("mem::insight-list", {
|
|
13404
|
+
project: args.project,
|
|
13405
|
+
minConfidence: args.minConfidence,
|
|
13406
|
+
limit: args.limit
|
|
13407
|
+
});
|
|
13408
|
+
return {
|
|
13409
|
+
status_code: 200,
|
|
13410
|
+
body: { content: [{
|
|
13411
|
+
type: "text",
|
|
13412
|
+
text: JSON.stringify(insightListResult, null, 2)
|
|
13413
|
+
}] }
|
|
13414
|
+
};
|
|
13415
|
+
}
|
|
12944
13416
|
case "memory_obsidian_export": {
|
|
12945
13417
|
const exportTypes = typeof args.types === "string" && args.types.trim() ? args.types.split(",").map((t) => t.trim()).filter(Boolean) : void 0;
|
|
12946
13418
|
const obsidianResult = await sdk.trigger("mem::obsidian-export", {
|
|
@@ -13424,6 +13896,10 @@ function startViewerServer(port, _kv, _sdk, secret, restPort) {
|
|
|
13424
13896
|
json(res, 502, { error: "upstream error" }, req);
|
|
13425
13897
|
}
|
|
13426
13898
|
});
|
|
13899
|
+
server.on("error", (err) => {
|
|
13900
|
+
if (err.code === "EADDRINUSE") console.warn(`[agentmemory] Viewer port ${port} already in use, skipping viewer.`);
|
|
13901
|
+
else console.error(`[agentmemory] Viewer error:`, err.message);
|
|
13902
|
+
});
|
|
13427
13903
|
server.listen(port, "127.0.0.1", () => {
|
|
13428
13904
|
console.log(`[agentmemory] Viewer: http://localhost:${port}`);
|
|
13429
13905
|
});
|
|
@@ -13681,6 +14157,7 @@ async function main() {
|
|
|
13681
14157
|
registerVerifyFunction(sdk, kv);
|
|
13682
14158
|
registerLessonsFunctions(sdk, kv);
|
|
13683
14159
|
registerObsidianExportFunction(sdk, kv);
|
|
14160
|
+
registerReflectFunctions(sdk, kv, provider);
|
|
13684
14161
|
registerCascadeFunction(sdk, kv);
|
|
13685
14162
|
registerSlidingWindowFunction(sdk, kv, provider);
|
|
13686
14163
|
registerQueryExpansionFunction(sdk, provider);
|
|
@@ -13725,7 +14202,7 @@ async function main() {
|
|
|
13725
14202
|
}
|
|
13726
14203
|
}
|
|
13727
14204
|
console.log(`[agentmemory] Ready. ${embeddingProvider ? "Triple-stream (BM25+Vector+Graph)" : "BM25+Graph"} search active.`);
|
|
13728
|
-
console.log(`[agentmemory] Endpoints:
|
|
14205
|
+
console.log(`[agentmemory] Endpoints: 103 REST + 43 MCP tools + 6 MCP resources + 3 MCP prompts`);
|
|
13729
14206
|
const viewerServer = startViewerServer(config.restPort + 2, kv, sdk, secret, config.restPort);
|
|
13730
14207
|
const autoForgetIntervalMs = parseInt(process.env.AUTO_FORGET_INTERVAL_MS || "3600000", 10);
|
|
13731
14208
|
const consolidationIntervalMs = parseInt(process.env.CONSOLIDATION_INTERVAL_MS || "7200000", 10);
|
|
@@ -13745,6 +14222,11 @@ async function main() {
|
|
|
13745
14222
|
}, 864e5).unref();
|
|
13746
14223
|
console.log(`[agentmemory] Lesson decay sweep: enabled (every 24h)`);
|
|
13747
14224
|
}
|
|
14225
|
+
if (process.env.INSIGHT_DECAY_ENABLED !== "false") setInterval(async () => {
|
|
14226
|
+
try {
|
|
14227
|
+
await sdk.trigger("mem::insight-decay-sweep", {});
|
|
14228
|
+
} catch {}
|
|
14229
|
+
}, 864e5).unref();
|
|
13748
14230
|
if (isConsolidationEnabled()) {
|
|
13749
14231
|
setInterval(async () => {
|
|
13750
14232
|
try {
|