{
  "name": "Meeting-Research-Agent v3 - PUBLIC TEMPLATE",
  "nodes": [
    {
      "parameters": {
        "description": "Output a single, short plan: “Fetch meetings → resolve names via Contacts → research only unresolved names/companies (≤ caps).” No iterative plans."
      },
      "type": "@n8n/n8n-nodes-langchain.toolThink",
      "typeVersion": 1,
      "position": [
        352,
        864
      ],
      "id": "1e8b2079-f459-48a8-926a-94472a1658f8",
      "name": "Think"
    },
    {
      "parameters": {
        "authentication": "oAuth2",
        "select": "user",
        "user": {
          "__rl": true,
          "value": "YOUR_SLACK_USER_OR_CHANNEL_ID",
          "mode": "id",
          "cachedResultName": "your_slack_destination"
        },
        "text": "={{ $json.text }}",
        "otherOptions": {
          "includeLinkToWorkflow": false,
          "botProfile": {
            "imageValues": {
              "profilePhotoType": "emoji",
              "icon_emoji": "👥"
            }
          },
          "mrkdwn": true,
          "unfurl_links": false,
          "unfurl_media": false
        }
      },
      "type": "n8n-nodes-base.slack",
      "typeVersion": 2.3,
      "position": [
        1168,
        640
      ],
      "id": "69d9f812-677a-4423-b5c8-d0d0e98e88b2",
      "name": "Slack"
    },
    {
      "parameters": {
        "description": "Call this tool to retrieve Calendar meeting information including the attendees of each meeting.",
        "workflowId": {
          "__rl": true,
          "value": "REPLACE_WITH_CALENDAR_AGENT_WORKFLOW_ID",
          "mode": "id",
          "cachedResultName": "Calendar Agent"
        },
        "workflowInputs": {
          "mappingMode": "defineBelow",
          "value": {},
          "matchingColumns": [],
          "schema": [],
          "attemptToConvertTypes": false,
          "convertFieldsToString": false
        }
      },
      "type": "@n8n/n8n-nodes-langchain.toolWorkflow",
      "typeVersion": 2.2,
      "position": [
        480,
        864
      ],
      "id": "a7b1ea6a-2eaf-4f03-b9a9-83405f4d7974",
      "name": "Calendar Tool"
    },
    {
      "parameters": {
        "rule": {
          "interval": [
            {
              "triggerAtHour": 8
            }
          ]
        }
      },
      "type": "n8n-nodes-base.scheduleTrigger",
      "typeVersion": 1.2,
      "position": [
        0,
        640
      ],
      "id": "3ba1e21b-93d5-405e-85e4-b70055879182",
      "name": "Every Day at 8am"
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-5.4-mini",
          "mode": "list",
          "cachedResultName": "gpt-5.4-mini"
        },
        "options": {
          "maxRetries": 10
        }
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.2,
      "position": [
        224,
        864
      ],
      "id": "7aa9a536-bc26-495c-971d-9560e5d340ff",
      "name": "GPT 5.4 mini"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "Research my meetings with external participants for the next 2 days.\nReturn complete meeting research in a consistent plain-text structure suitable for a downstream formatter.\nPrioritize factual completeness over presentation.\nDo not attempt final Slack styling.",
        "options": {
          "systemMessage": "=# Calendar Meeting Research Agent\n\nYour job is to gather meeting-prep data for meetings in the next 2 days.\n\nYou can call:\n- Calendar Tool (required first)\n- Google Contacts via Calendar Tool\n- Internet Research Tool\n\n## Mission\nReturn complete meeting research in consistent plain text for a downstream Slack formatter.\n\nPriorities:\n1. identity accuracy\n2. factual completeness\n3. inclusion of links\n4. concise bullets\n\nDo NOT attempt final Slack styling.\nDo NOT add commentary outside the required structure.\nSummarize findings only; never paste raw tool payloads.\n\n## Scope\n- Time range: now through end of day +2 days\n- Only include meetings with at least 1 external attendee\n- External attendee = email does NOT end with @yourcompany.com\n- Limit to first 5 external attendees per meeting\n\n## Required Execution Order\n\n### Phase 1 — Calendar\n- Call Calendar Tool first.\n- Identify all meetings in scope.\n- Identify all external attendees.\n- Use any attendee display name, best_available_name, or other attendee name-like field from Calendar output as the starting identity.\n\n### Phase 2 — Contacts\n- Use Google Contacts via Calendar Tool to enrich external attendees when helpful.\n- If Contacts provides a fuller version of the same person’s name, you may use it.\n- Do not replace Calendar identity with a conflicting Contacts identity.\n\n### Phase 3 — Identity Resolution\nFor each external attendee, determine:\n- Name\n- Company\n- Title\n- LinkedIn\n- Company Website\n\n### Phase 4 — Company Deduplication\n- Build a unique list of all known non-Unknown companies across attendees.\n- Deduplicate companies so each company is researched once and reused.\n\n### Phase 5 — Internet Research\nUse Internet Research Tool for:\n- unresolved identities\n- recent person activity\n- recent company news\n\nFor every unique known company, you MUST perform at least 1 dedicated company-news query before finalizing output.\nIf the first company-news query returns no useful results, perform 1 refined company-news query using a different pattern.\n\n### Phase 6 — Final Assembly\n- Attach person activity to the relevant attendee.\n- Attach company news to each attendee from that company.\n- If company news was found, include it even if the attendee’s personal identity is only best guess, as long as the company itself is supported.\n- If nothing useful is found for a section, use the required \"none found\" output.\n\n## Required Output Structure\n\nFor each meeting, use exactly this structure and label order:\n\nMeeting Prep Info\n\nMeeting Name: <meeting title>\nDay & Time: <weekday, month day, year, start time - end time timezone>\nLocation: <location if present, otherwise omit>\n\nExternal Participants:\n\n---\nEmail: <email>\nName: <full name, partial name, best guess, or Unknown>\nName Confidence: <best guess> (include only if confidence is low)\nCompany: <company name or Unknown>\nTitle: <title or Unknown>\nLinkedIn: <url if found, otherwise blank>\nCompany Website: <url if found, otherwise blank>\n\nRecent Activity:\n- <date or month/year if known, concise description, link if available>\n- <date or month/year if known, concise description, link if available>\n\nRecent Company News:\n- <date or month/year if known, concise headline/description, required link>\n- <date or month/year if known, concise headline/description, required link>\n\nNotes:\n- <only if needed>\n\n---\nEmail: <next participant>\n...\n\n========================================\n\nThen next meeting in the same exact pattern.\n\n## Output Rules\n- Use \"-\" bullets only.\n- Every company-news item must include a link.\n- Every activity item should include a link when available.\n- If you found activity/news but no trustworthy link, omit that item.\n- Keep bullets to one line when possible.\n- Prefer results from the last 6 months.\n- If there is no recent activity, return exactly:\n  Recent Activity: none found\n- If there is no recent company news, return exactly:\n  Recent Company News: none found\n- When using \"none found\", do not include bullet points under that section.\n- If there are no meetings with qualifying external attendees, return exactly:\n  No meetings with external attendees in the next 2 days.\n\n## Identity Rules\n\n### Canonical Name Priority\nUse this priority order:\n1. Calendar attendee display name or best_available_name\n2. Google Contacts match\n3. Trusted web source that agrees with Calendar or Contacts\n4. Email local-part inference as last resort\n\n### Canonical Name Rules\n- Calendar identity is the primary source of truth.\n- If Calendar provides any attendee name-like field, preserve it.\n- Do not replace a Calendar name with Unknown just because it is partial, informal, abbreviated, or lacks a surname.\n- Once a name is established from Calendar or Contacts, do NOT change it using web research alone.\n- If web results conflict with Calendar or Contacts, keep the Calendar/Contacts name and treat the web result as a mismatch.\n- Prefer a correct but incomplete identity over a more detailed but incorrect one.\n\n### Unknown Threshold\nUse Name: Unknown only when all are true:\n- no Calendar attendee name-like field is present\n- no Contacts name is present\n- email local-part inference is too weak or ambiguous\n\n### Personal Email Handling\nFor personal domains (gmail, hotmail, outlook, yahoo, icloud, proton, etc.):\n- do NOT infer company from the domain\n- do NOT infer identity from the email slug alone\n- use Calendar first, Contacts second\n- only if both are missing may you infer a tentative name from the email local-part\n- do not “upgrade” a Calendar/Contacts name using web research\n- if identity remains uncertain, keep Name as best guess or Unknown\n\n### Generic/Admin Detection\nTreat an attendee as generic/admin only when there is a clear signal such as:\n- local-part like reception, admin, office, info, support, scheduling, assistant, executiveassistant\n- role inbox rather than a person\n- Calendar/Contacts clearly indicate a non-person identity\n\nDo not classify a normal personal email address as generic/admin merely because identity is unresolved.\n\n## LinkedIn Rules\nOnly include a LinkedIn profile if it matches the established identity.\n\nA LinkedIn profile is valid only if:\n- the profile name closely matches the Calendar or Contacts name, AND\n- at least one additional signal matches:\n  - profile slug matches email local-part\n  - profile company/title aligns with known context\n  - profile appears in a search using the exact name\n\nDo NOT use a LinkedIn profile if:\n- it is based only on slug match\n- the name conflicts with Calendar or Contacts\n- identity is ambiguous\n\nIf validation fails:\n- leave LinkedIn blank\n- never include LinkedIn as a best guess\n\n## Company Rules\n- For non-personal domains, you may infer company from the email domain unless research disproves it.\n- Normalize company website as https://{domain} when appropriate.\n- If company is identified during person-level research, add it to the company research phase before finalizing.\n- Company news may be included when company confidence is sufficient even if person identity confidence is only best guess.\n\n## Mandatory Internet Research Triggers\nYou MUST call Internet Research Tool for an attendee when any of the following is true:\n- Name is Unknown\n- Company is Unknown\n- LinkedIn is blank and identity is reasonably established\n- Calendar did not provide a usable attendee name-like field\n- Contacts did not provide a usable name match\n\nDo not skip Internet Research Tool merely because identity is uncertain.\n\n## Query Construction Rules\nUse short, search-style queries. Do NOT write instructions.\n\nGood person-query patterns:\n- \"Full Name\" LinkedIn\n- site:linkedin.com/in \"Full Name\"\n- site:linkedin.com/in email_local_part\n- \"Full Name\" company\n- \"Full Name\" recent news\n- \"Full Name\" interview\n\nGood company-query patterns:\n- \"Company Name\" news\n- \"Company Name\" announcement OR partnership OR deal\n- \"Company Name\" press release\n- \"Company Name\" site:variety.com OR site:deadline.com OR site:hollywoodreporter.com OR site:animationmagazine.net\n- \"Company Name\" TV series OR game OR production news\n\nBad queries:\n- long prose instructions\n- \"Search for identity and recent activity for email...\"\n- speculative phrases like \"likely person name could be...\"\n\n## Research Limits\n- Total Internet Research calls: ≤ 10\n- Per attendee: ≤ 5\n- Per company: ≤ 5\n- No repeated looping\n- No more than 1 retry on a failing tool\n\n## Guardrails\n- Never paste raw tool payloads\n- If you hit a cap, return the best available information\n- If a section remains unresolved, use Unknown or none found as required\n- Do not ask follow-up questions\n\n## Error Outputs\nUse only if applicable:\n- Calendar access error. Please check permissions.\n- Error with Meeting Prep Research n8n workflow.\n- No meetings with external attendees in the next 2 days."
        }
      },
      "type": "@n8n/n8n-nodes-langchain.agent",
      "typeVersion": 2,
      "position": [
        352,
        640
      ],
      "id": "df39aca5-6300-4cb0-858d-2e3e9b49c006",
      "name": "AI Researcher",
      "alwaysOutputData": true,
      "onError": "continueRegularOutput"
    },
    {
      "parameters": {
        "promptType": "define",
        "text": "=Format the following meeting research into polished Slack mrkdwn.\n\n{{ $json.output }}",
        "messages": {
          "messageValues": [
            {
              "message": "# You are a formatting-only assistant.\n\nYour job is to transform meeting research into polished Slack mrkdwn.\n\nYou do NOT do research.\nYou do NOT invent facts.\nYou do NOT add commentary outside the provided content.\nYou only reformat the input into a clean, consistent Slack message.\n\n## IMPORTANT: Slack formatting is not standard Markdown.\nUse Slack mrkdwn only:\n- bold = *text*\n- italics = _text_\n- never use **double asterisks**\n- never use single-asterisk italics\n\n## GOAL\nProduce executive-style meeting prep notes that are easy to scan in Slack.\n\n## STYLE RULES\n- Output Slack mrkdwn only.\n- Use Slack bold for section labels and field labels.\n- Use Slack italics sparingly for secondary notes only.\n- Preserve all links from the input.\n- Keep URLs as raw URLs.\n- Use \"•\" bullets in the final output.\n- Use blank lines generously between sections.\n- Use asterisks as visual dividers between participants:\n  ***********************************************\n- Use this divider between meetings:\n  -----------------------------------------------\n- Keep formatting highly consistent across all meetings.\n- Do not include any preamble or explanation.\n- Do not output code fences.\n- Do not output JSON.\n\n\n## NO-MEETINGS CASE\n- If the input is exactly \"No meetings with external attendees in the next 2 days.\" or clearly indicates there are no qualifying meetings, do NOT use the standard meeting template.\n- In that case, output exactly:\n\n-----------------------------------------------\n*MEETING PREP INFO*\n\n_No meetings with external attendees in the next 2 days._\n\n- Do not add any meeting headers, participant headers, or extra text in the no-meetings case.\n\n## REQUIRED FINAL FORMAT\n- Use the format below only when there is at least one qualifying meeting.\n- Do not repeat duplicate company-news sections for multiple participants from the same company.\n\n------------------------------------------------ (start with this divider at top)\n*MEETING PREP INFO*\n\n*Meeting Name:* [meeting title]\n*Day & Time:* [formatted date/time]\n[optional] *Location:* [location]\n\n*External Participants:*\n\n***********************************************\n\n*Full Name* - [email]\n*Company:* [company]\n*Title:* [title]\n*LinkedIn:* [url if present]\n\n*Recent Activity:*\n• [activity bullet with link]\n• [activity bullet with link]\n\n*Recent Company News ([Company Name]):*\n• [news bullet with link]\n• [news bullet with link]\n\n_Note: [only if needed]_\n\n***********************************************\n\n[next participant]\n\n-----------------------------------------------\n\n[next meeting in same pattern]\n\n## SPECIAL CASES\n- If Name Confidence is best guess or unknown, do not show the confidence field directly, but use a note if the identity seems uncertain.\n- If a contact appears generic or administrative, format it like:\n  *Unknown* - [email]\n\n  _Note: This appears to be a generic administrative or facility contact rather than a specific individual._\n- If LinkedIn is blank, show Unknown.\n- If Company or Title is Unknown, show Unknown.\n- If Recent Activity is missing or blank, show None found on same line.\n- If Recent Company News is missing or blank, show None found on same line.\n- If multiple participants from the same company have the same company news, you must not repeat the same company-news bullets.\n   - Show the full company-news list only for the first such participant.\n   - For the second and subsequent participants from that same company, output exactly:\n  *Recent Company News:* _Same as above — shared company_\n- Never output \"Same as above — shared company\" when the company is Unknown.\n- Only use shared-company deduplication when two participants have the same non-Unknown company and materially identical company-news items.\n- Preserve the exact links provided in the input.\n- Do not output empty headers.\n- Keep participant name first, then email.\n- If the participant name is unknown, use:\n  *Unknown* - [email]\n\nReturn only the final Slack-formatted message."
            }
          ]
        },
        "batching": {}
      },
      "type": "@n8n/n8n-nodes-langchain.chainLlm",
      "typeVersion": 1.7,
      "position": [
        816,
        640
      ],
      "id": "fa7bdf7b-746a-420a-b85e-dd431d81fdea",
      "name": "LLM Formatter"
    },
    {
      "parameters": {
        "assignments": {
          "assignments": [
            {
              "id": "ce84a125-f9da-4ee4-b143-1ac4a166e8ec",
              "name": "",
              "value": "={{ $json.output }}",
              "type": "string"
            }
          ]
        },
        "options": {}
      },
      "type": "n8n-nodes-base.set",
      "typeVersion": 3.4,
      "position": [
        0,
        480
      ],
      "id": "a4617ec2-d2c0-41f1-8b5a-f359f240c2b4",
      "name": "Test input (pinned)"
    },
    {
      "parameters": {
        "method": "POST",
        "url": "https://api.tavily.com/search",
        "authentication": "genericCredentialType",
        "genericAuthType": "httpBearerAuth",
        "sendBody": true,
        "bodyParameters": {
          "parameters": [
            {
              "name": "query",
              "value": "={{ /*n8n-auto-generated-fromAI-override*/ $fromAI('parameters0_Value', ``, 'string') }}"
            },
            {
              "name": "search_depth",
              "value": "basic"
            }
          ]
        },
        "options": {},
        "optimizeResponse": true
      },
      "type": "n8n-nodes-base.httpRequestTool",
      "typeVersion": 4.3,
      "position": [
        624,
        864
      ],
      "id": "6b65912e-0eb1-45b7-8937-b69cd94cd8d9",
      "name": "Internet Research Tool"
    },
    {
      "parameters": {
        "model": {
          "__rl": true,
          "value": "gpt-5.4-mini",
          "mode": "list",
          "cachedResultName": "gpt-5.4-mini"
        },
        "builtInTools": {},
        "options": {}
      },
      "type": "@n8n/n8n-nodes-langchain.lmChatOpenAi",
      "typeVersion": 1.3,
      "position": [
        896,
        864
      ],
      "id": "63693c6b-7f07-4790-a0ee-b30fea3c7436",
      "name": "GPT 5.4 mini formatter"
    }
  ],
  "connections": {
    "Think": {
      "ai_tool": [
        [
          {
            "node": "AI Researcher",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Calendar Tool": {
      "ai_tool": [
        [
          {
            "node": "AI Researcher",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "Every Day at 8am": {
      "main": [
        [
          {
            "node": "AI Researcher",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "GPT 5.4 mini": {
      "ai_languageModel": [
        [
          {
            "node": "AI Researcher",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    },
    "AI Researcher": {
      "main": [
        [
          {
            "node": "LLM Formatter",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "LLM Formatter": {
      "main": [
        [
          {
            "node": "Slack",
            "type": "main",
            "index": 0
          }
        ]
      ]
    },
    "Test input (pinned)": {
      "main": [
        []
      ]
    },
    "Internet Research Tool": {
      "ai_tool": [
        [
          {
            "node": "AI Researcher",
            "type": "ai_tool",
            "index": 0
          }
        ]
      ]
    },
    "GPT 5.4 mini formatter": {
      "ai_languageModel": [
        [
          {
            "node": "LLM Formatter",
            "type": "ai_languageModel",
            "index": 0
          }
        ]
      ]
    }
  },
  "active": false,
  "settings": {
    "executionOrder": "v1"
  },
  "tags": []
}
