{
  "id": "FUpZPZE9KCnvVEti",
  "meta": {
    "templateCredsSetupCompleted": true
  },
  "name": "Tutor English messages from webhook using OpenRouter and Postgres memory",
  "tags": [],
  "nodes": [
    {
      "id": "bfa8e352-a943-4198-9cf5-2997a603a9a0",
      "name": "Webhook",
      "type": "n8n-nodes-base.webhook",
      "position": [
        -80,
        0
      ],
      "parameters": {
        "path": "e5626949-1022-4883-a045-3d118685694d",
        "options": {},
        "httpMethod": "POST",
        "responseMode": "responseNode"
      },
      "typeVersion": 2.1
    },
    {
      "id": "77cfcd9c-d5cb-4147-a517-1c5fce89749d",
      "name": "Respond to Webhook",
      "type": "n8n-nodes-base.respondToWebhook",
      "position": [
        1232,
        0
      ],
      "parameters": {
        "options": {},
        "respondWith": "json",
        "responseBody": "={\n  \"success\": true,\n  \"reply\": {{ JSON.stringify($json.message) }}\n}"
      },
      "typeVersion": 1.5
    },
    {
      "id": "23a20a79-7d83-45a1-b081-0e0fb09a5f8e",
      "name": "Edit Fields",
      "type": "n8n-nodes-base.set",
      "position": [
        240,
        0
      ],
      "parameters": {
        "options": {},
        "assignments": {
          "assignments": [
            {
              "id": "3d9a3593-0bae-45c7-a9e4-261f9aec0e00",
              "name": "user_id",
              "type": "string",
              "value": "={{$json.body.user_id}}"
            },
            {
              "id": "dc3f3d15-a058-4479-a11c-363acf3d24c1",
              "name": "message",
              "type": "string",
              "value": "={{$json.body.message}}"
            }
          ]
        }
      },
      "typeVersion": 3.4
    },
    {
      "id": "ec854594-cea9-4e7c-8773-9f94f0de673d",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "position": [
        496,
        0
      ],
      "parameters": {
        "text": "={{ $json.message }}",
        "options": {
          "systemMessage": "You are an advanced AI English tutor.\n\nRules:\n- remember previous conversations\n- help improve grammar\n- encourage confidence\n- explain mistakes simply\n- sound warm and natural"
        },
        "promptType": "define"
      },
      "typeVersion": 3.1
    },
    {
      "id": "1edfd4e2-5af4-4f9f-a03f-daf234fab7a9",
      "name": "OpenRouter Chat Model",
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenRouter",
      "position": [
        384,
        240
      ],
      "parameters": {
        "model": "poolside/laguna-m.1:free",
        "options": {}
      },
      "credentials": {
        "openRouterApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    },
    {
      "id": "3b424b31-876b-49ae-b424-3ace1979acab",
      "name": "Postgres Chat Memory",
      "type": "@n8n/n8n-nodes-langchain.memoryPostgresChat",
      "position": [
        752,
        240
      ],
      "parameters": {
        "sessionKey": "={{ $json.user_id }}",
        "sessionIdType": "customKey"
      },
      "credentials": {
        "postgres": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1.3
    },
    {
      "id": "51ae8112-8b54-4061-8273-7e934513a937",
      "name": "Sticky Note",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -528,
        -304
      ],
      "parameters": {
        "width": 352,
        "height": 464,
        "content": "## \ud83c\udf93 AI English Tutor\n\nThis workflow creates a conversational AI English tutor that remembers \neach student's history across sessions.\n\n**How to use:**\nPOST to /webhook/ai-tutor with:\n{\n  \"user_id\": \"student_123\",\n  \"message\": \"I goed to the market yesterday.\"\n}\n\n**What you need:**\n- OpenRouter API key (free tier works)\n- PostgreSQL database (for memory)\n- Supabase project with a `conversations` table\n  (columns: user_id, role, message)\n\n**To customize:**\n- Change the model in the OpenRouter node\n- Edit the system prompt in the AI Agent node\n- Swap Supabase for any other database node"
      },
      "typeVersion": 1
    },
    {
      "id": "63696c52-4333-42dd-ba02-e5ed59594f07",
      "name": "Sticky Note1",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        -112,
        -256
      ],
      "parameters": {
        "color": 6,
        "width": 224,
        "height": 240,
        "content": "## 1. Webhook\nListens for POST requests at /webhook/ai-tutor.\n\nExpected body:\n{\n  \"user_id\": \"string\",\n  \"message\": \"string\"\n}\n\nThe user_id is used to track memory per student."
      },
      "typeVersion": 1
    },
    {
      "id": "71fbef4d-dfa0-4b8b-af90-8426cac75ecc",
      "name": "Sticky Note2",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        176,
        -256
      ],
      "parameters": {
        "color": 6,
        "width": 224,
        "height": 240,
        "content": "## 2. Edit Fields\nExtracts user_id and message from the webhook body \nand passes them cleanly to the AI Agent.\n\n\u2699\ufe0f If you rename your input fields, update the \nexpressions here to match."
      },
      "typeVersion": 1
    },
    {
      "id": "0a57812b-0f47-45f6-9911-d77dd80d02c8",
      "name": "Sticky Note3",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        464,
        -368
      ],
      "parameters": {
        "color": 6,
        "width": 304,
        "height": 352,
        "content": "## 3. AI Agent\nThe core of the tutor. Sends the student's message \nto the LLM with a tutor system prompt.\n\nThe agent:\n- Corrects grammar with simple explanations\n- Remembers past conversations (via Postgres Memory)\n- Responds with encouragement and warmth\n\n\u270f\ufe0f Edit the system prompt here to change the \ntutor's subject, tone, or rules."
      },
      "typeVersion": 1
    },
    {
      "id": "24715753-6430-4c7a-ac6f-523b9b75b7f4",
      "name": "Sticky Note4",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        224,
        384
      ],
      "parameters": {
        "color": 6,
        "width": 336,
        "height": 272,
        "content": "## 3a. OpenRouter Chat Model\nConnects the AI Agent to an LLM via OpenRouter.\n\nDefault model: poolside/laguna-m.1:free\n\n\u270f\ufe0f Swap the model slug to use GPT-4o, Claude, \nGemini, or any other OpenRouter-supported model.\n\n\ud83d\udd11 Add your OpenRouter API key in the credentials."
      },
      "typeVersion": 1
    },
    {
      "id": "c450eefa-3176-49af-9c0d-3a3724f3c997",
      "name": "Sticky Note5",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        640,
        384
      ],
      "parameters": {
        "color": 6,
        "width": 336,
        "height": 272,
        "content": "## 3b. Postgres Chat Memory\nStores conversation history per student in Postgres.\n\nMemory is keyed by user_id, so each student gets \ntheir own persistent session \u2014 even across days.\n\n\ud83d\udd11 Add your Postgres credentials.\nMake sure the database is accessible from your \nn8n instance."
      },
      "typeVersion": 1
    },
    {
      "id": "cd54a075-2640-4792-9847-9defd3b9c0df",
      "name": "Sticky Note6",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        816,
        -368
      ],
      "parameters": {
        "color": 6,
        "width": 272,
        "height": 352,
        "content": "## 4. Supabase \n Log Conversation\nSaves each AI reply to the `conversations` table \nin Supabase for record-keeping and analytics.\n\nRequired table columns:\n- user_id (text)\n- role (text)\n- message (text)\n\n\ud83d\udd11 Add your Supabase credentials.\n\u270f\ufe0f Swap this node for Airtable, Google Sheets, \nor any other DB if you prefer."
      },
      "typeVersion": 1
    },
    {
      "id": "713efb6f-2362-41d0-9536-25cd4fc2db54",
      "name": "Sticky Note7",
      "type": "n8n-nodes-base.stickyNote",
      "position": [
        1136,
        -336
      ],
      "parameters": {
        "color": 6,
        "width": 304,
        "height": 320,
        "content": "## 5. Respond to Webhook\nReturns the tutor's reply as a JSON response \nto the original caller.\n\nResponse format:\n{\n  \"success\": true,\n  \"reply\": \"Tutor's message here\"\n}\n\n\u270f\ufe0f Edit the response body here if your frontend \nexpects a different format."
      },
      "typeVersion": 1
    },
    {
      "id": "448faf08-0be8-463b-9d0e-df9d9026add8",
      "name": "Log to Supabase",
      "type": "n8n-nodes-base.supabase",
      "position": [
        912,
        0
      ],
      "parameters": {
        "tableId": "conversations",
        "fieldsUi": {
          "fieldValues": [
            {
              "fieldId": "user_id",
              "fieldValue": "={{ $('Webhook').item.json.body.user_id }}"
            },
            {
              "fieldId": "role",
              "fieldValue": "={{ $('Webhook').item.json.body.message }}"
            },
            {
              "fieldId": "message",
              "fieldValue": "={{ $json.output }}"
            }
          ]
        }
      },
      "credentials": {
        "supabaseApi": {
          "name": "<your credential>"
        }
      },
      "typeVersion": 1
    }
  ],
  "active": true,
  "settings": {
    "binaryMode": "separate",
    "callerPolicy": "workflowsFromSameOwner",
    "timeSavedMode": "fixed",
    "availableInMCP": false,
    "executionOrder": "v1"
  },
  "versionId": "a6e49e32-9b41-4beb-9221-9ec350fe92d7",
  "connections": {
    "Webhook": {
      "main": [
        [
          {
            "node": "Edit Fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Log to Supabase",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Edit Fields": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Log to Supabase": {
      "main": [
        [
          {
            "node": "Respond to Webhook",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Postgres Chat Memory": {
      "ai_memory": [
        [
          {
            "node": "AI Agent",
            "type": "ai_memory",
            "index": 0
          }
        ]
      ]
    },
    "OpenRouter Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  }
}