Event Prospecting
BrowserbaseOfficialCreated by browserbase
- Skill
- Workflow
- Business and productivity
- MIT
- Low risk
- Has scripts
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
Download the skill as a ZIP:
https://autogineer.com/agent-skills/browserbase/event-prospecting/downloadUnzip 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 viabrowse cloud fetch --output, parses title + meta tags + visible body text, and automatically falls back tobrowse get markdownwhen fetch fails or returns thin JS-rendered content. NEVER hand-roll abrowse cloud fetch | sedpipeline. NEVER use WebFetch.All research output: subagents write one markdown file per company OR per person to
{OUTPUT_DIR}/companies/{slug}.mdor{OUTPUT_DIR}/people/{slug}.mdusing bash heredoc. NEVER use the Write tool orpython3 -c. Seereferences/example-research.mdfor 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.mdfor enforcement detail.
CRITICAL — Anti-hallucination rules (applies to main agent AND all subagents):
NEVER infer
product_description,industry, or a person'srole_reasonfrom 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_descriptionMUST quote or paraphrase a specific phrase fromextract_page.mjsoutput. If none of TITLE/META/OG/HEADINGS/BODY yield a recognizable product statement, writeUnknown — homepage content not accessibleand capicp_fit_scoreat 3.A person's
hookMUST quote or paraphrase a specific finding from abrowse cloud searchresult (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.
Setup — output dir + clean slate
Load profile — read
profiles/{user_slug}.jsonRecon — detect event platform
Extract people —
people.jsonlGroup by company —
seed_companies.txtICP triage — fast company-level scoring (1 call/company)
Filter — companies with
icp_fit_score >= --icp-thresholdDeep research — full Plan→Research→Synthesize on ICP fits
Enrich speakers — ask user: ICP-fit only (default) or all speakers
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:
If the user invoked with
--user-company <slug>, use that slug.Else, list
profiles/*.jsonexcludingexample.json. If exactly one profile exists, use it (and tell the user which one). If multiple exist, ask the user (plain chat) which one.If zero profiles exist, fail loudly and instruct the user to create one (copy
profiles/example.jsontoprofiles/<your_slug>.jsonand 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}.jsonThe 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 pathsSessionize:
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.jsonlIf 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.txtExpected: 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 -lThen 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 messageAfter 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.txtExpected: 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}(fromrecon.json.title),{EVENT_CONTEXT}(track / topic, manually inferred from the event homepage){COMPANY_LIST}→ contents of the batch file (each lineslug|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 -lAgent 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 returnsAfter 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.txtStep 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:
browse cloud search "{name} {company} linkedin"(always)browse cloud search "{name} podcast OR talk OR blog 2026"(deep+)browse cloud search "{name} github"(deeper)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}(fromrecon.json.title){LANES}→2for deep mode,4for deeper mode (substituted into# browse call N/{LANES}){PEOPLE_BATCH}→ contents of_batch_people_aa(each line a JSON record frompeople.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 messageAfter all subagents return, verify the people files exist:
ls {OUTPUT_DIR}/people/*.md | wc -l
# Should equal wc -l _people_to_enrich.jsonlStep 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} --openThis 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.htmlShow the top 5 people cards as a markdown table sorted by company ICP score, then offer to:
Adjust
--icp-thresholdand re-run Steps 6-9Export the CSV to a CRM
Files
- .gitignore210 B
- ATTRIBUTION.mdAdded by AutoGineer966 B
- LICENSE.txt1.0 KiB
- SKILL.md21.6 KiB
- profiles/example.json170 B
- references/event-platforms.md7.7 KiB
- references/example-research.md8.7 KiB
- references/report-template.html7.9 KiB
- references/research-patterns.md17.3 KiB
- references/workflow.md21.0 KiB
- scripts/__fixtures__/stripe-snapshot.json258 B
- scripts/compile_report.mjsScript39.2 KiB
- scripts/extract_event.mjsScript8.3 KiB
- scripts/extract_page.mjsScript5.4 KiB
- scripts/package.json103 B
- scripts/recon.mjsScript4.2 KiB
references/research-patterns.md
<!-- Plan→Research→Synthesize pattern adapted from company-research v1.1.0 (2026-04-25). -->
<!-- Source: /Users/jay/skills/skills/company-research/references/research-patterns.md -->
<!-- Keep in sync if the canonical pattern there changes meaningfully. -->
Event-Prospecting — Research Patterns
Contents
Plan→Research→Synthesize — canonical pattern (verbatim from company-research)
Self-Research (User's Company) — done by company-research, consumed here as a profile
Target Company Research — sub-question templates
Finding Format — schema for accumulated facts
Research Loop Rules — how to stop hallucinating
Depth Mode Behavior — quick / deep / deeper
Synthesis Instructions — turn findings into frontmatter
ICP Triage (Step 5 — fast pass) — event-specific
Deep Research (Step 7 — full pass) — event-specific
Person Enrichment (Step 8 — speakers at ICP fits only) — event-specific
Plan→Research→Synthesize
This reference defines two research contexts:
Self-Research — Deep research on the user's own company to build a strong ICP foundation. (For event-prospecting, this is done once by
company-researchand persisted inprofiles/{slug}.json. Event-prospecting reads the profile at Step 1.)Target Research — Research each ICP-fit company using Plan→Research→Synthesize.
Both use the same 3-phase pattern but with different sub-questions and goals.
Self-Research (User's Company)
This is the most important research in the pipeline. Every downstream decision depends on it.
Sub-Questions
"What does {company} sell and what specific problem does it solve?"
"Who are {company}'s existing customers? What industries, company sizes, and use cases?"
"Who are {company}'s competitors and what differentiates them?"
"What pricing model does {company} use and who is the typical buyer persona?"
"What use cases and pain points does {company}'s marketing emphasize?"
Page Discovery
Discover site pages dynamically — do NOT hardcode paths like /about or /customers:
Fetch
browse cloud fetch --allow-redirects "{company website}/sitemap.xml"— primary source, has ALL pagesScan sitemap URLs for keywords:
customer,case-stud,pricing,about,use-case,blog,docs,industry,solutionOptionally fetch
browse cloud fetch --allow-redirects "{company website}/llms.txt"for page descriptionsPick the 3-5 most relevant URLs from the sitemap and fetch those
Sitemap is the source of truth. llms.txt is bonus context but often incomplete.
External Research
Search:
"{company} customers use cases reviews"Search:
"{company} alternatives competitors vs"Fetch 1-2 of the most informative third-party results (G2, blog posts, comparisons)
Synthesis Output
From all findings, produce a company profile:
Company: name
Product: what they sell, how it works, key capabilities (2-3 sentences, specific)
Existing Customers: named customers or customer types found
Competitors: who they compete with, key differentiators
Use Cases: broad list of use cases the product serves (NOT tied to one vertical)
Do NOT include ICP, pitch angle, or sub-verticals in the profile. Those are per-run targeting decisions made in Step 2 after the profile is confirmed. The profile is a general-purpose company fact sheet that works regardless of which vertical you target next.
Why This Matters
A thin profile produces generic search queries, weak lead scoring, and cookie-cutter emails. A rich profile with specific customers, competitors, and use cases produces targeted queries, accurate scoring, and emails that reference real pain points.
Target Company Research
Sub-Question Templates
Generate sub-questions from these categories based on the ICP and enrichment fields requested. Not every category applies to every company — pick the most relevant.
Priority 1 (Always ask)
Product/Market: "What does {company} sell and who are their customers?"
ICP Fit: "How does {company}'s product/market relate to {sender's ICP description}?"
Priority 2 (Ask in deep/deeper)
Tech Stack: "What technologies, frameworks, or infrastructure does {company} use?"
Growth Signals: "Has {company} raised funding, launched products, or expanded recently?"
Pain Points: "What challenges might {company} face that {sender's product} addresses?"
Priority 3 (Ask in deeper only)
Decision Makers: "Who leads engineering, product, or growth at {company}?"
Competitive Landscape: "Who are {company}'s competitors and how are they differentiated?"
Customers/Case Studies: "Who are {company}'s notable customers and what results do they highlight?"
Search Query Patterns
For each sub-question, generate 2-3 search query variations:
# Product/Market
"{company name} what they do"
"{company name} product features customers"
# Tech Stack
"{company name} tech stack engineering blog"
"{company name} careers software engineer" (job posts reveal stack)
# Growth Signals
"{company name} funding round 2025 2026"
"{company name} launch announcement"
"{company name} hiring"
# Pain Points
"{company name} challenges {relevant domain}"
"{company name} {problem sender solves}"
# Decision Makers
"{company name} VP engineering CTO LinkedIn"
"{company name} head of growth product"Finding Format
Each finding is a self-contained factual statement tied to a source:
{
"subQuestion": "What does Acme sell and who are their customers?",
"fact": "Acme provides checkout optimization for Shopify stores, serving mid-market DTC brands with $5M-$50M revenue",
"sourceUrl": "https://acme.com/about",
"sourceTitle": "About Acme - Checkout Optimization",
"confidence": "high"
}Confidence levels:
high: Directly stated on the company's own website or official pressmedium: Inferred from job postings, third-party articles, or indirect signalslow: Speculative based on industry/category, or from outdated sources
Research Loop Rules
Process sub-questions by priority — Priority 1 first, then 2, then 3
3-5 findings per sub-question, then move on — Don't exhaust a topic
Use parallel tool calls — Search multiple queries simultaneously when possible
Rephrase, don't retry — If a search returns poor results, try different keywords
Fetch selectively — Don't fetch every URL from search results. Pick the 1-2 most relevant based on title and URL
Stop at step limit — Respect the depth mode's step budget per company
Homepage first — Always fetch the company's homepage before branching to other pages
Deduplicate findings — Don't record the same fact twice from different sources
Logo / customer-relationship direction (CRITICAL)
A logo on a company's homepage carries no implicit direction. Do NOT infer a buyer/seller relationship from logo placement alone — the direction is the opposite of what you'd guess in many cases.
If
{TARGET}'s homepage shows{USER_COMPANY}'s logo in a "trusted by" / "customers" / "loved by" / "powering" section, then the user is the target's customer, not the other way around. (Example: Browserbase's logo on Clerk's homepage means Browserbase uses Clerk for auth — Clerk is NOT a Browserbase customer.)If
{USER_COMPANY}'s homepage shows the target's logo in a "customers" section, then the target is the user's customer.If neither homepage carries the other's logo, do NOT claim any customer relationship.
Search results that say "X uses Y" or "X integrates Y" are stronger evidence than logos. Quote the source phrase.
When unsure of direction, write the relationship neutrally: "Browserbase and Clerk both serve dev-tools/agent ICP — possible co-marketing fit" — NOT "Clerk is a Browserbase customer".
The user's profile (profiles/{slug}.json) lists existing_customers. Only treat a target as an existing customer if its name appears in that array. Logos and assumptions don't qualify.
Depth Mode Behavior
Quick Mode (100+ leads)
Skip Phase A — No sub-question decomposition
Phase B: Fetch the company homepage. Run 1-2 supplementary searches if homepage data is thin.
Phase C: Extract available data, score ICP, write email from what's available
Budget: 2-3 total tool calls per company
Trade-off: Fast and cheap, but emails may be less personalized
Deep Mode (25-50 leads)
Phase A: Decompose into 2-3 sub-questions (Priority 1 + selected Priority 2)
Phase B: For each sub-question, run 2-3 searches + fetch 1-2 URLs. Target 3-5 findings per sub-question.
Phase C: Synthesize from all findings. ICP reasoning references specific evidence. Email uses the most specific/compelling finding.
Budget: 5-8 total tool calls per company
Trade-off: Good balance of depth and scale
Deeper Mode (10-25 leads)
Phase A: Decompose into 4-5 sub-questions (Priority 1 + 2 + selected Priority 3)
Phase B: Research exhaustively. Fetch multiple pages per company (homepage, about, blog, careers, product pages). Target 3-5 findings per sub-question.
Phase C: Synthesize with cited evidence. ICP reasoning is detailed. Email references multiple specific signals.
Budget: 10-15 total tool calls per company
Trade-off: High quality intelligence, but slow and expensive
Synthesis Instructions
After the research loop completes for a company, synthesize findings into the output record:
ICP Scoring
Score 1-10 using ALL accumulated findings as evidence:
8-10: Strong match. Multiple high-confidence findings confirm right industry, company stage, and clear pain point alignment. The pitch angle directly addresses a visible need supported by evidence.
5-7: Partial match. Some findings suggest relevance but key signals are missing or low-confidence. Adjacent industry or unclear pain point.
1-4: Weak match. Findings indicate wrong segment, too large/small, or no apparent connection to sender's product.
Write icp_fit_reasoning referencing specific findings: "Series A fintech (from Crunchbase), uses Selenium for scraping (from job posting), expanding to EU market (from blog) — strong fit for browser infrastructure."
Email Personalization
Use the richest, most specific findings for email context:
Opening: Use the most concrete finding (a specific product feature, a recent launch, a job posting)
Bridge: Connect a finding about their challenges/stack to the sender's pitch angle
If only low-confidence findings exist, keep the email shorter and more general — don't fabricate specificity
Enrichment Fields
Map findings to enrichment fields:
product_description→ from Product/Market findingsindustry→ inferred from Product/Marketemployee_estimate→ from LinkedIn search or careers page findingsfunding_info→ from Growth Signals findingsheadquarters→ from company homepage or about pagetarget_audience→ from Product/Market findingskey_features→ from product page findings
If a field has no supporting findings, leave it empty rather than guessing.
Anti-Hallucination Rules
Apply these at synthesis time. They exist because the failure mode — especially on Framer/Next.js landing pages with little server-rendered copy — is for the subagent to pattern-match visual cues onto the sender's ICP and fabricate a plausible-sounding description:
Typography is not a product. Never infer
product_description,industry, ortarget_audiencefrom fonts, design system, framework choice (Framer, Next.js, React), or site polish. "Framer-built" and "uses Geist Mono" are observations about tooling, not signals of what the company sells.No ICP leakage. If the homepage is thin and external search turns up nothing, do NOT default the target's description toward the sender's ICP. Manufacturing AI ≠ browser automation just because both use AI.
Quote, don't paraphrase from memory.
product_descriptionmust quote or closely paraphrase a specific phrase fromextract_page.mjsoutput (TITLE / META_DESCRIPTION / OG_DESCRIPTION / HEADINGS / BODY) or from an external search result. If no such phrase exists, writeUnknown — homepage content not accessible.Cap scores on thin evidence. If
product_descriptionisUnknown, seticp_fit_score≤ 3 andicp_fit_reasoning: Insufficient evidence — homepage returned no readable content. Do not justify a higher score on inferred signals alone.
ICP Triage (Step 5 — fast pass)
For each company in seed_companies.txt, run ONE tool call to fetch the homepage + extract a 1-line product description, then score against the ICP. Output goes to companies/{slug}.md with frontmatter:
company_name: OpenAI
website: https://openai.com
product_description: "AI lab building safe AGI for everyone"
icp_fit_score: 9
icp_fit_reasoning: "AI agents need cloud browser infrastructure at scale; ChatGPT Agent shipped Mar 2026"
triage_only: true # NOT yet deep-researched
event_context: "Stripe Sessions 2026 — featured speaker on AI track"Companies with icp_fit_score < {threshold} (default 6) stay as triage stubs and never get deep-researched. Companies above the threshold advance to Step 7.
Hard cap: 1 tool call per company. The only allowed call is node {SKILL_DIR}/scripts/extract_page.mjs "{company_homepage}". Anti-hallucination rule applies in full: if the homepage is JS-rendered and extract_page.mjs returns empty BODY, write product_description: Unknown — homepage content not accessible and cap the score at 3. Do NOT do a second search to "save" the company — the budget is one call.
The triage subagent batches its 10 extract_page.mjs calls and 10 heredoc writes into a SINGLE Bash invocation using && chaining and pipe-separated heredocs. One Bash call = one permission prompt.
Deep Research (Step 7 — full pass)
Identical to company-research's target research. The ICP-fit companies (typically 20-40% of the seed list) get the full Plan→Research→Synthesize treatment with sub-questions tailored to the event context.
Hard cap: 5 tool calls per company. Budget breakdown for deep mode:
1 call:
extract_page.mjson the homepage (re-extract; the triage version was scraped down to a 1-liner)2-3 calls:
browse cloud searchfor sub-questions from Priority 1 + 2 (product, tech stack, growth signals)1-2 calls:
extract_page.mjson the most relevant search results (case study, blog post, careers page)
Event-context tweaks the sub-questions. Instead of generic "What does {company} do?", the subagent asks "What is {company} doing with browser automation that's relevant to Stripe Sessions' agent track?" — the event name and any track/topic info from recon.json is woven into Priority 2 sub-questions.
The deep-research subagent OVERWRITES the triage stub with the richer file (frontmatter triage_only: false). The compile step looks at triage_only to decide rendering.
Person Enrichment (Step 8 — speakers at ICP fits only)
Per person at an ICP-fit company:
browse cloud search "{name} {company} linkedin"— verify role + harvest LinkedIn URL (always)browse cloud search "{name} podcast OR talk OR blog 2026"— last 6 months for hooks (deep+)browse cloud search "{name} github"— open-source signal (deeper)browse cloud search "{name} site:x.com OR site:twitter.com"— best-effort recent posts (deeper)
Hard cap: 4 tool calls per person. Deep mode runs lanes 1-2 (max 2 calls). Deeper mode runs lanes 1-4 (max 4 calls). Quick mode skips Step 8 entirely.
Each person yields a people/{slug}.md with frontmatter:
name: Greg Brockman
slug: greg-brockman
company: OpenAI
title: President & Co-founder
links:
linkedin: https://www.linkedin.com/in/thegdb/
x: https://x.com/gdb
github: https://github.com/gdb
blog: null
podcast: https://lexfridman.com/greg-brockman/
hook: "Recent Lex Fridman interview on agent reliability — direct fit for browser-infra durability story"
dm_opener: "Hey Greg — saw your Lex Fridman convo on agent reliability..."
role_reason: "Co-founder, sets infra direction"
event_name: "Stripe Sessions 2026"
event_context: "Panelist on Agents track"
icp_fit_score: 9 # inherited from companies/openai.mdThe dm_opener is 2-3 sentences, references the hook, names a Browserbase-style tie-in (or whatever the user's product wedge is from the profile), and ends with a soft CTA. It's what the AE pastes into LinkedIn. Generated from accumulated findings — never from memory or visual inference.
The hook source priority (run sequentially, stop at first hit):
Event-context: their talk title or panel topic at this event (always available, lowest-effort, often best for cold opener)
Recent activity (last 6 months): podcast / talk / blog / GitHub / LinkedIn post — surfaced by lanes 2-4
Company-context: signal from their company's recent news (funding, product launch) — pulled from the
companies/{slug}.mddeep-research file
If lane 1 succeeds, the subagent can skip lanes 2-4 in deep mode. In deeper mode, run all four lanes regardless to give the report richer link pills.
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