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