n8n-nodes-tembory 1.0.12 → 1.0.14

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 CHANGED
@@ -2,7 +2,15 @@
2
2
 
3
3
  Node de memoria operacional da Tembory para agentes de IA no n8n.
4
4
 
5
- Versao atual: `1.0.12`.
5
+ Versao atual: `1.0.14`.
6
+
7
+ ## 1.0.14
8
+
9
+ - Adiciona Active Summary persistente por namespace/thread para continuar conversas depois de minutos, horas ou dias.
10
+ - Separa cache tecnico curto do resumo SLM da memoria ativa persistente.
11
+ - Aumenta os defaults do resumo SLM nos presets de producao para preservar mais inteligencia antes de comprimir para o agente.
12
+ - Adiciona diagnosticos para saber se o contexto veio de SLM novo, active summary ou cache tecnico.
13
+ - Expõe controles avancados de Active Summary e cache tecnico no node.
6
14
 
7
15
  O Tembory entrega contexto rico para o AI Agent sem depender apenas do historico textual da conversa. Ele combina memoria semantica, working memory, decision state, fatos estaveis do lead, historico de tools, estado operacional, action ledger, timeline de entidades, compressao de memoria, grafo, mensagens recentes e diagnosticos.
8
16
 
@@ -10,6 +18,8 @@ Nos presets de producao, o contexto e organizado como um pacote acionavel: secoe
10
18
 
11
19
  Quando um modelo barato esta conectado ao Tembory, os presets de producao usam esse SLM para gerar um resumo ativo do contexto. Em modo `auto`, ele organiza tanto o estado atual (`working_memory`, `tool_history`, `operational_state`, `decision_state`) quanto memorias vetoriais recuperadas. Assim o primeiro turno ja pode receber highlights uteis mesmo antes de haver resultados do banco vetorial.
12
20
 
21
+ O resumo do SLM usa limites conservadores e cache curto por input para evitar chamadas duplicadas quando o AI Agent carrega a memoria mais de uma vez no mesmo turno.
22
+
13
23
  ## Smoke tecnico
14
24
 
15
25
  Antes de publicar uma versao, rode:
@@ -862,13 +862,17 @@ const getMemoryStore = (ctx) => {
862
862
  data.tembory.workingMemory = data.tembory.workingMemory || {};
863
863
  data.tembory.decisionState = data.tembory.decisionState || {};
864
864
  data.tembory.memoryCompression = data.tembory.memoryCompression || {};
865
+ data.tembory.activeSummary = data.tembory.activeSummary || {};
866
+ data.tembory.connectedModelSummaryCache = data.tembory.connectedModelSummaryCache || {};
865
867
  return data.tembory;
866
868
  }
867
869
  catch {
868
- global.__temboryMemory = global.__temboryMemory || { toolHistory: {}, recentMessages: {}, profileFacts: {}, workingMemory: {}, decisionState: {}, memoryCompression: {} };
870
+ global.__temboryMemory = global.__temboryMemory || { toolHistory: {}, recentMessages: {}, profileFacts: {}, workingMemory: {}, decisionState: {}, memoryCompression: {}, activeSummary: {}, connectedModelSummaryCache: {} };
869
871
  global.__temboryMemory.workingMemory = global.__temboryMemory.workingMemory || {};
870
872
  global.__temboryMemory.decisionState = global.__temboryMemory.decisionState || {};
871
873
  global.__temboryMemory.memoryCompression = global.__temboryMemory.memoryCompression || {};
874
+ global.__temboryMemory.activeSummary = global.__temboryMemory.activeSummary || {};
875
+ global.__temboryMemory.connectedModelSummaryCache = global.__temboryMemory.connectedModelSummaryCache || {};
872
876
  return global.__temboryMemory;
873
877
  }
874
878
  };
@@ -910,6 +914,12 @@ const applyOperationalPreset = (advanced = {}) => {
910
914
  productionBalanced: {
911
915
  summarySource: 'auto',
912
916
  includeConnectedModelSummary: true,
917
+ includeActiveSummary: true,
918
+ persistActiveSummary: true,
919
+ activeSummaryMaxChars: 1800,
920
+ activeSummaryRetentionDays: 30,
921
+ enableTransientSummaryCache: true,
922
+ transientSummaryCacheTTLSeconds: 300,
913
923
  compactStateSections: true,
914
924
  includeContextHeader: true,
915
925
  includeSummary: true,
@@ -935,12 +945,18 @@ const applyOperationalPreset = (advanced = {}) => {
935
945
  recentMessagesLastN: 6,
936
946
  vectorMemoryMaxChars: 360,
937
947
  contextMaxChars: 10000,
938
- connectedModelSummaryMaxChars: 900,
939
- connectedModelSummaryInputMaxChars: 3200,
948
+ connectedModelSummaryMaxChars: 1200,
949
+ connectedModelSummaryInputMaxChars: 4200,
940
950
  },
941
951
  productionCheap: {
942
952
  summarySource: 'activeContext',
943
953
  includeConnectedModelSummary: true,
954
+ includeActiveSummary: true,
955
+ persistActiveSummary: true,
956
+ activeSummaryMaxChars: 1500,
957
+ activeSummaryRetentionDays: 30,
958
+ enableTransientSummaryCache: true,
959
+ transientSummaryCacheTTLSeconds: 300,
944
960
  compactStateSections: true,
945
961
  includeContextHeader: true,
946
962
  includeSummary: false,
@@ -966,12 +982,18 @@ const applyOperationalPreset = (advanced = {}) => {
966
982
  recentMessagesLastN: 4,
967
983
  vectorMemoryMaxChars: 260,
968
984
  contextMaxChars: 7000,
969
- connectedModelSummaryMaxChars: 700,
970
- connectedModelSummaryInputMaxChars: 2400,
985
+ connectedModelSummaryMaxChars: 1000,
986
+ connectedModelSummaryInputMaxChars: 3600,
971
987
  },
972
988
  productionNano: {
973
989
  summarySource: 'auto',
974
990
  includeConnectedModelSummary: true,
991
+ includeActiveSummary: true,
992
+ persistActiveSummary: true,
993
+ activeSummaryMaxChars: 1600,
994
+ activeSummaryRetentionDays: 30,
995
+ enableTransientSummaryCache: true,
996
+ transientSummaryCacheTTLSeconds: 300,
975
997
  compactForAgent: true,
976
998
  includeContextHeader: true,
977
999
  includeSummary: true,
@@ -997,9 +1019,9 @@ const applyOperationalPreset = (advanced = {}) => {
997
1019
  toolHistoryLastN: 6,
998
1020
  recentMessagesLastN: 2,
999
1021
  vectorMemoryMaxChars: 220,
1000
- contextMaxChars: 6000,
1001
- connectedModelSummaryMaxChars: 700,
1002
- connectedModelSummaryInputMaxChars: 2200,
1022
+ contextMaxChars: 7000,
1023
+ connectedModelSummaryMaxChars: 1200,
1024
+ connectedModelSummaryInputMaxChars: 4200,
1003
1025
  },
1004
1026
  audit: {
1005
1027
  summarySource: 'auto',
@@ -1580,7 +1602,34 @@ const cleanModelSummaryText = (value, max = 900) => {
1580
1602
  .trim();
1581
1603
  return truncate(text, max);
1582
1604
  };
1583
- const buildConnectedModelSummaryInput = ({ query, profileFacts, workingMemory, decisionState, memoryCompression, operationalState, toolHistory, recentMessages, vectorMemories, highlights, adv }) => {
1605
+ const activeSummaryIsFresh = (entry, adv = {}) => {
1606
+ if (!entry || !entry.summary)
1607
+ return false;
1608
+ const retentionDays = Number(adv.activeSummaryRetentionDays ?? 30);
1609
+ if (retentionDays <= 0)
1610
+ return true;
1611
+ return Date.now() - Number(entry.updatedAt || entry.at || 0) < retentionDays * 86400000;
1612
+ };
1613
+ const readActiveSummary = (store, key, adv = {}) => {
1614
+ if (adv.includeActiveSummary === false)
1615
+ return '';
1616
+ const entry = store.activeSummary?.[key];
1617
+ if (!activeSummaryIsFresh(entry, adv))
1618
+ return '';
1619
+ return truncate(String(entry.summary || ''), Number(adv.activeSummaryMaxChars || 1600));
1620
+ };
1621
+ const writeActiveSummary = (store, key, summary, adv = {}) => {
1622
+ if (!store || !key || adv.persistActiveSummary === false || !summary)
1623
+ return false;
1624
+ store.activeSummary = store.activeSummary || {};
1625
+ store.activeSummary[key] = {
1626
+ summary: truncate(String(summary), Number(adv.activeSummaryMaxChars || 1600)),
1627
+ updatedAt: Date.now(),
1628
+ source: 'slm',
1629
+ };
1630
+ return true;
1631
+ };
1632
+ const buildConnectedModelSummaryInput = ({ query, activeSummary, profileFacts, workingMemory, decisionState, memoryCompression, operationalState, toolHistory, recentMessages, vectorMemories, highlights, adv }) => {
1584
1633
  const source = String(adv.summarySource || 'auto');
1585
1634
  if (source === 'off' || source === 'disabled' || adv.includeConnectedModelSummary === false)
1586
1635
  return '';
@@ -1588,6 +1637,7 @@ const buildConnectedModelSummaryInput = ({ query, profileFacts, workingMemory, d
1588
1637
  const includeActive = source === 'auto' || source === 'activeContext' || source === 'active';
1589
1638
  const payload = cleanContextValue({
1590
1639
  query: String(query || ''),
1640
+ existing_active_summary: activeSummary ? truncate(activeSummary, Number(adv.activeSummaryMaxChars || 1600)) : undefined,
1591
1641
  active_context: includeActive ? {
1592
1642
  profile_facts: renderProfileFacts(profileFacts),
1593
1643
  working_memory: compactWorkingMemoryForAgent(workingMemory || {}),
@@ -1608,7 +1658,7 @@ const buildConnectedModelSummaryInput = ({ query, profileFacts, workingMemory, d
1608
1658
  const hasVectorSignal = includeVectors && Array.isArray(payload.vector_memories) && payload.vector_memories.length > 0;
1609
1659
  if (!hasActiveSignal && !hasVectorSignal)
1610
1660
  return '';
1611
- return truncate(safeStringify(payload), Number(adv.connectedModelSummaryInputMaxChars || 3000));
1661
+ return truncate(safeStringify(payload), Number(adv.connectedModelSummaryInputMaxChars || 2000));
1612
1662
  };
1613
1663
  const invokeConnectedModelSummary = async (connectedLanguageModel, summaryInput, adv = {}) => {
1614
1664
  if (!connectedLanguageModel || typeof connectedLanguageModel.invoke !== 'function' || !summaryInput)
@@ -1616,10 +1666,10 @@ const invokeConnectedModelSummary = async (connectedLanguageModel, summaryInput,
1616
1666
  const response = await connectedLanguageModel.invoke([
1617
1667
  toBaseMessage({
1618
1668
  role: 'user',
1619
- content: `Organize the Tembory memory context for the next agent turn. Return concise Portuguese bullets only. Preserve IDs, dates, tool names, next action, do-not-repeat instructions, and contradictions. Do not invent facts.\n\nContext:\n${summaryInput}`,
1669
+ content: `Update the Tembory active summary for the next agent turn. Return only concise Portuguese bullets, no JSON and no markdown table. Preserve IDs, dates, tool names, confirmed decisions, pending actions, constraints, contradictions, and do-not-repeat instructions. Prefer durable useful context over raw logs. Do not invent facts.\n\nContext:\n${summaryInput}`,
1620
1670
  }),
1621
1671
  ]);
1622
- return cleanModelSummaryText(response, Number(adv.connectedModelSummaryMaxChars || 900));
1672
+ return cleanModelSummaryText(response, Number(adv.connectedModelSummaryMaxChars || 1200));
1623
1673
  };
1624
1674
  const contextSizeOfMessages = (messages = []) => {
1625
1675
  const perMessage = (messages || []).map((message, index) => {
@@ -1651,6 +1701,7 @@ const wrapTemboryMemory = (memory, ctx, memoryKey) => new Proxy(memory, {
1651
1701
  context: response.temboryContext,
1652
1702
  contextText: response.temboryContextText,
1653
1703
  summary: response.temborySummary,
1704
+ activeSummary: response.temboryActiveSummary,
1654
1705
  connectedModelSummary: response.temboryConnectedModelSummary,
1655
1706
  workingMemory: response.temboryWorkingMemory,
1656
1707
  decisionState: response.temboryDecisionState,
@@ -1698,7 +1749,7 @@ const wrapTemboryMemory = (memory, ctx, memoryKey) => new Proxy(memory, {
1698
1749
  return target[prop];
1699
1750
  },
1700
1751
  });
1701
- const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, workingMemory, decisionState, memoryCompression, operationalState, actionLedger, entityTimeline, vectorMemories, recentMessages, toolHistory, highlights, graph, diagnostics, connectedModelSummary, adv }) => {
1752
+ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, workingMemory, decisionState, memoryCompression, operationalState, actionLedger, entityTimeline, vectorMemories, recentMessages, toolHistory, highlights, graph, diagnostics, activeSummary, connectedModelSummary, adv }) => {
1702
1753
  const includeHeader = adv.includeContextHeader !== false;
1703
1754
  const includeSummary = adv.includeSummary !== false;
1704
1755
  const includeScores = adv.includeScores !== false;
@@ -1722,6 +1773,9 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1722
1773
  if (connectedModelSummary && adv.includeConnectedModelSummary !== false) {
1723
1774
  sections.push({ section: 'connected_model_summary', title: 'SLM summary', value: connectedModelSummary });
1724
1775
  }
1776
+ else if (activeSummary && adv.includeActiveSummary !== false) {
1777
+ sections.push({ section: 'active_summary', title: 'Active summary', value: activeSummary });
1778
+ }
1725
1779
  sections.push({
1726
1780
  section: 'working_memory',
1727
1781
  title: 'Working memory',
@@ -1790,6 +1844,9 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1790
1844
  if (connectedModelSummary && adv.includeConnectedModelSummary !== false) {
1791
1845
  sections.push({ section: 'connected_model_summary', title: 'SLM summary', value: connectedModelSummary });
1792
1846
  }
1847
+ else if (activeSummary && adv.includeActiveSummary !== false) {
1848
+ sections.push({ section: 'active_summary', title: 'Active summary', value: activeSummary });
1849
+ }
1793
1850
  sections.push({
1794
1851
  section: 'working_memory',
1795
1852
  title: 'Working memory',
@@ -2113,7 +2170,14 @@ class Mem0Memory {
2113
2170
  description: 'Define se o SLM resume working memory/tool state, memórias vetoriais, ambos ou nada.',
2114
2171
  },
2115
2172
  { displayName: 'Máximo de Caracteres de Entrada do SLM', name: 'connectedModelSummaryInputMaxChars', type: 'number', default: 3000 },
2116
- { displayName: 'Máximo de Caracteres do Resumo do SLM', name: 'connectedModelSummaryMaxChars', type: 'number', default: 900 },
2173
+ { displayName: 'Máximo de Caracteres do Resumo do SLM', name: 'connectedModelSummaryMaxChars', type: 'number', default: 1200 },
2174
+ { displayName: 'Incluir Active Summary', name: 'includeActiveSummary', type: 'boolean', default: true, description: 'Carrega o resumo ativo persistente da thread/sessão quando disponível.' },
2175
+ { displayName: 'Persistir Active Summary', name: 'persistActiveSummary', type: 'boolean', default: true, description: 'Salva o resumo atualizado pelo SLM para continuar a conversa depois de minutos, horas ou dias.' },
2176
+ { displayName: 'Máximo de Caracteres do Active Summary', name: 'activeSummaryMaxChars', type: 'number', default: 1600 },
2177
+ { displayName: 'Retenção do Active Summary (Dias)', name: 'activeSummaryRetentionDays', type: 'number', default: 30, description: 'Use 0 para não expirar pelo node. Não é cache técnico; é memória ativa persistente.' },
2178
+ { displayName: 'Ativar Cache Técnico do Resumo SLM', name: 'enableTransientSummaryCache', type: 'boolean', default: true, description: 'Evita chamadas duplicadas ao SLM para o mesmo pacote de contexto. Não substitui memória persistente.' },
2179
+ { displayName: 'TTL do Cache Técnico SLM (Segundos)', name: 'transientSummaryCacheTTLSeconds', type: 'number', default: 300 },
2180
+ { displayName: 'Máximo de Itens no Cache Técnico SLM', name: 'transientSummaryCacheMaxItems', type: 'number', default: 50 },
2117
2181
  { displayName: 'Máximo de Fatos no Resumo', name: 'summaryMaxFacts', type: 'number', default: 4 },
2118
2182
  { displayName: 'Incluir Scores', name: 'includeScores', type: 'boolean', default: true },
2119
2183
  { displayName: 'Incluir Diagnóstico', name: 'includeDiagnostics', type: 'boolean', default: false },
@@ -2768,11 +2832,20 @@ class Mem0Memory {
2768
2832
  vectorMemories,
2769
2833
  maxItems: adv.compressionMaxItems || 6,
2770
2834
  });
2835
+ const loadedActiveSummary = readActiveSummary(store, key, adv);
2771
2836
  let connectedModelSummary = '';
2837
+ const summaryDiagnostics = {
2838
+ source: 'none',
2839
+ transientCacheHit: false,
2840
+ activeSummaryLoaded: Boolean(loadedActiveSummary),
2841
+ activeSummaryUpdated: false,
2842
+ summaryChars: 0,
2843
+ };
2772
2844
  if (connectedLanguageModel && typeof connectedLanguageModel.invoke === 'function' && adv.includeSummary !== false && adv.includeConnectedModelSummary !== false) {
2773
2845
  try {
2774
2846
  const summaryInput = buildConnectedModelSummaryInput({
2775
2847
  query,
2848
+ activeSummary: loadedActiveSummary,
2776
2849
  profileFacts,
2777
2850
  workingMemory,
2778
2851
  decisionState,
@@ -2784,13 +2857,47 @@ class Mem0Memory {
2784
2857
  highlights,
2785
2858
  adv,
2786
2859
  });
2787
- connectedModelSummary = await invokeConnectedModelSummary(connectedLanguageModel, summaryInput, adv);
2860
+ const cacheKey = summaryInput ? stableHash({
2861
+ key,
2862
+ source: adv.summarySource || 'auto',
2863
+ input: summaryInput,
2864
+ max: adv.connectedModelSummaryMaxChars || 1200,
2865
+ }) : '';
2866
+ const cached = cacheKey ? store.connectedModelSummaryCache[cacheKey] : null;
2867
+ const transientCacheEnabled = adv.enableTransientSummaryCache !== false;
2868
+ const transientTtlSeconds = Number(adv.transientSummaryCacheTTLSeconds || adv.connectedModelSummaryCacheTTLSeconds || 300);
2869
+ if (transientCacheEnabled && cached && cached.summary && Date.now() - Number(cached.at || 0) < transientTtlSeconds * 1000) {
2870
+ connectedModelSummary = cached.summary;
2871
+ connectedAi.languageModelSummaryCached = true;
2872
+ summaryDiagnostics.source = 'transient_cache';
2873
+ summaryDiagnostics.transientCacheHit = true;
2874
+ }
2875
+ else {
2876
+ connectedModelSummary = await invokeConnectedModelSummary(connectedLanguageModel, summaryInput, adv);
2877
+ if (cacheKey && connectedModelSummary) {
2878
+ store.connectedModelSummaryCache[cacheKey] = { summary: connectedModelSummary, at: Date.now() };
2879
+ const keys = Object.keys(store.connectedModelSummaryCache);
2880
+ for (const oldKey of keys.slice(0, Math.max(0, keys.length - Number(adv.transientSummaryCacheMaxItems || adv.connectedModelSummaryCacheMaxItems || 50))))
2881
+ delete store.connectedModelSummaryCache[oldKey];
2882
+ }
2883
+ summaryDiagnostics.source = connectedModelSummary ? 'fresh_slm' : 'none';
2884
+ }
2788
2885
  connectedAi.languageModelSummary = Boolean(connectedModelSummary);
2789
2886
  }
2790
2887
  catch (error) {
2791
2888
  connectedAi.errors.push(`languageModel.invoke: ${error.message || String(error)}`);
2792
2889
  }
2793
2890
  }
2891
+ if (!connectedModelSummary && loadedActiveSummary) {
2892
+ connectedModelSummary = loadedActiveSummary;
2893
+ summaryDiagnostics.source = 'active_summary';
2894
+ }
2895
+ if (connectedModelSummary) {
2896
+ summaryDiagnostics.summaryChars = String(connectedModelSummary).length;
2897
+ if (summaryDiagnostics.source === 'fresh_slm') {
2898
+ summaryDiagnostics.activeSummaryUpdated = writeActiveSummary(store, key, connectedModelSummary, adv);
2899
+ }
2900
+ }
2794
2901
  const diagnostics = {
2795
2902
  vectorMemories: vectorMemories.length,
2796
2903
  recentMessages: recentMessages.length,
@@ -2799,6 +2906,7 @@ class Mem0Memory {
2799
2906
  project: project || null,
2800
2907
  memoryNamespace: key,
2801
2908
  connectedAi,
2909
+ activeSummary: summaryDiagnostics,
2802
2910
  };
2803
2911
  const contextHealth = deriveContextHealth({
2804
2912
  userId: key,
@@ -2837,6 +2945,7 @@ class Mem0Memory {
2837
2945
  highlights,
2838
2946
  graph,
2839
2947
  diagnostics,
2948
+ activeSummary: loadedActiveSummary,
2840
2949
  connectedModelSummary,
2841
2950
  adv,
2842
2951
  });
@@ -2883,6 +2992,9 @@ class Mem0Memory {
2883
2992
  includeOperationalState: adv.includeOperationalState !== false,
2884
2993
  includeActionLedger: adv.includeActionLedger !== false,
2885
2994
  includeEntityTimeline: adv.includeEntityTimeline !== false,
2995
+ includeActiveSummary: adv.includeActiveSummary !== false,
2996
+ persistActiveSummary: adv.persistActiveSummary !== false,
2997
+ enableTransientSummaryCache: adv.enableTransientSummaryCache !== false,
2886
2998
  compactForAgent: Boolean(adv.compactForAgent),
2887
2999
  includeWorkingMemory: adv.includeWorkingMemory !== false,
2888
3000
  includeDecisionState: adv.includeDecisionState !== false,
@@ -2891,6 +3003,7 @@ class Mem0Memory {
2891
3003
  },
2892
3004
  context: contextText,
2893
3005
  summary,
3006
+ activeSummary: loadedActiveSummary,
2894
3007
  connectedModelSummary,
2895
3008
  contextHealth,
2896
3009
  contextQualityScore: contextHealth.quality_score,
@@ -2938,6 +3051,7 @@ class Mem0Memory {
2938
3051
  temboryContext: audit,
2939
3052
  temboryContextText: contextText,
2940
3053
  temborySummary: summary,
3054
+ temboryActiveSummary: loadedActiveSummary,
2941
3055
  temboryConnectedModelSummary: connectedModelSummary,
2942
3056
  temboryContextHealth: contextHealth,
2943
3057
  temboryContextQualityScore: contextHealth.quality_score,
@@ -3073,6 +3187,9 @@ exports.__private = {
3073
3187
  compactToolResult,
3074
3188
  compactToolHistoryForAgent,
3075
3189
  compactOperationalStateForAgent,
3190
+ activeSummaryIsFresh,
3191
+ readActiveSummary,
3192
+ writeActiveSummary,
3076
3193
  buildConnectedModelSummaryInput,
3077
3194
  cleanModelSummaryText,
3078
3195
  invokeConnectedModelSummary,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.0.12",
3
+ "version": "1.0.14",
4
4
  "description": "Tembory node for n8n AI Agents with profile, tools, timeline, graph and semantic memory",
5
5
  "license": "MIT",
6
6
  "homepage": "https://tembory.com",
@@ -48,7 +48,6 @@
48
48
  "n8n",
49
49
  "tembory",
50
50
  "tembory-memory",
51
- "elephant-brain",
52
51
  "memory",
53
52
  "ai",
54
53
  "llm",