AutomationFlowsAI & RAG › Moss Proactive Engine

Moss Proactive Engine

moss proactive engine. Uses googleCalendar, agent, lmChatDeepSeek, telegram. Scheduled trigger; 8 nodes.

Cron / scheduled trigger★★★★☆ complexityAI-powered8 nodesGoogle CalendarAgentLm Chat Deep SeekTelegram
AI & RAG Trigger: Cron / scheduled Nodes: 8 Complexity: ★★★★☆ AI nodes: yes Added:

This workflow follows the Agent → Google Calendar recipe pattern — see all workflows that pair these two integrations.

The workflow JSON

Copy or download the full n8n JSON below. Paste it into a new n8n workflow, add your credentials, activate. Full import guide →

Download .json
{
  "name": "moss proactive engine",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "*/10 * * * *"
            }
          ]
        }
      },
      "id": "node-schedule",
      "name": "Schedule Trigger",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.1,
      "position": [
        0,
        0
      ]
    },
    {
      "parameters": {
        "operation": "getAll",
        "calendar": {
          "__rl": true,
          "value": "YOUR_CALENDAR_ID",
          "mode": "list",
          "cachedResultName": "YOUR_CALENDAR_ID"
        },
        "limit": 20,
        "timeMin": "={{ $now.startOf('day') }}",
        "timeMax": "={{ $now.endOf('day') }}",
        "options": {
          "orderBy": "startTime"
        }
      },
      "alwaysOutputData": true,
      "id": "node-get-events",
      "name": "get upcoming events",
      "type": "n8n-nodes-base.googleCalendar",
      "typeVersion": 1.3,
      "position": [
        220,
        0
      ],
      "credentials": {
        "googleCalendarOAuth2Api": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// moss proactive engine \u2014 signal processor\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Signal schema:\n//   type:               signal category\n//   id:                 dedup key (type:source_id:YYYY-MM-DD)\n//   urgency:            'immediate' | 'soon'\n//   title:              human-readable title\n//   detail:             supporting info for AI\n//   starts_in_minutes:  (calendar only) minutes until start\n//   suggested_actions:  ['notify'] \u2014 extend here for future action types\n//   action_context:     arbitrary data for future action handlers\n//                       e.g. location \u2192 route query, doc_url \u2192 doc prep\n// \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n\nconst https = require('https');\nconst fs = require('fs');\n\n// TODOIST_TOKEN: fill in on VPS directly, never commit real token\nconst TODOIST_TOKEN = 'YOUR_TODOIST_API_TOKEN';\nconst CHAT_ID = $env['MOSS_OWNER_CHAT_ID'] || '';\nif (!CHAT_ID) throw new Error('MOSS_OWNER_CHAT_ID not set in environment');\n\nconst DEDUP_PATH = '/home/node/.n8n/moss_notified.json';\nconst now = new Date();\nconst today = now.toISOString().slice(0, 10);\n\n// \u2500\u2500 Load & prune dedup log \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nlet notified = {};\ntry {\n  notified = JSON.parse(fs.readFileSync(DEDUP_PATH, 'utf8'));\n  // Remove entries older than 2 days to keep file small\n  for (const key of Object.keys(notified)) {\n    if (notified[key] < today) delete notified[key];\n  }\n} catch(e) { notified = {}; }\n\n// \u2500\u2500 Calendar signals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Notify for events starting in 5\u201320 min\n// Window accounts for 10-min polling interval\nconst calItems = $input.all().filter(i => i.json && i.json.summary);\nconst calSignals = [];\n\nfor (const item of calItems) {\n  const e = item.json;\n  const startStr = e.start?.dateTime || e.start?.date;\n  if (!startStr) continue;\n  const start = new Date(startStr);\n  const minutesUntil = (start - now) / 60000;\n\n  if (minutesUntil >= 5 && minutesUntil <= 20) {\n    const key = `calendar_remind:${e.id}:${today}`;\n    if (!notified[key]) {\n      calSignals.push({\n        type: 'calendar_remind',\n        id: key,\n        urgency: 'immediate',\n        title: e.summary,\n        detail: [e.location, e.description].filter(Boolean).join(' | ').slice(0, 200),\n        starts_in_minutes: Math.round(minutesUntil),\n        suggested_actions: ['notify'],\n        // action_context \u2014 future hooks:\n        //   location \u2192 trigger route query sub-workflow\n        //   doc_url  \u2192 trigger document prep sub-workflow\n        action_context: {\n          event_id: e.id,\n          location: e.location || null,\n          doc_url: null  // placeholder: parse from description in future\n        }\n      });\n    }\n  }\n}\n\n// \u2500\u2500 Todoist signals \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\nfunction getTasks(filter) {\n  return new Promise((resolve) => {\n    const opts = {\n      hostname: 'api.todoist.com',\n      path: '/api/v1/tasks?filter=' + encodeURIComponent(filter),\n      method: 'GET',\n      headers: { 'Authorization': 'Bearer ' + TODOIST_TOKEN }\n    };\n    const req = https.request(opts, res => {\n      let raw = '';\n      res.on('data', c => raw += c);\n      res.on('end', () => { try { resolve(JSON.parse(raw)); } catch(e) { resolve({}); } });\n    });\n    req.on('error', () => resolve({}));\n    req.end();\n  });\n}\n\nconst [overdueData, todayData] = await Promise.all([\n  getTasks('overdue'),\n  getTasks('today & !overdue')\n]);\n\nconst taskSignals = [];\n\n// Overdue tasks \u2014 one reminder per task per day\nfor (const t of (overdueData.results || [])) {\n  const key = `task_overdue:${t.id}:${today}`;\n  if (!notified[key]) {\n    taskSignals.push({\n      type: 'task_overdue',\n      id: key,\n      urgency: 'soon',\n      title: t.content,\n      detail: t.due?.string || '',\n      suggested_actions: ['notify'],\n      action_context: { task_id: t.id }\n    });\n  }\n}\n\n// Today's tasks with a specific due time, starting soon (within 30 min)\nfor (const t of (todayData.results || [])) {\n  if (!t.due?.datetime) continue;\n  const dueTime = new Date(t.due.datetime);\n  const minutesUntil = (dueTime - now) / 60000;\n  if (minutesUntil >= 0 && minutesUntil <= 30) {\n    const key = `task_due_soon:${t.id}:${today}`;\n    if (!notified[key]) {\n      taskSignals.push({\n        type: 'task_due_soon',\n        id: key,\n        urgency: 'immediate',\n        title: t.content,\n        detail: `due in ${Math.round(minutesUntil)} min`,\n        suggested_actions: ['notify'],\n        action_context: { task_id: t.id }\n      });\n    }\n  }\n}\n\n// \u2500\u2500 FUTURE SIGNAL SOURCES \u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\n// Add new signal collectors here following the same pattern.\n// Example stubs (not yet implemented):\n//\n// const routeSignals = await gatherRouteSignals(calSignals);\n//   \u2192 for events with location, check commute time, alert if need to leave soon\n//\n// const docPrepSignals = await gatherDocPrepSignals(calSignals);\n//   \u2192 for events with doc_url in description, prep a summary before the meeting\n//\n// Push additional signals into the array below.\n// The AI Agent and action router downstream will handle them.\n\nconst signals = [...calSignals, ...taskSignals];\n\n// Only emit today's overdue tasks in first morning run (before 10am)\n// to avoid spamming overdue tasks all day.\n// After 10am, only re-surface overdue if they're also due today with a time.\nconst hour = now.getHours();\nconst filteredSignals = signals.filter(s => {\n  if (s.type === 'task_overdue' && hour >= 10) return false;\n  return true;\n});\n\nreturn [{\n  json: {\n    chatId: CHAT_ID,\n    signals: filteredSignals,\n    hasSignals: filteredSignals.length > 0,\n    notified,\n    today\n  }\n}];"
      },
      "id": "node-process-signals",
      "name": "process signals",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        460,
        0
      ]
    },
    {
      "parameters": {
        "conditions": {
          "options": {
            "caseSensitive": false,
            "leftValue": "",
            "typeValidation": "loose"
          },
          "conditions": [
            {
              "leftValue": "={{ $json.hasSignals }}",
              "rightValue": true,
              "operator": {
                "type": "boolean",
                "operation": "true"
              }
            }
          ],
          "combinator": "and"
        }
      },
      "id": "node-if-signals",
      "name": "has signals?",
      "type": "n8n-nodes-base.if",
      "typeVersion": 2,
      "position": [
        700,
        0
      ]
    },
    {
      "parameters": {
        "agent": "conversationalAgent",
        "promptType": "define",
        "text": "=\u6839\u636e\u4ee5\u4e0b\u5f85\u63a8\u9001\u4fe1\u53f7\uff0c\u751f\u6210\u4e00\u6761\u7b80\u6d01\u7684\u4e3b\u52a8\u63d0\u9192\u6d88\u606f\u53d1\u7ed9\u7528\u6237\u3002\n\n\u4fe1\u53f7\u5217\u8868\uff08JSON\uff09\uff1a\n{{ JSON.stringify($json.signals, null, 2) }}\n\n\u5f53\u524d\u65f6\u95f4\uff1a{{ $now.format('HH:mm') }}\n\n\u683c\u5f0f\u8981\u6c42\uff1a\n- \u6309\u7d27\u8feb\u7a0b\u5ea6\u6392\u5e8f\uff0c\u6700\u7d27\u6025\u7684\u653e\u6700\u524d\n- calendar_remind\uff1a\u8bf4\u6e05\u695a\u51e0\u5206\u949f\u540e\u5f00\u59cb\uff0c\u6709\u5730\u70b9\u5c31\u5e26\u4e0a\n- task_overdue\uff1a\u7b80\u77ed\u5217\u51fa\uff0c\u4e0d\u8981\u5570\u55e6\n- task_due_soon\uff1a\u8bf4\u6e05\u695a\u8fd8\u6709\u51e0\u5206\u949f\u622a\u6b62\n- \u591a\u6761\u4fe1\u53f7\u5408\u5e76\u6210\u4e00\u6761\u6d88\u606f\uff0c\u4e0d\u8981\u9010\u6761\u53d1\n- \u63a7\u5236\u5728 150 \u5b57\u4ee5\u5185",
        "options": {
          "systemMessage": "\u4f60\u662f moss\uff0c\u7528\u6237\u7684\u79c1\u4eba AI \u52a9\u7406\uff0c\u5973\u6027\u3002\u73b0\u5728\u662f\u4e3b\u52a8\u63a8\u9001\u6a21\u5f0f\u2014\u2014\u4f60\u4e3b\u52a8\u627e\u7528\u6237\uff0c\u4e0d\u662f\u56de\u590d\u7528\u6237\u3002\n\n\u98ce\u683c\uff1a\n- \u7b80\u6d01\u3001\u6709\u6e29\u5ea6\uff0c\u4e0d\u505a\u4f5c\n- \u76f4\u63a5\u8bf4\u91cd\u70b9\uff0c\u4e0d\u5e9f\u8bdd\n- \u7528\u4e2d\u6587\n- \u4e0d\u8981\u52a0\u4efb\u4f55\u5f00\u573a\u767d\uff0c\u76f4\u63a5\u8bf4\u4e8b"
        }
      },
      "id": "node-ai-agent",
      "name": "AI Agent",
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 1.7,
      "position": [
        960,
        -120
      ]
    },
    {
      "parameters": {
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatDeepSeek",
      "typeVersion": 1,
      "position": [
        960,
        80
      ],
      "id": "node-deepseek",
      "name": "DeepSeek Chat Model",
      "credentials": {
        "deepSeekApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "chatId": "={{ $('process signals').item.json.chatId }}",
        "text": "={{ $json.output }}",
        "additionalFields": {
          "appendAttribution": false,
          "parse_mode": "Markdown"
        }
      },
      "id": "node-telegram-send",
      "name": "Send Alert",
      "type": "n8n-nodes-base.telegram",
      "typeVersion": 1.2,
      "position": [
        1200,
        -120
      ],
      "credentials": {
        "telegramApi": {
          "name": "<your credential>"
        }
      }
    },
    {
      "parameters": {
        "jsCode": "const fs = require('fs');\nconst DEDUP_PATH = '/home/node/.n8n/moss_notified.json';\n\nconst { signals, notified, today } = $('process signals').item.json;\n\n// Mark all pushed signals as notified\nfor (const s of signals) {\n  notified[s.id] = today;\n}\n\nfs.writeFileSync(DEDUP_PATH, JSON.stringify(notified, null, 2));\n\nreturn [{ json: { saved: signals.length, keys: signals.map(s => s.id) } }];"
      },
      "id": "node-save-dedup",
      "name": "save dedup log",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1440,
        -120
      ]
    }
  ],
  "connections": {
    "Schedule Trigger": {
      "main": [
        [
          {
            "node": "get upcoming events",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "get upcoming events": {
      "main": [
        [
          {
            "node": "process signals",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "process signals": {
      "main": [
        [
          {
            "node": "has signals?",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "has signals?": {
      "main": [
        [
          {
            "node": "AI Agent",
            "type": "main",
            "index": 0
          }
        ],
        []
      ]
    },
    "AI Agent": {
      "main": [
        [
          {
            "node": "Send Alert",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "DeepSeek Chat Model": {
      "ai_languageModel": [
        [
          {
            "node": "AI Agent",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "Send Alert": {
      "main": [
        [
          {
            "node": "save dedup log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  }
}

Credentials you'll need

Each integration node will prompt for credentials when you import. We strip credential IDs before publishing — you'll add your own.

Pro

For the full experience including quality scoring and batch install features for each workflow upgrade to Pro

About this workflow

moss proactive engine. Uses googleCalendar, agent, lmChatDeepSeek, telegram. Scheduled trigger; 8 nodes.

Source: https://github.com/sethlsx/moss/blob/bd15b4f7f5068f0e2c54318448815e98681cfc28/workflows/moss_proactive.json — original creator credit. Request a take-down →

More AI & RAG workflows → · Browse all categories →

Related workflows

Workflows that share integrations, category, or trigger type with this one. All free to copy and import.

AI & RAG

BoomerBobBot.TP. Uses agent, telegramTrigger, telegram, memoryBufferWindow. Event-driven trigger; 95 nodes.

Agent, Telegram Trigger, Telegram +10
AI & RAG

AI Agent Workflow. Uses telegramTrigger, chatTrigger, telegram, openAi. Event-driven trigger; 82 nodes.

Telegram Trigger, Chat Trigger, Telegram +7
AI & RAG

This workflow is for beauty salons who want consistent, high‑quality social media content without writing every post manually. It also suits agencies and automation builders who manage multiple beauty

Telegram, Google Sheets Trigger, Agent +26
AI & RAG

This workflow turns Telegram into a personal assistant that manages your Google Calendar and Todoist, sends daily briefings, and alerts you when meetings are booked or things break. Morning briefing -

Google Calendar, Telegram, Error Trigger +8
AI & RAG

Who Is This For?

Telegram, Google Sheets Trigger, Lm Chat Mistral Cloud +17