n8n-nodes-tembory 1.0.8 → 1.0.11

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,10 +2,12 @@
2
2
 
3
3
  Node de memoria operacional da Tembory para agentes de IA no n8n.
4
4
 
5
- Versao atual: `1.0.1`.
5
+ Versao atual: `1.0.11`.
6
6
 
7
7
  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
8
 
9
+ Nos presets de producao, o contexto e organizado como um pacote acionavel: secoes vazias sao removidas, evidencias de tools ficam canonicas em `tool_history`/`operational_state`, e blocos como `decision_state`, `working_memory` e `memory_compression` sao compactados sem perder IDs, proximas acoes, slots, reservas, confirmacoes e bloqueios de repeticao.
10
+
9
11
  ## Smoke tecnico
10
12
 
11
13
  Antes de publicar uma versao, rode:
@@ -27,7 +27,7 @@ const snapshotJson = (value) => {
27
27
  }
28
28
  };
29
29
  const truncate = (value, max = MAX_TEXT) => {
30
- const text = typeof value === 'string' ? value : safeStringify(value);
30
+ const text = value === undefined || value === null ? '' : (typeof value === 'string' ? value : safeStringify(value));
31
31
  if (text.length <= max)
32
32
  return text;
33
33
  return `${text.slice(0, max)}\n... [truncated ${text.length - max} chars]`;
@@ -43,7 +43,7 @@ const getConnectedEmbedding = async (ctx, itemIndex) => {
43
43
  };
44
44
  const createClientVectorMemory = async (embedding, text, metadata, ids = {}) => ({
45
45
  text: truncate(text, 4000),
46
- embedding: await embedding.embedQuery(String(text || '')),
46
+ embedding: await embedQueryCached(embedding, String(text || '')),
47
47
  user_id: ids.user_id,
48
48
  agent_id: ids.agent_id,
49
49
  run_id: ids.run_id,
@@ -53,7 +53,7 @@ const saveClientVectorMemories = async (ctx, memories, ids = {}) => {
53
53
  const valid = memories.filter((memory) => String(memory === null || memory === void 0 ? void 0 : memory.text || '').trim());
54
54
  if (!valid.length)
55
55
  return null;
56
- return GenericFunctions_1.mem0ApiRequest.call(ctx, 'POST', '/elefai/v1/vector-memories', {
56
+ return GenericFunctions_1.mem0ApiRequest.call(ctx, 'POST', '/tembory/v1/vector-memories', {
57
57
  memories: valid,
58
58
  user_id: ids.user_id,
59
59
  agent_id: ids.agent_id,
@@ -64,10 +64,10 @@ const searchClientVectorMemories = async (ctx, embedding, query, body = {}) => {
64
64
  const text = String(query || '').trim();
65
65
  if (!text)
66
66
  return { results: [] };
67
- return GenericFunctions_1.mem0ApiRequest.call(ctx, 'POST', '/elefai/v1/vector-memories/search', {
67
+ return GenericFunctions_1.mem0ApiRequest.call(ctx, 'POST', '/tembory/v1/vector-memories/search', {
68
68
  ...body,
69
69
  query: text,
70
- embedding: await embedding.embedQuery(text),
70
+ embedding: await embedQueryCached(embedding, text),
71
71
  });
72
72
  };
73
73
  const stableStringify = (value) => {
@@ -87,6 +87,31 @@ const hashString = (value) => {
87
87
  return (hash >>> 0).toString(36);
88
88
  };
89
89
  const stableHash = (value) => hashString(typeof value === 'string' ? value : stableStringify(value));
90
+ const embeddingCacheByModel = new WeakMap();
91
+ const MAX_EMBEDDING_CACHE_ITEMS = 500;
92
+ const embedQueryCached = async (embedding, text) => {
93
+ const query = String(text || '');
94
+ if (!embedding || typeof embedding.embedQuery !== 'function')
95
+ return undefined;
96
+ let cache = embeddingCacheByModel.get(embedding);
97
+ if (!cache) {
98
+ cache = new Map();
99
+ embeddingCacheByModel.set(embedding, cache);
100
+ }
101
+ const key = stableHash(query);
102
+ if (cache.has(key))
103
+ return cache.get(key);
104
+ const promise = Promise.resolve(embedding.embedQuery(query)).catch((error) => {
105
+ cache.delete(key);
106
+ throw error;
107
+ });
108
+ cache.set(key, promise);
109
+ if (cache.size > MAX_EMBEDDING_CACHE_ITEMS) {
110
+ const firstKey = cache.keys().next().value;
111
+ cache.delete(firstKey);
112
+ }
113
+ return promise;
114
+ };
90
115
  const pickText = (values, preferredKeys) => {
91
116
  for (const key of preferredKeys) {
92
117
  const value = values === null || values === void 0 ? void 0 : values[key];
@@ -142,11 +167,11 @@ const scoreOf = (m) => {
142
167
  var _a, _b, _c, _d;
143
168
  return (_d = (_c = (_b = (_a = m === null || m === void 0 ? void 0 : m.score) !== null && _a !== void 0 ? _a : m === null || m === void 0 ? void 0 : m.similarity) !== null && _b !== void 0 ? _b : m === null || m === void 0 ? void 0 : m.relevance) !== null && _c !== void 0 ? _c : m === null || m === void 0 ? void 0 : m.distance) !== null && _d !== void 0 ? _d : undefined;
144
169
  };
145
- const scoreMetaOf = (m) => (m && typeof m === 'object' && (m.elefaiScore || m._elefaiScore)) || {};
146
- const withElefaiScore = (m, elefaiScore) => {
170
+ const scoreMetaOf = (m) => (m && typeof m === 'object' && (m.temboryScore || m._temboryScore)) || {};
171
+ const withTemboryScore = (m, temboryScore) => {
147
172
  if (!m || typeof m !== 'object')
148
173
  return m;
149
- return { ...m, elefaiScore };
174
+ return { ...m, temboryScore };
150
175
  };
151
176
  const normalizeFactValue = (value) => String(value || '').replace(/\*\*/g, '').replace(/\s+/g, ' ').replace(/[.,;:]+$/g, '').trim();
152
177
  const profileSourceRank = (source = '') => {
@@ -479,8 +504,8 @@ const deriveEntityTimeline = (profileFacts = {}, graph = [], recentMessages = []
479
504
  }
480
505
  return pruneByLimit(deduped.sort((a, b) => String(a.at || '').localeCompare(String(b.at || ''))), maxItems || 24);
481
506
  };
482
- const RECENT_MESSAGE_MARKER = '__elefai_recent_message_v1__';
483
- const TOOL_HISTORY_MARKER = '__elefai_tool_history_v1__';
507
+ const RECENT_MESSAGE_MARKER = '__tembory_recent_message_v1__';
508
+ const TOOL_HISTORY_MARKER = '__tembory_tool_history_v1__';
484
509
  const encodeRecentMessage = (recent, threadId) => `${RECENT_MESSAGE_MARKER}${safeStringify({
485
510
  role: recent.role || 'user',
486
511
  content: recent.content || '',
@@ -830,21 +855,21 @@ const extractToolCalls = (outputValues = {}) => {
830
855
  const getMemoryStore = (ctx) => {
831
856
  try {
832
857
  const data = ctx.getWorkflowStaticData('global');
833
- data.elefaiBrain = data.elefaiBrain || {};
834
- data.elefaiBrain.toolHistory = data.elefaiBrain.toolHistory || {};
835
- data.elefaiBrain.recentMessages = data.elefaiBrain.recentMessages || {};
836
- data.elefaiBrain.profileFacts = data.elefaiBrain.profileFacts || {};
837
- data.elefaiBrain.workingMemory = data.elefaiBrain.workingMemory || {};
838
- data.elefaiBrain.decisionState = data.elefaiBrain.decisionState || {};
839
- data.elefaiBrain.memoryCompression = data.elefaiBrain.memoryCompression || {};
840
- return data.elefaiBrain;
858
+ data.tembory = data.tembory || {};
859
+ data.tembory.toolHistory = data.tembory.toolHistory || {};
860
+ data.tembory.recentMessages = data.tembory.recentMessages || {};
861
+ data.tembory.profileFacts = data.tembory.profileFacts || {};
862
+ data.tembory.workingMemory = data.tembory.workingMemory || {};
863
+ data.tembory.decisionState = data.tembory.decisionState || {};
864
+ data.tembory.memoryCompression = data.tembory.memoryCompression || {};
865
+ return data.tembory;
841
866
  }
842
867
  catch {
843
- global.__elefaiBrainMemory = global.__elefaiBrainMemory || { toolHistory: {}, recentMessages: {}, profileFacts: {}, workingMemory: {}, decisionState: {}, memoryCompression: {} };
844
- global.__elefaiBrainMemory.workingMemory = global.__elefaiBrainMemory.workingMemory || {};
845
- global.__elefaiBrainMemory.decisionState = global.__elefaiBrainMemory.decisionState || {};
846
- global.__elefaiBrainMemory.memoryCompression = global.__elefaiBrainMemory.memoryCompression || {};
847
- return global.__elefaiBrainMemory;
868
+ global.__temboryMemory = global.__temboryMemory || { toolHistory: {}, recentMessages: {}, profileFacts: {}, workingMemory: {}, decisionState: {}, memoryCompression: {} };
869
+ global.__temboryMemory.workingMemory = global.__temboryMemory.workingMemory || {};
870
+ global.__temboryMemory.decisionState = global.__temboryMemory.decisionState || {};
871
+ global.__temboryMemory.memoryCompression = global.__temboryMemory.memoryCompression || {};
872
+ return global.__temboryMemory;
848
873
  }
849
874
  };
850
875
  const namespacePart = (value) => String(value || '').trim().replace(/\s+/g, '_').replace(/[:|]+/g, '-');
@@ -881,6 +906,7 @@ const applyOperationalPreset = (advanced = {}) => {
881
906
  recentMessagesLastN: 8,
882
907
  },
883
908
  productionBalanced: {
909
+ compactStateSections: true,
884
910
  includeContextHeader: true,
885
911
  includeSummary: true,
886
912
  includeScores: false,
@@ -903,8 +929,11 @@ const applyOperationalPreset = (advanced = {}) => {
903
929
  lastN: 8,
904
930
  toolHistoryLastN: 10,
905
931
  recentMessagesLastN: 6,
932
+ vectorMemoryMaxChars: 360,
933
+ contextMaxChars: 10000,
906
934
  },
907
935
  productionCheap: {
936
+ compactStateSections: true,
908
937
  includeContextHeader: true,
909
938
  includeSummary: false,
910
939
  includeScores: false,
@@ -927,6 +956,8 @@ const applyOperationalPreset = (advanced = {}) => {
927
956
  lastN: 4,
928
957
  toolHistoryLastN: 5,
929
958
  recentMessagesLastN: 4,
959
+ vectorMemoryMaxChars: 260,
960
+ contextMaxChars: 7000,
930
961
  },
931
962
  productionNano: {
932
963
  compactForAgent: true,
@@ -1303,7 +1334,7 @@ const deriveContextHealth = ({ userId = '', project = '', vectorMemories = [], r
1303
1334
  const missing = Object.entries(checks).filter(([, ok]) => !ok).map(([key]) => key);
1304
1335
  const agenda = operationalState.agenda_state || {};
1305
1336
  return {
1306
- kind: 'elefai.brain.context_health.v1',
1337
+ kind: 'tembory.context_health.v1',
1307
1338
  namespace: userId || null,
1308
1339
  project: project || null,
1309
1340
  quality_score: Math.max(0, Math.min(1000, score)),
@@ -1384,12 +1415,63 @@ const compactToolResult = (result, max = 360) => {
1384
1415
  return truncate(text, max);
1385
1416
  };
1386
1417
  const compactToolHistoryForAgent = (toolHistory = [], maxItems = 6, includeResults = true) => pruneByLimit(toolHistory || [], maxItems).map((tool) => ({
1418
+ id: tool.call_id || tool.id,
1387
1419
  name: tool.name,
1388
1420
  status: tool.ok === false ? 'failed' : 'ok',
1389
1421
  at: tool.at,
1390
1422
  input: truncate(String(tool.input || ''), 180) || undefined,
1391
1423
  result: includeResults ? compactToolResult(tool.result, 360) : undefined,
1392
1424
  }));
1425
+ const cleanContextValue = (value) => {
1426
+ if (Array.isArray(value)) {
1427
+ return value.map(cleanContextValue).filter((item) => item !== undefined && item !== null && !(Array.isArray(item) && item.length === 0) && !(typeof item === 'object' && !Array.isArray(item) && Object.keys(item).length === 0));
1428
+ }
1429
+ if (!value || typeof value !== 'object')
1430
+ return value === '' ? undefined : value;
1431
+ const out = {};
1432
+ for (const [key, item] of Object.entries(value)) {
1433
+ const cleaned = cleanContextValue(item);
1434
+ if (cleaned === undefined || cleaned === null)
1435
+ continue;
1436
+ if (Array.isArray(cleaned) && cleaned.length === 0)
1437
+ continue;
1438
+ if (typeof cleaned === 'object' && !Array.isArray(cleaned) && Object.keys(cleaned).length === 0)
1439
+ continue;
1440
+ out[key] = cleaned;
1441
+ }
1442
+ return out;
1443
+ };
1444
+ const compactWorkingMemoryForAgent = (memory = {}) => cleanContextValue({
1445
+ current_goal: memory.current_goal,
1446
+ current_task: memory.current_task,
1447
+ last_user_intent: memory.last_user_intent,
1448
+ last_user_message: truncate(memory.last_user_message, 220),
1449
+ active_entities: memory.active_entities,
1450
+ open_decisions: memory.open_decisions,
1451
+ last_error: memory.last_error,
1452
+ next_expected_action: memory.next_expected_action,
1453
+ updated_at: memory.updated_at,
1454
+ });
1455
+ const compactDecisionStateForAgent = (state = {}) => cleanContextValue({
1456
+ current_intent: state.current_intent,
1457
+ active_decisions: (state.active_decisions || []).slice(-4).map((decision) => cleanContextValue({
1458
+ id: decision.id,
1459
+ status: decision.status,
1460
+ decision: truncate(decision.decision || decision.summary || decision.reason, 260),
1461
+ tool: decision.tool || decision.next_tool || decision.required_tool,
1462
+ reservation_id: decision.reservation_id,
1463
+ confirmation_id: decision.confirmation_id,
1464
+ at: decision.at || decision.updated_at,
1465
+ })),
1466
+ do_not_repeat_tools: state.do_not_repeat_tools,
1467
+ agenda_decision_state: state.agenda_decision_state,
1468
+ latest_tool: state.latest_tool ? cleanContextValue({
1469
+ name: state.latest_tool.name,
1470
+ status: state.latest_tool.status || (state.latest_tool.ok === false ? 'failed' : 'ok'),
1471
+ at: state.latest_tool.at,
1472
+ }) : undefined,
1473
+ conflict_policy: state.conflict_policy,
1474
+ });
1393
1475
  const compactOperationalStateForAgent = (state = {}) => {
1394
1476
  const agenda = state.agenda_state || {};
1395
1477
  return {
@@ -1419,6 +1501,21 @@ const compactMemoryCompressionForAgent = (compression = {}) => ({
1419
1501
  } : undefined,
1420
1502
  active_memory_count: ((compression.workflow_summary || {}).active_memory_count) || 0,
1421
1503
  });
1504
+ const compactActionLedgerForAgent = (ledger = [], maxItems = 6, includeResults = true) => pruneByLimit(ledger || [], maxItems).map((item) => cleanContextValue({
1505
+ action_id: item.action_id,
1506
+ kind: item.kind,
1507
+ tool: item.tool || item.name,
1508
+ status: item.status,
1509
+ at: item.at,
1510
+ result: includeResults ? compactToolResult(item.result, 260) : undefined,
1511
+ }));
1512
+ const compactEntityTimelineForAgent = (timeline = [], maxItems = 8) => pruneByLimit(timeline || [], maxItems).map((item) => cleanContextValue({
1513
+ entity: item.entity || item.source || item.name,
1514
+ event: truncate(item.event || item.fact || item.relation || item.kind || item.value, 220),
1515
+ target: item.target,
1516
+ at: item.at,
1517
+ source: item.source_type || item.source,
1518
+ }));
1422
1519
  const compactVectorMemoriesForAgent = (vectorMemories = [], toolHistory = [], maxItems = 3) => {
1423
1520
  const hasStructuredTools = Array.isArray(toolHistory) && toolHistory.length > 0;
1424
1521
  return (vectorMemories || [])
@@ -1435,7 +1532,7 @@ const contextSizeOfMessages = (messages = []) => {
1435
1532
  const chars = perMessage.reduce((sum, item) => sum + item.chars, 0);
1436
1533
  return { chars, approx_tokens: approxTokenCount((messages || []).map((m) => m.content || '').join('\n')), messages: perMessage };
1437
1534
  };
1438
- const wrapElefaiMemory = (memory, ctx, memoryKey) => new Proxy(memory, {
1535
+ const wrapTemboryMemory = (memory, ctx, memoryKey) => new Proxy(memory, {
1439
1536
  get(target, prop) {
1440
1537
  if (prop === 'loadMemoryVariables') {
1441
1538
  return async (values = {}) => {
@@ -1444,29 +1541,33 @@ const wrapElefaiMemory = (memory, ctx, memoryKey) => new Proxy(memory, {
1444
1541
  ]);
1445
1542
  try {
1446
1543
  const response = await target.loadMemoryVariables(values);
1447
- const chatHistory = snapshotJson(response[memoryKey] || response.chatHistory || []);
1544
+ const cacheHit = Boolean(response.temboryDiagnostics && response.temboryDiagnostics.cacheHit);
1545
+ const chatHistory = cacheHit
1546
+ ? [{ cached: true, messages: Array.isArray(response[memoryKey] || response.chatHistory) ? (response[memoryKey] || response.chatHistory).length : 0 }]
1547
+ : snapshotJson(response[memoryKey] || response.chatHistory || []);
1448
1548
  ctx.addOutputData(n8n_workflow_1.NodeConnectionTypes.AiMemory, index, [
1449
1549
  [{
1450
1550
  json: {
1451
1551
  action: 'loadMemoryVariables',
1552
+ cached: cacheHit || undefined,
1452
1553
  chatHistory,
1453
- context: response.elefaiBrainContext,
1454
- contextText: response.elefaiBrainContextText,
1455
- summary: response.elefaiBrainSummary,
1456
- connectedModelSummary: response.elefaiBrainConnectedModelSummary,
1457
- workingMemory: response.elefaiBrainWorkingMemory,
1458
- decisionState: response.elefaiBrainDecisionState,
1459
- memoryCompression: response.elefaiBrainMemoryCompression,
1460
- profileFacts: response.elefaiBrainProfileFacts,
1461
- operationalState: response.elefaiBrainOperationalState,
1462
- actionLedger: response.elefaiBrainActionLedger,
1463
- entityTimeline: response.elefaiBrainEntityTimeline,
1464
- vectorMemories: response.elefaiBrainVectorMemories,
1465
- graph: response.elefaiBrainGraph,
1466
- recentMessages: response.elefaiBrainRecentMessages,
1467
- recentHighlights: response.elefaiBrainRecentHighlights,
1468
- toolHistory: response.elefaiBrainToolHistory,
1469
- diagnostics: response.elefaiBrainDiagnostics,
1554
+ context: response.temboryContext,
1555
+ contextText: response.temboryContextText,
1556
+ summary: response.temborySummary,
1557
+ connectedModelSummary: response.temboryConnectedModelSummary,
1558
+ workingMemory: response.temboryWorkingMemory,
1559
+ decisionState: response.temboryDecisionState,
1560
+ memoryCompression: response.temboryMemoryCompression,
1561
+ profileFacts: response.temboryProfileFacts,
1562
+ operationalState: response.temboryOperationalState,
1563
+ actionLedger: response.temboryActionLedger,
1564
+ entityTimeline: response.temboryEntityTimeline,
1565
+ vectorMemories: response.temboryVectorMemories,
1566
+ graph: response.temboryGraph,
1567
+ recentMessages: response.temboryRecentMessages,
1568
+ recentHighlights: response.temboryRecentHighlights,
1569
+ toolHistory: response.temboryToolHistory,
1570
+ diagnostics: response.temboryDiagnostics,
1470
1571
  },
1471
1572
  }],
1472
1573
  ]);
@@ -1505,12 +1606,13 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1505
1606
  const includeSummary = adv.includeSummary !== false;
1506
1607
  const includeScores = adv.includeScores !== false;
1507
1608
  const compactForAgent = Boolean(adv.compactForAgent);
1609
+ const compactStateSections = Boolean(adv.compactStateSections);
1508
1610
  const sections = [];
1509
1611
  if (includeHeader) {
1510
1612
  sections.push({
1511
1613
  section: 'context_header',
1512
1614
  title: 'Tembory context',
1513
- value: compactForAgent
1615
+ value: compactForAgent || compactStateSections
1514
1616
  ? 'Read-only memory. Follow next_expected_action when present. Before calling downstream tools, verify required prior tool context in tool_history or operational_state. Do not repeat tools listed in do_not_repeat_tools.'
1515
1617
  : 'Use this context as read-only memory. Prefer it over guessing. Do not mention internal section names to the user. Treat next_expected_action as an instruction, not as a suggestion. If it says to call a tool now, call that tool instead of asking the user the same question again. If the user asks to continue, chooses a slot, says ok/sim, reserve, confirm, update, cancel, or performs any downstream action that depends on a prior tool result, first verify the required prior result in tool_history, recent_messages, or vector memories. If the required prior result is absent, do not call the downstream tool; ask for the missing context or call the appropriate prerequisite tool.',
1516
1618
  });
@@ -1559,7 +1661,7 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1559
1661
  why_null: adv.includeMemoryCompression === false ? 'memory compression disabled' : undefined,
1560
1662
  });
1561
1663
  const audit = {
1562
- kind: 'elefai.brain.context.v1',
1664
+ kind: 'tembory.context.v1',
1563
1665
  meta: { user_id: userId, payloadFormat, query, compact_for_agent: true },
1564
1666
  sections,
1565
1667
  diagnostics,
@@ -1580,13 +1682,15 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1580
1682
  return [{ role: 'system', content: truncate(sections.map(renderCompactSection).join('\n\n'), Number(adv.contextMaxChars || 6000)) }];
1581
1683
  }
1582
1684
  if (includeSummary) {
1583
- const summary = vectorMemories.slice(0, Number(adv.summaryMaxFacts || 4)).map((memory) => contextMemoryText(memory, 360)).filter(Boolean);
1685
+ const summary = compactStateSections
1686
+ ? compactVectorMemoriesForAgent(vectorMemories, toolHistory, Number(adv.summaryMaxFacts || 4))
1687
+ : vectorMemories.slice(0, Number(adv.summaryMaxFacts || 4)).map((memory) => contextMemoryText(memory, 360)).filter(Boolean);
1584
1688
  sections.push({ section: 'summary', title: 'Summary', value: summary.length ? summary : null, why_null: summary.length ? undefined : 'no vector memories to summarize' });
1585
1689
  }
1586
1690
  sections.push({
1587
1691
  section: 'working_memory',
1588
1692
  title: 'Working memory',
1589
- value: adv.includeWorkingMemory === false ? null : (workingMemory || {}),
1693
+ value: adv.includeWorkingMemory === false ? null : (compactStateSections ? compactWorkingMemoryForAgent(workingMemory || {}) : (workingMemory || {})),
1590
1694
  why_null: adv.includeWorkingMemory === false ? 'working memory disabled' : undefined,
1591
1695
  });
1592
1696
  sections.push({
@@ -1610,31 +1714,31 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1610
1714
  sections.push({
1611
1715
  section: 'decision_state',
1612
1716
  title: 'Decision state',
1613
- value: adv.includeDecisionState === false ? null : (decisionState || {}),
1717
+ value: adv.includeDecisionState === false ? null : (compactStateSections ? compactDecisionStateForAgent(decisionState || {}) : (decisionState || {})),
1614
1718
  why_null: adv.includeDecisionState === false ? 'decision state disabled' : undefined,
1615
1719
  });
1616
1720
  sections.push({
1617
1721
  section: 'operational_state',
1618
1722
  title: 'Operational state',
1619
- value: adv.includeOperationalState === false ? null : operationalState,
1723
+ value: adv.includeOperationalState === false ? null : (compactStateSections ? compactOperationalStateForAgent(operationalState || {}) : operationalState),
1620
1724
  why_null: adv.includeOperationalState === false ? 'operational state disabled' : undefined,
1621
1725
  });
1622
1726
  sections.push({
1623
1727
  section: 'action_ledger',
1624
1728
  title: 'Action ledger',
1625
- value: adv.includeActionLedger === false ? null : (actionLedger || []),
1729
+ value: adv.includeActionLedger === false ? null : (compactStateSections ? compactActionLedgerForAgent(actionLedger || [], adv.actionLedgerMaxItems || 6, adv.includeToolResults !== false) : (actionLedger || [])),
1626
1730
  why_null: adv.includeActionLedger === false ? 'action ledger disabled' : undefined,
1627
1731
  });
1628
1732
  sections.push({
1629
1733
  section: 'entity_timeline',
1630
1734
  title: 'Entity timeline',
1631
- value: adv.includeEntityTimeline === false ? null : (entityTimeline || []),
1735
+ value: adv.includeEntityTimeline === false ? null : (compactStateSections ? compactEntityTimelineForAgent(entityTimeline || [], adv.entityTimelineMaxItems || 8) : (entityTimeline || [])),
1632
1736
  why_null: adv.includeEntityTimeline === false ? 'entity timeline disabled' : undefined,
1633
1737
  });
1634
1738
  sections.push({
1635
1739
  section: 'vector',
1636
1740
  title: 'Vector memories',
1637
- value: vectorMemories.map((m) => {
1741
+ value: compactStateSections ? compactVectorMemoriesForAgent(vectorMemories, toolHistory, Number(adv.maxReturn || adv.topK || 4)) : vectorMemories.map((m) => {
1638
1742
  const scoreMeta = scoreMetaOf(m);
1639
1743
  const rawScore = scoreOf(m);
1640
1744
  return {
@@ -1652,7 +1756,7 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1652
1756
  sections.push({
1653
1757
  section: 'memory_compression',
1654
1758
  title: 'Memory compression',
1655
- value: adv.includeMemoryCompression === false ? null : (memoryCompression || {}),
1759
+ value: adv.includeMemoryCompression === false ? null : (compactStateSections ? compactMemoryCompressionForAgent(memoryCompression || {}) : (memoryCompression || {})),
1656
1760
  why_null: adv.includeMemoryCompression === false ? 'memory compression disabled' : undefined,
1657
1761
  });
1658
1762
  sections.push({
@@ -1664,7 +1768,11 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1664
1768
  sections.push({
1665
1769
  section: 'recent_messages',
1666
1770
  title: 'Recent messages',
1667
- value: recentMessages || [],
1771
+ value: compactStateSections ? pruneByLimit(recentMessages || [], adv.recentMessagesLastN || 4).map((message) => cleanContextValue({
1772
+ role: message.role,
1773
+ content: truncate(message.content, 260),
1774
+ at: message.at,
1775
+ })) : recentMessages || [],
1668
1776
  });
1669
1777
  sections.push({
1670
1778
  section: 'recent_highlights',
@@ -1675,18 +1783,18 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1675
1783
  section: 'tool_history',
1676
1784
  title: 'Tool history',
1677
1785
  value: {
1678
- enabled: adv.includeToolHistory !== false,
1679
- items: adv.includeToolHistory === false ? [] : toolHistory.map((tool) => ({
1680
- id: tool.id,
1681
- turnId: tool.turnId,
1682
- sequence: tool.sequence,
1786
+ enabled: adv.includeToolHistory !== false,
1787
+ items: adv.includeToolHistory === false ? [] : (compactStateSections ? compactToolHistoryForAgent(toolHistory, adv.toolHistoryLastN || 8, adv.includeToolResults !== false) : toolHistory.map((tool) => ({
1788
+ id: tool.id,
1789
+ turnId: tool.turnId,
1790
+ sequence: tool.sequence,
1683
1791
  name: tool.name,
1684
1792
  input: tool.input,
1685
1793
  ok: tool.ok,
1686
1794
  at: tool.at,
1687
- source: tool.source,
1688
- result: adv.includeToolResults === false ? undefined : tool.result,
1689
- })),
1795
+ source: tool.source,
1796
+ result: adv.includeToolResults === false ? undefined : tool.result,
1797
+ }))),
1690
1798
  },
1691
1799
  });
1692
1800
  if (adv.includeDiagnostics) {
@@ -1697,7 +1805,7 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1697
1805
  });
1698
1806
  }
1699
1807
  const audit = {
1700
- kind: 'elefai.brain.context.v1',
1808
+ kind: 'tembory.context.v1',
1701
1809
  meta: { user_id: userId, payloadFormat, query },
1702
1810
  sections,
1703
1811
  diagnostics,
@@ -1711,17 +1819,32 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
1711
1819
  return `## ${section.title}\n${section.value.length ? section.value.map((item) => typeof item === 'string' ? `- ${item}` : `- ${truncate(safeStringify(item), 900)}`).join('\n') : '(empty)'}`;
1712
1820
  return `## ${section.title}\n${truncate(safeStringify(section.value), 1600)}`;
1713
1821
  };
1822
+ const sectionIsEmpty = (section) => {
1823
+ if (!compactStateSections)
1824
+ return false;
1825
+ if (section.section === 'context_header' || section.section === 'tool_guard' || section.section === 'working_memory' || section.section === 'decision_state' || section.section === 'operational_state' || section.section === 'tool_history')
1826
+ return false;
1827
+ if (section.value === null || section.value === undefined)
1828
+ return true;
1829
+ if (Array.isArray(section.value) && section.value.length === 0)
1830
+ return true;
1831
+ if (typeof section.value === 'object' && !Array.isArray(section.value) && Object.keys(section.value).length === 0)
1832
+ return true;
1833
+ return false;
1834
+ };
1835
+ const renderableSections = compactStateSections ? sections.filter((section) => !sectionIsEmpty(section)) : sections;
1714
1836
  if (payloadFormat === 'auditJson') {
1715
1837
  return [{ role: 'system', content: JSON.stringify(audit, null, 2) }];
1716
1838
  }
1717
1839
  if (payloadFormat === 'auditBlocks') {
1718
- return sections.map((section) => ({ role: 'system', content: renderSection(section) }));
1840
+ return renderableSections.map((section) => ({ role: 'system', content: renderSection(section) }));
1719
1841
  }
1720
- const text = sections.map(renderSection).join('\n\n');
1842
+ const text = renderableSections.map(renderSection).join('\n\n');
1843
+ const maxChars = Number(adv.contextMaxChars || 16000);
1721
1844
  if (payloadFormat === 'auditText' || payloadFormat === 'singleSystemMessage' || payloadFormat === 'structured') {
1722
- return [{ role: 'system', content: truncate(text, 16000) }];
1845
+ return [{ role: 'system', content: truncate(text, maxChars) }];
1723
1846
  }
1724
- return [{ role: 'system', content: truncate(text, 16000) }];
1847
+ return [{ role: 'system', content: truncate(text, maxChars) }];
1725
1848
  };
1726
1849
  class Mem0Memory {
1727
1850
  constructor() {
@@ -1869,7 +1992,8 @@ class Mem0Memory {
1869
1992
  { displayName: 'MMR Lambda', name: 'mmrLambda', type: 'number', typeOptions: { minValue: 0, maxValue: 1, numberPrecision: 2 }, default: 0.5, description: 'Equilíbrio entre relevância e diversidade no MMR', displayOptions: { show: { '/retrievalMode': ['hybrid'] } } },
1870
1993
  { displayName: 'Incluir Cabeçalho de Contexto', name: 'includeContextHeader', type: 'boolean', default: true },
1871
1994
  { displayName: 'Compactar Contexto Para Agente', name: 'compactForAgent', type: 'boolean', default: false, description: 'Entrega ao agente apenas o estado acionável: working memory, decisões, estado operacional e histórico de tools compacto. Mantém detalhes completos no audit/debug.' },
1872
- { displayName: 'Máximo de Caracteres do Contexto', name: 'contextMaxChars', type: 'number', default: 6000, description: 'Limite final aplicado quando Compactar Contexto Para Agente estiver ativo.' },
1995
+ { displayName: 'Organizar Seções de Produção', name: 'compactStateSections', type: 'boolean', default: false, description: 'Renderiza blocos de produção com campos canônicos, remove seções vazias e evita duplicar evidências já presentes em tool_history/operational_state.' },
1996
+ { displayName: 'Máximo de Caracteres do Contexto', name: 'contextMaxChars', type: 'number', default: 6000, description: 'Limite final aplicado ao contexto entregue ao agente em modos estruturados de produção.' },
1873
1997
  { displayName: 'Incluir Resumo', name: 'includeSummary', type: 'boolean', default: true },
1874
1998
  { displayName: 'Máximo de Fatos no Resumo', name: 'summaryMaxFacts', type: 'number', default: 4 },
1875
1999
  { displayName: 'Incluir Scores', name: 'includeScores', type: 'boolean', default: true },
@@ -1903,6 +2027,7 @@ class Mem0Memory {
1903
2027
  async supplyData(itemIndex) {
1904
2028
  const memoryKey = this.getNodeParameter('memoryKey', itemIndex);
1905
2029
  let currentMessages = [];
2030
+ const loadCache = new Map();
1906
2031
  const memory = {
1907
2032
  memoryKeys: [memoryKey],
1908
2033
  inputKey: 'input',
@@ -1933,7 +2058,38 @@ class Mem0Memory {
1933
2058
  },
1934
2059
  },
1935
2060
  loadMemoryVariables: async (inputValues = {}) => {
1936
- const result = await Mem0Memory.prototype.loadMemoryVariablesForItem.call(this, itemIndex, inputValues);
2061
+ const cacheKey = stableHash({ itemIndex, inputValues, memoryKey });
2062
+ let result;
2063
+ if (loadCache.has(cacheKey)) {
2064
+ result = snapshotJson(loadCache.get(cacheKey));
2065
+ result.response = result.response || {};
2066
+ result.response.temboryDiagnostics = {
2067
+ ...(result.response.temboryDiagnostics || {}),
2068
+ cacheHit: true,
2069
+ cacheScope: 'supplyData.loadMemoryVariables',
2070
+ };
2071
+ if (result.response.temboryContext) {
2072
+ result.response.temboryContext = {
2073
+ kind: result.response.temboryContext.kind,
2074
+ userId: result.response.temboryContext.userId,
2075
+ project: result.response.temboryContext.project,
2076
+ query: result.response.temboryContext.query,
2077
+ retrievalMode: result.response.temboryContext.retrievalMode,
2078
+ payloadFormat: result.response.temboryContext.payloadFormat,
2079
+ contextQualityScore: result.response.temboryContext.contextQualityScore,
2080
+ cacheHit: true,
2081
+ };
2082
+ }
2083
+ delete result.response.temboryContextText;
2084
+ }
2085
+ else {
2086
+ result = await Mem0Memory.prototype.loadMemoryVariablesForItem.call(this, itemIndex, inputValues);
2087
+ loadCache.set(cacheKey, snapshotJson(result));
2088
+ if (loadCache.size > 20) {
2089
+ const firstKey = loadCache.keys().next().value;
2090
+ loadCache.delete(firstKey);
2091
+ }
2092
+ }
1937
2093
  const chatHistory = (result.response[memoryKey] || []).map(toBaseMessage);
1938
2094
  const extra = { ...(result.response || {}) };
1939
2095
  delete extra[memoryKey];
@@ -1945,6 +2101,7 @@ class Mem0Memory {
1945
2101
  };
1946
2102
  },
1947
2103
  saveContext: async (inputValues = {}, outputValues = {}) => {
2104
+ loadCache.clear();
1948
2105
  const input = pickText(inputValues, ['input', 'chatInput', 'text', 'query', 'question']);
1949
2106
  const output = pickText(outputValues, ['output', 'response', 'text', 'answer']);
1950
2107
  if (input)
@@ -1954,7 +2111,7 @@ class Mem0Memory {
1954
2111
  await Mem0Memory.prototype.saveContextForItem.call(this, itemIndex, inputValues, outputValues);
1955
2112
  },
1956
2113
  };
1957
- return { response: wrapElefaiMemory(memory, this, memoryKey) };
2114
+ return { response: wrapTemboryMemory(memory, this, memoryKey) };
1958
2115
  }
1959
2116
  async saveMessagesForItem(itemIndex, messages = []) {
1960
2117
  const inputValues = {};
@@ -2227,7 +2384,7 @@ class Mem0Memory {
2227
2384
  }
2228
2385
  if (connectedEmbedding && typeof connectedEmbedding.embedQuery === 'function' && String(query || '').trim()) {
2229
2386
  try {
2230
- await connectedEmbedding.embedQuery(String(query));
2387
+ await embedQueryCached(connectedEmbedding, String(query));
2231
2388
  connectedAi.embeddingQuery = true;
2232
2389
  }
2233
2390
  catch (error) {
@@ -2365,7 +2522,7 @@ class Mem0Memory {
2365
2522
  }
2366
2523
  ranked = selected;
2367
2524
  }
2368
- const finalMemories = ranked.slice(0, maxReturn).map((r) => withElefaiScore(r.m, {
2525
+ const finalMemories = ranked.slice(0, maxReturn).map((r) => withTemboryScore(r.m, {
2369
2526
  semanticScore: r.semanticScore,
2370
2527
  recencyScore: r.recencyScore,
2371
2528
  hybridScore: r.hybrid,
@@ -2375,7 +2532,7 @@ class Mem0Memory {
2375
2532
  payload = finalMemories.map((m) => { var _a, _b; return ({ role: 'system', content: (_b = (_a = m.memory) !== null && _a !== void 0 ? _a : m.text) !== null && _b !== void 0 ? _b : JSON.stringify(m) }); });
2376
2533
  }
2377
2534
  else {
2378
- vectorMemories = semMemories.map((m) => withElefaiScore(m, {
2535
+ vectorMemories = semMemories.map((m) => withTemboryScore(m, {
2379
2536
  semanticScore: scoreOf(m),
2380
2537
  source: 'semantic',
2381
2538
  }));
@@ -2467,10 +2624,10 @@ class Mem0Memory {
2467
2624
  catch { }
2468
2625
  }
2469
2626
  let connectedModelSummary = '';
2470
- if (connectedLanguageModel && typeof connectedLanguageModel.invoke === 'function' && (vectorMemories.length || String(query || '').trim()) && adv.includeSummary !== false) {
2627
+ if (connectedLanguageModel && typeof connectedLanguageModel.invoke === 'function' && vectorMemories.length && adv.includeSummary !== false) {
2471
2628
  try {
2472
2629
  const facts = vectorMemories.slice(0, Number(adv.summaryMaxFacts || 4)).map((memory) => contextMemoryText(memory, 500)).filter(Boolean).join('\n') || '(no vector memories found)';
2473
- if (facts || String(query || '').trim()) {
2630
+ if (facts) {
2474
2631
  const response = await connectedLanguageModel.invoke([
2475
2632
  toBaseMessage({
2476
2633
  role: 'user',
@@ -2583,7 +2740,7 @@ class Mem0Memory {
2583
2740
  const summary = vectorMemories.slice(0, Number(adv.summaryMaxFacts || 4)).map((memory) => contextMemoryText(memory, 360)).filter(Boolean);
2584
2741
  const contextText = payload.map((message) => String(message.content || '')).join('\n\n');
2585
2742
  const audit = {
2586
- kind: 'elefai.brain.context.v1',
2743
+ kind: 'tembory.context.v1',
2587
2744
  userId: key,
2588
2745
  project: project || undefined,
2589
2746
  query,
@@ -2654,25 +2811,25 @@ class Mem0Memory {
2654
2811
  return {
2655
2812
  response: {
2656
2813
  [memoryKey]: payload,
2657
- elefaiBrainContext: audit,
2658
- elefaiBrainContextText: contextText,
2659
- elefaiBrainSummary: summary,
2660
- elefaiBrainConnectedModelSummary: connectedModelSummary,
2661
- elefaiBrainContextHealth: contextHealth,
2662
- elefaiBrainContextQualityScore: contextHealth.quality_score,
2663
- elefaiBrainWorkingMemory: workingMemory,
2664
- elefaiBrainDecisionState: decisionState,
2665
- elefaiBrainMemoryCompression: memoryCompression,
2666
- elefaiBrainProfileFacts: renderProfileFacts(profileFacts),
2667
- elefaiBrainOperationalState: operationalState,
2668
- elefaiBrainActionLedger: actionLedger,
2669
- elefaiBrainEntityTimeline: entityTimeline,
2670
- elefaiBrainVectorMemories: normalizedVectorMemories,
2671
- elefaiBrainGraph: graph,
2672
- elefaiBrainRecentMessages: recentMessages,
2673
- elefaiBrainRecentHighlights: highlights,
2674
- elefaiBrainToolHistory: audit.toolHistory,
2675
- elefaiBrainDiagnostics: diagnostics,
2814
+ temboryContext: audit,
2815
+ temboryContextText: contextText,
2816
+ temborySummary: summary,
2817
+ temboryConnectedModelSummary: connectedModelSummary,
2818
+ temboryContextHealth: contextHealth,
2819
+ temboryContextQualityScore: contextHealth.quality_score,
2820
+ temboryWorkingMemory: workingMemory,
2821
+ temboryDecisionState: decisionState,
2822
+ temboryMemoryCompression: memoryCompression,
2823
+ temboryProfileFacts: renderProfileFacts(profileFacts),
2824
+ temboryOperationalState: operationalState,
2825
+ temboryActionLedger: actionLedger,
2826
+ temboryEntityTimeline: entityTimeline,
2827
+ temboryVectorMemories: normalizedVectorMemories,
2828
+ temboryGraph: graph,
2829
+ temboryRecentMessages: recentMessages,
2830
+ temboryRecentHighlights: highlights,
2831
+ temboryToolHistory: audit.toolHistory,
2832
+ temboryDiagnostics: diagnostics,
2676
2833
  },
2677
2834
  };
2678
2835
  }
@@ -2788,6 +2945,7 @@ exports.__private = {
2788
2945
  isNoisyProfileValue,
2789
2946
  approxTokenCount,
2790
2947
  contextSizeOfMessages,
2948
+ embedQueryCached,
2791
2949
  compactToolResult,
2792
2950
  compactToolHistoryForAgent,
2793
2951
  compactOperationalStateForAgent,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "n8n-nodes-tembory",
3
- "version": "1.0.8",
3
+ "version": "1.0.11",
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",
@@ -42,20 +42,20 @@ const toolHistory = [
42
42
  ok: true,
43
43
  at: '2026-05-14T05:34:34.017Z',
44
44
  source: 'used_tools_text',
45
- result: '[{"args":{"tool":"agenda_pre_reservar_horario","reservation_id":"RES-ELEFAI-LAB-001","selected_from_message":"dia 13","status":"pre_reserved","next_step":"confirmar_agendamento"}}]',
45
+ result: '[{"args":{"tool":"agenda_pre_reservar_horario","reservation_id":"RES-TEMBORY-LAB-001","selected_from_message":"dia 13","status":"pre_reserved","next_step":"confirmar_agendamento"}}]',
46
46
  },
47
47
  ];
48
48
 
49
49
  const vectorMemories = [
50
50
  {
51
- memory: '[Used tools: Tool: agenda_pre_reservar_horario, Input: , Result: [{"args":{"tool":"agenda_pre_reservar_horario","reservation_id":"RES-ELEFAI-LAB-001","selected_from_message":"dia 13","status":"pre_reserved","next_step":"confirmar_agendamento"}}]] Calling agenda_pre_reservar_horario with input: {"id":"call_bfHlltcjI1vCagRkav0fj8Ha"}\nPré-reserva feita para o dia 13.\nSe quiser, posso confirmar agora.',
51
+ memory: '[Used tools: Tool: agenda_pre_reservar_horario, Input: , Result: [{"args":{"tool":"agenda_pre_reservar_horario","reservation_id":"RES-TEMBORY-LAB-001","selected_from_message":"dia 13","status":"pre_reserved","next_step":"confirmar_agendamento"}}]] Calling agenda_pre_reservar_horario with input: {"id":"call_bfHlltcjI1vCagRkav0fj8Ha"}\nPré-reserva feita para o dia 13.\nSe quiser, posso confirmar agora.',
52
52
  created_at: '2026-05-14T05:34:35.373020Z',
53
- elefaiScore: { semanticScore: 0.71, recencyScore: 0.99, hybridScore: 0.81, source: 'semantic' },
53
+ temboryScore: { semanticScore: 0.71, recencyScore: 0.99, hybridScore: 0.81, source: 'semantic' },
54
54
  },
55
55
  {
56
56
  memory: '[Used tools: Tool: agenda_consultar_disponibilidade, Input: , Result: [{"args":{"tool":"agenda_consultar_disponibilidade","available_slots":"2026-05-12T09:00,2026-05-12T14:00,2026-05-13T10:30","timezone":"America/Sao_Paulo","next_step":"escolher_um_horario"}}]] Temos estes horários vagos:\n- 12/05 às 09:00\n- 12/05 às 14:00\n- 13/05 às 10:30',
57
57
  created_at: '2026-05-14T05:34:14.717988Z',
58
- elefaiScore: { semanticScore: 0.7, recencyScore: 0.99, hybridScore: 0.8, source: 'semantic' },
58
+ temboryScore: { semanticScore: 0.7, recencyScore: 0.99, hybridScore: 0.8, source: 'semantic' },
59
59
  },
60
60
  { memory: 'Oi! Como posso ajudar?', created_at: '2026-05-14T05:33:47.194630Z' },
61
61
  { memory: 'oi', created_at: '2026-05-14T05:33:47.194355Z' },
@@ -87,7 +87,7 @@ const common = {
87
87
  toolHistory,
88
88
  highlights: [
89
89
  'tool agenda_consultar_disponibilidade: [{"args":{"available_slots":"2026-05-12T09:00,2026-05-12T14:00,2026-05-13T10:30"}}]',
90
- 'tool agenda_pre_reservar_horario: [{"args":{"reservation_id":"RES-ELEFAI-LAB-001","status":"pre_reserved"}}]',
90
+ 'tool agenda_pre_reservar_horario: [{"args":{"reservation_id":"RES-TEMBORY-LAB-001","status":"pre_reserved"}}]',
91
91
  ],
92
92
  graph: [],
93
93
  diagnostics: {},
@@ -106,7 +106,7 @@ for (const [name, adv] of modes) {
106
106
  size: core.contextSizeOfMessages(messages),
107
107
  has_next_expected_action: /next_expected_action/.test(text),
108
108
  has_do_not_repeat_tools: /do_not_repeat_tools/.test(text),
109
- has_reservation_id: /RES-ELEFAI-LAB-001/.test(text),
109
+ has_reservation_id: /RES-TEMBORY-LAB-001/.test(text),
110
110
  has_available_slots: /available_slots/.test(text),
111
111
  has_duplicate_calling_text: /Calling agenda_pre_reservar_horario/.test(text),
112
112
  }, null, 2));