{
  "name": "Nod Agent with Approval Flow",
  "nodes": [
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "nod-webhook",
        "responseMode": "onReceived",
        "options": {}
      },
      "id": "webhook-receive",
      "name": "Nod Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        60,
        500
      ],
      "webhookId": ""
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-user-msg",
              "leftValue": "={{ $('Nod Webhook').item.json.body.type }}",
              "rightValue": "user_message",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "route-event",
      "name": "Is User Message?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        440,
        200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"processing_start\",\n  \"conversation_id\": \"{{ $('Nod Webhook').item.json.body.conversation_id }}\",\n  \"status_text\": \"Processing your message...\"\n}"
      },
      "id": "log-activity-thinking",
      "name": "Processing: Start",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        680,
        80
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"alert\",\n  \"title\": \"Received message\",\n  \"text\": \"Processing: {{ $('Nod Webhook').item.json.body.text }}\"\n}"
      },
      "id": "log-activity-received",
      "name": "Log Activity: Received",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        680,
        260
      ]
    },
    {
      "parameters": {
        "method": "GET",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/conversations/{{ $('Nod Webhook').item.json.body.conversation_id }}/messages?limit=20",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "options": {}
      },
      "id": "fetch-history",
      "name": "Fetch Conversation History",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        940,
        260
      ]
    },
    {
      "parameters": {
        "mode": "runOnceForAllItems",
        "jsCode": "const items = $input.all();\nconst messages = items.map(item => item.json);\nconst history = messages.reverse().map(m => '[' + (m.sender || 'unknown') + '] ' + (m.sender_name || 'Unknown') + ': ' + (m.text || '')).join('\\n');\nconst today = new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' });\nreturn [{json: {history: history || '(no prior messages)', today: today}}];"
      },
      "id": "build-context",
      "name": "Build Context",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1200,
        260
      ]
    },
    {
      "parameters": {
        "model": "gpt-4o",
        "options": {
          "temperature": 0.7
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "=You are a helpful AI agent connected to Nod. Be concise \u2014 the user reads on a phone.\n\nToday's date is: {{ $('Build Context').item.json.today }}\n\nYou will receive conversation history followed by the latest user message. Use the history to understand context \u2014 avoid repeating information you already provided, follow up naturally on prior topics, and reference earlier messages when relevant. Respond ONLY to the latest message, but informed by the full conversation.\n\nIMPORTANT RULE FOR WEB SEARCH:\nIf you need to search the web for up-to-date information (news, prices, weather, recent events, facts you're unsure about), you MUST respond with ONLY this exact format:\nSEARCH: <your search query>\nREASON: <why you need this search and what you're trying to find out for the user>\n\nDo NOT add any other text when requesting a search. Just the SEARCH and REASON lines.\nBe specific in your search query \u2014 include today's date or the current month/year when searching for recent or current information. Never guess the date.\nIf you can answer from your own knowledge, answer normally."
            },
            {
              "role": "user",
              "content": "=Conversation history (oldest first):\n{{ $('Build Context').item.json.history }}\n\nLatest message from user: {{ $('Nod Webhook').item.json.body.text }}"
            }
          ]
        }
      },
      "id": "ai-agent",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        1460,
        260
      ],
      "credentials": {
        "openAiApi": {
          "id": "",
          "name": "OpenAI API"
        }
      }
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-search",
              "leftValue": "={{ $json.message.content }}",
              "rightValue": "SEARCH:",
              "operator": {
                "type": "string",
                "operation": "startsWith"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "check-search",
      "name": "Needs Web Search?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        1720,
        260
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"decision\",\n  \"title\": \"Web Search Approval\",\n  \"description\": {{ JSON.stringify(($('AI Agent').item.json.message.content || '').replace('SEARCH:', '').replace('REASON:', '\\nReason:').trim()) }},\n  \"kind\": \"approval\",\n  \"conversation_id\": \"{{ $('Nod Webhook').item.json.body.conversation_id }}\"\n}"
      },
      "id": "send-approval-request",
      "name": "Request Search Approval",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1980,
        80
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"alert\",\n  \"title\": \"Awaiting approval\",\n  \"text\": \"Waiting for user to approve web search\"\n}"
      },
      "id": "log-activity-waiting",
      "name": "Log Activity: Awaiting Approval",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2240,
        80
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"message\",\n  \"text\": {{ JSON.stringify($('AI Agent').item.json.message.content || 'Sorry, I could not generate a response.') }},\n  \"conversation_id\": \"{{ $('Nod Webhook').item.json.body.conversation_id }}\"\n}"
      },
      "id": "send-reply-direct",
      "name": "Send Reply (no search)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1980,
        260
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"processing_stop\",\n  \"conversation_id\": \"{{ $('Nod Webhook').item.json.body.conversation_id }}\"\n}"
      },
      "id": "log-activity-done-direct",
      "name": "Processing: Stop (direct)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2240,
        260
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-approved",
              "leftValue": "={{ $('Nod Webhook').item.json.body.status }}",
              "rightValue": "approved",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "check-approval",
      "name": "Was Approved?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        680,
        540
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"alert\",\n  \"title\": \"Search approved\",\n  \"text\": \"User approved web search. Executing...\"\n}"
      },
      "id": "log-activity-approved",
      "name": "Log Activity: Approved",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        940,
        440
      ]
    },
    {
      "parameters": {
        "options": {
          "systemMessage": "=You are a research agent. The user approved a web search request. You have a web_search tool that searches Google via Serper API.\n\nToday's date is: {{ new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }) }}\n\nYour task:\n1. Understand what the user needs based on the context provided\n2. Use the web_search tool with well-crafted queries \u2014 always include the current date, month, or year when searching for recent or current information. Never guess the date.\n3. You can search multiple times \u2014 if the first results aren't relevant, refine your query and search again\n4. Analyze the results critically before responding\n5. Once you have enough information, provide a concise, accurate answer\n\nBe thorough but concise \u2014 the user reads on a phone. Include relevant links when useful."
        },
        "promptType": "define",
        "text": "=Research request: {{ $('Nod Webhook').item.json.body.description || 'research request' }}\n\nThe user approved this web search. Research the topic thoroughly and provide a helpful, concise answer."
      },
      "id": "research-agent",
      "name": "Research Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 1.7,
      "position": [
        1200,
        440
      ]
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-4o",
          "mode": "list",
          "cachedResultName": "GPT-4O"
        },
        "options": {}
      },
      "id": "research-agent-model",
      "name": "OpenAI Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        1120,
        660
      ],
      "credentials": {
        "openAiApi": {
          "id": "",
          "name": "OpenAI API"
        }
      }
    },
    {
      "parameters": {
        "name": "web_search",
        "description": "Search Google for current information. Returns titles, snippets, and links from Google results. Use specific, well-crafted queries for best results.",
        "jsCode": "const response = await this.helpers.httpRequest({\n  method: 'POST',\n  url: 'https://google.serper.dev/search',\n  headers: {\n    'X-API-KEY': $('Credentials').item.json.serperApiKey,\n    'Content-Type': 'application/json'\n  },\n  body: JSON.stringify({ q: query }),\n  json: true\n});\nconst results = (response.organic || []).slice(0, 8).map(r => r.title + '\\n' + r.snippet + '\\n' + r.link).join('\\n\\n');\nreturn results || 'No results found.';"
      },
      "id": "serper-tool",
      "name": "Web Search Tool",
      "type": "@n8n/n8n-nodes-langchain.toolCode",
      "typeVersion": 1.1,
      "position": [
        1300,
        660
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"message\",\n  \"text\": {{ JSON.stringify($('Research Agent').item.json.output || 'Sorry, I could not complete the research.') }},\n  \"conversation_id\": \"{{ $('Nod Webhook').item.json.body.conversation_id }}\"\n}"
      },
      "id": "send-reply-with-search",
      "name": "Send Reply (with search)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1460,
        440
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"processing_stop\",\n  \"conversation_id\": \"{{ $('Nod Webhook').item.json.body.conversation_id }}\"\n}"
      },
      "id": "log-activity-done-search",
      "name": "Processing: Stop (search)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1720,
        440
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"alert\",\n  \"title\": \"Search declined\",\n  \"text\": \"User rejected web search. Responding without search.\"\n}"
      },
      "id": "log-activity-declined",
      "name": "Log Activity: Declined",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        940,
        780
      ]
    },
    {
      "parameters": {
        "model": "gpt-4o",
        "options": {
          "temperature": 0.7
        },
        "messages": {
          "values": [
            {
              "role": "system",
              "content": "You are a helpful AI agent connected to Nod. The user declined your web search request, so answer using only your existing knowledge. Be concise \u2014 the user is on a phone."
            },
            {
              "role": "user",
              "content": "={{ $('Nod Webhook').item.json.body.text || 'the original question' }}"
            }
          ]
        }
      },
      "id": "ai-agent-no-search",
      "name": "AI Fallback (no search)",
      "type": "@n8n/n8n-nodes-langchain.openAi",
      "typeVersion": 1.8,
      "position": [
        1200,
        780
      ],
      "credentials": {
        "openAiApi": {
          "id": "",
          "name": "OpenAI API"
        }
      }
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"message\",\n  \"text\": {{ JSON.stringify($('AI Fallback (no search)').item.json.message.content || 'Sorry, I could not generate a response.') }},\n  \"conversation_id\": \"{{ $('Nod Webhook').item.json.body.conversation_id }}\"\n}"
      },
      "id": "send-reply-no-search",
      "name": "Send Reply (declined)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1460,
        780
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"processing_stop\",\n  \"conversation_id\": \"{{ $('Nod Webhook').item.json.body.conversation_id }}\"\n}"
      },
      "id": "log-activity-done-declined",
      "name": "Processing: Stop (declined)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1720,
        780
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-decision",
              "leftValue": "={{ $('Nod Webhook').item.json.body.type }}",
              "rightValue": "decision_resolved",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "route-decision",
      "name": "Is Decision Resolved?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        440,
        540
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"processing_stop\",\n  \"conversation_id\": \"{{ $('Nod Webhook').item.json.body.conversation_id }}\"\n}"
      },
      "id": "log-activity-done-awaiting",
      "name": "Processing: Stop (awaiting)",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2500,
        80
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": true,
            "leftValue": "",
            "typeValidation": "strict"
          },
          "conditions": [
            {
              "id": "cond-task-due",
              "leftValue": "={{ $('Nod Webhook').item.json.body.type }}",
              "rightValue": "task_due",
              "operator": {
                "type": "string",
                "operation": "equals"
              }
            }
          ],
          "combinator": "and"
        },
        "options": {}
      },
      "id": "route-task",
      "name": "Is Task Due?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2.2,
      "position": [
        440,
        1000
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"processing_start\",\n  \"status_text\": \"Running task: {{ $('Nod Webhook').item.json.body.task_name }}\"\n}"
      },
      "id": "task-processing-start",
      "name": "Task: Processing Start",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        680,
        1000
      ]
    },
    {
      "parameters": {
        "options": {
          "systemMessage": "=You are a task execution agent connected to Nod. You have a web_search tool \u2014 use it freely without asking for permission.\n\nToday's date is: {{ new Date().toLocaleDateString('en-US', { weekday: 'long', year: 'numeric', month: 'long', day: 'numeric' }) }}\n\nYou are executing a scheduled task. Follow the task prompt carefully, research as needed using web_search, and produce a clear, well-structured result.\n\nFORMAT YOUR OUTPUT AS MARKDOWN:\n- Use **bold** for key facts and highlights\n- Use headers (##) to organize sections\n- Use bullet lists for multiple items\n- Include relevant links when available\n- Keep it concise \u2014 the user reads on a phone"
        },
        "promptType": "define",
        "text": "=Task: {{ $('Nod Webhook').item.json.body.task_name }}\n\nInstructions: {{ $('Nod Webhook').item.json.body.task_prompt }}"
      },
      "id": "task-agent",
      "name": "Task Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 1.7,
      "position": [
        940,
        1000
      ]
    },
    {
      "parameters": {
        "modelId": {
          "__rl": true,
          "value": "gpt-4o",
          "mode": "list",
          "cachedResultName": "GPT-4O"
        },
        "options": {}
      },
      "id": "task-agent-model",
      "name": "Task Agent Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        860,
        1200
      ],
      "credentials": {
        "openAiApi": {
          "id": "",
          "name": "OpenAI API"
        }
      }
    },
    {
      "parameters": {
        "name": "web_search",
        "description": "Search Google for current information. Returns titles, snippets, and links.",
        "jsCode": "const response = await this.helpers.httpRequest({\n  method: 'POST',\n  url: 'https://google.serper.dev/search',\n  headers: {\n    'X-API-KEY': $('Credentials').item.json.serperApiKey,\n    'Content-Type': 'application/json'\n  },\n  body: JSON.stringify({ q: query }),\n  json: true\n});\nconst results = (response.organic || []).slice(0, 8).map(r => r.title + '\\n' + r.snippet + '\\n' + r.link).join('\\n\\n');\nreturn results || 'No results found.';"
      },
      "id": "task-serper-tool",
      "name": "Task Web Search",
      "type": "@n8n/n8n-nodes-langchain.toolCode",
      "typeVersion": 1.1,
      "position": [
        1040,
        1200
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/tasks/runs/{{ $('Nod Webhook').item.json.body.run_id }}/complete",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"status\": \"completed\",\n  \"summary\": {{ JSON.stringify(($('Task Agent').item.json.output || '').slice(0, 1000)) }}\n}"
      },
      "id": "task-complete",
      "name": "Complete Task Run",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1200,
        1000
      ]
    },
    {
      "parameters": {
        "method": "POST",
        "url": "={{ $('Credentials').item.json.nodApiUrl }}/api/agent/events",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "X-Nod-Agent-Id",
              "value": "={{ $('Credentials').item.json.agentId }}"
            },
            {
              "name": "X-Nod-Secret",
              "value": "={{ $('Credentials').item.json.agentSecret }}"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"type\": \"processing_stop\"\n}"
      },
      "id": "task-processing-stop",
      "name": "Task: Processing Stop",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1460,
        1000
      ]
    },
    {
      "parameters": {
        "mode": "raw",
        "jsonOutput": "={\n  \"agentId\": \"YOUR_AGENT_ID\",\n  \"agentSecret\": \"YOUR_AGENT_SECRET\",\n  \"serperApiKey\": \"YOUR_SERPER_API_KEY\",\n  \"nodApiUrl\": \"https://api.asknod.ai\",\n  \"body\": {{ JSON.stringify($json.body) }},\n  \"headers\": {{ JSON.stringify($json.headers) }}\n}",
        "options": {}
      },
      "id": "globals",
      "name": "Credentials",
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        240,
        500
      ]
    }
  ],
  "connections": {
    "Nod Webhook": {
      "main": [
        [
          {
            "node": "Credentials",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Activity: Received": {
      "main": [
        [
          {
            "node": "Fetch Conversation History",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Processing: Start": {
      "main": []
    },
    "Fetch Conversation History": {
      "main": [
        [
          {
            "node": "Build Context",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Build Context": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Needs Web Search?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Needs Web Search?": {
      "main": [
        [
          {
            "node": "Request Search Approval",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Send Reply (no search)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Request Search Approval": {
      "main": [
        [
          {
            "node": "Log Activity: Awaiting Approval",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Activity: Awaiting Approval": {
      "main": [
        [
          {
            "node": "Processing: Stop (awaiting)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log Activity: Approved": {
      "main": [
        [
          {
            "node": "Research Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Research Agent": {
      "main": [
        [
          {
            "node": "Send Reply (with search)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "OpenAI Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "Research Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Web Search Tool": {
      "ai_tool": [
        [
          {
            "node": "Research Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Send Reply (with search)": {
      "main": [
        [
          {
            "node": "Processing: Stop (search)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Processing: Stop (search)": {
      "main": []
    },
    "Send Reply (no search)": {
      "main": [
        [
          {
            "node": "Processing: Stop (direct)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Processing: Stop (direct)": {
      "main": []
    },
    "Log Activity: Declined": {
      "main": [
        [
          {
            "node": "AI Fallback (no search)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Fallback (no search)": {
      "main": [
        [
          {
            "node": "Send Reply (declined)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Send Reply (declined)": {
      "main": [
        [
          {
            "node": "Processing: Stop (declined)",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Processing: Stop (declined)": {
      "main": []
    },
    "Was Approved?": {
      "main": [
        [
          {
            "node": "Log Activity: Approved",
            "type": "main",
            "index": 0
          }
        ],
        [
          {
            "node": "Log Activity: Declined",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Is User Message?": {
      "main": [
        [
          {
            "node": "Log Activity: Received",
            "type": "main",
            "index": 0
          },
          {
            "node": "Processing: Start",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Is Decision Resolved?": {
      "main": [
        [
          {
            "node": "Was Approved?",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Processing: Stop (awaiting)": {
      "main": []
    },
    "Is Task Due?": {
      "main": [
        [
          {
            "node": "Task: Processing Start",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "Task: Processing Start": {
      "main": [
        [
          {
            "node": "Task Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Task Agent": {
      "main": [
        [
          {
            "node": "Complete Task Run",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Task Agent Model": {
      "ai_languageModel": [
        [
          {
            "node": "Task Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Task Web Search": {
      "ai_tool": [
        [
          {
            "node": "Task Agent",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Complete Task Run": {
      "main": [
        [
          {
            "node": "Task: Processing Stop",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Task: Processing Stop": {
      "main": []
    },
    "Credentials": {
      "main": [
        [
          {
            "node": "Is User Message?",
            "type": "main",
            "index": 0
          },
          {
            "node": "Is Decision Resolved?",
            "type": "main",
            "index": 0
          },
          {
            "node": "Is Task Due?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "pinData": {},
  "settings": {
    "executionOrder": "v1"
  },
  "staticData": null,
  "tags": [],
  "triggerCount": 1,
  "updatedAt": "2026-03-25T00:00:00.000Z",
  "versionId": "4"
}