leadbay_followup_check_in — independently scanned and version-tracked by SaferSkills.
SaferSkills independently audited leadbay_followup_check_in (Agent Skill) and scored it 100/100 (green). The audit ran 55 deterministic rules across Security, Supply Chain, Maintenance, Transparency, and Community; it found 0 high-severity and 0 lower-severity findings. The full rule-by-rule trace and per-finding evidence are below. Free, methodology-open.
Findings & checks · 0 flagged
Every scanned point with the score it earned and what moved between them.
First recorded scan — no prior version to compare against.
The primary manifest — the file an agent reads to learn what this artifact does.
You keep your own memory of how this user likes to work — tone, naming, formatting, what they ask you to skip. Leadbay does not store that and does not need to.
What Leadbay does need is anything that changes who it should find. When the user states targeting criteria in conversation ("I target fleets over 100 vehicles", "carriers are a bad fit unless they do last-mile delivery", "climate engineering is also my market"), call leadbay_refine_lead_targeting so it changes what Leadbay surfaces for the whole org and on every future refresh — not just this conversation. When they say a specific lead is wrong for them, record the dislike with their words as its reason rather than noting it.
Run the Leadbay follow-up check-in for me. Treat this prompt the same way for any equivalent ask: "leads I should follow up with", "already known leads", "what's overdue", "before my trip to [city]", "leads I haven't contacted", "who should I re-engage today".
These rules apply to every Leadbay workflow that calls leadbay_pull_leads, leadbay_bulk_qualify_leads, leadbay_research_lead_by_id, leadbay_import_and_qualify, or leadbay_enrich_titles. Treat timeouts and stream-closed errors as transient, not as signals to replan.
After your first leadbay_pull_leads call, capture response.lens.id into your working memory and pass it explicitly as the `lensId` argument to every subsequent call in this session — including any re-pulls, bulk qualifies, or research calls that accept it. (Field-name caveat: the response nests it as lens.id; the parameter on subsequent calls is lensId.) The active lens can shift between calls (5-minute client cache + backend last_requested_lens can change if the user touches the web UI). A lens shift mid-workflow throws away your top-10 work.
leadbay_bulk_qualify_leads and leadbay_import_and_qualify accept wait_for_completion:false and return immediately. They hand back different ids: bulk_qualify_leads returns {status:'running', notification_id, lead_ids, lens_id} — poll leadbay_qualify_status with those. import_and_qualify returns {status:'running', import_ids} and no notification_id at all — poll leadbay_import_status({importIds, dry_run}) with those. Check it until it completes, per A LAUNCHED JOB below. Use the async pattern by default — the blocking default can exceed the MCP client's per-call timeout on large batches and produce a misleading "Request timed out" even though the server is still working.
leadbay_research_lead_by_id fan-outleadbay_research_lead_by_id is composite and reads many sub-resources. Calling it on 10 leads in parallel can saturate the transport and produce "Tool permission stream closed" errors that look like permission failures but are really backpressure. Call it sequentially, or at most 3 in parallel. If one call fails with a stream/timeout error, retry that one call once before moving on; on a second failure, note the lead and continue — do not abandon the remaining leads.
If a Leadbay tool returns "Request timed out", "stream closed", or any other transport-level error (distinct from a Leadbay-issued error payload), the work may still be running server-side. Do this in order:
wait_for_completion:false and poll the status tool with the returned id. Don't re-pull leads; that can shift the lens.If pull_leads itself fails and you have no prior batch, then yes — retry it, explicitly pass the lensId you captured (if any), and continue.
Leadbay has no cancel. Once leadbay_enrich_titles, leadbay_bulk_qualify_leads, leadbay_import_leads or leadbay_import_and_qualify has returned a launched or running result, that work is queued on Leadbay and runs to completion, and the quota it uses is already committed. A discovery, preview or dry_run result launched nothing and is not covered here.
The user cancelling in the chat, a request timeout, or a closed stream stops YOUR waiting, never the job. cancelled: true means we stopped watching, not that the work stopped. What to do next depends on what you are holding:
handle covers a second time — that uses the quota again on the same rows. leadbay_import_status takes importIds, so pass the values of import_ids under that name. A qualification started by leadbay_import_and_qualify has no notification of its own: resume it with leadbay_qualify_status({lead_ids, lens_id}).
failed[] entrieswith error:"not_queued", or a rows_pending_upload count. Poll the handle for what was launched and re-run for that subset only, never for the whole batch.
returned. Check leadbay_account_status first: the launch may have landed and finished. Calling the same tool again with the same arguments will usually hand back the job already launched rather than starting a second one, but that guard is in-memory, five minutes, and per process, so it is best-effort — say what you are about to re-run before you use the user's quota on it.
A result saying the job is still working (still_running, next_poll, a running status) holds only ids to check it with. If the user's request needs the output:
does not look broken.
finished: wait_seconds: 45 where accepted, else every 15–30 s. A stop_reason or an early flat count is not finished.
Calling the launcher again is not a check: it can charge twice. Stop early only if the user said not to wait or the check tool says the job stalled. Then say it is not ready, show what landed, and that asking again fetches the rest.
Same resume-check semantics as leadbay_daily_check_in Phase 0 — if you see prior phase completions in the task list, resume from the next phase instead of restarting.
Call leadbay_pull_followups (NOT leadbay_pull_leads — those are different entry points; see §1.6 of the prospecting overview). If the user mentioned a city, sector, recency window, or other filter, build a FilterItem and pass it as set_filter so the result is narrowed (and the filter persists for the user's next session). If the user said something generic ("anyone I should follow up on?"), call with no set_filter to get the default Monitor view.
Disambiguation rule for discovery-sounding phrases in follow-up context: If the user says "best leads", "top leads", "leads for today", or "show me my leads" while you're already in the follow-up workflow context, they mean their Monitor pipeline — NOT a fresh Discover batch. Use leadbay_pull_followups, not leadbay_pull_leads. The cross-mode pivot at the end of Phase 2 lets them switch to Discovery if they intended that.
For geo filters specifically: prefer the city shortcut on leadbay_pull_followups({city: "Berlin"}) — the composite resolves the free-text city via /geo/search, returns ambiguities to disambiguate when needed (status: "ambiguous_locations" → pick an id → re-call with city_id), then merges the resolved admin_area into the Monitor filter as location_ids. If the user has already given you a numeric id, pass it as city_id. Don't guess admin_area ids — let the resolver do it.
One workspace = one country — a country name is NEVER a location filter. The admin-area index holds no country nodes, so "France" matches the commune of Francs and "United States" matches Statesboro: the call is silently fenced to one village and every conclusion from it is wrong. City AND country named? Keep the city, drop the country.
On `code: "COUNTRY_LEVEL_LOCATION"` read `country_locations[].axis` and `[].kind` — the recovery differs per case and they are NOT interchangeable, and do NOT retry with another spelling or a nearby city.
axis: "include":
home_country, or "nationwide" / "everywhere" → drop that ONE value. Omit the geo argument (city / locations / location_ids) only if nothing else was on it — then the result covers the whole workspace. If other values remain, keep them and describe the result as those places.foreign_country ("leads in France" on a US workspace) → unsupported, not unfiltered. Do NOT re-run without the argument: whole-workspace results are US leads and answer nothing about France. Say the workspace holds only its own country's companies.supranational ("EU", "EMEA") → name what the workspace covers, then offer the whole-workspace view as an explicit choice rather than assuming it.country_indeterminate (custom/staging backend) → its country is unknown, so claim nothing about what it holds.axis: "exclude" reverses all of that — never "omit the argument", which returns the very companies the user asked to remove. Excluding this workspace's own country would empty it; excluding any other country is a harmless no-op. Either way drop the value and ask what to carve out instead.
On a lens-WRITING tool (new_lens, adjust_audience, update_lens_filter) write NOTHING, with no re-call in any form: when the country was the only scope; for ANY foreign_country or supranational INCLUDE however much else came with it — the sectors and sizes were QUALIFYING that territory, not a second request, so writing them alone saves a real audience for a territory nobody asked about; and for ANY non-foreign_country exclude hit, likewise — dropping it and writing the rest inverts the ask.
Never infer WHICH country this workspace serves from the user's wording — "the whole US" does not make it one. Read _meta.region on any tool result — it outranks any recalled memory; on custom, claim nothing.
Place names never go in keywords, sectors or leadbay_refine_lead_targeting — text matches, not geo filters.
⚠ On this prompt, omitting the geo argument is only HALF of a whole-workspace read. leadbay_pull_followups defaults filtered to true, so dropping the country still reads the Monitor view through whatever filter a previous session persisted — an old city filter comes back as a small, plausible cohort and you would report it as everything. When the ask covers the whole workspace and nothing else was requested, pass filtered:false. When other criteria WERE requested (a sector, a recency window, a real city), re-send those in set_filter instead — that overwrites the stored filter, so filtered:false would throw away the very scope I asked for. Either way, read active_filters off the response and describe the scope from THAT, not from what you intended to send.
TRAVEL / IN-PERSON ROUTING — when the user's intent is geographic and visual ("I'm going to NYC next week", "leads I should visit in person", "this week's trip", "show me followups in <city>", "plan my itinerary", "trip itinerary", "show on a map", "leads in Texas / California", or any phrasing that asks for a map / geographic / trip-planning view — INCLUDING state- and region-level place names, but NEVER a country):
pull_followups: city / city_id / set_filter). Same response shape — just the explicit entry-point so the agent and the host know to route geographically. ### <Company Name> · <City>, <State or Country>
★ <one-sentence summary: score + sector fit + why-now>. <Contact ask:> reach **[<First Last>](<linkedin_page or constructed people-search URL>)**, <role>. ☎ <phone>.linkedin_page when present; otherwise build https://www.linkedin.com/search/results/people/?keywords=<First>+<Last>+<Company-stripped-of-suffixes> (URL-encode). Strip Inc / LLC / Corp / GmbH / Ltd / Co from the company name before searching. Same rules as the canonical pull_leads table renderer.+1 212-555-0100 or (212) 555-0100 — every modern chat renderer auto-linkifies to tel:. Don't wrap in markdown.When the response carries reply_check, do what it says before rendering: open each last_logged_email message and its thread in my mailbox and log any reply from the contact with leadbay_report_outreach. Tell me which leads replied. If you have no mail tool, say that replies were not checked.
GATE — DEFER TO TOOL RENDERING. When you call a Leadbay composite that ships its own RENDERING block (every composite in 0.9.0+ does), render the response using that block's recipe verbatim — score bars, glyph palette, column order, hide-list, link priorities, all of it. Do NOT substitute prose, a numbered list, or a different column structure even when an orchestrating prompt's body suggests alternate framing. Prompt-specific commentary (motivational nudges, summaries, next-action recommendations) belongs ABOVE or BELOW the canonical table, never in place of it.
If the prompt's body and the tool's RENDERING appear to conflict, the tool's RENDERING wins for the structural layout; the prompt's voice wins for the commentary that surrounds it.
Markdown table with FOUR columns, sorted by last_monitor_action_at desc. NO score bar in this view — discovery owns the ▰❖▱ visual identity; follow-up uses status badges. Active-pushback leads are already excluded server-side.
Active-filters line ABOVE the table, · -separated chips from active_filters.criteria:
| Criterion type | Chip |
|---|---|
location_ids | 📍 \<resolved name\> |
sector_ids | 🏷 \<sector name\> |
keywords | 🔍 \<keyword\> |
size | 👥 \<min\>–\<max\> |
last_action_date | 📅 \<window\> |
last_action | 🎯 \<action types\> |
liked / yc | ⭐ liked / 🏅 YC |
custom_field* | ⚙ \<field name\> |
Render *No filters applied.* when empty.
Column 1 — Status (DERIVED from existing fields, priority order):
epilogue_status == "EPILOGUE_INTEREST_VALIDATED_OR_MEETING_PLANED" → 🎯 Meeting bookedepilogue_status == "EPILOGUE_COULD_NOT_REACH_STILL_TRYING" → ⚡ Trying to reachepilogue_status == "EPILOGUE_STILL_CHASING" + last_prospecting_action_at within 14d → 🟢 In progressepilogue_status == "EPILOGUE_NOT_INTERESTED_LOST" → ❄ Not interested (usually filtered out)epilogue_status == null + last_prospecting_action_at == null → ✨ Newepilogue_status == null + last_prospecting_action_at > 60d → 💤 DormantAppend <relative time> of last_monitor_action_at (today/Nd/Nw/Nmo). For ✨, show new. Line 2: [Company](website) bold. Line 3: short location · compact size.
Column 2 — AI take (3 lines from split_ai_summary verbatim, <br>-separated):
Yes…→✅, No…→❌, Maybe…→🤔, else→💡.ai_summary italic, no emoji.Column 3 — History & notes
<relative time> ago · <last_prospecting_action> (humanize: snake_case → Title Case; drop LEAD_ prefix). When null: *Never touched.*epilogue_status set): 📌 Outcome: <user-facing label> · <relative time> — NEVER show the wire-format EPILOGUE_* value.📝 *<note clipped ≤ 14 words>* (<rel time>).*0 prior touches · 0 notes*.Column 4 — Contacts (max 3 lines, recommended_contact first):
★ [Name](linkedin_page or people-search) · ☎ phone · 📧 emailMarkers: ★ recommended, 💎 hot in web_insights key_people. Channel pills: ☎ phone (rec.phone_number → lead.phone_numbers[0] (co) → ⚪ phone); 📧 email (rec.email → ⚪ email). When no visible contact has email/direct phone: append *enrich first* in italic.
Hide: id, location.pos, web_fetch_in_progress, enrichment_in_progress, stale_at, sector_id, zero counters, social_presence booleans (except °-flag), score, string "null".
Legend (user-facing — NEVER say "epilogue"):
🎯 Meeting booked · ⚡ Trying to reach · 🟢 In progress · 💤 Dormant · ✨ New · 🔥 Hot · ❄ Not interested · ★ recommended · 💎 hot in web_insights · ☎ (co) = company line · ⚪ not enriched
MANDATORY: every contact name in your output — table cells, prose, headers, "Reach <Name>" callouts — MUST be wrapped in markdown link syntax `[Name](URL)`. Never render a contact name as bare text. A plain-text name is a broken contact card; the underlined name is the user's primary affordance for "take me to this person's profile". No "no URL available" exception — the search URL below is always constructable from name + company.
URL priority (first applicable wins):
contact.linkedin_page when it's a string starting with https:// (the MCP coerces the legacy literal "null" string to real null before you see it).https://www.linkedin.com/search/results/people/?keywords=<First>+<Last>+<Company>. URL-encode params. Strip Inc / LLC / Corp / Ltd / GmbH / Co / S.A. / S.L. / PLC / AG / SAS / SARL suffixes from the company. Append a trailing ° to the rendered name ONLY when this fallback is in use AND social_presence.linkedin == false. Never append ° when a real linkedin_page was used.Never link a person's name to the company's LinkedIn page (and vice versa) — the two surfaces are different and conflating them quietly degrades the workflow.
Use the lead's website as the company-name link target — prefix https:// if the value is a bare hostname. (The MCP does NOT synthesize a Leadbay-app deep-link URL; the team has not standardized one. Linking to website is always real data.)
When the response carries social_urls (the post-fix multi-platform URL block on rich-lead responses), render every non-null platform as a pill chip in the company-info row. Iterate over social_urls's keys — never hardcode a fixed list — and emit each as [<platform-label>](<url>). Skip platforms whose URL is null.
social_presence carries booleans for the same 6 platforms (crunchbase, facebook, instagram, linkedin, tiktok, twitter) — useful when you only care that the company has a profile somewhere. Use it as the °-flag signal in the contact people-search fallback (see linking/contact-linkedin).
ABOVE the table, add a 1–3 sentence "Where to start today" paragraph that names the single highest-urgency row and explains why (recent hot signal, overdue commitment, never-touched-but-strong-fit, etc.) — speak to urgency and momentum. Do NOT repeat the AI take column from the table.
Unlike leadbay_daily_check_in which deep-dives on every promising lead in Phase 4, this prompt waits for the user to point at a row. Reason: follow-up batches are typically larger and the user is triaging recall, not researching cold.
When the user picks a row, call leadbay_research_lead_by_id on that single lead (or leadbay_research_lead_by_name_fuzzy if they only have the name) and offer to leadbay_prepare_outreach once they say "let's reach out".
If the user wants to filter the whole portfolio by a web-research signal — "which of my leads acquired a company since 2025", "find everyone with a funding signal", "who changed CEO" — do NOT loop leadbay_research_lead_by_id per row, and do NOT guess from freshness fields. Call leadbay_scan_portfolio_signals({query, since?}) once: it bulk-reads the cached signals across the portfolio and returns only the matches, campaign-ready. Offer to build a campaign from the matches.
Close the gap — don't just report it. The scan returns not_researched[] (leads with no cached Leadbay signal) and may surface matches whose cached signal is thin or undated. These are the leads where the answer is genuinely unknown, not absent. Rather than stopping at "K aren't researched yet", offer to fill the gap and refine:
not_researched / thin-signal leads for the exact signal the user asked about (company name + the query terms, e.g. "<Company> acquisition 2025"). Do this only for the gap leads, not the whole portfolio — the cached matches are already answered.matched cohort, and cite the source URL/date you found. Never silently merge a web-found signal into the campaign-ready cohort as if Leadbay had verified it.leadbay_bulk_qualify_leads so Leadbay runs its own web_fetch and the signal lands in the portfolio's cached signals[] on the next scan.This keeps the honesty guarantee intact (Leadbay's cached signals[] stay the source of truth) while still answering the user's question for the leads Leadbay hasn't researched yet.
SIGNAL HONESTY — never infer signals from freshness. stale_at, web_fetch_in_progress, fetch_at are freshness markers, not signal indicators — signal presence is read ONLY from the actual signals[] / web_fetch.content entries. For "which of my leads have signal X" across a portfolio, call `leadbay_scan_portfolio_signals` (bulk-reads cached signals); don't loop leadbay_research_lead_by_id per lead or guess from freshness. A lead with no cached content is not_researched, not "no match"; never report a signal verdict for a lead you never read.
Below the table, offer the cross-mode pivot in one short line so the user can redirect if you guessed wrong on entry-point routing: "Want to see NEW leads from your wishlist instead?" — that routes back to leadbay_daily_check_in (Discovery via leadbay_pull_leads).
IRON LAW — DO NOT TAKE OUTBOUND ACTION. Do not call leadbay_report_outreach, except to log a reply found in Phase 1b. Do not draft an outreach message into a tool argument. Outreach is the user's call after they've reviewed the follow-up list.
Render this acknowledgment VERBATIM as the last line of your message:
STOP — awaiting user decision. I will not take any further action until you tell me what to do next.Do not propose a next action. Do not call any more tools. Hand control back to the user.
~30 seconds. Free. No account. Every finding cites a rule and a line of evidence.