n8n-nodes-tembory 1.0.31 → 1.0.33

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.
@@ -295,7 +295,7 @@ const extractProfileFactsFromText = (text, source = 'message', at = nowIso()) =>
295
295
  const canExtractStrongProfileFacts = sourceName !== 'assistant_message';
296
296
  if (!content.trim())
297
297
  return facts;
298
- if (/\[Used tools:|Calling\s+agenda_|"tool"\s*:|"args"\s*:|confirmation_id|reservation_id/i.test(content))
298
+ if (/\[Used tools:|Calling\s+[A-Za-z_][A-Za-z0-9_.:-]*|"tool"\s*:|"args"\s*:|confirmation_id|reservation_id/i.test(content))
299
299
  return facts;
300
300
  const email = /\b[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}\b/i.exec(content);
301
301
  if (email && canExtractStrongProfileFacts)
@@ -407,74 +407,63 @@ const safeParseToolPayload = (value) => {
407
407
  };
408
408
  const compactToolPayload = (value) => truncate(typeof value === 'string' ? value : safeStringify(value), 900);
409
409
  const maybeToolResult = (tool, includeResults = true) => includeResults === false ? undefined : compactToolPayload(safeParseToolPayload(tool === null || tool === void 0 ? void 0 : tool.result));
410
- const containsSelectedSlot = (text = '') => /\b(?:às|as|das|para)?\s*\d{1,2}(?::\d{2}|h\d{0,2})?\b/i.test(String(text || '')) || /\b\d{1,2}\/\d{1,2}(?:\/\d{2,4})?\b/.test(String(text || ''));
411
- const availabilityFromRecentMessages = (recentMessages = []) => {
412
- const messages = Array.isArray(recentMessages) ? recentMessages : [];
413
- const candidates = [...messages].reverse().filter((message) => {
414
- const role = String(message.role || '').toLowerCase();
415
- const text = String(message.content || message.text || message.memory || '');
416
- if (role && role !== 'assistant' && role !== 'ai' && role !== 'system')
417
- return false;
418
- return /\b(?:tem|tenho|há|ha)\s+vagas?\b|\bop[][]es?\s+dispon[ií]veis\b|\bavailable_slots\b/i.test(text)
419
- && (/\b\d{1,2}\/\d{1,2}(?:\/\d{2,4})?\b/.test(text) || /\b\d{1,2}:\d{2}\b/.test(text));
420
- });
421
- const latest = candidates[0];
422
- if (!latest)
423
- return null;
424
- return {
425
- text: truncate(String(latest.content || latest.text || latest.memory || ''), 900),
426
- at: latest.at || latest.created_at || latest.createdAt || nowIso(),
427
- source: latest.source || 'recent_message',
428
- };
429
- };
430
- const preReservationFromRecentMessages = (recentMessages = []) => {
431
- const messages = Array.isArray(recentMessages) ? recentMessages : [];
432
- const candidates = [...messages].reverse().filter((message) => {
433
- const role = String(message.role || '').toLowerCase();
434
- const text = String(message.content || message.text || message.memory || '');
435
- if (role && role !== 'assistant' && role !== 'ai' && role !== 'system')
436
- return false;
437
- return /\b(pr[eé][-\s]?reservad[oa]|pre[-\s]?reservad[oa]|reservation_id|RES-[A-Z0-9-]+)\b/i.test(text)
438
- && !/\b(confirmad[oa]|confirmation_id|CONF-[A-Z0-9-]+)\b/i.test(text);
439
- });
440
- const latest = candidates[0];
441
- if (!latest)
442
- return null;
443
- return {
444
- text: truncate(String(latest.content || latest.text || latest.memory || ''), 900),
445
- at: latest.at || latest.created_at || latest.createdAt || nowIso(),
446
- source: latest.source || 'recent_message',
447
- };
410
+ const isToolName = (value = '') => /^[A-Za-z_][A-Za-z0-9_.:-]{1,127}$/.test(String(value || ''));
411
+ const pickRequiredToolFromAction = (text = '') => {
412
+ const next = String(text || '');
413
+ const protectedCall = /\bdo not call\s+([A-Za-z_][A-Za-z0-9_.:-]{1,127})\b[\s\S]*?\bcall\s+([A-Za-z_][A-Za-z0-9_.:-]{1,127})\b/i.exec(next);
414
+ if (protectedCall && isToolName(protectedCall[2]))
415
+ return protectedCall[2];
416
+ if (/\bdo not call\b/i.test(next))
417
+ return '';
418
+ const mustCall = /\bMUST\s+call\s+([A-Za-z_][A-Za-z0-9_.:-]{1,127})\b/i.exec(next);
419
+ if (mustCall && isToolName(mustCall[1]))
420
+ return mustCall[1];
421
+ const call = /\bcall\s+([A-Za-z_][A-Za-z0-9_.:-]{1,127})\b/i.exec(next);
422
+ if (call && isToolName(call[1]))
423
+ return call[1];
424
+ return '';
448
425
  };
449
426
  const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessages = [], includeResults = true) => {
450
427
  const tools = Array.isArray(toolHistory) ? toolHistory : [];
451
428
  const successfulTools = tools.filter((tool) => tool.ok !== false);
452
- const byName = (name) => successfulTools.filter((tool) => tool.name === name);
453
- const availability = byName('agenda_consultar_disponibilidade');
454
- const recentAvailability = availability.length ? null : availabilityFromRecentMessages(recentMessages);
455
- const hasAvailability = availability.length > 0 || Boolean(recentAvailability);
456
- const reservations = byName('agenda_pre_reservar_horario');
457
- const recentPreReservation = reservations.length ? null : preReservationFromRecentMessages(recentMessages);
458
- const hasPreReservation = reservations.length > 0 || Boolean(recentPreReservation);
459
- const confirmations = byName('agenda_confirmar_agendamento');
429
+ const failedTools = tools.filter((tool) => tool.ok === false);
430
+ const toolCountsByName = {};
431
+ const latestByName = {};
432
+ const failedByName = {};
433
+ const recentSequence = [];
434
+ for (let index = 0; index < tools.length; index += 1) {
435
+ const tool = tools[index];
436
+ const name = String(tool.name || 'unknown_tool');
437
+ const compactInput = compactToolPayload(safeParseToolPayload(tool.input));
438
+ const compactResult = maybeToolResult(tool, includeResults);
439
+ const reason = truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 260);
440
+ toolCountsByName[name] = (toolCountsByName[name] || 0) + 1;
441
+ latestByName[name] = {
442
+ name,
443
+ ok: tool.ok !== false,
444
+ at: tool.at || null,
445
+ input: compactInput,
446
+ result: compactResult,
447
+ reason,
448
+ source: tool.source || 'unknown',
449
+ };
450
+ recentSequence.push(cleanContextValue({
451
+ sequence: tool.sequence || index + 1,
452
+ tool: name,
453
+ status: tool.ok === false ? 'failed' : 'ok',
454
+ at: tool.at || null,
455
+ reason,
456
+ input: compactInput,
457
+ output: compactResult,
458
+ }));
459
+ if (tool.ok === false)
460
+ failedByName[name] = (failedByName[name] || 0) + 1;
461
+ }
460
462
  const lastTool = tools[tools.length - 1] || null;
461
- const lastReservation = reservations[reservations.length - 1] || null;
462
- const lastConfirmation = confirmations[confirmations.length - 1] || null;
463
- const hasPendingReservation = Boolean((lastReservation && (!lastConfirmation || String(lastReservation.at || '') > String(lastConfirmation.at || ''))) || (recentPreReservation && !lastConfirmation));
464
463
  const blockedWithoutContext = [];
465
- if (!hasAvailability)
466
- blockedWithoutContext.push('agenda_pre_reservar_horario');
467
- if (!hasPreReservation)
468
- blockedWithoutContext.push('agenda_confirmar_agendamento');
469
- const guidance = [];
470
- if (!hasAvailability)
471
- guidance.push('No availability result is known for this session; consult availability before reserving or confirming.');
472
- else if (hasPendingReservation)
473
- guidance.push('A pre-reservation exists after the latest confirmation; confirmation can use the latest pre-reservation context.');
474
- else if (lastConfirmation)
475
- guidance.push('The latest reservation appears confirmed; do not confirm again unless the user explicitly asks to repeat or change it.');
476
- else if (hasAvailability)
477
- guidance.push('Availability is known; if the user chooses one listed slot, reserve without repeating availability.');
464
+ const guidance = [
465
+ 'Use tool_state and action_ledger as the source of truth for prior tool calls, inputs, outputs, failures and decisions. Domain-specific tool policy must come from the agent prompt, not from memory.',
466
+ ];
478
467
  return {
479
468
  profile_complete: Boolean(profileFacts && profileFacts.name && profileFacts.company && profileFacts.email && profileFacts.phone),
480
469
  last_tool: lastTool ? { name: lastTool.name, ok: lastTool.ok, at: lastTool.at } : null,
@@ -482,20 +471,22 @@ const deriveOperationalState = (toolHistory = [], profileFacts = {}, recentMessa
482
471
  total: tools.length,
483
472
  ok: successfulTools.length,
484
473
  failed: tools.length - successfulTools.length,
485
- agenda_consultar_disponibilidade: availability.length,
486
- agenda_pre_reservar_horario: reservations.length,
487
- agenda_confirmar_agendamento: confirmations.length,
474
+ by_name: toolCountsByName,
488
475
  },
489
- agenda_state: {
490
- has_availability: hasAvailability,
491
- has_pre_reservation: hasPreReservation,
492
- has_confirmation: confirmations.length > 0,
493
- has_pending_pre_reservation: hasPendingReservation,
494
- latest_availability_result: availability.length ? maybeToolResult(availability[availability.length - 1], includeResults) : (recentAvailability && includeResults !== false ? recentAvailability.text : null),
495
- latest_availability_source: availability.length ? 'tool_history' : (recentAvailability ? recentAvailability.source : null),
496
- latest_pre_reservation_result: lastReservation ? maybeToolResult(lastReservation, includeResults) : (recentPreReservation && includeResults !== false ? recentPreReservation.text : null),
497
- latest_pre_reservation_source: lastReservation ? 'tool_history' : (recentPreReservation ? recentPreReservation.source : null),
498
- latest_confirmation_result: lastConfirmation ? maybeToolResult(lastConfirmation, includeResults) : null,
476
+ tool_state: {
477
+ total: tools.length,
478
+ ok: successfulTools.length,
479
+ failed: failedTools.length,
480
+ names: Object.keys(toolCountsByName),
481
+ counts_by_name: toolCountsByName,
482
+ failed_by_name: failedByName,
483
+ latest_by_name: latestByName,
484
+ recent_sequence: pruneByLimit(recentSequence, 20),
485
+ last_successful_tool: successfulTools.length ? {
486
+ name: successfulTools[successfulTools.length - 1].name,
487
+ at: successfulTools[successfulTools.length - 1].at || null,
488
+ result: maybeToolResult(successfulTools[successfulTools.length - 1], includeResults),
489
+ } : null,
499
490
  },
500
491
  blocked_without_context: Array.from(new Set(blockedWithoutContext)),
501
492
  guidance,
@@ -510,6 +501,7 @@ const deriveActionLedger = (toolHistory = [], maxItems = 20, includeResults = tr
510
501
  kind: 'tool_call',
511
502
  name: tool.name || 'unknown_tool',
512
503
  status: tool.ok === false ? 'failed' : 'ok',
504
+ reason: truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 260),
513
505
  input: compactToolPayload(safeParseToolPayload(tool.input)),
514
506
  result: maybeToolResult(tool, includeResults),
515
507
  at: tool.at || null,
@@ -586,6 +578,7 @@ const deriveEntityTimeline = (profileFacts = {}, graph = [], recentMessages = []
586
578
  };
587
579
  const RECENT_MESSAGE_MARKER = '__tembory_recent_message_v1__';
588
580
  const TOOL_HISTORY_MARKER = '__tembory_tool_history_v1__';
581
+ const TOOL_LEDGER_MARKER = '__tembory_tool_ledger_v1__';
589
582
  const encodeRecentMessage = (recent, threadId) => `${RECENT_MESSAGE_MARKER}${safeStringify({
590
583
  role: recent.role || 'user',
591
584
  content: recent.content || '',
@@ -604,6 +597,21 @@ const encodeToolCall = (tool, threadId) => `${TOOL_HISTORY_MARKER}${safeStringif
604
597
  source: tool.source || 'n8n',
605
598
  thread_id: threadId,
606
599
  })}`;
600
+ const encodeToolLedger = (tools = [], threadId) => `${TOOL_LEDGER_MARKER}${safeStringify({
601
+ thread_id: threadId,
602
+ generated_at: nowIso(),
603
+ tools: sortToolHistory(tools || []).map((tool, index) => ({
604
+ id: tool.id || tool.callId || tool.call_id || '',
605
+ turn_id: tool.turnId || tool.turn_id || '',
606
+ sequence: tool.sequence || index + 1,
607
+ name: tool.name || tool.tool || tool.toolName || '',
608
+ input: tool.input || '',
609
+ ok: tool.ok !== false,
610
+ result: tool.result || '',
611
+ at: tool.at || nowIso(),
612
+ source: tool.source || 'n8n',
613
+ })),
614
+ })}`;
607
615
  const parseRecentMessageMarker = (text) => {
608
616
  if (!text || typeof text !== 'string' || !text.startsWith(RECENT_MESSAGE_MARKER))
609
617
  return null;
@@ -644,6 +652,30 @@ const parseToolHistoryMarker = (text) => {
644
652
  return null;
645
653
  }
646
654
  };
655
+ const parseToolLedgerMarker = (text) => {
656
+ if (!text || typeof text !== 'string' || !text.startsWith(TOOL_LEDGER_MARKER))
657
+ return [];
658
+ try {
659
+ const parsed = JSON.parse(text.slice(TOOL_LEDGER_MARKER.length));
660
+ const tools = Array.isArray(parsed?.tools) ? parsed.tools : [];
661
+ return tools
662
+ .filter((tool) => tool && tool.name)
663
+ .map((tool, index) => ({
664
+ id: tool.id || tool.call_id || tool.callId || '',
665
+ turnId: tool.turn_id || tool.turnId || '',
666
+ sequence: tool.sequence || index + 1,
667
+ name: String(tool.name),
668
+ input: tool.input === undefined ? '' : String(tool.input),
669
+ ok: tool.ok !== false,
670
+ result: truncate(tool.result || '', 1000),
671
+ at: tool.at || parsed.generated_at || nowIso(),
672
+ source: tool.source || 'tool_ledger_marker',
673
+ }));
674
+ }
675
+ catch {
676
+ return [];
677
+ }
678
+ };
647
679
  const recentMessageFromMemory = (item) => {
648
680
  const meta = metadataOf(item);
649
681
  const content = meta.content || item.memory || item.text || item.value || item.content || item.data;
@@ -686,7 +718,7 @@ const toolHistoryFromMemory = (item) => {
686
718
  source: 'semantic_fact',
687
719
  };
688
720
  }
689
- const namedTool = meta.kind === 'tool_history' ? /(?:tool|ferramenta)\s+([A-Za-z0-9_.:-]*(?:agenda_[A-Za-z0-9_.:-]+)[A-Za-z0-9_.:-]*)/i.exec(String(content || '')) : null;
721
+ const namedTool = meta.kind === 'tool_history' ? /(?:tool|ferramenta)\s+([A-Za-z_][A-Za-z0-9_.:-]{1,127})/i.exec(String(content || '')) : null;
690
722
  if (namedTool) {
691
723
  return {
692
724
  id: meta.id || meta.call_id || meta.callId || '',
@@ -699,26 +731,6 @@ const toolHistoryFromMemory = (item) => {
699
731
  source: 'semantic_named_tool',
700
732
  };
701
733
  }
702
- const text = meta.kind === 'tool_history' ? String(content || '').toLowerCase() : '';
703
- const inferredAgendaTool = text.includes('confirm') && text.includes('agendamento')
704
- ? 'agenda_confirmar_agendamento'
705
- : text.includes('pré-reserva') || text.includes('pre-reserva') || text.includes('pre reserva') || text.includes('pré reserva')
706
- ? 'agenda_pre_reservar_horario'
707
- : (text.includes('horários disponíveis') || text.includes('horarios disponiveis') || text.includes('available') || text.includes('disponibilidade')) && text.includes('agenda')
708
- ? 'agenda_consultar_disponibilidade'
709
- : '';
710
- if (inferredAgendaTool) {
711
- return {
712
- id: meta.id || meta.call_id || meta.callId || '',
713
- turnId: meta.turn_id || meta.turnId || '',
714
- name: inferredAgendaTool,
715
- input: '',
716
- ok: true,
717
- result: truncate(content || '', 1000),
718
- at: meta.at || item.created_at || item.createdAt || nowIso(),
719
- source: 'semantic_inference',
720
- };
721
- }
722
734
  if (meta.kind !== 'tool_history')
723
735
  return null;
724
736
  const name = meta.name || meta.tool || meta.toolName;
@@ -738,6 +750,9 @@ const toolHistoryFromMemory = (item) => {
738
750
  const explicitToolHistoryItemsFromMemory = (item) => {
739
751
  const meta = metadataOf(item);
740
752
  const content = meta.content || item.memory || item.text || item.value || item.content || item.data;
753
+ const ledger = parseToolLedgerMarker(content);
754
+ if (ledger.length)
755
+ return ledger;
741
756
  const marked = parseToolHistoryMarker(content);
742
757
  if (marked)
743
758
  return [marked];
@@ -1268,17 +1283,18 @@ const normalizeIntentText = (value = '') => String(value || '')
1268
1283
  .toLowerCase()
1269
1284
  .normalize('NFD')
1270
1285
  .replace(/[\u0300-\u036f]/g, '');
1271
- const hasConfirmIntent = (value = '') => /\b(confirm\w*|confim\w*|cnfirm\w*|cofnirm\w*|ocnfi\w*|ocnfia\w*|fechar)\b/.test(normalizeIntentText(value));
1286
+ const hasCommitIntent = (value = '') => /\b(confirm\w*|confim\w*|cnfirm\w*|cofnirm\w*|ocnfi\w*|ocnfia\w*|fechar|aprovar|finalizar|prosseguir|continuar|sim)\b/.test(normalizeIntentText(value));
1272
1287
  const isConversationRecallQuery = (value = '') => {
1273
1288
  const text = normalizeIntentText(value);
1274
1289
  return /\b(minha|qual|quais|o que|oq|voce|vc|lembra|lembrar|sabe|diga|fala)\b.{0,80}\b(ultima|ultimas|msg|msgs|mensagem|mensagens|pergunta|perguntei|falei|disse)\b/.test(text)
1275
1290
  || /\b(o que|oq)\b.{0,30}\b(eu)\b.{0,30}\b(falei|disse|perguntei|mandei)\b/.test(text)
1276
1291
  || /\b(nao|nao)\b.{0,20}\b(lembra|sabe)\b.{0,60}\b(msg|msgs|mensagem|pergunta|falei|disse|perguntei)\b/.test(text);
1277
1292
  };
1278
- const isAgendaStatusQuery = (value = '') => {
1293
+ const isOperationalStatusQuery = (value = '') => {
1279
1294
  const text = normalizeIntentText(value);
1280
- return /\b(ja|ainda|tinha|tenho|foi|esta|ta)\b.{0,60}\b(agend\w*|agned\w*|ganed\w*|marc\w*|reserv\w*|confirm\w*)\b/.test(text)
1281
- || /\b(agend\w*|agned\w*|ganed\w*|marc\w*|reserv\w*|confirm\w*)\b.{0,60}\b(ja|ainda|feito|feita|confirmado|confirmada)\b/.test(text);
1295
+ return /\b(quais?|qual|lista|mostra|resume|status|estado|historico|hist[oó]rico|outputs?|retornos?|resultado|resultados?)\b.{0,80}\b(tools?|ferramentas?|apis?|a[cç][oõ]es?|chamadas?|executadas?|usadas?)\b/.test(text)
1296
+ || /\b(tools?|ferramentas?|apis?|a[cç][oõ]es?|chamadas?|executadas?|usadas?)\b.{0,80}\b(quais?|qual|lista|mostra|resume|status|estado|historico|hist[oó]rico|outputs?|retornos?|resultado|resultados?)\b/.test(text)
1297
+ || /\b(ja|ainda|tinha|tenho|foi|esta|ta)\b.{0,60}\b(feito|feita|executad\w*|chamad\w*|concluid\w*|confirm\w*|criad\w*|atualizad\w*|agend\w*|agned\w*|ganed\w*|marc\w*|reserv\w*)\b/.test(text);
1282
1298
  };
1283
1299
  const recentUserMessages = (recentMessages = []) => [...(recentMessages || [])]
1284
1300
  .filter((msg) => /^(user|human)$/i.test(String(msg.role || '')))
@@ -1336,8 +1352,8 @@ const buildConversationFrame = ({ query = '', recentMessages = [], workingMemory
1336
1352
  };
1337
1353
  const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalState = {}, workingMemory = {} }) => {
1338
1354
  const recall = isConversationRecallQuery(query);
1339
- const agendaStatus = isAgendaStatusQuery(query);
1340
- if (!recall && !agendaStatus)
1355
+ const operationalStatus = isOperationalStatusQuery(query);
1356
+ if (!recall && !operationalStatus)
1341
1357
  return null;
1342
1358
  const previousUser = previousUserMessageForQuery(query, recentMessages);
1343
1359
  const firstUser = firstUserMessageFromConversation(recentMessages);
@@ -1345,31 +1361,22 @@ const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalSta
1345
1361
  .filter((msg) => normalizeIntentText(msg.content).trim() !== normalizeIntentText(query).trim())
1346
1362
  .slice(0, 3)
1347
1363
  .map((msg) => ({ role: msg.role, content: truncate(msg.content, 500), at: msg.at }));
1348
- const agenda = (operationalState || {}).agenda_state || {};
1364
+ const toolState = (operationalState || {}).tool_state || {};
1349
1365
  const previousUserMessage = previousUser ? truncate(previousUser.content, 500) : previousUserFallbackFromWorkingMemory(query, workingMemory);
1350
1366
  const focus = cleanContextValue({
1351
1367
  current_user_request: truncate(query, 500),
1352
- intent: recall ? 'conversation_recall' : 'agenda_status_question',
1368
+ intent: recall ? 'conversation_recall' : 'tool_or_status_question',
1353
1369
  first_user_message: firstUser ? truncate(firstUser.content, 500) : null,
1354
1370
  previous_user_message: previousUserMessage,
1355
1371
  recent_user_messages: users,
1356
- agenda_status: agendaStatus ? {
1357
- reservation_status: agenda.has_confirmation && !agenda.has_pending_pre_reservation
1358
- ? 'confirmed'
1359
- : agenda.has_pending_pre_reservation
1360
- ? 'pending_pre_reservation'
1361
- : agenda.has_pre_reservation
1362
- ? 'pre_reserved'
1363
- : agenda.has_availability
1364
- ? 'availability_known_not_scheduled'
1365
- : 'none',
1366
- has_confirmation: Boolean(agenda.has_confirmation && !agenda.has_pending_pre_reservation),
1367
- has_pending_pre_reservation: Boolean(agenda.has_pending_pre_reservation),
1368
- has_availability: Boolean(agenda.has_availability),
1372
+ tool_status: operationalStatus ? {
1373
+ executed_tools: toolState.names || [],
1374
+ last_successful_tool: toolState.last_successful_tool || null,
1375
+ failed_by_name: toolState.failed_by_name || {},
1369
1376
  } : undefined,
1370
1377
  instruction: recall
1371
- ? 'Answer the user meta-question from previous_user_message/recent_user_messages and the chronological transcript. Do not answer with agenda availability unless the user asks for availability.'
1372
- : 'Answer whether the appointment/reservation is already scheduled using agenda_status. Do not offer availability as the main answer unless there is no schedule/reservation state.',
1378
+ ? 'Answer the user meta-question from previous_user_message/recent_user_messages and the chronological transcript. Do not call tools for recall-only questions.'
1379
+ : 'Answer status questions from tool_state, tool_history and action_ledger. Do not call tools unless the agent prompt requires it.',
1373
1380
  });
1374
1381
  if (!previousUserMessage)
1375
1382
  focus.previous_user_message = null;
@@ -1377,47 +1384,29 @@ const buildCurrentTurnFocus = ({ query = '', recentMessages = [], operationalSta
1377
1384
  };
1378
1385
  const buildActionDirective = ({ workingMemory = {}, operationalState = {} }) => {
1379
1386
  const next = String((workingMemory || {}).next_expected_action || '');
1380
- const match = /\bagenda_(consultar_disponibilidade|pre_reservar_horario|confirmar_agendamento)\b/.exec(next);
1381
- if (!match || !/\bcall\b|\bMUST\b/i.test(next))
1387
+ const tool = pickRequiredToolFromAction(next);
1388
+ if (!tool || !/\bcall\b|\bMUST\b/i.test(next))
1382
1389
  return null;
1383
- const tool = match[0];
1384
- const agenda = (operationalState || {}).agenda_state || {};
1390
+ const toolState = (operationalState || {}).tool_state || {};
1391
+ const latestForTool = ((toolState.latest_by_name || {})[tool]) || null;
1385
1392
  return cleanContextValue({
1386
1393
  required_tool: tool,
1387
1394
  next_expected_action: next,
1388
- agenda_state: {
1389
- has_availability: Boolean(agenda.has_availability),
1390
- latest_availability_source: agenda.latest_availability_source,
1391
- has_pre_reservation: Boolean(agenda.has_pre_reservation),
1392
- has_pending_pre_reservation: Boolean(agenda.has_pending_pre_reservation),
1393
- has_confirmation: Boolean(agenda.has_confirmation),
1395
+ tool_state: {
1396
+ last_tool: (operationalState || {}).last_tool || null,
1397
+ required_tool_last_result: latestForTool,
1398
+ counts_by_name: toolState.counts_by_name || {},
1399
+ failed_by_name: toolState.failed_by_name || {},
1394
1400
  },
1395
- instruction: `You MUST call ${tool} now. Do not answer with "posso", "se quiser", "já posso" or ask for the same confirmation instead of calling the required tool. Only skip this tool if it is explicitly blocked in Tool guard.`,
1401
+ instruction: `If the agent prompt requires this action, call ${tool} now using tool_state/action_ledger as evidence. Do not infer domain policy from memory; memory only provides prior conversation, decisions, inputs and outputs.`,
1396
1402
  });
1397
1403
  };
1398
1404
  const inferToolGuard = ({ query, recentMessages, toolHistory, vectorMemories }) => {
1399
- const text = normalizeIntentText(query);
1400
- const names = new Set((toolHistory || []).map((tool) => String(tool.name || '')));
1401
- const hasRecentContext = (recentMessages || []).some((msg) => /reserva|pré-reserva|pre-reserva|agend|confirm|hor[aá]rio|dispon/i.test(String(msg.content || '')));
1402
- const hasVectorContext = (vectorMemories || []).some((memory) => /reserva|pré-reserva|pre-reserva|agend|confirm|hor[aá]rio|dispon/i.test(memoryText(memory)));
1403
- const hasAvailabilityTool = names.has('agenda_consultar_disponibilidade');
1404
- const hasPreReservationTool = names.has('agenda_pre_reservar_horario');
1405
- const hasAnyContext = names.size > 0 || hasRecentContext || hasVectorContext;
1406
- const blocked = [];
1407
- const reasons = [];
1408
- if (!hasAnyContext) {
1409
- blocked.push('agenda_pre_reservar_horario', 'agenda_confirmar_agendamento');
1410
- reasons.push('no prior availability or pre-reservation context was found for this session');
1411
- }
1412
- if (hasConfirmIntent(text) && !hasPreReservationTool) {
1413
- blocked.push('agenda_confirmar_agendamento');
1414
- reasons.push('confirmation requested but no structured agenda_pre_reservar_horario tool result was found; assistant text or vector memories are not enough to confirm');
1415
- }
1416
- if ((/pre.?reserv|pre.?reserv|reservar/.test(text)) && !hasAvailabilityTool) {
1417
- blocked.push('agenda_pre_reservar_horario');
1418
- reasons.push('reservation requested but no structured agenda_consultar_disponibilidade tool result was found');
1419
- }
1420
- return { blockedTools: Array.from(new Set(blocked)), reasons };
1405
+ return {
1406
+ blockedTools: [],
1407
+ reasons: [],
1408
+ instruction: 'Memory does not block domain tools by hardcoded rules. The agent prompt decides which tool to call; use tool_state/tool_history/action_ledger to verify prior inputs, outputs and failures.',
1409
+ };
1421
1410
  };
1422
1411
  const inferUserIntent = (query = '', recentMessages = []) => {
1423
1412
  const latestUser = [...(recentMessages || [])].reverse().find((msg) => /^(user|human)$/i.test(String(msg.role || '')));
@@ -1426,20 +1415,14 @@ const inferUserIntent = (query = '', recentMessages = []) => {
1426
1415
  const normalizedQuery = normalizeIntentText(query).trim();
1427
1416
  if (isConversationRecallQuery(query))
1428
1417
  return 'conversation_recall';
1429
- if (isAgendaStatusQuery(query))
1430
- return 'agenda_status_question';
1418
+ if (isOperationalStatusQuery(query))
1419
+ return 'operational_status_question';
1431
1420
  if (/^(ok|sim|pode|pode sim|isso|isso mesmo|confirmo|confirmar)$/.test(normalizedQuery))
1432
1421
  return 'affirm';
1433
- if (hasConfirmIntent(text))
1434
- return 'confirm';
1435
- if (containsSelectedSlot(text))
1436
- return 'select_slot';
1437
- if (/\b(pre.?reserv|pre.?reserv|reservar|segurar|marcar)\b/.test(text))
1438
- return 'pre_reserve';
1439
- if (/\b(disponibilidade|horarios?|agenda(?:r|mento)?|quando pode|tem vaga|quero agendar|gostaria de agendar|preciso agendar)\b/.test(text))
1440
- return 'check_availability';
1441
- if (/\b(cancel|desmarcar|remarcar|alterar|mudar)\b/.test(text))
1442
- return 'change_or_cancel';
1422
+ if (hasCommitIntent(text))
1423
+ return 'commit_or_continue';
1424
+ if (/\b(buscar|busca|criar|cria|atualizar|atualiza|consultar|consulta|reservar|reserva|agendar|agenda|abrir|abre|cancelar|cancela|enviar|envia|gerar|gera|validar|valida|processar|processa|executar|executa)\b/.test(text))
1425
+ return 'tool_action_candidate';
1443
1426
  if (/\b(meu nome|email|telefone|empresa|prefiro|preferencia)\b/.test(text))
1444
1427
  return 'profile_update';
1445
1428
  if (text.trim())
@@ -1447,41 +1430,14 @@ const inferUserIntent = (query = '', recentMessages = []) => {
1447
1430
  return 'unknown';
1448
1431
  };
1449
1432
  const deriveNextExpectedAction = (intent, operationalState = {}) => {
1450
- const agenda = operationalState.agenda_state || {};
1451
- if (intent === 'affirm') {
1452
- if (agenda.has_pending_pre_reservation)
1453
- return 'MUST call agenda_confirmar_agendamento now; do not ask the same confirmation question again';
1454
- if (agenda.has_availability)
1455
- return 'MUST call agenda_pre_reservar_horario now using the selected or latest discussed slot; do not ask again';
1456
- return 'call agenda_consultar_disponibilidade before reserving';
1457
- }
1458
- if (intent === 'confirm') {
1459
- if (agenda.has_pending_pre_reservation)
1460
- return 'MUST call agenda_confirmar_agendamento now using the latest pre-reservation context; do not ask again';
1461
- if (agenda.has_availability)
1462
- return 'MUST call agenda_pre_reservar_horario first using the selected or latest discussed slot; do not ask for permission again';
1463
- return 'do not call agenda_confirmar_agendamento; call agenda_consultar_disponibilidade first because no availability is known';
1464
- }
1465
- if (intent === 'select_slot') {
1466
- if (agenda.has_availability)
1467
- return 'MUST call agenda_pre_reservar_horario now using the selected slot; do not ask for confirmation before pre-reserving';
1468
- return 'call agenda_consultar_disponibilidade before pre-reserving';
1469
- }
1470
- if (intent === 'pre_reserve') {
1471
- if (agenda.has_availability)
1472
- return 'MUST call agenda_pre_reservar_horario now using one of the known available slots';
1473
- return 'call agenda_consultar_disponibilidade before pre-reserving';
1474
- }
1475
- if (intent === 'check_availability')
1476
- return agenda.has_availability ? 'reuse known availability unless the user asks to refresh' : 'call agenda_consultar_disponibilidade';
1477
- if (intent === 'change_or_cancel')
1478
- return 'inspect current reservation state before changing or cancelling';
1433
+ if (['affirm', 'commit_or_continue', 'tool_action_candidate'].includes(intent))
1434
+ return 'continue according to the agent prompt using conversation_frame, tool_state, tool_history and action_ledger; do not apply domain-specific memory rules';
1479
1435
  if (intent === 'profile_update')
1480
1436
  return 'save stable profile facts and continue the conversation';
1481
1437
  if (intent === 'conversation_recall')
1482
- return 'answer directly using recent_messages.previous_user_message; do not call agenda tools';
1483
- if (intent === 'agenda_status_question')
1484
- return 'answer whether there is a confirmed or pending reservation using operational_state; do not consult availability unless status is unknown';
1438
+ return 'answer directly using previous_user_message and conversation_history_chronological; do not call tools for recall-only questions';
1439
+ if (intent === 'operational_status_question')
1440
+ return 'answer status questions from tool_state, tool_history and action_ledger; do not call tools unless the agent prompt requires it';
1485
1441
  return 'answer using retrieved context and avoid unnecessary tool calls';
1486
1442
  };
1487
1443
  const deriveWorkingMemory = ({ query = '', profileFacts = {}, recentMessages = [], toolHistory = [], operationalState = {}, previous = {} }) => {
@@ -1506,11 +1462,11 @@ const deriveWorkingMemory = ({ query = '', profileFacts = {}, recentMessages = [
1506
1462
  };
1507
1463
  };
1508
1464
  const deriveDecisionState = ({ query = '', toolHistory = [], operationalState = {}, workingMemory = {} }) => {
1509
- const agenda = operationalState.agenda_state || {};
1510
1465
  const decisions = [];
1511
1466
  const doNotRepeatTools = [];
1512
1467
  const intent = workingMemory.last_user_intent || inferUserIntent(query, []);
1513
1468
  const now = nowIso();
1469
+ const toolState = operationalState.tool_state || {};
1514
1470
  const pushDecision = (id, decision, reason, appliesTo, extra = {}) => {
1515
1471
  decisions.push({
1516
1472
  id,
@@ -1523,41 +1479,25 @@ const deriveDecisionState = ({ query = '', toolHistory = [], operationalState =
1523
1479
  created_at: extra.created_at || now,
1524
1480
  valid_until: extra.valid_until || null,
1525
1481
  superseded_by: extra.superseded_by || null,
1482
+ tool: extra.tool,
1526
1483
  });
1527
1484
  };
1528
- if (agenda.has_availability) {
1529
- doNotRepeatTools.push('agenda_consultar_disponibilidade');
1530
- pushDecision('agenda_availability_known', 'do not consult availability again unless the user asks to refresh', 'availability result already exists in tool_history', 'agenda_flow', { confidence: 0.9 });
1531
- }
1532
- if (agenda.has_pending_pre_reservation) {
1533
- doNotRepeatTools.push('agenda_pre_reservar_horario');
1534
- pushDecision('agenda_pre_reservation_pending', 'confirmation should use the latest pre-reservation context', 'a pre-reservation exists after the latest confirmation', 'agenda_flow', { confidence: 0.92 });
1535
- }
1536
- if (agenda.has_confirmation && !agenda.has_pending_pre_reservation) {
1537
- doNotRepeatTools.push('agenda_confirmar_agendamento');
1538
- pushDecision('agenda_confirmation_done', 'do not confirm again unless the user explicitly asks to repeat or change it', 'latest reservation appears confirmed', 'agenda_flow', { confidence: 0.9 });
1539
- }
1540
- const reservationStatus = agenda.has_confirmation && !agenda.has_pending_pre_reservation
1541
- ? 'confirmed'
1542
- : agenda.has_pending_pre_reservation
1543
- ? 'pending_pre_reservation'
1544
- : agenda.has_pre_reservation
1545
- ? 'pre_reserved'
1546
- : agenda.has_availability
1547
- ? 'availability_known'
1548
- : 'none';
1485
+ for (const name of Object.keys(toolState.counts_by_name || {})) {
1486
+ doNotRepeatTools.push(name);
1487
+ }
1488
+ if (toolState.last_successful_tool)
1489
+ pushDecision('last_successful_tool_recorded', `latest successful tool is ${toolState.last_successful_tool.name}`, 'tool_state records successful tool execution', 'tool_orchestration', { confidence: 0.9, tool: toolState.last_successful_tool.name });
1490
+ for (const [name, count] of Object.entries(toolState.failed_by_name || {}))
1491
+ pushDecision(`tool_failed_${name}`, `${name} failed ${count} time(s); inspect tool output before retrying`, 'tool_state records failed tool execution', 'tool_orchestration', { confidence: 0.86, tool: name });
1549
1492
  return {
1550
1493
  active_decisions: decisions,
1551
1494
  do_not_repeat_tools: Array.from(new Set(doNotRepeatTools)),
1552
1495
  current_intent: intent,
1553
- agenda_decision_state: {
1554
- pending_pre_reservation: Boolean(agenda.has_pending_pre_reservation),
1555
- active_reservation: Boolean(agenda.has_pre_reservation || agenda.has_confirmation),
1556
- confirmed_reservation: Boolean(agenda.has_confirmation && !agenda.has_pending_pre_reservation),
1557
- superseded_reservation: Boolean(agenda.has_confirmation && agenda.has_pending_pre_reservation),
1558
- reschedule_requested: intent === 'change_or_cancel' && Boolean(agenda.has_confirmation || agenda.has_pre_reservation),
1559
- cancel_requested: /\b(cancelar|cancele|desmarcar|remover)\b/i.test(String(query || '')),
1560
- reservation_status: reservationStatus,
1496
+ tool_decision_state: {
1497
+ executed_tools: Object.keys(toolState.counts_by_name || {}),
1498
+ failed_tools: Object.keys(toolState.failed_by_name || {}),
1499
+ last_successful_tool: toolState.last_successful_tool || null,
1500
+ do_not_repeat_tools: Array.from(new Set(doNotRepeatTools)),
1561
1501
  evaluated_at: now,
1562
1502
  },
1563
1503
  conflict_policy: 'prefer active decisions with newer timestamps; ignore superseded or expired memories unless auditing',
@@ -1608,7 +1548,12 @@ const deriveMemoryCompression = ({ recentMessages = [], toolHistory = [], profil
1608
1548
  session_summary: {
1609
1549
  messages: lastMessages.length,
1610
1550
  tools: lastTools,
1611
- agenda_state: operationalState.agenda_state || {},
1551
+ tool_state: (operationalState || {}).tool_state ? {
1552
+ names: ((operationalState.tool_state || {}).names || []).slice(0, maxItems),
1553
+ counts_by_name: (operationalState.tool_state || {}).counts_by_name || {},
1554
+ failed_by_name: (operationalState.tool_state || {}).failed_by_name || {},
1555
+ last_successful_tool: (operationalState.tool_state || {}).last_successful_tool || null,
1556
+ } : {},
1612
1557
  },
1613
1558
  entity_summary: profile,
1614
1559
  workflow_summary: {
@@ -1654,7 +1599,6 @@ const deriveContextHealth = ({ userId = '', project = '', vectorMemories = [], r
1654
1599
  score += weights[key] || 0;
1655
1600
  }
1656
1601
  const missing = Object.entries(checks).filter(([, ok]) => !ok).map(([key]) => key);
1657
- const agenda = operationalState.agenda_state || {};
1658
1602
  return {
1659
1603
  kind: 'tembory.context_health.v1',
1660
1604
  namespace: userId || null,
@@ -1669,11 +1613,10 @@ const deriveContextHealth = ({ userId = '', project = '', vectorMemories = [], r
1669
1613
  tool_history: Array.isArray(toolHistory) ? toolHistory.length : 0,
1670
1614
  active_decisions: Array.isArray(decisionState.active_decisions) ? decisionState.active_decisions.length : 0,
1671
1615
  },
1672
- agenda_state: {
1673
- has_availability: Boolean(agenda.has_availability),
1674
- has_pre_reservation: Boolean(agenda.has_pre_reservation),
1675
- has_confirmation: Boolean(agenda.has_confirmation),
1676
- has_pending_pre_reservation: Boolean(agenda.has_pending_pre_reservation),
1616
+ tool_state: {
1617
+ names: (((operationalState || {}).tool_state || {}).names || []).slice(0, 12),
1618
+ last_successful_tool: (((operationalState || {}).tool_state || {}).last_successful_tool) || null,
1619
+ failed_by_name: (((operationalState || {}).tool_state || {}).failed_by_name) || {},
1677
1620
  },
1678
1621
  generated_at: nowIso(),
1679
1622
  };
@@ -1684,13 +1627,13 @@ const contextMemoryText = (memory, max = 700) => {
1684
1627
  if (tools.length) {
1685
1628
  const names = tools.map((tool) => `${tool.name || 'tool'}:${tool.ok === false ? 'failed' : 'ok'}`).join(', ');
1686
1629
  const ids = tools.map((tool) => tool.result || tool.input || '').join(' ');
1687
- const idMatch = /(reservation_id|confirmation_id|id)["':\s]+([A-Za-z0-9_.:-]+)/i.exec(ids);
1630
+ const idMatch = /([A-Za-z_][A-Za-z0-9_.:-]*id)["':\s]+([A-Za-z0-9_.:-]+)/i.exec(ids);
1688
1631
  return truncate(`[tool_events_extracted] ${names}${idMatch ? ` ${idMatch[1]}=${idMatch[2]}` : ''}. See Tool history for structured details.`, max);
1689
1632
  }
1690
1633
  return truncate(text, max);
1691
1634
  };
1692
1635
  const approxTokenCount = (text) => Math.ceil(String(text || '').length / 4);
1693
- const importantJsonFields = ['reservation_id', 'confirmation_id', 'selected_from_message', 'status', 'next_step', 'available_slots', 'timezone', 'error'];
1636
+ const importantJsonFields = ['id', 'status', 'state', 'next_step', 'action', 'intent', 'tool', 'input', 'output', 'result', 'error', 'message', 'reason', 'customer_id', 'user_id', 'lead_id', 'ticket_id', 'charge_id'];
1694
1637
  const pickImportantFields = (value) => {
1695
1638
  if (value === null || value === undefined)
1696
1639
  return {};
@@ -1720,9 +1663,7 @@ const compactToolResult = (result, max = 360) => {
1720
1663
  return undefined;
1721
1664
  try {
1722
1665
  const parsed = JSON.parse(text);
1723
- const picked = pickImportantFields(parsed);
1724
- if (Object.keys(picked).length)
1725
- return truncate(safeStringify(picked), max);
1666
+ return truncate(safeStringify(parsed), max);
1726
1667
  }
1727
1668
  catch { }
1728
1669
  const picked = {};
@@ -1741,8 +1682,9 @@ const compactToolHistoryForAgent = (toolHistory = [], maxItems = 6, includeResul
1741
1682
  name: tool.name,
1742
1683
  status: tool.ok === false ? 'failed' : 'ok',
1743
1684
  at: tool.at,
1744
- input: truncate(String(tool.input || ''), 180) || undefined,
1745
- result: includeResults ? compactToolResult(tool.result, 360) : undefined,
1685
+ reason: truncate(String(tool.reason || tool.decision || tool.why || tool.source || 'recorded tool call'), 140),
1686
+ input: truncate(String(tool.input || ''), 140) || undefined,
1687
+ result: includeResults ? compactToolResult(tool.result, 240) : undefined,
1746
1688
  }));
1747
1689
  const cleanContextValue = (value) => {
1748
1690
  if (Array.isArray(value)) {
@@ -1786,7 +1728,7 @@ const compactDecisionStateForAgent = (state = {}) => cleanContextValue({
1786
1728
  at: decision.at || decision.updated_at,
1787
1729
  })),
1788
1730
  do_not_repeat_tools: state.do_not_repeat_tools,
1789
- agenda_decision_state: state.agenda_decision_state,
1731
+ tool_decision_state: state.tool_decision_state,
1790
1732
  latest_tool: state.latest_tool ? cleanContextValue({
1791
1733
  name: state.latest_tool.name,
1792
1734
  status: state.latest_tool.status || (state.latest_tool.ok === false ? 'failed' : 'ok'),
@@ -1795,19 +1737,21 @@ const compactDecisionStateForAgent = (state = {}) => cleanContextValue({
1795
1737
  conflict_policy: state.conflict_policy,
1796
1738
  });
1797
1739
  const compactOperationalStateForAgent = (state = {}) => {
1798
- const agenda = state.agenda_state || {};
1740
+ const counts = state.tool_counts || {};
1741
+ const toolState = state.tool_state || {};
1799
1742
  return {
1800
1743
  last_tool: state.last_tool || null,
1801
- tool_counts: state.tool_counts || {},
1802
- agenda_state: {
1803
- has_availability: Boolean(agenda.has_availability),
1804
- has_pre_reservation: Boolean(agenda.has_pre_reservation),
1805
- has_confirmation: Boolean(agenda.has_confirmation),
1806
- has_pending_pre_reservation: Boolean(agenda.has_pending_pre_reservation),
1807
- latest_availability: compactToolResult(agenda.latest_availability_result),
1808
- latest_pre_reservation: compactToolResult(agenda.latest_pre_reservation_result),
1809
- latest_confirmation: compactToolResult(agenda.latest_confirmation_result),
1744
+ tool_counts: {
1745
+ total: counts.total || 0,
1746
+ ok: counts.ok || 0,
1747
+ failed: counts.failed || 0,
1810
1748
  },
1749
+ tool_state: (toolState.names || []).length ? {
1750
+ names: toolState.names,
1751
+ counts_by_name: toolState.counts_by_name || {},
1752
+ failed_by_name: toolState.failed_by_name || {},
1753
+ last_successful_tool: toolState.last_successful_tool || undefined,
1754
+ } : undefined,
1811
1755
  blocked_without_context: state.blocked_without_context || [],
1812
1756
  guidance: (state.guidance || []).slice(0, 3),
1813
1757
  };
@@ -1815,12 +1759,6 @@ const compactOperationalStateForAgent = (state = {}) => {
1815
1759
  const compactMemoryCompressionForAgent = (compression = {}) => ({
1816
1760
  turn_summary: (compression.turn_summary || []).slice(-3),
1817
1761
  session_tools: (((compression.session_summary || {}).tools) || []).slice(-6),
1818
- agenda_state: ((compression.session_summary || {}).agenda_state) ? {
1819
- has_availability: Boolean(compression.session_summary.agenda_state.has_availability),
1820
- has_pre_reservation: Boolean(compression.session_summary.agenda_state.has_pre_reservation),
1821
- has_confirmation: Boolean(compression.session_summary.agenda_state.has_confirmation),
1822
- has_pending_pre_reservation: Boolean(compression.session_summary.agenda_state.has_pending_pre_reservation),
1823
- } : undefined,
1824
1762
  active_memory_count: ((compression.workflow_summary || {}).active_memory_count) || 0,
1825
1763
  });
1826
1764
  const compactActionLedgerForAgent = (ledger = [], maxItems = 6, includeResults = true) => pruneByLimit(ledger || [], maxItems).map((item) => cleanContextValue({
@@ -1829,7 +1767,9 @@ const compactActionLedgerForAgent = (ledger = [], maxItems = 6, includeResults =
1829
1767
  tool: item.tool || item.name,
1830
1768
  status: item.status,
1831
1769
  at: item.at,
1832
- result: includeResults ? compactToolResult(item.result, 260) : undefined,
1770
+ reason: item.reason,
1771
+ input: item.input,
1772
+ result: includeResults ? compactToolResult(item.result, 180) : undefined,
1833
1773
  }));
1834
1774
  const compactEntityTimelineForAgent = (timeline = [], maxItems = 8) => pruneByLimit(timeline || [], maxItems).map((item) => cleanContextValue({
1835
1775
  entity: item.entity || item.source || item.name,
@@ -2047,8 +1987,8 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
2047
1987
  section: 'context_header',
2048
1988
  title: 'Tembory context',
2049
1989
  value: compactForAgent || compactStateSections
2050
- ? 'Read-only memory. Conversation frame is authoritative for the full recent transcript, including first/current/previous client messages. 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.'
2051
- : 'Use this context as read-only memory. Prefer it over guessing. Do not mention internal section names to the user. The Conversation frame is authoritative for the full recent transcript, including first, current, previous, and recent client messages. 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.',
1990
+ ? 'Read-only operational memory. Conversation frame is authoritative for user/assistant transcript. Tool history/action ledger are authoritative for tool calls, reasons, inputs, outputs, status and timestamps. The agent prompt owns domain policy and decides which tool to call.'
1991
+ : 'Use this context as read-only operational memory. Do not mention internal section names to the user. Conversation frame is authoritative for the chronological user/assistant transcript. Tool history and action ledger are authoritative for what tools were called, why they were called, what input was sent, what output returned, whether they failed, and when they ran. Memory must not invent domain-specific tool rules; the agent prompt owns tool orchestration policy.',
2052
1992
  });
2053
1993
  }
2054
1994
  sections.push({
@@ -2184,9 +2124,9 @@ const buildContextMessages = ({ payloadFormat, query, userId, profileFacts, work
2184
2124
  value: {
2185
2125
  blocked_tools: guard.blockedTools,
2186
2126
  reasons: guard.reasons,
2187
- instruction: guard.blockedTools.length
2127
+ instruction: guard.instruction || (guard.blockedTools.length
2188
2128
  ? `Do not call these tools now: ${guard.blockedTools.join(', ')}. Ask for the missing context or execute the prerequisite step first.`
2189
- : 'No downstream tool is blocked by missing memory prerequisites.',
2129
+ : 'No downstream tool is blocked by missing memory prerequisites.'),
2190
2130
  },
2191
2131
  });
2192
2132
  sections.push({
@@ -2915,6 +2855,13 @@ class Mem0Memory {
2915
2855
  thread_id: threadId,
2916
2856
  }, ids));
2917
2857
  }
2858
+ clientMemories.push(await createClientVectorMemory(connectedEmbedding, encodeToolLedger(toolHistoryForTurn, threadId), {
2859
+ kind: 'tool_ledger',
2860
+ thread_id: threadId,
2861
+ project: project || undefined,
2862
+ source: 'n8n_connected_embedding',
2863
+ generated_at: nowIso(),
2864
+ }, ids));
2918
2865
  }
2919
2866
  await saveClientVectorMemories(this, clientMemories, ids);
2920
2867
  if (adv.includeRecentMessages !== false && recentForMem0.length) {
@@ -2968,6 +2915,23 @@ class Mem0Memory {
2968
2915
  tool: tool.name,
2969
2916
  });
2970
2917
  }
2918
+ await safePersistLegacyMemory(this, {
2919
+ messages: [{ role: 'system', content: encodeToolLedger(toolHistoryForTurn, threadId) }],
2920
+ infer: false,
2921
+ user_id: body.user_id,
2922
+ agent_id: body.agent_id,
2923
+ run_id: body.run_id,
2924
+ metadata: {
2925
+ kind: 'tool_ledger',
2926
+ thread_id: threadId,
2927
+ project: project || undefined,
2928
+ source: 'tembory_transcript',
2929
+ generated_at: nowIso(),
2930
+ },
2931
+ }, {
2932
+ kind: 'tool_ledger',
2933
+ user_id: body.user_id,
2934
+ });
2971
2935
  }
2972
2936
  return;
2973
2937
  }
@@ -3015,6 +2979,20 @@ class Mem0Memory {
3015
2979
  },
3016
2980
  });
3017
2981
  }
2982
+ await GenericFunctions_1.mem0ApiRequest.call(this, 'POST', '/v1/memories/', {
2983
+ messages: [{ role: 'system', content: encodeToolLedger(toolHistoryForTurn, threadId) }],
2984
+ infer: false,
2985
+ user_id: body.user_id,
2986
+ agent_id: body.agent_id,
2987
+ run_id: body.run_id,
2988
+ metadata: {
2989
+ kind: 'tool_ledger',
2990
+ thread_id: threadId,
2991
+ project: project || undefined,
2992
+ source: 'tembory_transcript',
2993
+ generated_at: nowIso(),
2994
+ },
2995
+ });
3018
2996
  }
3019
2997
  if (adv.persistToolFactsToMem0 && toolCalls.length) {
3020
2998
  const facts = toolCalls.map((tool) => `Tool ${tool.name} input=${tool.input}${tool.result ? ` result=${tool.result}` : ''}`).join('\n');
@@ -3712,8 +3690,6 @@ exports.__private = {
3712
3690
  explicitToolHistoryItemsFromMemory,
3713
3691
  toolHistoryFromMemory,
3714
3692
  cleanAssistantTranscriptText,
3715
- availabilityFromRecentMessages,
3716
- preReservationFromRecentMessages,
3717
3693
  buildActionDirective,
3718
3694
  recentMessageFromMemory,
3719
3695
  previousUserFallbackFromWorkingMemory,
@@ -3724,8 +3700,10 @@ exports.__private = {
3724
3700
  applyToolHistoryWindow,
3725
3701
  dedupeRecentMessages,
3726
3702
  parseToolHistoryMarker,
3703
+ parseToolLedgerMarker,
3727
3704
  parseRecentMessageMarker,
3728
3705
  encodeToolCall,
3706
+ encodeToolLedger,
3729
3707
  encodeRecentMessage,
3730
3708
  userKeyFrom,
3731
3709
  applyOperationalPreset,