{
  "name": "BSW Growth Agent \u00b7 Lite (Free Tier)",
  "nodes": [
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "field": "cronExpression",
              "expression": "0 13 * * *"
            }
          ]
        }
      },
      "id": "trigger-cron",
      "name": "Cron \u00b7 Daily 7am MDT",
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        240,
        200
      ],
      "notes": "Wakes the agent up daily at 7am Boulder/Denver time (13:00 UTC during MDT). Adjust cron expression for your timezone \u2014 UTC values: PT=14, ET=11, UK=06, CET=05."
    },
    {
      "parameters": {
        "httpMethod": "POST",
        "path": "discovery-engine-manual",
        "responseMode": "lastNode"
      },
      "id": "trigger-manual",
      "name": "Manual \u00b7 Webhook",
      "type": "n8n-nodes-base.webhook",
      "typeVersion": 2,
      "position": [
        240,
        360
      ],
      "notes": "Manual trigger for testing and live demos. Hit the webhook URL to fire the agent on demand."
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_YOUR_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "ICP",
          "mode": "name"
        },
        "options": {}
      },
      "id": "read-icp",
      "name": "Read ICP from Sheets",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        460,
        280
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "notes": "CONFIG: ICP tab columns are icp_description, signal_keywords, subreddits (comma-separated). Edit the Sheet to change agent behavior \u2014 no redeploy needed."
    },
    {
      "parameters": {
        "operation": "download",
        "fileId": {
          "__rl": true,
          "value": "REPLACE_WITH_VOICE_MD_FILE_ID",
          "mode": "id"
        },
        "options": {
          "binaryPropertyName": "voiceMd"
        }
      },
      "id": "read-voice",
      "name": "Read voice.md from Drive",
      "type": "n8n-nodes-base.googleDrive",
      "typeVersion": 3,
      "position": [
        460,
        460
      ],
      "credentials": {
        "googleDriveOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "notes": "CONFIG: voice.md in your Drive 'agentic-architect' folder. Edit the Doc and the next run inherits the change. 5+ example emails recommended."
    },
    {
      "parameters": {
        "url": "=https://hn.algolia.com/api/v1/search_by_date?query={{ encodeURIComponent(($('Read ICP from Sheets').item.json.signal_keywords || '').split(',')[0].trim() || 'startup') }}&tags=story&hitsPerPage=20&numericFilters=created_at_i>{{ Math.floor(Date.now()/1000) - 7*24*3600 }}",
        "method": "GET",
        "authentication": "none",
        "options": {
          "timeout": 15000
        }
      },
      "id": "search-hn",
      "name": "Search \u00b7 HN Algolia",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        700,
        240
      ],
      "notes": "Real web search \u00b7 HN Algolia API \u00b7 no auth, no key, no signup. Queries by the FIRST entry in signal_keywords (edit the ICP sheet to change what's searched)."
    },
    {
      "parameters": {
        "url": "=https://www.reddit.com/r/{{ ($('Read ICP from Sheets').item.json.subreddits || 'SaaS+Entrepreneur+AI_Agents').replaceAll(',', '+').replaceAll(' ', '') }}/new.json?limit=25",
        "method": "GET",
        "authentication": "none",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "User-Agent",
              "value": "bsw-growth-agent/1.0 (open-source customer-discovery agent)"
            }
          ]
        },
        "options": {
          "timeout": 15000
        }
      },
      "id": "search-reddit",
      "name": "Search \u00b7 Reddit JSON",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        700,
        400
      ],
      "notes": "Real web search \u00b7 Reddit public JSON \u00b7 no auth, no key. Reads subreddit list from the ICP sheet's 'subreddits' column (comma-separated, default: SaaS,Entrepreneur,AI_Agents). User-Agent header required to avoid 429."
    },
    {
      "parameters": {
        "language": "javaScript",
        "jsCode": "// Combine HN + Reddit search results into a single normalized list\n// for the Groq discovery extractor.\n\nconst hnResp     = $('Search \u00b7 HN Algolia').item.json;\nconst redditResp = $('Search \u00b7 Reddit JSON').item.json;\n\nconst posts = [];\n\n// HN Algolia hits \u2192 normalize\nfor (const hit of (hnResp.hits || [])) {\n  posts.push({\n    source: 'hn',\n    title:  hit.title || hit.story_title || '',\n    url:    hit.url || `https://news.ycombinator.com/item?id=${hit.objectID}`,\n    text:   hit.story_text || hit.comment_text || '',\n    author: hit.author || '',\n    points: hit.points ?? 0,\n    created_at: hit.created_at || ''\n  });\n}\n\n// Reddit children \u2192 normalize\nfor (const child of (redditResp?.data?.children || [])) {\n  const d = child.data || {};\n  posts.push({\n    source: 'reddit',\n    title:  d.title || '',\n    url:    'https://www.reddit.com' + (d.permalink || ''),\n    text:   d.selftext || '',\n    author: d.author || '',\n    points: d.score ?? 0,\n    created_at: d.created_utc ? new Date(d.created_utc * 1000).toISOString() : '',\n    subreddit: d.subreddit || ''\n  });\n}\n\n// Trim text to keep prompt size reasonable\nfor (const p of posts) {\n  if (p.text && p.text.length > 600) p.text = p.text.slice(0, 600) + '...';\n}\n\nreturn [{ json: { posts, count: posts.length } }];\n"
      },
      "id": "combine-results",
      "name": "Combine \u00b7 HN + Reddit",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        920,
        320
      ],
      "notes": "Normalizes HN + Reddit results into a single posts array for the discovery LLM call."
    },
    {
      "parameters": {
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "method": "POST",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"meta-llama/llama-4-scout-17b-16e-instruct\",\n  \"max_tokens\": 2000,\n  \"temperature\": 0.2,\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You are a customer-discovery research agent for a lean startup.\\n\\nICP context:\\n{{ $('Read ICP from Sheets').item.json.icp_description }}\\n\\nSignal keywords (any of these in a post = potential signal): {{ $('Read ICP from Sheets').item.json.signal_keywords }}\\n\\nReturn ONLY a valid JSON array. No prose. No markdown. No explanation. Start with [ and end with ].\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"=Below are {{ $json.count }} recent posts from Hacker News and Reddit. Score each one 0-10 for ICP fit and signal strength. Return only posts scored 6 or higher.\\n\\nPosts:\\n{{ JSON.stringify($json.posts, null, 0) }}\\n\\nReturn JSON array, max 15 items, schema (one object per qualifying post):\\n[\\n  {\\n    \\\"person\\\": \\\"author handle from the post\\\",\\n    \\\"signal_type\\\": \\\"pain | hiring | complaint | tool_ask\\\",\\n    \\\"source_url\\\": \\\"the post URL verbatim\\\",\\n    \\\"evidence_quote\\\": \\\"a 1-2 sentence verbatim quote from the post title or text\\\",\\n    \\\"score\\\": 6,\\n    \\\"company\\\": \\\"company name if mentioned, else empty string\\\",\\n    \\\"company_url\\\": \\\"company website if mentioned, else empty string\\\"\\n  }\\n]\"\n    }\n  ]\n}",
        "options": {}
      },
      "id": "discovery-groq",
      "name": "Discovery \u00b7 Groq Llama 4 Scout",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        1140,
        320
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "notes": "Sub-agent #1 \u2014 Llama 4 Scout on Groq scores the search results against ICP. Non-reasoning model, ~1.5s per call. Groq has no built-in web_search like Anthropic does, so we feed it pre-fetched HN + Reddit results from the previous step."
    },
    {
      "parameters": {
        "language": "javaScript",
        "jsCode": "// Parse Groq's OpenAI-format response and extract the leads JSON array.\n// Groq returns { choices: [{ message: { content: \"...\" } }] }.\n\nconst response = $input.first().json;\nconst rawText = response?.choices?.[0]?.message?.content || '';\n\n// Strip any leading/trailing prose around the JSON array\nconst jsonMatch = rawText.match(/\\[[\\s\\S]*\\]/);\nif (!jsonMatch) {\n  return [{ json: { error: 'No JSON array in response', raw: rawText, leads: [] } }];\n}\n\nlet leads;\ntry {\n  leads = JSON.parse(jsonMatch[0]);\n} catch (e) {\n  return [{ json: { error: 'JSON parse failed', message: e.message, raw: jsonMatch[0], leads: [] } }];\n}\n\n// Filter to leads scored 6 or higher (defense in depth \u2014 model already prompted to filter)\nconst qualified = leads.filter(l => (l.score ?? 0) >= 6);\n\nreturn qualified.map(lead => ({ json: lead }));\n"
      },
      "id": "parse-leads",
      "name": "Parse \u00b7 Extract qualified leads",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1360,
        320
      ],
      "notes": "Parses Groq's OpenAI-format response, extracts JSON array, filters to score >= 6."
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_YOUR_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Sent",
          "mode": "name"
        },
        "options": {}
      },
      "id": "read-sent-log",
      "name": "Read Sent log",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        1580,
        240
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "notes": "Idempotency: read the Sent log so we can dedup against already-contacted people."
    },
    {
      "parameters": {
        "language": "javaScript",
        "jsCode": "// Idempotent dedup \u2014 drop any lead already in the Sent log.\n\nconst leads = $input.all().map(i => i.json);\nconst sentRows = $('Read Sent log').all().map(i => i.json);\n\nconst contactedUrls    = new Set(sentRows.map(r => r.source_url).filter(Boolean));\nconst contactedHandles = new Set(sentRows.map(r => r.person).filter(Boolean));\n\nconst fresh = leads.filter(lead => {\n  if (lead.source_url && contactedUrls.has(lead.source_url)) return false;\n  if (lead.person     && contactedHandles.has(lead.person))    return false;\n  return true;\n});\n\nfresh.sort((a, b) => (b.score ?? 0) - (a.score ?? 0));\nconst top5 = fresh.slice(0, 5);\n\nreturn top5.map(lead => ({ json: lead }));\n"
      },
      "id": "dedup",
      "name": "Dedup \u00b7 top 5 fresh leads",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        1800,
        320
      ],
      "notes": "Idempotency primitive. Filters out already-contacted leads. Keeps top 5 by score for the expensive enrichment step."
    },
    {
      "parameters": {
        "url": "=https://r.jina.ai/{{ $json.company_url || $json.source_url }}",
        "method": "GET",
        "authentication": "none",
        "options": {
          "timeout": 30000
        }
      },
      "id": "jina-enrich",
      "name": "Jina Reader \u00b7 enrich top 5",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2020,
        320
      ],
      "notes": "Progressive enrichment via Jina Reader. No key, no signup \u2014 just hit https://r.jina.ai/<URL> and get clean markdown back. Free forever."
    },
    {
      "parameters": {
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "method": "POST",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"llama-3.1-8b-instant\",\n  \"max_tokens\": 300,\n  \"temperature\": 0.3,\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You write concise 2-line company summaries. Output plain text. No JSON. No markdown.\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"=Page extract:\\n\\n{{ ($json.data || '').toString().slice(0, 4000) }}\\n\\nIn exactly two sentences, what does this company do and what's their current state?\"\n    }\n  ]\n}",
        "options": {}
      },
      "id": "summarize-groq",
      "name": "Summarize \u00b7 Groq Llama 3.1 8B",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2240,
        320
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "notes": "Cheap classifier extracts a 2-sentence company context from the Jina extract. Cascade pattern \u2014 small fast model for the summary, same model for drafting (paid tier uses Sonnet here for nuance)."
    },
    {
      "parameters": {
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "method": "POST",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"meta-llama/llama-4-scout-17b-16e-instruct\",\n  \"max_tokens\": 600,\n  \"temperature\": 0.5,\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"=You write customer-discovery emails for a lean startup founder. Goal of every email: an INTERVIEW ASK \u2014 not a pitch. Soft CTA. 80-110 words. Follow the voice.md file exactly.\\n\\n--- voice.md contents ---\\n{{ $('Read voice.md from Drive').item.binary.voiceMd.toString('utf-8') }}\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"=Draft a customer-discovery email.\\n\\nPerson: {{ $('Dedup \u00b7 top 5 fresh leads').item.json.person || 'unknown' }}\\nTheir signal: {{ $('Dedup \u00b7 top 5 fresh leads').item.json.signal_type }} \u2014 {{ $('Dedup \u00b7 top 5 fresh leads').item.json.evidence_quote }}\\nSource URL: {{ $('Dedup \u00b7 top 5 fresh leads').item.json.source_url }}\\nCompany context (2 sentences): {{ $('Summarize \u00b7 Groq Llama 3.1 8B').item.json.choices[0].message.content }}\\n\\nReturn ONLY the email subject and body in this exact format:\\nSUBJECT: [subject line]\\nBODY:\\n[email body]\"\n    }\n  ]\n}",
        "options": {}
      },
      "id": "draft-groq",
      "name": "Draft \u00b7 Groq Llama 4 Scout + voice.md",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        2460,
        320
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "notes": "Sub-agent #2 \u2014 Llama 4 Scout drafts the personalized email using voice.md inline in the system prompt. The paid tier uses Sonnet 4.6 with Anthropic prompt caching here for better voice match \u2014 Groq does not support prompt caching."
    },
    {
      "parameters": {
        "language": "javaScript",
        "jsCode": "// Parse the SUBJECT: / BODY: format from the Groq response.\nconst response = $input.first().json;\nconst text = (response?.choices?.[0]?.message?.content || '').trim();\n\nconst subjectMatch = text.match(/^SUBJECT:\\s*(.+?)\\s*\\n/i);\nconst bodyMatch    = text.match(/BODY:\\s*\\n([\\s\\S]+)$/i);\n\nconst subject = subjectMatch ? subjectMatch[1].trim() : 're: a quick question';\nconst body    = bodyMatch ? bodyMatch[1].trim() : text;\n\n// Carry forward the lead context for downstream nodes.\nconst lead = {\n  person:         $('Dedup \u00b7 top 5 fresh leads').item.json.person,\n  signal_type:    $('Dedup \u00b7 top 5 fresh leads').item.json.signal_type,\n  source_url:     $('Dedup \u00b7 top 5 fresh leads').item.json.source_url,\n  evidence_quote: $('Dedup \u00b7 top 5 fresh leads').item.json.evidence_quote,\n  score:          $('Dedup \u00b7 top 5 fresh leads').item.json.score,\n  company:        $('Dedup \u00b7 top 5 fresh leads').item.json.company,\n  company_url:    $('Dedup \u00b7 top 5 fresh leads').item.json.company_url\n};\n\nreturn [{ json: { subject, body, lead } }];\n"
      },
      "id": "parse-draft",
      "name": "Parse \u00b7 Subject + Body",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        2680,
        320
      ],
      "notes": "Splits the Groq SUBJECT/BODY response into structured fields for the Gmail node."
    },
    {
      "parameters": {
        "resource": "draft",
        "operation": "create",
        "subject": "={{ $json.subject }}",
        "message": "={{ $json.body }}",
        "options": {
          "sendTo": "={{ $json.lead.person ? $json.lead.person + '@unknown.example' : 'TODO_RESOLVE_EMAIL@example.com' }}"
        }
      },
      "id": "gmail-create-draft",
      "name": "Gmail \u00b7 createDraft (HITL gate)",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        2900,
        320
      ],
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "notes": "HITL GATE \u2014 the agent NEVER sends. Drops a draft in your Drafts folder. You approve and send manually. Real-world usage requires resolving the recipient's actual email (add an Apollo/Hunter step here)."
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_YOUR_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Sent",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "date": "={{ new Date().toISOString().slice(0,10) }}",
            "person": "={{ $json.lead.person }}",
            "signal_type": "={{ $json.lead.signal_type }}",
            "source_url": "={{ $json.lead.source_url }}",
            "score": "={{ $json.lead.score }}",
            "draft_subject": "={{ $json.subject }}",
            "status": "pending_review"
          }
        }
      },
      "id": "log-sent",
      "name": "Append \u00b7 Sent log",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        3120,
        320
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "notes": "Logs the draft to the Sent sheet with status=pending_review. Founder updates to 'sent' after approving and sending the Gmail draft."
    },
    {
      "parameters": {
        "url": "https://api.groq.com/openai/v1/chat/completions",
        "method": "POST",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpHeaderAuth",
        "sendHeaders": true,
        "headerParameters": {
          "parameters": [
            {
              "name": "Content-Type",
              "value": "application/json"
            }
          ]
        },
        "sendBody": true,
        "specifyBody": "json",
        "jsonBody": "={\n  \"model\": \"llama-3.1-8b-instant\",\n  \"max_tokens\": 400,\n  \"temperature\": 0.4,\n  \"messages\": [\n    {\n      \"role\": \"system\",\n      \"content\": \"You write a brief daily digest email for a founder running a customer-discovery agent. One paragraph. Friendly. Specific numbers. Format your output as:\\nSUBJECT: [subject]\\nBODY:\\n[body]\"\n    },\n    {\n      \"role\": \"user\",\n      \"content\": \"=Today the agent processed {{ $('Combine \u00b7 HN + Reddit').item.json.count }} recent posts from HN + Reddit, scored {{ $('Parse \u00b7 Extract qualified leads').all().length }} as qualified, deduped to {{ $('Dedup \u00b7 top 5 fresh leads').all().length }} fresh leads, and drafted {{ $('Parse \u00b7 Subject + Body').all().length }} emails sitting in Gmail Drafts.\\n\\nTop signal types today: {{ $('Dedup \u00b7 top 5 fresh leads').all().map(i => i.json.signal_type).join(', ') }}\\n\\nWrite a short morning digest email to the founder. Friendly tone. Tell them what to do next (approve drafts before 5pm).\"\n    }\n  ]\n}",
        "options": {}
      },
      "id": "digest-groq",
      "name": "Digest \u00b7 Groq Llama 3.1 8B",
      "type": "n8n-nodes-base.httpRequest",
      "typeVersion": 4.2,
      "position": [
        3340,
        200
      ],
      "credentials": {
        "httpHeaderAuth": {
          "name": "<your credential>"
        }
      },
      "notes": "Sub-agent #3 \u2014 generates the morning digest summary."
    },
    {
      "parameters": {
        "language": "javaScript",
        "jsCode": "const response = $input.first().json;\nconst text = (response?.choices?.[0]?.message?.content || '').trim();\n\nconst subjectMatch = text.match(/^SUBJECT:\\s*(.+?)\\s*\\n/i) || text.match(/^Subject:\\s*(.+?)\\s*\\n/);\nconst bodyMatch    = text.match(/(?:BODY:|Body:)\\s*\\n([\\s\\S]+)$/i);\n\nconst subject = subjectMatch ? subjectMatch[1].trim() : 'Discovery Pulse \u2014 ' + new Date().toISOString().slice(0,10);\nconst body    = bodyMatch ? bodyMatch[1].trim() : text;\n\nreturn [{ json: { subject, body } }];\n"
      },
      "id": "parse-digest",
      "name": "Parse \u00b7 Digest fields",
      "type": "n8n-nodes-base.code",
      "typeVersion": 2,
      "position": [
        3560,
        200
      ]
    },
    {
      "parameters": {
        "resource": "message",
        "operation": "send",
        "subject": "={{ $json.subject }}",
        "message": "={{ $json.body }}",
        "options": {
          "sendTo": "REPLACE_WITH_YOUR_EMAIL@example.com"
        }
      },
      "id": "gmail-send-digest",
      "name": "Gmail \u00b7 Send digest to founder",
      "type": "n8n-nodes-base.gmail",
      "typeVersion": 2.1,
      "position": [
        3780,
        200
      ],
      "credentials": {
        "gmailOAuth2": {
          "name": "<your credential>"
        }
      },
      "notes": "Sends the digest email to YOU. The only place the agent actually sends \u2014 and it's only sending to you, not to prospects."
    },
    {
      "parameters": {
        "documentId": {
          "__rl": true,
          "value": "REPLACE_WITH_YOUR_SHEET_ID",
          "mode": "id"
        },
        "sheetName": {
          "__rl": true,
          "value": "Runs",
          "mode": "name"
        },
        "columns": {
          "mappingMode": "defineBelow",
          "value": {
            "date": "={{ new Date().toISOString().slice(0,10) }}",
            "leads_found": "={{ $('Combine \u00b7 HN + Reddit').item.json.count }}",
            "qualified": "={{ $('Parse \u00b7 Extract qualified leads').all().length }}",
            "drafts": "={{ $('Parse \u00b7 Subject + Body').all().length }}",
            "errors": "0",
            "notes": "auto \u00b7 lite (groq+jina)"
          }
        }
      },
      "id": "log-run",
      "name": "Append \u00b7 Runs audit log",
      "type": "n8n-nodes-base.googleSheets",
      "typeVersion": 4.5,
      "position": [
        4000,
        200
      ],
      "credentials": {
        "googleSheetsOAuth2Api": {
          "name": "<your credential>"
        }
      },
      "notes": "Audit log \u2014 every run appends a row. After 2 weeks of clean runs, promote the agent up the trust ladder."
    }
  ],
  "connections": {
    "Cron \u00b7 Daily 7am MDT": {
      "main": [
        [
          {
            "node": "Read ICP from Sheets",
            "type": "main",
            "index": 0
          },
          {
            "node": "Read voice.md from Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Manual \u00b7 Webhook": {
      "main": [
        [
          {
            "node": "Read ICP from Sheets",
            "type": "main",
            "index": 0
          },
          {
            "node": "Read voice.md from Drive",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read ICP from Sheets": {
      "main": [
        [
          {
            "node": "Search \u00b7 HN Algolia",
            "type": "main",
            "index": 0
          },
          {
            "node": "Search \u00b7 Reddit JSON",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read voice.md from Drive": {
      "main": [
        []
      ]
    },
    "Search \u00b7 HN Algolia": {
      "main": [
        [
          {
            "node": "Combine \u00b7 HN + Reddit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Search \u00b7 Reddit JSON": {
      "main": [
        [
          {
            "node": "Combine \u00b7 HN + Reddit",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Combine \u00b7 HN + Reddit": {
      "main": [
        [
          {
            "node": "Discovery \u00b7 Groq Llama 4 Scout",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Discovery \u00b7 Groq Llama 4 Scout": {
      "main": [
        [
          {
            "node": "Parse \u00b7 Extract qualified leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse \u00b7 Extract qualified leads": {
      "main": [
        [
          {
            "node": "Read Sent log",
            "type": "main",
            "index": 0
          },
          {
            "node": "Dedup \u00b7 top 5 fresh leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Read Sent log": {
      "main": [
        [
          {
            "node": "Dedup \u00b7 top 5 fresh leads",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Dedup \u00b7 top 5 fresh leads": {
      "main": [
        [
          {
            "node": "Jina Reader \u00b7 enrich top 5",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Jina Reader \u00b7 enrich top 5": {
      "main": [
        [
          {
            "node": "Summarize \u00b7 Groq Llama 3.1 8B",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Summarize \u00b7 Groq Llama 3.1 8B": {
      "main": [
        [
          {
            "node": "Draft \u00b7 Groq Llama 4 Scout + voice.md",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Draft \u00b7 Groq Llama 4 Scout + voice.md": {
      "main": [
        [
          {
            "node": "Parse \u00b7 Subject + Body",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse \u00b7 Subject + Body": {
      "main": [
        [
          {
            "node": "Gmail \u00b7 createDraft (HITL gate)",
            "type": "main",
            "index": 0
          },
          {
            "node": "Append \u00b7 Sent log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail \u00b7 createDraft (HITL gate)": {
      "main": [
        [
          {
            "node": "Digest \u00b7 Groq Llama 3.1 8B",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Digest \u00b7 Groq Llama 3.1 8B": {
      "main": [
        [
          {
            "node": "Parse \u00b7 Digest fields",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Parse \u00b7 Digest fields": {
      "main": [
        [
          {
            "node": "Gmail \u00b7 Send digest to founder",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Gmail \u00b7 Send digest to founder": {
      "main": [
        [
          {
            "node": "Append \u00b7 Runs audit log",
            "type": "main",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1",
    "errorWorkflow": ""
  },
  "versionId": "bsw-growth-agent-lite-v2.0.0",
  "id": "bsw-growth-agent-lite",
  "meta": {
    "templateCredsSetupCompleted": false,
    "description": "FREE-TIER version of the BSW Growth Agent. Uses Groq's free Llama 4 Scout (drafting/discovery) + Llama 3.1 8B Instant (cheap classification cascade), HN Algolia + Reddit JSON for search (both no-auth), Jina Reader for web extraction (no auth). Total cost: $0 within Groq's free rate limits and n8n.cloud's 14-day trial. Configure ICP / signal_keywords / subreddits / voice.md without touching the workflow JSON. Repo: github.com/sudosoph/bsw26-agentic-workflows \u00b7 MIT licensed."
  },
  "tags": [
    {
      "name": "agentic-workflow"
    },
    {
      "name": "customer-discovery"
    },
    {
      "name": "bsw-2026"
    },
    {
      "name": "open-source"
    },
    {
      "name": "free-tier"
    },
    {
      "name": "groq-llama"
    },
    {
      "name": "jina-reader"
    }
  ]
}