{
  "id": "LG8PdQSHjLMnJTrj",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Conversational Bot: Auto-Escalate to Human with Status Tracking & Timeout",
  "tags": [],
  "nodes": [
    {
      "id": "2320f7e5-7de1-4639-bd74-39c2fb0ff7f3",
      "name": "When clicking \u2018Execute workflow\u2019",
      "type": "n8n-nodes-base.manualTrigger",
      "position": [
        -720,
        0
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "5306a7f7-2d93-4eb2-b5dc-664647814f3d",
      "name": "Set \u2014 Input Mapping",
      "type": "n8n-nodes-base.set",
      "position": [
        0,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "492e15b4-05c0-43f2-a0f1-12af3269823c",
              "name": "sessionId",
              "type": "string",
              "value": "5218112345678"
            },
            {
              "id": "8d02acd0-a1fd-4e6c-b57f-ed9241462883",
              "name": "userMessage",
              "type": "string",
              "value": "What are your business hours?"
            },
            {
              "id": "d5972e4e-7998-45c5-ba39-ba6b77980110",
              "name": "channel",
              "type": "string",
              "value": "whatsapp"
            },
            {
              "id": "e18998ae-fc88-47c1-a74b-067b12bd47ae",
              "name": "messageId",
              "type": "string",
              "value": "wamid.HBgLNTIxODExMjM0NTY3OBUCABIYFkE3RkVERTBE"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "09681468-2a47-40b1-b7ed-b4ef89315a57",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -144,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 384,
        "height": 432,
        "content": "## Entry \u2014 Mock Data for Testing\nClick \"Test workflow\" to run with sample data.\n\nIn production, replace this node's values with\nexpressions from your webhook payload:\n  sessionId   \u2192 data.key.remoteJid (Evolution API)\n  userMessage \u2192 data.message.conversation\n  channel     \u2192 \"whatsapp\"\n  messageId   \u2192 data.key.id  (used for deduplication)"
      },
      "typeVersion": 1
    },
    {
      "id": "faf16c28-a7e7-4851-af48-7fd34ffe0b90",
      "name": "Sheets \u2014 Lookup Processed Message",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        304,
        0
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $('Set \u2014 Input Mapping').item.json.messageId }}",
              "lookupColumn": "=messageId"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1NNf1MZ18ez-Vi_fVxU_adktUIVsKuff1CoAo5bQkC08/edit#gid=0",
          "cachedResultName": "processed_messages"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/1NNf1MZ18ez-Vi_fVxU_adktUIVsKuff1CoAo5bQkC08/edit?usp=sharing"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "a12f948d-640c-45f7-be9e-dd1550b898bb",
      "name": "If \u2014 Already Processed?",
      "type": "n8n-nodes-base.if",
      "onError": "continueRegularOutput",
      "position": [
        736,
        0
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "06eb552b-4110-4a23-bb45-828981731b8d",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.isDuplicate }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "5766eaa2-5872-4e54-be6c-c40e55943d34",
      "name": "NoOp \u2014 Duplicate Ignored",
      "type": "n8n-nodes-base.noOp",
      "position": [
        928,
        -336
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "10ab1c5c-b653-4284-a0d4-09ed0d257128",
      "name": "Set \u2014 Normalize Lookup Result",
      "type": "n8n-nodes-base.set",
      "position": [
        512,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "abeb32d0-158f-4b04-9177-25b6960807d4",
              "name": "sessionId",
              "type": "string",
              "value": "={{ $('Set \u2014 Input Mapping').item.json.sessionId }}"
            },
            {
              "id": "3029b38c-69e9-425e-912b-e1aca543cd8c",
              "name": "userMessage",
              "type": "string",
              "value": "={{ $('Set \u2014 Input Mapping').item.json.userMessage }}"
            },
            {
              "id": "b5adf2da-0425-4c66-9c98-0eac8ad8c2f1",
              "name": "channel",
              "type": "string",
              "value": "={{ $('Set \u2014 Input Mapping').item.json.channel }}"
            },
            {
              "id": "e2aa0fe2-0b97-4067-bb25-0f8df870fc11",
              "name": "messageId",
              "type": "string",
              "value": "={{ $('Set \u2014 Input Mapping').item.json.messageId }}"
            },
            {
              "id": "459eadef-5f35-4a0e-bc13-ec823835aeb3",
              "name": "isDuplicate",
              "type": "boolean",
              "value": "={{ $json.messageId !== undefined && $json.messageId !== \"\" }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "80522287-4785-45bd-97b4-eb551a47fddf",
      "name": "Sheets \u2014 Lookup Escalation Status",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        1216,
        96
      ],
      "parameters": {
        "options": {},
        "filtersUI": {
          "values": [
            {
              "lookupValue": "={{ $('Set \u2014 Normalize Lookup Result').item.json.sessionId }}",
              "lookupColumn": "sessionId"
            }
          ]
        },
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 370189781,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1NNf1MZ18ez-Vi_fVxU_adktUIVsKuff1CoAo5bQkC08/edit#gid=370189781",
          "cachedResultName": "escalations"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/1NNf1MZ18ez-Vi_fVxU_adktUIVsKuff1CoAo5bQkC08/edit?usp=sharing"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7,
      "alwaysOutputData": true
    },
    {
      "id": "8a9acaed-022d-471b-81af-d737098f663a",
      "name": "Set \u2014 Normalize Escalation Status",
      "type": "n8n-nodes-base.set",
      "position": [
        1440,
        96
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "d8ccd29b-fe84-4069-8dd7-39730cbbd4f0",
              "name": "sessionId",
              "type": "string",
              "value": "={{ $('Set \u2014 Normalize Lookup Result').item.json.sessionId }}"
            },
            {
              "id": "968351ab-5596-47fc-b3d2-d15b4662a73b",
              "name": "userMessage",
              "type": "string",
              "value": "={{ $('Set \u2014 Normalize Lookup Result').item.json.userMessage }}"
            },
            {
              "id": "edc578ea-3fe9-427a-a906-57c9a4b72592",
              "name": "channel",
              "type": "string",
              "value": "={{ $('Set \u2014 Normalize Lookup Result').item.json.channel }}"
            },
            {
              "id": "6b0851db-c1fc-4546-8f3e-115213cbbbe4",
              "name": "messageId",
              "type": "string",
              "value": "={{ $('Set \u2014 Normalize Lookup Result').item.json.messageId }}"
            },
            {
              "id": "bc416f4f-33de-4b5c-8bd4-4b877628c0c7",
              "name": "status",
              "type": "string",
              "value": "={{ $json.status ?? \"RESOLVED\" }}"
            },
            {
              "id": "bbc25b50-c40e-4cde-adb5-fdd007659e5e",
              "name": "escalatedAt",
              "type": "string",
              "value": "={{ $json.escalatedAt ?? \"\" }}"
            },
            {
              "id": "99f71d9d-da28-4f5f-ac43-e15aa68a6f93",
              "name": "isMuted",
              "type": "boolean",
              "value": "={{ $json.status === \"PENDING\" }}"
            },
            {
              "id": "183ee4cd-8907-4c43-b143-b859c215ca0f",
              "name": "timeoutExpired",
              "type": "boolean",
              "value": "={{ $json.escalatedAt ? ((Date.now() - new Date($json.escalatedAt).getTime()) / 60000) > $('Set \u2014 Workflow Config').item.json.escalationTimeoutMinutes : false }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "0ac4a977-ec43-4079-a30c-0c3d7bfc30d9",
      "name": "If \u2014 Is Bot Muted?",
      "type": "n8n-nodes-base.if",
      "position": [
        1648,
        96
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "62979393-95af-4880-a325-23b016db0958",
              "operator": {
                "type": "boolean",
                "operation": "true",
                "singleValue": true
              },
              "leftValue": "={{ $json.isMuted && !$json.timeoutExpired }}",
              "rightValue": ""
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "ff01b6f4-38b4-4e34-9461-c72a922a5e57",
      "name": "NoOp \u2014 Human is Handling",
      "type": "n8n-nodes-base.noOp",
      "position": [
        1856,
        -96
      ],
      "parameters": {},
      "typeVersion": 1
    },
    {
      "id": "52493fdb-e648-45ea-a833-e72bda6968cd",
      "name": "Set \u2014 Workflow Config",
      "type": "n8n-nodes-base.set",
      "position": [
        -400,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "39e3dcd3-52fa-4476-91dc-ef8c6e47e085",
              "name": "escalationTimeoutMinutes",
              "type": "number",
              "value": 30
            },
            {
              "id": "0e0a3e72-b4bd-480b-9263-ef6d22464977",
              "name": "notifyChannel",
              "type": "string",
              "value": "slack"
            },
            {
              "id": "64ae1904-f88b-4c03-b9e8-32651000c741",
              "name": "holdingMessage",
              "type": "string",
              "value": "I'm connecting you with a human agent. Please wait a moment. \ud83d\ude4f"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "7227bdaa-cf1c-4f3f-ba80-91f753b0830d",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -576,
        -240
      ],
      "parameters": {
        "color": 7,
        "width": 432,
        "height": 432,
        "content": "## \u2699\ufe0f Workflow Config\nCentral place to customize this template:\n\n- escalationTimeoutMinutes: how long the bot stays\n  muted before auto-reactivating (default: 30)\n- notifyChannel: label only \u2014 used in \"Set \u2014 Notify\n  Human\" for your reference. Replace that node with\n  your actual Slack/Email/WhatsApp node; this field\n  doesn't trigger any routing by itself.\n- holdingMessage: what the user sees while waiting\n  for a human agent"
      },
      "typeVersion": 1
    },
    {
      "id": "4e393068-66ec-403d-b505-e04a6fd35358",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1808,
        -464
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 544,
        "content": "## Human is Handling\nBot stays silent \u2014 a human agent has taken over\nthis conversation (status = PENDING in \"escalations\").\n\n\u26a0\ufe0f Production considerations (not implemented in this template):\n\n1. Conversation log \u2014 if the user sends multiple messages\n   while waiting, each one lands here and is currently\n   dropped. Consider appending to a \"conversation_log\"\n   sheet so the human agent sees full context.\n\n2. User acknowledgment \u2014 the user gets no confirmation\n   that follow-up messages were received. Decide whether\n   to send a one-time \"still waiting\" ack vs. staying\n   fully silent.\n\n3. Pending message visibility \u2014 the human reviewing the\n   \"escalations\" sheet has no signal that NEW messages\n   arrived after the initial escalation. Consider a\n   \"pendingMessageCount\" or \"lastMessageAt\" column."
      },
      "typeVersion": 1
    },
    {
      "id": "631581e4-044e-4eee-b84c-119a39620b30",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        1840,
        608
      ],
      "parameters": {
        "text": "={{ $('Set \u2014 Normalize Escalation Status').item.json.userMessage }}",
        "options": {
          "systemMessage": "You are a customer service assistant for a business.\nYou do NOT have access to this business's specific\ninformation (hours, pricing, locations, policies,\norder status, inventory, etc.) unless explicitly\nprovided to you in this conversation.\n\nRules:\n- If the user asks about business-specific information\n  you don't have (hours, prices, availability, policies,\n  order/account details), respond with EXACTLY:\nESCALATE_TO_HUMAN\n\n- If the user asks a general question you CAN answer\n  with general knowledge (e.g. \"what is a refund\",\n  \"how does shipping usually work\"), answer normally.\n\n- Never ask the user for more details about the business\n  itself \u2014 you cannot look anything up. If you don't have\n  the information, escalate immediately.\n\n- Respond with ESCALATE_TO_HUMAN alone, with no other\n  text, when escalating."
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "ab62726f-35fe-41a2-abf1-d1ba36104eab",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "position": [
        1840,
        720
      ],
      "parameters": {
        "model": {
          "__rl": true,
          "mode": "list",
          "value": "gpt-4o-mini",
          "cachedResultName": "gpt-4o-mini"
        },
        "options": {},
        "builtInTools": {}
      },
      "credentials": {
        "openAiApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "6bafd22e-8690-4007-bc91-268e91f66e58",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1808,
        128
      ],
      "parameters": {
        "color": 7,
        "width": 512,
        "height": 752,
        "content": "## AI Agent \u2014 Intent Analysis\nThis template uses a binary signal (ESCALATE_TO_HUMAN)\nfor simplicity. Production considerations:\n\n1. RAG context \u2014 connect a vector store (Pinecone,\n   Upstash Vector, etc.) with your FAQ/docs so the AI\n   answers more correctly and escalates less often.\n\n2. Confidence scoring \u2014 instead of binary escalate/don't,\n   have the AI return a confidence score (0-100). Below\n   a threshold \u2192 escalate. This avoids hard-coded trigger\n   phrases entirely and is more robust against prompt\n   injection or hallucinated trigger text.\n\n3. Escalation reason \u2014 have the AI also output WHY it's\n   escalating (e.g. \"billing dispute\", \"out of scope\",\n   \"user frustrated\"). Store this in the \"escalations\"\n   sheet so the human agent has context before they even\n   open the chat.\n\n4. Sentiment/urgency detection \u2014 a frustrated user\n   (\"this is the third time I'm asking!\") should escalate\n   even if the AI technically \"knows\" the answer, since\n   poor experience compounds. Consider a separate\n   sentiment check alongside the knowledge check."
      },
      "typeVersion": 1
    },
    {
      "id": "1ce1822c-370c-4b47-ab84-7c31ffe63f45",
      "name": "Simple Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryBufferWindow",
      "position": [
        1936,
        736
      ],
      "parameters": {},
      "typeVersion": 1.3
    },
    {
      "id": "9e165917-579e-4ac8-842c-0cf8cfa464de",
      "name": "Set \u2014 Extract AI Response",
      "type": "n8n-nodes-base.set",
      "position": [
        2400,
        608
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "349ad1e1-d98a-4f75-9146-90ff843b05bd",
              "name": "aiResponse",
              "type": "string",
              "value": "={{ $json.output }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "ddb5d63c-4ccd-49dc-9c3c-67da94b6e1c8",
      "name": "If \u2014 Needs Escalation?",
      "type": "n8n-nodes-base.if",
      "position": [
        2608,
        608
      ],
      "parameters": {
        "options": {},
        "conditions": {
          "options": {
            "version": 3,
            "leftValue": "",
            "caseSensitive": true,
            "typeValidation": "strict"
          },
          "combinator": "and",
          "conditions": [
            {
              "id": "7eba3234-4f56-4838-b1e3-d5e0a7eba246",
              "operator": {
                "type": "string",
                "operation": "contains"
              },
              "leftValue": "={{ $('Set \u2014 Extract AI Response').item.json.aiResponse.trim().toUpperCase() }}",
              "rightValue": "ESCALATE_TO_HUMAN"
            }
          ]
        }
      },
      "typeVersion": 2.3
    },
    {
      "id": "8dd5d821-3584-458d-b210-8138ddd23a7c",
      "name": "Set \u2014 Normal Response",
      "type": "n8n-nodes-base.set",
      "position": [
        2896,
        864
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "2d71571b-6d60-4ea4-bc73-d8645aec906f",
              "name": "response",
              "type": "string",
              "value": "={{ $('Set \u2014 Extract AI Response').item.json.aiResponse }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "3b00cda8-a822-4a2e-a009-0efda7f15d71",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2784,
        688
      ],
      "parameters": {
        "color": 7,
        "width": 336,
        "height": 336,
        "content": "## Normal Response\nAI answered directly \u2014 no escalation needed.\n\nIn production: connect this to your messaging\nnode (Evolution API / Twilio / WhatsApp Cloud)\nto send `response` back to the user."
      },
      "typeVersion": 1
    },
    {
      "id": "e6f29559-cb88-4d0f-96cf-512a82645aa0",
      "name": "Sheets \u2014 Upsert Escalation",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        2832,
        320
      ],
      "parameters": {
        "columns": {
          "value": {
            "status": "PENDING",
            "channel": "={{ $('Set \u2014 Normalize Escalation Status').item.json.channel }}",
            "sessionId": "={{ $('Set \u2014 Normalize Escalation Status').item.json.sessionId }}",
            "resolvedAt": "",
            "escalatedAt": "={{ $now.toISO() }}",
            "userMessage": "={{ $('Set \u2014 Normalize Escalation Status').item.json.userMessage }}"
          },
          "schema": [
            {
              "id": "sessionId",
              "type": "string",
              "display": true,
              "removed": false,
              "required": false,
              "displayName": "sessionId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "userMessage",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "userMessage",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "channel",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "channel",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "status",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "status",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "escalatedAt",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "escalatedAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "resolvedAt",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "resolvedAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [
            "sessionId"
          ],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "appendOrUpdate",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": 370189781,
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1NNf1MZ18ez-Vi_fVxU_adktUIVsKuff1CoAo5bQkC08/edit#gid=370189781",
          "cachedResultName": "escalations"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/1NNf1MZ18ez-Vi_fVxU_adktUIVsKuff1CoAo5bQkC08/edit?usp=sharing"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "a9095370-280b-4cd5-af52-08b631d5fe4f",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        2704,
        160
      ],
      "parameters": {
        "color": 7,
        "width": 400,
        "height": 336,
        "content": "## Escalation Log \u2014 Upsert\n\"Append or Update\" by sessionId handles both:\n- First-time escalation \u2192 new row\n- Re-escalation after timeout \u2192 updates existing row\n  (clears resolvedAt, refreshes escalatedAt)"
      },
      "typeVersion": 1
    },
    {
      "id": "8685a0ec-97ea-457e-9ece-430d7c99cd24",
      "name": "Set \u2014 Escalation Message",
      "type": "n8n-nodes-base.set",
      "position": [
        3136,
        320
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "ae72f3c9-8206-467c-872a-702298b0a842",
              "name": "response",
              "type": "string",
              "value": "={{ $('Set \u2014 Workflow Config').item.json.holdingMessage }}"
            }
          ]
        },
        "includeOtherFields": true
      },
      "typeVersion": 3.4
    },
    {
      "id": "32e6a07e-62c4-4b0c-a31b-72fc0468baae",
      "name": "Set \u2014 Notify Human",
      "type": "n8n-nodes-base.set",
      "position": [
        3344,
        320
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "fb4259c5-49f3-4931-944e-5c3d894d09e6",
              "name": "notifyChannel",
              "type": "string",
              "value": "={{ $('Set \u2014 Workflow Config').item.json.notifyChannel }}"
            },
            {
              "id": "5b5e2ede-2d34-4418-bf74-7305fab4d8ce",
              "name": "notifyMessage",
              "type": "string",
              "value": "=\ud83d\udea8 Escalation \n\u2014 Session: {{ $('Set \u2014 Normalize Escalation Status').item.json.sessionId }} ({{ $('Set \u2014 Normalize Escalation Status').item.json.channel }}) \n\u2014 Message: {{ $('Set \u2014 Normalize Escalation Status').item.json.userMessage }}"
            },
            {
              "id": "b4ca4311-cc5d-400d-9a2b-805429614e26",
              "name": "notifyTimestamp",
              "type": "string",
              "value": "={{ $now.toISO() }}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "18892345-42b5-4e80-a61f-489f10bb4332",
      "name": "Sheets \u2014 Append Processed Message",
      "type": "n8n-nodes-base.googleSheets",
      "position": [
        976,
        96
      ],
      "parameters": {
        "columns": {
          "value": {
            "messageId": "={{ $('Set \u2014 Normalize Lookup Result').item.json.messageId }}",
            "processedAt": "={{ $now.toISO() }}"
          },
          "schema": [
            {
              "id": "messageId",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "messageId",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            },
            {
              "id": "processedAt",
              "type": "string",
              "display": true,
              "required": false,
              "displayName": "processedAt",
              "defaultMatch": false,
              "canBeUsedToMatch": true
            }
          ],
          "mappingMode": "defineBelow",
          "matchingColumns": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        },
        "options": {},
        "operation": "append",
        "sheetName": {
          "__rl": true,
          "mode": "list",
          "value": "gid=0",
          "cachedResultUrl": "https://docs.google.com/spreadsheets/d/1NNf1MZ18ez-Vi_fVxU_adktUIVsKuff1CoAo5bQkC08/edit#gid=0",
          "cachedResultName": "processed_messages"
        },
        "documentId": {
          "__rl": true,
          "mode": "url",
          "value": "https://docs.google.com/spreadsheets/d/1NNf1MZ18ez-Vi_fVxU_adktUIVsKuff1CoAo5bQkC08/edit?usp=sharing"
        }
      },
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 4.7
    },
    {
      "id": "c889cd65-0d10-47da-965e-edaecbe74b7a",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        912,
        -144
      ],
      "parameters": {
        "color": 7,
        "width": 256,
        "height": 432,
        "content": "## Deduplication Log\nMarks this messageId as processed, so Evolution's\nretries (same messageId) get caught by\n\"If \u2014 Already Processed?\" on re-delivery.\n\nThis is NOT a conversation log \u2014 it only tracks\nmessage IDs for idempotency."
      },
      "typeVersion": 1
    },
    {
      "id": "52e77eb6-4222-4292-9bbb-37d1da009c38",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -1296,
        -336
      ],
      "parameters": {
        "width": 576,
        "height": 784,
        "content": "## \ud83d\udea8 Smart Auto-Escalation to Human Agent\n\nAn AI agent answers customer questions and automatically\nhands off to a human when it can't help \u2014 with full\nstate tracking so the bot stays silent while a human\nis handling the conversation, and reactivates on its own\nafter a configurable timeout.\n\n### How it works\n1. Incoming message \u2192 deduplication check (Evolution-safe)\n2. Lookup escalation status in Google Sheets\n3. If a human is already handling (status=PENDING,\n   timeout not expired) \u2192 bot stays silent\n4. Otherwise \u2192 AI Agent analyzes the message\n   - Knows the answer \u2192 responds directly\n   - Doesn't know / business-specific \u2192 ESCALATE_TO_HUMAN\n5. On escalation \u2192 logs to Sheets, sends holding message\n   to user, notifies human agent\n\n### How to use\n1. Add OpenAI credentials to \"OpenAI Chat Model\"\n2. Connect Google Sheets credentials (2 sheets needed:\n   \"escalations\" and \"processed_messages\" \u2014 see sticky\n   notes near each Sheets node for column structure)\n3. Replace \"Trigger \u2014 Manual\" with your webhook\n   (Evolution API, Twilio, WhatsApp Cloud)\n4. Replace \"Set \u2014 Notify Human\" with your actual\n   notification node (Slack, Email, etc.)\n5. Adjust \"Set \u2014 Workflow Config\" for your timeout\n   and messaging preferences\n\n### Production notes\nThis template uses Google Sheets to demonstrate the\nstate-tracking pattern. For high-volume production,\nswap Sheets for Redis or PostgreSQL \u2014 the logic\n(lookup by sessionId, check status + timeout) stays\nthe same.\n\n**Author:** Luis Rend\u00f3n"
      },
      "typeVersion": 1
    }
  ],
  "active": false,
  "settings": {
    "binaryMode": "separate",
    "executionOrder": "v1"
  },
  "versionId": "344d6c8f-2fcc-4fc5-8c52-e2bebf615eaa",
  "connections": {
    "AI Agent": {
      "main": [
        [
          {
            "node": "Set \u2014 Extract AI Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Simple Memory": {
      "ai_memory": [
        [
          {
            "node": "AI Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "If \u2014 Is Bot Muted?": {
      "main": [
        [
          {
            "node": "NoOp \u2014 Human is Handling",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set \u2014 Input Mapping": {
      "main": [
        [
          {
            "node": "Sheets \u2014 Lookup Processed Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set \u2014 Workflow Config": {
      "main": [
        [
          {
            "node": "Set \u2014 Input Mapping",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If \u2014 Needs Escalation?": {
      "main": [
        [
          {
            "node": "Sheets \u2014 Upsert Escalation",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Set \u2014 Normal Response",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "If \u2014 Already Processed?": {
      "main": [
        [
          {
            "node": "NoOp \u2014 Duplicate Ignored",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Sheets \u2014 Append Processed Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set \u2014 Escalation Message": {
      "main": [
        [
          {
            "node": "Set \u2014 Notify Human",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set \u2014 Extract AI Response": {
      "main": [
        [
          {
            "node": "If \u2014 Needs Escalation?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets \u2014 Upsert Escalation": {
      "main": [
        [
          {
            "node": "Set \u2014 Escalation Message",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set \u2014 Normalize Lookup Result": {
      "main": [
        [
          {
            "node": "If \u2014 Already Processed?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Set \u2014 Normalize Escalation Status": {
      "main": [
        [
          {
            "node": "If \u2014 Is Bot Muted?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets \u2014 Append Processed Message": {
      "main": [
        [
          {
            "node": "Sheets \u2014 Lookup Escalation Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets \u2014 Lookup Escalation Status": {
      "main": [
        [
          {
            "node": "Set \u2014 Normalize Escalation Status",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Sheets \u2014 Lookup Processed Message": {
      "main": [
        [
          {
            "node": "Set \u2014 Normalize Lookup Result",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "When clicking \u2018Execute workflow\u2019": {
      "main": [
        [
          {
            "node": "Set \u2014 Workflow Config",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  }
}