Generated banner for Event Prospecting
Generated logo for Event Prospecting

Event Prospecting

BrowserbaseOfficialCreated by browserbase

  • Skill
  • Workflow
  • Business and productivity
  • MIT
  • Low risk
  • Has scripts

From the author

Event prospecting skill. Takes a conference / event speakers URL, extracts the people, filters their companies against the user's ICP, then deep-researches only the speakers at ICP-fit companies. Outputs a person-first HTML report where each card answers "why should the AE talk to this person?" with all public links and a one-click DM opener. Use when the user wants to: (1) find leads at a specific conference, (2) prep for an event, (3) research event speakers, (4) build a target list from a sponsor/exhibitor page, (5) scrape conference speakers and rank by ICP fit. Triggers: "find leads at {event}", "research speakers at", "prospect this conference", "stripe sessions leads", "ai engineer summit prospects", "event prospecting", "scrape conference speakers", "who should I meet at".
— the description by browserbase

AutoGineer summary

Written by AutoGineer from the skill’s own files, not by the author

This skill streamlines event preparation by automating the identification and research of conference speakers. It extracts speaker lists from event websites, filters companies based on a user-defined Ideal Customer Profile (ICP), and performs deep research on high-fit targets. The final output is a structured HTML report containing actionable insights, public links, and DM openers for each prospect, designed to help account executives prioritize their outreach efforts.

What it does

  • Extracts speaker lists from various event platforms using automated detection.
  • Filters companies against a user-provided ICP to identify high-potential leads.
  • Performs deep research on ICP-fit companies and their respective speakers.
  • Generates a person-first HTML report with company-grouped cards.
  • Provides a CSV file ready for cold-outbound import.
  • Creates one-click DM openers based on public signals and event context.

When to use it

  • When you need to identify and rank leads at a specific conference.
  • When preparing for an upcoming event to maximize networking efficiency.
  • When you want to research event speakers to personalize your outreach.
  • When building a target list from event sponsor or exhibitor pages.

Advantages

  • Saves significant time by automating manual research and data extraction.
  • Ensures outreach is focused on high-fit prospects based on your specific ICP.
  • Provides context-rich insights that help craft personalized, relevant messages.
  • Standardizes the prospecting workflow across different events and platforms.

Usage examples

“find leads at {event}”
The agent extracts speakers from the specified event URL, filters them by your ICP, and generates an HTML report of high-fit prospects.
“research speakers at {event}”
The agent performs deep research on speakers associated with companies that match your ICP, providing a rationale for why you should reach out.
“prospect this conference”
The agent initiates the full prospecting pipeline, from event platform detection to the final compilation of an outreach-ready report.
“who should I meet at {event}”
The agent identifies the most relevant speakers based on your profile and presents them in a ranked list with actionable outreach hooks.

Install

  1. Download the skill as a ZIP:

    https://autogineer.com/agent-skills/browserbase/event-prospecting/download
  2. Unzip it into the cross-client skills folder of your project:

    mkdir -p .agents/skills && unzip event-prospecting.zip -d .agents/skills

Documentation by browserbase

Event Prospecting

Take a conference URL → get a ranked list of people the AE should talk to, with a "why reach out" rationale per person.

Required: BROWSERBASE_API_KEY env var and the browse CLI installed (npm install -g browse). Use browse cloud ... for API calls and browse open / browse get markdown for JS-heavy speaker pages.

Path rules: Always use the full literal path in all Bash commands — NOT ~ or $HOME (both trigger "shell expansion syntax" approval prompts). Resolve the home directory once and use it everywhere. When constructing subagent prompts, replace {SKILL_DIR} with the full literal path (typically /Users/jay/skills/skills/event-prospecting).

Output directory: All event prospecting output goes to ~/Desktop/{event_slug}_prospects_{YYYY-MM-DD-HHMM}/. Final deliverable is index.html (people grouped by company, ranked by company ICP), with companies.html and people.html (filterable) as alternate views, plus results.csv for cold-outbound import.

CRITICAL — Tool restrictions (applies to main agent AND all subagents):

  • All web searches: use browse cloud search. NEVER use WebSearch.

  • All page content extraction: use node {SKILL_DIR}/scripts/extract_page.mjs "<url>". This script fetches via browse cloud fetch --output, parses title + meta tags + visible body text, and automatically falls back to browse get markdown when fetch fails or returns thin JS-rendered content. NEVER hand-roll a browse cloud fetch | sed pipeline. NEVER use WebFetch.

  • All research output: subagents write one markdown file per company OR per person to {OUTPUT_DIR}/companies/{slug}.md or {OUTPUT_DIR}/people/{slug}.md using bash heredoc. NEVER use the Write tool or python3 -c. See references/example-research.md for both file formats.

  • Report compilation: use node {SKILL_DIR}/scripts/compile_report.mjs {OUTPUT_DIR} --open.

  • Subagents must use ONLY the Bash tool. No other tools allowed.

  • HARD TOOL-CALL CAPS: ICP triage = 1 call/company; deep research = 5 calls/company; person enrichment = 4 calls/person. See references/workflow.md for enforcement detail.

CRITICAL — Anti-hallucination rules (applies to main agent AND all subagents):

  • NEVER infer product_description, industry, or a person's role_reason from a site's fonts, framework, design system, or typography. These are cosmetic and say nothing about what the company sells or what the person does.

  • NEVER let the user's own ICP leak into a target's description. If you don't know what the target does, write Unknown — do not pattern-match them onto the ICP.

  • product_description MUST quote or paraphrase a specific phrase from extract_page.mjs output. If none of TITLE/META/OG/HEADINGS/BODY yield a recognizable product statement, write Unknown — homepage content not accessible and cap icp_fit_score at 3.

  • A person's hook MUST quote or paraphrase a specific finding from a browse cloud search result (podcast title, blog headline, GitHub repo, talk abstract). If no public signal exists in the last 6 months, fall back to event-context (their talk title at this event).

CRITICAL — Minimize permission prompts:

  • Subagents MUST batch ALL file writes into a SINGLE Bash call using chained heredocs. One Bash call = one permission prompt.

  • Batch ALL searches and ALL fetches into single Bash calls using && chaining.

Pipeline Overview

Follow these 10 steps in order. Do not skip steps or reorder.

  1. Setup — output dir + clean slate

  2. Load profile — read profiles/{user_slug}.json

  3. Recon — detect event platform

  4. Extract people — people.jsonl

  5. Group by company — seed_companies.txt

  6. ICP triage — fast company-level scoring (1 call/company)

  7. Filter — companies with icp_fit_score >= --icp-threshold

  8. Deep research — full Plan→Research→Synthesize on ICP fits

  9. Enrich speakers — ask user: ICP-fit only (default) or all speakers

  10. Compile report — HTML + CSV, open in browser

The user invokes the skill with a URL like /event-prospecting <URL>. Parse EVENT_URL from that invocation message. Defaults: DEPTH=deep, ICP_THRESHOLD=6. The USER_SLUG (ICP profile) is auto-resolved in Step 1 from whatever profile files exist locally — there is no built-in default profile. Do NOT ask the user to confirm the URL — they already gave you it.


Step 0: Setup Output Directory

Derive the output directory from the URL the user gave you. Do NOT hardcode any event name.

# EVENT_URL came from the invocation message (whatever the user typed after `/event-prospecting`)
EVENT_SLUG=$(node -e 'const h = new URL(process.argv[1]).hostname.replace(/^www\./,""); console.log(h.split(".")[0])' "$EVENT_URL")
TIMESTAMP=$(date +%Y-%m-%d-%H%M)
OUTPUT_DIR=/Users/jay/Desktop/${EVENT_SLUG}_prospects_${TIMESTAMP}
mkdir -p "$OUTPUT_DIR/companies" "$OUTPUT_DIR/people"

Use the full literal home path — never ~ or $HOME. Pass {OUTPUT_DIR} as the full literal path to all subagent prompts.

Step 1: Load User Profile

The profile defines the ICP that ICP triage and deep research score against. Load from {SKILL_DIR}/profiles/{user_slug}.json (interchangeable across all GTM skills — same shape as company-research). example.json is a template, not a real profile — never use it.

DO NOT look outside {SKILL_DIR}/profiles/ for profiles — never reach into other skills' directories. If a profile is needed elsewhere, the user copies it explicitly.

Resolution order:

  1. If the user invoked with --user-company <slug>, use that slug.

  2. Else, list profiles/*.json excluding example.json. If exactly one profile exists, use it (and tell the user which one). If multiple exist, ask the user (plain chat) which one.

  3. If zero profiles exist, fail loudly and instruct the user to create one (copy profiles/example.json to profiles/<your_slug>.json and fill it in, or run the company-research skill which builds one automatically).

PROFILES=$(ls {SKILL_DIR}/profiles/*.json 2>/dev/null | xargs -n1 basename | sed 's/\.json$//' | grep -v '^example$')
COUNT=$(echo "$PROFILES" | grep -c .)

if [ -z "$USER_SLUG" ]; then
  if [ "$COUNT" -eq 0 ]; then
    echo "No profiles found in {SKILL_DIR}/profiles/. Copy profiles/example.json to profiles/<your_slug>.json and fill it in, or run the company-research skill to build one."
    exit 1
  elif [ "$COUNT" -eq 1 ]; then
    USER_SLUG=$PROFILES
    echo "Using the only profile available: ${USER_SLUG}"
  else
    echo "Multiple profiles found:"
    echo "$PROFILES" | sed 's/^/  - /'
    echo "Re-invoke with --user-company <slug> to pick one."
    exit 1
  fi
fi

test -f {SKILL_DIR}/profiles/${USER_SLUG}.json || {
  echo "Profile not found: profiles/${USER_SLUG}.json"
  exit 1
}
cat {SKILL_DIR}/profiles/${USER_SLUG}.json

The profile yields: company, product, icp_description, existing_customers. These get embedded verbatim in every subagent prompt downstream.

Step 2: Recon

Detect the event platform and extraction strategy. One command:

node {SKILL_DIR}/scripts/recon.mjs {EVENT_URL} {OUTPUT_DIR}

Writes {OUTPUT_DIR}/recon.json with platform, strategy, and (for Next.js) nextDataPaths. See references/event-platforms.md for the platform catalog and detection priority.

Expected outcomes:

  • Stripe Sessions class (Next.js): platform: "next-data", 1-3 paths

  • Sessionize: platform: "sessionize"

  • Lu.ma / Eventbrite: platform: "luma" | "eventbrite"

  • Anything else: platform: "custom", strategy: "markdown" (best-effort fallback)

Step 3: Extract People

node {SKILL_DIR}/scripts/extract_event.mjs {OUTPUT_DIR} --user-company {USER_SLUG}

Reads recon.json, dispatches to the platform-specific extractor, writes people.jsonl (one speaker per line) and seed_companies.txt (deduped companies).

The --user-company flag also drops the host-org's own employees (a Stripe-hosted event drops Stripe employees) and the user's own employees from the speaker list — those aren't prospects.

Sanity-check the output:

wc -l {OUTPUT_DIR}/people.jsonl {OUTPUT_DIR}/seed_companies.txt
head -3 {OUTPUT_DIR}/people.jsonl

If people.jsonl is empty or under ~10 lines, recon picked the wrong platform — see references/event-platforms.md and re-run with adjusted strategy.

Step 4: Group by Company

extract_event.mjs emits seed_companies.txt already (one company per line, deduped, sorted). This step is informational — verify the count looks reasonable before fanning out:

wc -l {OUTPUT_DIR}/seed_companies.txt

Expected: roughly 0.4-0.6× the speaker count (most events have ~2 speakers per company on average, some companies send 5+, many send 1).

Step 5: ICP Triage

Fast pass — one tool call per company, no deep research. Score every company in seed_companies.txt against the user's ICP and write a thin triage stub to companies/{slug}.md. Companies with icp_fit_score >= --icp-threshold (default 6) advance to Step 7's deep research; the rest stay as triage stubs.

Dispatch pattern: split seed_companies.txt into batches of ~10 and fan out N subagents in a SINGLE Agent batch (multiple Agent tool calls in one message). Each subagent runs the prompt from references/workflow.md → "ICP Triage" section. Hard cap: 1 tool call per company (just extract_page.mjs on the homepage), enforced via the # browse call N/1 comment pattern.

# Build batch files: each batch line is "name|guessed_homepage|slug".
# extract_event.mjs only emits company NAMES (no URLs), so we slugify and guess
# https://{slug-without-spaces}.com as the canonical homepage. The triage subagent
# is allowed to write product_description: "Unknown — homepage content not accessible"
# and cap score at 3 if the guessed URL 404s — that's the documented fallback in
# workflow.md (rule 3 of the ICP Triage prompt). Burning a real browse cloud search to
# discover the URL would bust the 1-call-per-company HARD CAP.
node -e '
const fs = require("fs");
const slugify = (s) => (s || "").toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "");
const seed = fs.readFileSync("{OUTPUT_DIR}/seed_companies.txt", "utf-8").split("\n").filter(Boolean);
const lines = seed.map(c => {
  const slug = slugify(c);
  const guessedHost = c.toLowerCase().replace(/[^a-z0-9]/g, "");
  return `${c}|https://${guessedHost}.com|${slug}`;
});
fs.writeFileSync("{OUTPUT_DIR}/_seed_with_urls.txt", lines.join("\n") + "\n");
'

# Split into ~10-company batches
split -l 10 {OUTPUT_DIR}/_seed_with_urls.txt {OUTPUT_DIR}/_batch_triage_

# Count batches → number of subagents to dispatch (cap at 6 per message; second wave for the rest)
ls {OUTPUT_DIR}/_batch_triage_* | wc -l

Then in a single message, dispatch one Agent call per batch (up to 6 in parallel; subsequent waves after the first returns). Each Agent gets the prompt from references/workflow.md → "ICP Triage" with these substitutions before sending:

  • {SKILL_DIR} → full literal skill path (e.g. /Users/jay/skills/skills/event-prospecting)

  • {OUTPUT_DIR} → full literal output path

  • {USER_COMPANY}, {USER_PRODUCT}, {ICP_DESCRIPTION} → from the loaded profile

  • {EVENT_NAME} → recon.json .title

  • {COMPANY_LIST} → contents of the batch file (e.g. cat {OUTPUT_DIR}/_batch_triage_aa)

  • {TOTAL} → number of lines in this batch (substitute into # browse call N/{TOTAL})

Agent dispatch (skeleton, repeat per batch in one message):

Agent(
  description: "ICP triage batch aa",
  prompt: <ICP Triage prompt from workflow.md with all placeholders substituted>,
  subagent_type: "general-purpose"
)
Agent(
  description: "ICP triage batch ab",
  prompt: <same prompt template, COMPANY_LIST swapped to batch ab>,
  subagent_type: "general-purpose"
)
... up to 6 per message

After all subagents return, verify every company in seed_companies.txt has a corresponding companies/{slug}.md:

ls {OUTPUT_DIR}/companies/*.md | wc -l
# Should equal `wc -l {OUTPUT_DIR}/seed_companies.txt`

Clean up the batch files: rm {OUTPUT_DIR}/_batch_triage_*.

Step 6: Filter by ICP Threshold

Read each companies/*.md frontmatter, keep those with icp_fit_score >= 6 (or whatever --icp-threshold is). Write the surviving company slugs to {OUTPUT_DIR}/icp_fits.txt:

THRESHOLD=6   # from --icp-threshold flag
for f in {OUTPUT_DIR}/companies/*.md; do
  score=$(awk '/^icp_fit_score:/{print $2; exit}' "$f")
  if [ -n "$score" ] && [ "$score" -ge "$THRESHOLD" ]; then
    basename "$f" .md
  fi
done > {OUTPUT_DIR}/icp_fits.txt

wc -l {OUTPUT_DIR}/icp_fits.txt

Expected: 20-40% of seed_companies.txt. If the survival rate is < 10%, the threshold may be too high or the ICP description too narrow — surface a warning to the user.

Step 7: Deep Research

Full Plan→Research→Synthesize on ICP-fit companies only. Hard cap: 5 tool calls per company (homepage extract + 2-3 sub-question searches + 1-2 supplementary fetches). Subagents OVERWRITE the existing companies/{slug}.md triage stub with the richer deep-research version (frontmatter triage_only: false).

Dispatch pattern: split icp_fits.txt into batches of ~5 (deep mode default) and fan out one Agent per batch in a SINGLE message (up to 6 Agents per message). Each Agent gets the prompt from references/workflow.md → "Deep Research" with these substitutions:

  • {SKILL_DIR}, {OUTPUT_DIR}, {USER_COMPANY}, {USER_PRODUCT}, {ICP_DESCRIPTION}

  • {EVENT_NAME} (from recon.json .title), {EVENT_CONTEXT} (track / topic, manually inferred from the event homepage)

  • {COMPANY_LIST} → contents of the batch file (each line slug|website)

# Build {company-slug|website} pairs by reading frontmatter from each triage stub
while read slug; do
  website=$(awk '/^website:/{print $2; exit}' {OUTPUT_DIR}/companies/${slug}.md)
  echo "${slug}|${website}"
done < {OUTPUT_DIR}/icp_fits.txt > {OUTPUT_DIR}/_deep_targets.txt

# Split into ~5-company batches (deep mode)
split -l 5 {OUTPUT_DIR}/_deep_targets.txt {OUTPUT_DIR}/_batch_deep_
ls {OUTPUT_DIR}/_batch_deep_* | wc -l

Agent dispatch (skeleton, repeat per batch in one message):

Agent(
  description: "Deep research batch aa",
  prompt: <Deep Research prompt from workflow.md with all placeholders substituted; COMPANY_LIST = cat _batch_deep_aa>,
  subagent_type: "general-purpose"
)
Agent(
  description: "Deep research batch ab",
  prompt: <same template, COMPANY_LIST = cat _batch_deep_ab>,
  subagent_type: "general-purpose"
)
... up to 6 per message; second wave after the first returns

After all subagents return, verify the deep-research files exist and have triage_only: false:

grep -l "triage_only: false" {OUTPUT_DIR}/companies/*.md | wc -l
# Should equal wc -l icp_fits.txt

Step 8: Enrich Speakers

Per person: harvest LinkedIn URL, recent activity (podcast / blog / talk / GitHub / X), and write people/{slug}.md. Hard cap: 4 tool calls per person, three lanes:

  1. browse cloud search "{name} {company} linkedin" (always)

  2. browse cloud search "{name} podcast OR talk OR blog 2026" (deep+)

  3. browse cloud search "{name} github" (deeper)

  4. browse cloud search "{name} site:x.com OR site:twitter.com" (deeper, best-effort)

Quick mode: skip Step 8 entirely. Deep mode: lanes 1-2. Deeper mode: lanes 1-4.

Step 8a — Ask the user: scope of enrichment

Before dispatching, compute the two candidate counts and ask the user to choose. The default is ICP-fit only (faster, cheaper, what most users want); enriching every speaker is opt-in because cost scales linearly with people enriched.

TOTAL=$(wc -l < {OUTPUT_DIR}/people.jsonl)
ICP_FITS=$(node -e '
const fs = require("fs");
const fits = new Set(fs.readFileSync("{OUTPUT_DIR}/icp_fits.txt", "utf-8").split("\n").filter(Boolean));
const slug2name = {};
for (const slug of fits) {
  const md = fs.readFileSync(`{OUTPUT_DIR}/companies/${slug}.md`, "utf-8");
  const m = md.match(/^company_name:\s*(.+)$/m);
  if (m) slug2name[slug] = m[1].trim();
}
const want = new Set(Object.values(slug2name).map(s => s.toLowerCase()));
const ppl = fs.readFileSync("{OUTPUT_DIR}/people.jsonl","utf-8").split("\n").filter(Boolean).map(JSON.parse);
console.log(ppl.filter(p => p.company && want.has(p.company.toLowerCase())).length);
')

# Lanes per person: 2 (deep) or 4 (deeper) — match {DEPTH}
LANES=2   # or 4 for deeper
echo "ICP fits: ${ICP_FITS} speakers × ${LANES} = $((ICP_FITS * LANES)) calls"
echo "All:      ${TOTAL} speakers × ${LANES} = $((TOTAL * LANES)) calls"

Then ask via AskUserQuestion — clean two-option choice with the quantified cost on each:

AskUserQuestion(questions: [
  {
    question: "Enrich which speakers?",
    header: "Enrichment scope",
    multiSelect: false,
    options: [
      { label: "ICP fits only", description: "${ICP_FITS} speakers, ~$((ICP_FITS * LANES)) calls (recommended)" },
      { label: "All speakers", description: "${TOTAL} speakers, ~$((TOTAL * LANES)) calls" }
    ]
  }
])

Save the chosen scope as ENRICH_SCOPE=icp_fits or ENRICH_SCOPE=all. If the user picks "All speakers" and TOTAL × LANES > 600, print a warning and ask once more — that's a 10+ minute run with hundreds of tool calls.

Step 8b — Filter and batch
# Build _people_to_enrich.jsonl based on ENRICH_SCOPE
if [ "$ENRICH_SCOPE" = "all" ]; then
  cp {OUTPUT_DIR}/people.jsonl {OUTPUT_DIR}/_people_to_enrich.jsonl
else
  node -e '
const fs = require("fs");
const fits = new Set(fs.readFileSync("{OUTPUT_DIR}/icp_fits.txt", "utf-8").split("\n").filter(Boolean));
const slug2name = {};
for (const slug of fits) {
  const md = fs.readFileSync(`{OUTPUT_DIR}/companies/${slug}.md`, "utf-8");
  const m = md.match(/^company_name:\s*(.+)$/m);
  if (m) slug2name[slug] = m[1].trim();
}
const wantNames = new Set(Object.values(slug2name).map(s => s.toLowerCase()));
const lines = fs.readFileSync("{OUTPUT_DIR}/people.jsonl", "utf-8").split("\n").filter(Boolean);
const keep = lines.filter(l => {
  const p = JSON.parse(l);
  return p.company && wantNames.has(p.company.toLowerCase());
});
fs.writeFileSync("{OUTPUT_DIR}/_people_to_enrich.jsonl", keep.join("\n") + "\n");
console.error(`Enriching ${keep.length} of ${lines.length} speakers`);
'
fi

# Split into ~5-person batches
split -l 5 {OUTPUT_DIR}/_people_to_enrich.jsonl {OUTPUT_DIR}/_batch_people_

Then in a single message, dispatch one Agent call per batch (up to 6 per message) with the prompt from references/workflow.md → "Person Enrichment". Each subagent's prompt should include:

  • {SKILL_DIR}, {OUTPUT_DIR}, {DEPTH} (deep | deeper)

  • {USER_COMPANY}, {USER_PRODUCT}, {ICP_DESCRIPTION}

  • {EVENT_NAME} (from recon.json .title)

  • {LANES} → 2 for deep mode, 4 for deeper mode (substituted into # browse call N/{LANES})

  • {PEOPLE_BATCH} → contents of _batch_people_aa (each line a JSON record from people.jsonl)

Agent dispatch (skeleton, repeat per batch in one message):

Agent(
  description: "Person enrichment batch aa",
  prompt: <Person Enrichment prompt from workflow.md with all placeholders substituted; PEOPLE_BATCH = cat _batch_people_aa>,
  subagent_type: "general-purpose"
)
Agent(
  description: "Person enrichment batch ab",
  prompt: <same template, PEOPLE_BATCH = cat _batch_people_ab>,
  subagent_type: "general-purpose"
)
... up to 6 per message

After all subagents return, verify the people files exist:

ls {OUTPUT_DIR}/people/*.md | wc -l
# Should equal wc -l _people_to_enrich.jsonl

Step 9: Compile Report

Generate the company-grouped HTML index, alternate views, and CSV in one command:

node {SKILL_DIR}/scripts/compile_report.mjs {OUTPUT_DIR} --open

This generates:

  • {OUTPUT_DIR}/index.html — people grouped by company, ranked by company ICP score (opens in browser)

  • {OUTPUT_DIR}/people.html — filterable speaker list (alternate view)

  • {OUTPUT_DIR}/companies.html — ICP-ranked company table with attendees

  • {OUTPUT_DIR}/results.csv — cold-outbound-ready spreadsheet

Then present a summary in chat:

## Event Prospecting Complete — {Event Name}

- **Total speakers extracted**: {count}
- **Unique companies**: {count}
- **ICP fits (score ≥ {threshold})**: {count}
- **Speakers enriched**: {count}
- **Score distribution** (companies):
  - Strong fit (8-10): {count}
  - Partial fit (5-7): {count}
  - Weak fit (1-4): {count}
- **Report opened in browser**: {OUTPUT_DIR}/index.html

Show the top 5 people cards as a markdown table sorted by company ICP score, then offer to:

  • Adjust --icp-threshold and re-run Steps 6-9

  • Export the CSV to a CRM

Files

references/workflow.md

Event-Prospecting Workflow

Subagent prompt templates and tool-call governance for every fan-out step in the pipeline. The main agent in SKILL.md dispatches Agent batches that load these prompts; each subagent must obey the HARD TOOL-CALL CAPS below or the run is invalidated.

Contents

  • Discovery — recon + extract (NOT fanned out; main agent runs these directly)

  • ICP Triage — fast company-level scoring (1 call/company hard cap)

  • Deep Research — full Plan→Research→Synthesize on ICP fits (5 calls/company hard cap)

  • Person Enrichment — speakers at ICP-fit companies (4 calls/person hard cap)

  • Compilation — HTML + CSV via compile_report.mjs

  • Wave Management — sizing, parallelism, error handling


Discovery

Recon + extract are deterministic single-process scripts run by the main agent. NOT fanned out. See SKILL.md Steps 2-4 for the orchestrator commands. This section exists only to document the artifacts the downstream subagents consume:

  • {OUTPUT_DIR}/recon.json — platform + extraction strategy (read by extract_event.mjs)

  • {OUTPUT_DIR}/people.jsonl — one JSON-encoded speaker per line (read by Step 8 batching)

  • {OUTPUT_DIR}/seed_companies.txt — deduped, sorted company names (read by Step 5 batching)


ICP Triage

HARD TOOL-CALL CAP: 1 tool call per company. The only allowed call is extract_page.mjs on the company homepage. NO follow-up searches, NO sitemap discovery, NO secondary fetches. If the homepage returns thin content, write Unknown and cap the score at 3 — that is the correct behavior, not a failure.

ENFORCEMENT — at the start of every Bash call, prepend a comment like # browse call N/1 so the cap is visible in tool output. If a subagent emits more than K calls for a batch of K companies, the main agent's compile step will detect the over-budget run from the call log and flag it.

Subagent prompt template — substitute the curly-brace placeholders before dispatching:

You are an ICP triage subagent for the event-prospecting skill. For each company in your batch, run ONE tool call to fetch the homepage, then score it against the user's ICP and write a triage stub to {OUTPUT_DIR}/companies/{slug}.md.

CONTEXT:
- User's company: {USER_COMPANY}
- User's product: {USER_PRODUCT}
- ICP description: {ICP_DESCRIPTION}
- Event name: {EVENT_NAME}
- Output directory: {OUTPUT_DIR}    ← write company files HERE, full literal path

COMPANIES TO TRIAGE (one per line — `name|guessed_homepage|slug`):
{COMPANY_LIST}

The guessed_homepage is a heuristic (`https://{lowercased company name without spaces}.com`). For most companies it's correct. For a few it 404s — that's expected and the fallback is documented in rule 3 below.

The slug is the canonical filename to write to: `{OUTPUT_DIR}/companies/{slug}.md`. Use it verbatim — do not re-slugify the name yourself or you'll create duplicate files.

TOOL RULES — CRITICAL, FOLLOW EXACTLY:
1. You may ONLY use the Bash tool. No exceptions.
2. The ONLY allowed extraction call is:
     node {SKILL_DIR}/scripts/extract_page.mjs "<homepage_url>" --max-chars 2000
3. HARD TOOL-CALL CAP: ONE call per company. If a homepage returns FETCH_OK: false with empty BODY (e.g. the guessed URL 404s), write product_description: "Unknown — homepage content not accessible" and cap icp_fit_score at 3. DO NOT attempt a second call to "save" the company.
4. ENFORCEMENT — at the start of EVERY Bash call, prepend a comment like `# browse call N/{TOTAL}` where N counts up and TOTAL is the number of companies in your batch. Example for a 10-company batch:
     # browse call 1/10
     node {SKILL_DIR}/scripts/extract_page.mjs "https://openai.com" --max-chars 2000
5. BANNED TOOLS: WebFetch, WebSearch, Write, Read, Glob, Grep — ALL BANNED. Use ONLY Bash.
6. NEVER use ~ or $HOME — full literal paths only.

ANTI-HALLUCINATION RULES:
- NEVER infer product_description from fonts, framework, or design system. Typography is not a product.
- NEVER let the user's ICP leak into the target's description. If you don't know what the target does, write "Unknown".
- product_description MUST quote or closely paraphrase a phrase from extract_page.mjs output (TITLE / META_DESCRIPTION / OG_DESCRIPTION / HEADINGS / BODY). If none yield a recognizable product statement, write "Unknown — homepage content not accessible" and cap icp_fit_score at 3.

ICP SCORING RUBRIC (event-aware):
- 8-10: Strong match. Homepage clearly states a product/audience that aligns with {ICP_DESCRIPTION}. Bonus if their event presence (talk topic, sponsor tier) suggests they're working in the user's wedge.
- 5-7: Partial match. Adjacent industry, OR clear product but unclear pain-point alignment.
- 1-4: Weak match. Wrong segment, or homepage too thin to assess (cap at 3 if Unknown).

OUTPUT — write ALL company files in a SINGLE Bash call using chained heredocs:

# browse call 1/{TOTAL}
node {SKILL_DIR}/scripts/extract_page.mjs "{url1}" --max-chars 2000 && \
# browse call 2/{TOTAL}
node {SKILL_DIR}/scripts/extract_page.mjs "{url2}" --max-chars 2000 && \
... && \
cat << 'COMPANY_MD' > {OUTPUT_DIR}/companies/{slug1}.md
---
company_name: {name1}
website: {url1}
product_description: {description1}
icp_fit_score: {score1}
icp_fit_reasoning: {reasoning1}
triage_only: true
event_context: {EVENT_NAME} — {how they show up at the event, e.g. "speaker on Agents track"}
---

## Triage Notes
{1-2 sentences citing the homepage phrase that drove the score}
COMPANY_MD
cat << 'COMPANY_MD' > {OUTPUT_DIR}/companies/{slug2}.md
---
...
---

...
COMPANY_MD

Use 'COMPANY_MD' (quoted) as the heredoc delimiter to prevent shell variable expansion.

Report back ONLY: "ICP triage batch: {scored}/{total} companies, score distribution: high={N} mid={N} low={N}".
Do NOT return raw homepage content or per-company reasoning to the main conversation.

Deep Research

HARD TOOL-CALL CAP: 5 tool calls per company. Budget breakdown:

  • 1 call: extract_page.mjs on the homepage

  • 2-3 calls: browse cloud search for sub-questions (Priority 1 + selected Priority 2)

  • 1-2 calls: extract_page.mjs on the most relevant search results (case study / blog / careers)

ENFORCEMENT — at the start of every Bash call, prepend # browse call N/{TOTAL} where TOTAL is 5 × batch_size. A 5-company batch caps at 25 total tool calls. The main agent's compile step monitors this from the call log.

Subagent prompt template:

You are a deep-research subagent for the event-prospecting skill. For each ICP-fit company in your batch, follow the Plan→Research→Synthesize pattern from references/research-patterns.md and OVERWRITE the existing triage stub at {OUTPUT_DIR}/companies/{slug}.md with the deep-research version.

CONTEXT:
- User's company: {USER_COMPANY}
- User's product: {USER_PRODUCT}
- ICP description: {ICP_DESCRIPTION}
- Event name: {EVENT_NAME}
- Event context: {EVENT_CONTEXT}   ← e.g. "AI track / Agents / Infra"
- Output directory: {OUTPUT_DIR}

COMPANIES TO RESEARCH (one per line, slug|website format):
{COMPANY_LIST}

TOOL RULES — CRITICAL:
1. You may ONLY use the Bash tool. No exceptions.
2. All searches:  browse cloud search "..." --num-results 10
3. All page extractions:  node {SKILL_DIR}/scripts/extract_page.mjs "URL" --max-chars 3000
   (uses `--output` to avoid the stdout JSON envelope, preserves meta tags, and falls back to browse get markdown when needed)
   DO NOT hand-roll a `browse cloud fetch | sed` pipeline. Use raw `browse cloud fetch` only for sitemap.xml / llms.txt.
4. HARD TOOL-CALL CAP: 5 calls per company. Budget:
     1× extract_page on homepage
     2-3× browse cloud search on sub-questions
     1-2× extract_page on the best search result
   DO NOT exceed 5 calls per company. If you've burned the budget, synthesize from what you have.
5. ENFORCEMENT — at the start of EVERY Bash call, prepend a comment like `# browse call N/5 (company: {slug})`. Reset N to 1 for each company in the batch.
6. BATCH all writes: write ALL deep-research files in a SINGLE Bash call using chained heredocs.
7. BANNED TOOLS: WebFetch, WebSearch, Write, Read, Glob, Grep — ALL BANNED.
8. NEVER use ~ or $HOME — full literal paths.

ANTI-HALLUCINATION RULES (same as research-patterns.md):
- Typography is not a product.
- No ICP leakage — if homepage is thin and search yields nothing, write "Unknown" and cap score at 3.
- product_description MUST quote/paraphrase a phrase from extract_page.mjs output or a search result.
- LOGO DIRECTION: a logo on a homepage does NOT establish a customer relationship. If {TARGET}'s homepage shows {USER_COMPANY}'s logo in a "trusted by"/"customers" section, the USER is the TARGET's customer — NOT the other way around. Only call a target an "existing customer" if its name appears in the user profile's `existing_customers` array. Otherwise describe the relationship neutrally (e.g. "shared ecosystem", "possible partnership", "adjacent stack").

RESEARCH PATTERN per company (deep mode):

Phase A — Plan:
Decompose into 2-3 sub-questions. Always include "What does {company} do?" (Priority 1). Add 1-2 from Priority 2 chosen for event-context relevance. EXAMPLE for an Agents-track company:
  - "What does {company} sell and who are their customers?"
  - "What is {company} doing with browser automation or AI agents that's relevant to {EVENT_NAME}'s {EVENT_CONTEXT}?"
  - "Has {company} raised funding, launched products, or expanded recently?"

Phase B — Research Loop:
1. # browse call 1/5 — extract_page on homepage
2. # browse call 2/5 — browse cloud search for Priority 1 sub-question
3. # browse call 3/5 — browse cloud search for event-context sub-question
4. # browse call 4/5 — extract_page on the most relevant search result
5. # browse call 5/5 — (optional) one more search OR fetch if budget remains
Accumulate findings: factual statement + source URL + confidence level (high/medium/low).

Phase C — Synthesize:
1. Score ICP fit 1-10 using the rubric (high-confidence findings + event relevance lift the score; thin evidence caps at 3).
2. Fill enrichment fields: product_description, industry, target_audience, key_features, employee_estimate, funding_info, headquarters.
3. Write event_relevance: how the company shows up at the event (speaker count, track, sponsor tier, demo).
4. Reference specific findings in icp_fit_reasoning.

OUTPUT — overwrite the triage stub. ALL files in a SINGLE Bash call.

**FORMAT RULES — non-negotiable, parser breaks if violated**:
- Every file MUST have a closing `---` line after the YAML frontmatter, BEFORE the first markdown section. Do NOT skip it.
- All structured data goes in the YAML frontmatter (above the closing `---`). Markdown sections (`## Product`, `## Research Findings`, `## Event Relevance`) go AFTER the closing `---`.

cat << 'COMPANY_MD' > {OUTPUT_DIR}/companies/{slug}.md
---
company_name: {name}
website: {url}
product_description: {description}
industry: {industry}
target_audience: {audience}
key_features: {feature1} | {feature2} | {feature3}
icp_fit_score: {score}
icp_fit_reasoning: {reasoning, references findings}
employee_estimate: {estimate}
funding_info: {funding}
headquarters: {location}
triage_only: false
event_context: {EVENT_NAME} — {how they show up}
event_relevance: {speaker count, track, demo expectations}
---

## Product
{2-3 sentences specific, sourced}

## Research Findings
- **[{confidence}]** {fact} (source: {url})
- ...

## Event Relevance
{how this company connects to {EVENT_NAME}; pitch angle for AE conversation at the event}
COMPANY_MD

Report back ONLY: "Deep research batch: {researched}/{total} companies, {findings_count} total findings, avg ICP score {N.N}".

Person Enrichment

HARD TOOL-CALL CAP: 4 tool calls per person. Lanes:

  1. browse cloud search "{name} {company} linkedin" — always (deep + deeper)

  2. browse cloud search "{name} podcast OR talk OR blog 2026" — deep + deeper

  3. browse cloud search "{name} github" — deeper only

  4. browse cloud search "{name} site:x.com OR site:twitter.com" — deeper only, best-effort

Deep mode: lanes 1-2 (max 2 calls/person). Deeper mode: lanes 1-4 (max 4 calls/person).

ENFORCEMENT — every Bash call prepends # browse call N/{LANES} (person: {slug}), where LANES is 2 (deep) or 4 (deeper). Reset N to 1 for each person.

Subagent prompt template:

You are a person-enrichment subagent for the event-prospecting skill. For each person in your batch, run 2-4 browse cloud searches to harvest LinkedIn + recent activity + GitHub + X presence, generate a hook + DM opener, and write {OUTPUT_DIR}/people/{slug}.md.

CONTEXT:
- User's company: {USER_COMPANY}
- User's product: {USER_PRODUCT}
- ICP description: {ICP_DESCRIPTION}
- Event name: {EVENT_NAME}
- Depth mode: {DEPTH}    ← `deep` (2 lanes) or `deeper` (4 lanes)
- Output directory: {OUTPUT_DIR}

PEOPLE TO ENRICH (one JSON record per line):
{PEOPLE_BATCH}

Each record has fields:
  { "name": "...", "title": "...", "company": "...", "linkedin": "...", "slug": "...", "bio": "...", "image": "..." }

The `image` field is the speaker's headshot URL extracted from the event site (may be null on platforms that don't expose it). PRESERVE it verbatim into the people/{slug}.md frontmatter as `image: {url}` — do NOT fetch, replace, or "improve" it.

TOOL RULES — CRITICAL:
1. You may ONLY use the Bash tool. No exceptions.
2. All searches:  browse cloud search "..." --num-results 5
3. HARD TOOL-CALL CAP per person:
     deep mode:    2 calls (lanes 1 + 2)
     deeper mode:  4 calls (lanes 1 + 2 + 3 + 4)
   DO NOT exceed the cap. If a lane fails (no useful result), DO NOT compensate by running a fifth call.
4. ENFORCEMENT — at the start of EVERY Bash call, prepend a comment like `# browse call N/{LANES} (person: {slug})`. Reset N to 1 for each person in the batch.
5. BATCH all writes: write ALL people files in a SINGLE Bash call using chained heredocs.
6. BANNED TOOLS: WebFetch, WebSearch, Write, Read, Glob, Grep — ALL BANNED.
7. NEVER use ~ or $HOME — full literal paths.

ANTI-HALLUCINATION RULES:
- A person's `hook` MUST quote or paraphrase a SPECIFIC finding from a browse cloud search result. NEVER infer from "they look senior" or "their company is AI-y".
- If lanes 2-4 yield no public signal in the last 6 months, fall back to event-context (their talk title from the bio field). Event-context is always available and beats a fabricated hook.
- The DM opener MUST reference the hook verbatim. If the hook is event-context, name the talk title. Do NOT name a podcast or blog post the person didn't actually appear in.

LANE PROMPTS (run only the lanes for your DEPTH):

Lane 1 (always):
  # browse call 1/{LANES} (person: {slug})
  browse cloud search "\"{name}\" \"{company}\" linkedin" --num-results 5
  → harvest LinkedIn URL + verify current title

Lane 2 (deep + deeper):
  # browse call 2/{LANES} (person: {slug})
  browse cloud search "\"{name}\" podcast OR talk OR blog 2026" --num-results 5
  → harvest most-recent activity. If a podcast/blog/talk URL appears, that's a candidate hook.

Lane 3 (deeper only):
  # browse call 3/{LANES} (person: {slug})
  browse cloud search "\"{name}\" github" --num-results 5
  → harvest github.com/{handle} URL if present

Lane 4 (deeper only):
  # browse call 4/{LANES} (person: {slug})
  browse cloud search "\"{name}\" site:x.com OR site:twitter.com" --num-results 5
  → attempt to find x.com/{handle} URL + most recent post topic; leave null if no direct profile appears

HOOK SOURCE PRIORITY (run sequentially, stop at first hit):
1. Recent activity (lane 2): podcast title / blog headline / talk title from the last 6 months. THIS IS THE BEST HOOK — concrete, dated, public.
2. Event-context: their talk title or panel topic at {EVENT_NAME} (extracted from the bio field passed in the JSON record). ALWAYS available, even when external search yields nothing.
3. Company-context: pull from {OUTPUT_DIR}/companies/{company_slug}.md `event_relevance` line (read via `awk` from the existing file; this is allowed because the file is local, not a tool call).

DM OPENER FORMAT:
2-3 sentences:
  - sentence 1: reference the hook explicitly
  - sentence 2: 1-line wedge tie-in to {USER_PRODUCT}
  - sentence 3: soft CTA ("worth a 15-min walkthrough?", "open to a quick chat?", etc.)
NEVER salesy. NEVER reference Browserbase by name unless the user's profile says to. Use the user's wedge framing from {USER_PRODUCT}.

OUTPUT — write ALL people files in a SINGLE Bash call using chained heredocs.

**FORMAT RULES — non-negotiable, parser breaks if violated**:
- Every file MUST have a closing `---` line after the YAML frontmatter. Do NOT skip it.
- `hook`, `dm_opener`, `role_reason` MUST be YAML frontmatter fields — NEVER markdown sections like `## Hook` or `## DM Opener`.
- `links` MUST be a nested YAML object (`links:` then indented `linkedin:`, `x:`, etc.). NEVER flat top-level keys.
- `dm_opener` is a YAML pipe scalar (`dm_opener: |` then indented multi-line text).

cat << 'PERSON_MD' > {OUTPUT_DIR}/people/{slug}.md
---
name: {full name}
slug: {slug}
company: {company}
company_slug: {company_slug}
title: {title}
image: {image url from input record, or null}
links:
  linkedin: {url or null}
  x: {url or null}
  github: {url or null}
  blog: {url or null}
  podcast: {url or null}
hook: {1 sentence, sourced}
dm_opener: |
  {sentence 1: hook reference}
  {sentence 2: wedge tie-in}
  {sentence 3: soft CTA}
role_reason: {why this person matters at the company}
event_name: {EVENT_NAME}
event_context: {their role at the event — talk title, panel topic, sponsor employee, etc.}
icp_fit_score: {inherited from companies/{company_slug}.md}
icp_fit_reasoning: {inherited}
enriched_at: {ISO timestamp}
---

## Why reach out
- **Why the company**: {1 line, references companies/{company_slug}.md}
- **Why the person**: {role_reason restated as 1 line}
- **Hook**: {hook, with source URL inline}

## Public links
{bullet list of every harvested link, one per line}

## Recent activity
- **[{confidence}]** {finding} (source: {url})
- ...
PERSON_MD

Report back ONLY: "Person enrichment batch: {enriched}/{total} people, {hook_count_event} event-context hooks + {hook_count_recent} recent-activity hooks".

Compilation

After all subagents complete, the main agent runs the compile step ONCE. NOT fanned out. From SKILL.md Step 9:

node {SKILL_DIR}/scripts/compile_report.mjs {OUTPUT_DIR} --user-company {USER_SLUG} --open

The compile script:

  1. Reads every companies/*.md and people/*.md

  2. Joins people to their company files (via company_slug frontmatter)

  3. Sorts people by inherited icp_fit_score desc

  4. Renders:

    • index.html — person-first card grid (the primary deliverable)

    • people.html — filterable speaker grid (alternate view, with chips for company / role bucket / ICP band)

    • companies.html — ICP-ranked company table with attendees expandable per row

    • results.csv — flat one-row-per-person spreadsheet for cold-outbound import

  5. Opens index.html in the default browser (--open flag)

The compile step does NOT mutate any .md files. All HTML is generated fresh from the markdown sources every run, so re-running compile after a manual edit to a .md file regenerates the report.


Wave Management

Key Principle: Maximize Parallelism, Minimize Prompts

Launch as many subagents as possible in a single Agent fan-out (up to ~6 Agent calls per message). Each subagent MUST batch all its Bash operations into a single call to minimize permission prompts. One subagent batch = one Bash call = one permission prompt.

Sizing Formula
seed_companies = wc -l seed_companies.txt
icp_fits      = wc -l icp_fits.txt    (typically 20-40% of seed)
people_to_enrich = wc -l _people_to_enrich.jsonl  (typically 1.5-2.5× icp_fits)

triage_subagents  = ceil(seed_companies / 10)        # 10 companies/subagent, 1 call each
deep_subagents    = ceil(icp_fits / 5)               # 5 companies/subagent, 5 calls each
person_subagents  = ceil(people_to_enrich / 5)       # 5 people/subagent, 2-4 calls each

For Stripe Sessions (99 seed → ~30 ICP fits → ~50 people):

  • Triage: 10 subagents × 10 calls = 100 calls (matches the cost model: 99 calls)

  • Deep research: 6 subagents × 25 calls = 150 calls

  • Person enrichment: 10 subagents × ~10 calls = 100 calls

  • Total: ~350 tool calls, matches the design doc cost model.

Wave Cadence

Dispatch all subagents for a given step in a single Agent fan-out message (up to 6 per message; if more needed, run a second wave after the first completes). Do NOT serialize subagents that can run in parallel.

Error Handling
  • If a single subagent fails, log the error and continue. The compile step ignores missing files gracefully.

  • If >50% of subagents in a wave fail, pause and surface to the user before continuing.

  • If extract_page.mjs returns FETCH_OK: false with empty BODY, the triage subagent should write product_description: Unknown — homepage content not accessible and cap score at 3 (NOT skip the company — the file must exist for compile to render the row).

  • The HARD TOOL-CALL CAP is non-negotiable. If a subagent exceeds its budget, the run is invalidated for that batch (compile step warns; user can re-dispatch).

Requirements and compatibility

Written for: Claude Code

Works with: Claude Code, Claude apps, OpenAI Codex, GitHub Copilot, Cursor, Gemini CLI, Kiro, OpenCode, Goose, Amp, Roo Code, JetBrains Junie, Factory Droid, Devin (ex-Windsurf), Mistral Vibe, OpenHands, Letta

“Works with” means the tool reads the open Agent Skills format; a skill written for one tool may use features others lack.

Needs: Declares allowed tools, Names Claude Code tools, Needs an API key, Needs Python, Needs Node.js

The author says: Requires browse CLI (`npm install -g browse`) and BROWSERBASE_API_KEY env var. The same `browse` binary covers both API commands and JS-rendered page fallback.

Security

What our scanner noticed· Low risk

  • Instructions that use the network
  • Scripts that your agent may run

This skill includes scripts that your agent may run. Read them before you use it.

A static scan is not a guarantee. Skills are third-party content: review a skill before you let an agent use it.

Attribution and licence

Created by
browserbase
Publisher
Browserbase (Official)
Source
browserbase/skills · skills/event-prospecting · commit 6811ca3
Licence
MIT License (redistribution allowed)
  • ATTRIBUTION.md (written by AutoGineer)
Synced

AutoGineer is not affiliated with or endorsed by browserbase or Browserbase.

Download ZIP

The ZIP holds the skill’s files, its licence files and an ATTRIBUTION.md. Download event-prospecting.zip