agentleFS
superagntsuperagnt/leverage/skills/packaged/warm-outbound-engine/SKILL.md

warm-outbound-engine

Skill6 starsChanged 2 months agoMITView on GitHub

This skill should be used when the user wants to run the LinkedIn Warm Outbound skill, or asks to \"scrape LinkedIn post engagers\", \"build outbound from people engaging with my market\", \"find warm leads on LinkedIn\", \"turn LinkedIn engagement into a sequence\", \"write outreach from what people commented\", \"offer each lead a free personalized resource\", \"auto-reply to LinkedIn or cold email replies\", or \"put my LinkedIn outbound on autopilot\", or wants recurring engagement-sourced prospecting staged into HeyReach, Instantly, AgentMail or their own Gmail. Runs on the superagnt MCP: LinkedIn post + engager data, first-party enrichment, workspace database, and one sending lane of the user's choice, with a research-backed cold outreach library and a recurring version that runs daily or every other day. Stages campaigns stopped; starting a sequence is always the human's.

The file

Show its rules and text

We split this skill into the rules it gives an agent

17 rules. About 0 are common, 11 are distinctive, and 6 are very specific to this project.

  • ≈0

    Common · estimate

    Many files like this have a rule of this kind.

  • ≈11

    Distinctive · estimate

    This author's own choices. Worth a look.

  • 6

    Very specific

    Tied to their project's names, files or tools. Probably skip.

  • Common
  • Distinctive
  • Very specific
  1. Very specific:

    Read when step 1 set a first_touch_asset.

    line 36
  2. Distinctive:

    If that fails, say so once and proceed: the steps below stand alone.

    line 50
  3. Distinctive:

    If none are listed, wait about 10 seconds and check again before concluding superagnt isn't connected or falling back to web search.

    line 55
  4. Distinctive:

    Ask once, in one message, and draft what you can from their website so they only have to correct it:

    line 119
  5. Distinctive:

    Create the tables. If they exist from an earlier version of this skill, add the missing columns instead of recreating them:

    line 138
  6. Distinctive:

    Write more phrases like the families with replies.

    line 211
  7. Distinctive:

    Prefer posts with real engagement.

    line 240
  8. Distinctive:

    Check the profile URL first (free): a missing or malformed one never becomes a paid call.

    line 276
  9. Very specific:

    Only for leads that cleared the bar, and only when the lane sends email: data_agnt_people_email_finder from name + domain, then always …

    line 295
  10. Very specific:

    Read references/cold-outreach-playbook.md before writing a single message.

    line 310
  11. Very specific:

    Write each lead's message into leads.first_message and tag opener_variant (plain, or asset when step 1 set a first_touch_asset).

    line 329
  12. Distinctive:

    In every lane: stage, do not start: Add the leads, leave the campaign stopped, and report what is waiting.

    line 417
  13. Distinctive:

    This client's own scheduler: If the client can schedule a recurring task whose runs have the superagnt tools, set the sweep up there.

    line 433
  14. Distinctive:

    Every report carries the score distribution line ("scored N: X qualified, Y under the bar, Z gated"), which tells thin sourcing apart from …

    line 490
  15. Very specific:

    Stage, never start: No campaign is started, activated or resumed by this skill.

    line 501
  16. Distinctive:

    Never invent: No made-up personalization, numbers or claims about a lead's business, in a message or an asset.

    line 512
  17. Very specific:

    Never scrape a post's engagers twice, and never contact a linkedin_url already in leads.

    line 517

≈ “Common” is an estimate: at least 1 in 5 of the 350,495 skill files we read have a rule of the same kind, not the same words. “Very specific” is our guess from the words: it names a file, a command, a setting or the project itself.

Keep what fits and drop the rest. Your version, ready for your agents.
Files
SKILL.md

LinkedIn Warm Outbound

Outbound sourced from engagement, not from a list. Every lead in the table did something in public first: reacted to, or commented on, a post about the problem you solve. Builds on the base lead-generation skill for the enrichment mechanics — read that for per-tool detail; this one adds the engagement source, the scoring gate, the first message, and the four sending lanes.

The intended arc: run it once end to end in this client, look at what it staged, then put it on autopilot: the same sweep daily or every other day, on this client's own scheduler or a deployed superagnt agent (step 7).

The outreach library

This skill ships a reference library in references/, researched September 2026 from full YouTube transcripts (Alex Hormozi, Nick Abraham, Alex Berman), Nick Abraham's LinkedIn outreach posts on X, and agnt_'s own production system. Read the matching file before the step that needs it, and prefer it over generic outreach folklore.

  • references/icp-scoring-rubric.md: the two-layer scorer (a narrow gate, then one holistic score against the ICP description) and the weekly calibration loop. Read before step 4.
  • references/cold-outreach-playbook.md: targeting, connection requests, first messages, follow-ups, replies, sending limits and benchmarks, per lane. Read before step 5.
  • references/free-resource-first-touch.md: build each qualified lead a personalized asset from their own data and offer it instead of a pitch. Read when step 1 set a first_touch_asset.
  • references/webhook-auto-reply.md: vendor webhooks into a deployed agent that classifies replies, logs them, and drafts or sends answers under approval.
  • references/autopilot-agent-team.md: the deployed version of step 7, a recurring agent team on superagnt: the team shape, data jobs for the per-lead work, cadence, cost, and how real deployments broke.

For LinkedIn via HeyReach, also load the platform's own doctrine: agnt_guidance_load({ "name": "linkedin-outreach" }).

No references/ folder next to this file (a harness that saved only SKILL.md)? Fetch the one you need from https://raw.githubusercontent.com/superagnt/plugins/main/plugins/warm-outbound-engine/skills/warm-outbound-engine/references/<file>. If that fails, say so once and proceed: the steps below stand alone.

Step 0 — tools

Codex: superagnt's tools can take a few seconds to appear in a new chat. If none are listed, wait about 10 seconds and check again before concluding superagnt isn't connected or falling back to web search.

agnt_tools_list_enabled; if missing on a flat server: agnt_tools_enable({ "families": ["data:linkedin", "database"] }) (the first-party data_agnt_* people/company tools are always on). A grouped server already serves every data source and the database: nothing to enable. That is the whole first run — do not enable a sending lane yet. requires_upgrade → confirm_url to the user → wait → re-run. (The warm-outbound-engine skill endpoint pre-enables these two.)

Tool names on grouped servers

This skill writes flat (per-operation) names. New servers list grouped tools instead: one tool per resource with an action argument. Flat names still resolve there but are not listed, so most clients will not let you call them. The calls this skill makes:

FlatGrouped tool, action
data_linkedin_search_posts / _search_post_by_hashtagdata_linkedin_search: posts / hashtag_posts
data_linkedin_get_company_s_postdata_linkedin_companies: posts
data_linkedin_get_profile_s_postsdata_linkedin_activity: posts
data_linkedin_get_post_reactionsdata_linkedin_posts: reactions
data_linkedin_get_profile_post_commentdata_linkedin_posts: comments
data_linkedin_get_profile_data_by_urldata_linkedin_profiles: profile_by_url
data_linkedin_get_company_by_domaindata_linkedin_companies: by_domain
data_agnt_companies_enrichdata_agnt_companies: enrich
data_agnt_people_email_finder / _email_verifierdata_agnt_contacts: find_email / verify_email
agnt_db_selectagnt_db_read: select
agnt_db_insert / agnt_db_upsertagnt_db_write: insert / upsert
agnt_db_execute_sqlagnt_db_sql: execute_sql
agnt_tools_enableagnt_tools_write: enable
agnt_guidance_loadagnt_guidance_read: load
connection_heyreach_campaign_get_by_id / _get_sequenceconnection_heyreach_campaigns_read: get / sequence
connection_heyreach_list_add_leads_v2connection_heyreach_list_members_write: add_leads
connection_heyreach_campaign_add_leads_v2connection_heyreach_campaign_leads_write: add
connection_heyreach_campaign_startconnection_heyreach_campaigns_write: start
connection_instantly_listcampaignconnection_instantly_campaigns_read: list
connection_instantly_bulkaddleadsconnection_instantly_leads_write: add
connection_instantly_activatecampaignconnection_instantly_campaigns_write: activate
connection_agentmail_create_inboxconnection_agentmail_inboxes_write: create
connection_agentmail_create_draftconnection_agentmail_drafts_write: create
connection_agentmail_send_draft / _reply_to_messageconnection_agentmail_messages_send: send_draft / reply
connection_agentmail_list_messagesconnection_agentmail_messages_read: list
connection_gmail_save_draftconnection_gmail_mail_write: save_draft
connection_gmail_send_emailconnection_gmail_mail_send: send

agnt_tools_list_enabled keeps its name on both surfaces. connection_* tools are grouped the same way as the rest; the lane calls are in the table.

Grouped data calls return a markdown table that drops fields this skill keys on. Pass response_format: "json" on every LinkedIn call in steps 3 and 4: you get every field of the view instead of the default columns. Then posts carry urn (the comments call takes it) and url; reactions carry urn (the member id for linkedin_member_urn) and url (the reactor's profile); comments carry author_url (the commenter's profile) and url, which is the link to the comment itself, never a lead's profile. For any field the view does not carry, such as a post author's profile URL, pass response_format: "raw": the full payload, exactly what the flat tool returns.

Step 1 — the ICP, and the searches it implies (first run only)

Ask once, in one message, and draft what you can from their website so they only have to correct it:

  • who buys, in their words: the roles that own the problem, the kind of company, the situation in which they feel it, and who looks close but never buys. This becomes the ICP description the score reads (references/icp-scoring-rubric.md);
  • where they can sell, and who never to contact: competitors, customers, partners, their own team;
  • a watchlist: the competitors whose posts their buyers react to, and 5 to 10 creators whose audience is the buyer. Optional, and the steadiest source there is;
  • which channel they send from: HeyReach, Instantly, AgentMail or Gmail (recorded now, enabled at step 6);
  • optionally, what they could give a prospect for free that proves they are good at their job (a report, a teardown, a sample list, a working setup of their product). See references/free-resource-first-touch.md; if they have nothing, skip it and the plain opener still works.

Create the tables. If they exist from an earlier version of this skill, add the missing columns instead of recreating them:

icp_profile(id, sells text, problem text, icp_description text,
            trigger_signals text, target_geography text, personas text,
            do_not_contact text, qualify_threshold int default 50,
            lane text, first_touch_asset text, updated_at timestamptz)
watchlist(linkedin_url unique, kind text, name text, added_at timestamptz)
leads(id, person, linkedin_url unique, linkedin_member_urn unique, title,
      company, company_domain, email, email_verified boolean,
      source_kind text, pond text, source_post_url, engagement text,
      engagement_text text, persona text, fit_score int, fit_reason text,
      opener_variant text, first_message text, status text,
      staged_at timestamptz, replied_at timestamptz, reply_intent text,
      updated_at timestamptz)
search_history(term, kind, pond, searched_at default now())
scraped_posts(post_url unique, post_urn, pond, term_used, author, snippet,
              scraped_at)

Then write the answers to icp_profile (agnt_db_insert) and the watchlist (kind is competitor or creator). The do-not-contact list goes in before the first scrape: engager lists are full of competitors, and messaging one is the fastest way to get your outreach posted about.

Upsert key on leads is linkedin_url — it is the only identifier that survives a job change. LinkedIn also spells the same person as an encoded member id (ACoA…) in some payloads, so keep linkedin_member_urn too and dedupe on both. On a grouped server the default markdown view leaves out the member id and a commenter's profile URL: request response_format: "json" (see Step 0).

Step 2 — choose this run's sources, then write its searches

Where you fish matters more than how hard. There are three ponds, and only one of them is the obvious one:

  1. Buyer behavior (write these first). Posts written by people who have the problem right now: hiring for the function ("hiring a [role that owns the problem]"), scaling or straining ("our [process] is breaking"), evaluating ("anyone using a [category] tool for [job]?"), or the pain stated plainly at the end of a bad week. On these posts the author is a lead too, and the strongest one there is: writing the post is the intent signal.
  2. The watchlist. The recent posts of the competitors and creators in watchlist. Everyone reacting to a competitor's product content is already shopping; a creator's audience is the buyer by construction.
  3. Topic. Keywords and hashtags for the space. The widest pond and the noisiest: people who talk about a category for a living are often the ones selling into it, which is why it is one pond of three.

Over a week, aim for roughly 40% buyer-behavior searches, 30% watchlist and 30% topic. A run gives its searches to one or two ponds rather than all three shallowly; the first run covers all three.

Do not keep a fixed keyword menu. Generate 3–6 fresh searches per run from the ICP description, steered by two queries. Run both with agnt_db_execute_sql; agnt_db_select can't express a join or a now() - interval filter:

-- what has actually converted, per pond and term
SELECT sp.pond, sp.term_used, count(l.id) AS leads,
       count(*) FILTER (WHERE l.fit_score > 50) AS qualified,
       count(*) FILTER (WHERE l.replied_at IS NOT NULL) AS replied
FROM scraped_posts sp JOIN leads l ON l.source_post_url = sp.post_url
GROUP BY 1, 2 ORDER BY replied DESC, qualified DESC;

-- what not to repeat yet
SELECT term FROM search_history WHERE searched_at > now() - interval '14 days';

Write more phrases like the families with replies; drop the phrasing patterns that produced leads and no replies. Never reuse a term from the 14-day window; everything older is fair game again. Give the next run's searches to the pond that is producing.

The single highest-leverage rule here: write the language your BUYER posts in, not the language your industry sells in. Category and execution terms ("shelf reset", "category review", "Q3 launch at Target") surface posts whose engagers are buyers. Vendor/martech terms ("retail media network", "martech stack") surface posts engaged with by other vendors, and a run of those poisons the table with people who will never buy. Test every generated term: would a buyer plausibly write or react to a post using this phrase?

Insert each term into search_history with its kind (keyword, hashtag, watchlist) and pond as soon as its search call returns, zero results included, so a run that dies halfway keeps the history of what it actually searched. A term whose call failed (credits, provider error) is not recorded and stays eligible next run.

Step 3 — the sweep

  1. Find the candidate posts: data_linkedin_search_posts for keyword searches (buyer behavior and topic), data_linkedin_search_post_by_hashtag for hashtags (topic only), and for a watchlist entry data_linkedin_get_company_s_post (a company page) or data_linkedin_get_profile_s_posts (a person), taking its 1–2 most engaged posts from the last couple of months. 3–5 posts per run across all ponds — that cap is deliberate, it is the run's whole data cost.
  2. Prefer posts with real engagement; a post with 4 reactions is not worth a scrape when a 90-reaction one is in the same result set.
  3. Skip posts already in scraped_posts. Record the rest, with their pond, as you take them.
  4. data_linkedin_get_post_reactions for reactors, data_linkedin_get_profile_post_comment for commenters (grouped: data_linkedin_posts actions reactions and comments, with response_format: "json"). Take a reactor's profile from url and member id from urn; take a commenter's profile from author_url, never from the comment's url. Record engagement ('reacted' | 'commented') per lead — commenters convert better and are worth a different opener. Keep what they did in engagement_text: the comment itself for commenters, the reaction type for reactors. It is the one personalization hook every lead comes with.
  5. On a buyer-behavior post written by a person, add the author as a lead too (source_kind = 'author', with the post itself as engagement_text); everyone else is source_kind = 'engager'. Never add a competitor as the author of its own post, and drop the employees of a watched company when you pull its engagers.
  6. Drop anyone whose linkedin_url or linkedin_member_urn is already in leads. Dedup at the source, never at send time. A grouped call's default markdown table has no member id, and on comments its url is the comment link, so a dedupe against it silently lets duplicates through: fetch the json view first.

Geography and other filters belong on the enriched profile, not on the search payload. LinkedIn's post-search results omit location for plenty of genuinely in-market posters; filtering there silently starves the funnel. Take the lead, filter it in step 4.

Step 4 — score, then enrich

Two layers, in this order, from references/icp-scoring-rubric.md (read it before the first scoring pass). The score gates the spend: a lead that fails it never costs an email lookup.

  1. Check the profile URL first (free): a missing or malformed one never becomes a paid call.
  2. data_linkedin_get_profile_data_by_url → real title, company, location, and the canonical profile URL. Dedupe again on both keys.
  3. The gate: reject only an explicitly out-of-region location or an empty or fake profile (fit_score = 3, with the reason), and drop anyone on the do-not-contact list. Never judge the role here: a gate that guesses at fit from a headline starves the funnel.
  4. data_agnt_companies_enrich (or data_linkedin_get_company_by_domain) → domain, size, industry. Research the company further only when the score can't be settled without it.
  5. The score: one holistic judgment against icp_profile.icp_description: fit_score 0–100, a two- or three-sentence fit_reason that cites the evidence and names the uncertainty, and a persona only when confident. Above the bar (qualify_threshold, default 50) qualifies, and an unresolved maybe qualifies; a score at or under it needs a concrete wrong-way reason. Below the bar → status = 'disqualified' and stop: no email lookup, no message, ever.
  6. Only for leads that cleared the bar, and only when the lane sends email: data_agnt_people_email_finder from name + domain, then always data_agnt_people_email_verifier. LinkedIn URL → email is not a one-step lookup. Unverified stays email_verified = false; those rows are still workable on LinkedIn. When icp_profile.lane is HeyReach, skip both calls.
  7. agnt_db_upsert with status moving new → scored → enriched → staged, so a failed run resumes instead of restarting. enriched means ready to message. Never overwrite a known value with a blank.

Above ~500 leads a run, this belongs in a data job rather than a longer session — one cheap structured call per lead (references/autopilot-agent-team.md).

Step 5 — write the first touch

Read references/cold-outreach-playbook.md before writing a single message; it has the rules per lane. The ones that matter most:

  • The message has one job: a reply. It reads one-to-one. No company intro, no feature list, no calendar link in the first touch.
  • Open with the topic they engage with, never the creator. "Saw you've been in a lot of conversations about <topic>" works; "saw you liked <creator>'s post" reads as surveillance and gets screenshotted back to the creator. For commenters, the substance of what they said (engagement_text) is fair game, in your own words; never quote it back or link the post. For authors (source_kind = 'author'), their own post is the natural opener ("saw you're hiring your first SDRs"): it's their public statement, not someone else's audience. A generic compliment is worse than nothing.
  • Give before you ask, then ask small. A yes/no question or permission to send something useful, not a meeting.
  • Never invent a detail or a number. Exact figures from their own data, or none at all.

Write each lead's message into leads.first_message and tag opener_variant (plain, or asset when step 1 set a first_touch_asset). With an asset, build it for the top-scored leads first and offer it in the message: references/free-resource-first-touch.md.

Show the user the first five messages (and the first five assets) before anything is staged. After they approve the style, show a sample each run.

Step 6 — stage into the lane, once

Enable only the lane recorded in icp_profile.lane. Four lanes:

HeyReach — LinkedIn sequences

agnt_tools_enable({ "families": ["connection:heyreach"] }), then load the platform's HeyReach doctrine once: agnt_guidance_load({ "name": "linkedin-outreach" }). The campaign engine owns delivery (timing, acceptance, reply-stop); this skill owns judgment (who, what to say, what to do with replies).

  1. The user builds the campaign in HeyReach: the sequence, a sender account, created with "Create empty list". Read it before touching it: connection_heyreach_campaign_get_by_id for the status, connection_heyreach_campaign_get_sequence for the variables each step uses.
  2. Personalization rides on the lead at enrollment and can't be changed afterwards. Pass the whole first message as one custom variable (customUserFields: [{ "name": "fullMessage", "value": ... }]) and make the sequence's message step just that variable; also fill every other variable the sequence references, plus firstName, lastName, companyName and position. A missing variable falls back to generic copy silently. Write the message to the row, read it back, then enroll.
  3. Stage without starting: connection_heyreach_list_add_leads_v2 into the campaign's lead list works while the campaign is paused, and nothing sends until the user resumes it. connection_heyreach_campaign_add_leads_v2 only works on an active campaign and starts delivery for those leads, so use it only when the user has said the campaign is live and wants leads flowing into it.
  4. Leads already in HeyReach are skipped silently: a successful call does not mean every lead was added. Stamp staged_at only on rows you can see in the list afterwards.

Two settings that decide whether this works: send a blank connection request (a request carrying a pitch converts materially worse than an empty one), and turn on exclude-in-other-campaigns so one person never appears in two sequences.

Instantly — email sequences

agnt_tools_enable({ "families": ["connection:instantly"] })

connection_instantly_listcampaign to pick the persona-matched campaign, then read its first step to see which variable it expects. connection_instantly_bulkaddleads with campaign_id, the verified-email rows only (an unverified address in an Instantly campaign is a bounce charged against the sending domain), and skip_if_in_workspace: true. The first message rides in the lead's personalization field or a custom_variables key that the step references.

AgentMail — an inbox the agent owns

agnt_tools_enable({ "families": ["connection:agentmail"] })

connection_agentmail_create_inbox once (its own domain — never the user's primary), then connection_agentmail_create_draft per lead (to, subject, text; never send_at) and connection_agentmail_send_draft when the user releases them. Replies come back through connection_agentmail_list_messages / connection_agentmail_reply_to_message, which is what makes this lane the one where the agent can actually hold the thread.

Gmail — the user's own mailbox

Nothing to enable: a connected mailbox is already on. Once the user has connected Gmail, connection_gmail_* is served on the workspace MCP like any connected vendor; agnt_tools_enable cannot connect it. Not connected yet → ask the user to connect Gmail on their superagnt connections page, then re-run agnt_tools_list_enabled.

connection_gmail_save_draft per lead (to, subject, body as HTML) puts each first message in the user's Drafts, where they review and send it themselves. That is the default. connection_gmail_send_email sends for real from the user's own address, gated only by their client's tool approval, so call it only on an explicit instruction for that run. For the standing version (step 7), wire the same ops onto the deployed agent as flat tools entries: { kind: "integration", provider_id: "gmail", operation_id: "save_draft" } and "send_email", where every send surfaces for approval.

In every lane: stage, do not start. Add the leads, leave the campaign stopped, and report what is waiting. Pressing start is the user's act, and it is the last point at which a bad list can still be caught.

Step 7 — put it on autopilot

One batch is not the product. A list built once and never refilled goes quiet within weeks: the sequence outruns the leads and the channel looks dead. Once a run has worked end to end and the user liked what it staged, offer the standing version: the same sweep (steps 2 to 6), run daily or every other day. Smaller, more frequent runs keep the leads fresh (people who engaged in the last day or two are the warm ones), keep each run cheap, and keep the sending account under its limits.

There are two ways to run it. Offer whichever fits the user:

  • This client's own scheduler. If the client can schedule a recurring task whose runs have the superagnt tools, set the sweep up there. Each run starts with no chat history, which is fine: the ICP, the leads and the search history are in the workspace database, and the run reads them like step 2 does. Write the task so a fresh session can do it alone: run this skill's sweep for the stored ICP, the lane, the per-run cap, stage and never start, then report. Runs use the user's plan for this client; data calls still spend superagnt data credits. A scheduled run starts with nobody signed in, so check two things before promising it: the client can run unattended on this machine (its own sign-in, such as a long-lived token for Claude Code, or an API key or ChatGPT login for Codex), and something will fire it (the client's own scheduler, cron or launchd). Test one unattended run. Every run reads strangers' posts and comments, nobody is there to approve a tool call, and superagnt doesn't gate tools for a client. So give the scheduled run an allowlist in its client permissions, not a deny list: the agnt_* and data_* tools in the table under Step 0, the lane's read tool, and its one staging call: connection_heyreach_list_members_write (HeyReach), connection_instantly_leads_write (Instantly) or connection_gmail_mail_write (Gmail drafts). Nothing that sends or starts goes on it: not connection_gmail_mail_send, connection_agentmail_messages_send, connection_heyreach_inbox_send, connection_heyreach_campaigns_write, connection_heyreach_campaign_leads_write (its add can resume a paused campaign), connection_instantly_emails_send or connection_instantly_campaigns_write, nor their flat names on a per-operation server. The AgentMail lane stays out of this route: an AgentMail draft created with send_at sends itself, and no permission stops that, so run it attended or on the deployed agent. In the test run, check the scheduled run's tool list: none of those tools may appear. If the client can't restrict a scheduled run's tools, use the deployed agent. Have each run report into a table or dashboard view the user reads, not only a log, and have the next attended session check the last run's searched_at so a dead schedule gets noticed. If you are not sure the client can schedule, or that its scheduled runs see the superagnt tools, say so and use the other way.
  • A deployed superagnt agent on a schedule. For when the client can't schedule, runs must happen with the user's machine off, replies should be answered as they arrive, or the volume calls for splitting the work. references/autopilot-agent-team.md covers the team shape (who does the judgment, what moves into a data job to cut AI cost, what webhooks trigger) and the ways real deployments of this exact motion have broken. Agent runs spend AI credits.

The reply side is references/webhook-auto-reply.md either way: without it leads.replied_at stays empty, step 2's steering query has nothing to learn from, and the engine generates volume without ever getting warmer.

Confirm the cadence and the expected spend with the user before scheduling or deploying anything, and hand over the confirm_url on any requires_upgrade (a plan limit, spent data credits, or a trial that ended).

Step 8 — report

From the table, never from memory (agnt_db_select): posts scraped, new leads, how many cleared the score bar, how many staged and into what, and the reply rate per search-term family, per pond and per opener_variant. Every report carries the score distribution line ("scored N: X qualified, Y under the bar, Z gated"), which tells thin sourcing apart from a bar set too high; when nothing qualified, list the five closest calls with their reasons instead of an empty table. Once replies flow back: replies by intent and the median time to answer one. Once a week, run the calibration loop in references/icp-scoring-rubric.md with the user. Name the terms that earned replies and the terms that did not — that paragraph is what the next run reads.

Hard rules

  • Stage, never start. No campaign is started, activated or resumed by this skill. connection_heyreach_campaign_start and connection_instantly_activatecampaign are the user's to call.
  • Never send from the user's own mailbox without an explicit instruction for that run. Drafts are the default in the Gmail and AgentMail lanes.
  • Replies to humans are approved. A deployed reply agent sends only what the user approved, unless the user explicitly relaxed a named reply class (references/webhook-auto-reply.md).
  • One lane. Enabling all four loads hundreds of tools into the context for no gain, and staging the same person into two channels is how a warm lead becomes a complaint.
  • Never invent. No made-up personalization, numbers or claims about a lead's business, in a message or an asset.
  • The do-not-contact list is absolute. Nobody on it is looked up, staged or messaged, ever. A lead under the score bar never costs an email lookup, an asset or a message.
  • Never scrape a post's engagers twice, and never contact a linkedin_url already in leads.
<!-- skill_id: warm-outbound-engine · source: https://github.com/superagnt/plugins -->

Download now (ZIP)

Does it work?

Reviews from people and from AI agents that ran it: what they used it for, and whether it worked.

No reviews yet

People and AI agents can review whether this works in practice. Say what you used it for, and whether it worked.

  • It worked
  • Partly
  • It didn't
What did you use it for? e.g. Reviewing pull requests in a Rails app

Using an agent? It can post a review for you after it has run the file: the MCP tool registry_write, action report. How to connect one.

Your trail

Files you open will collect here, so you can find them again.

Open two or more, and you can remix the rules you like from each into one list. Kept in this browser only.

Now do this for your own.

Bring your CLAUDE.md, AGENTS.md or skills. agentleFS shows which of your rules are common, which are yours and which only fit one project, and keeps one version your agents read everywhere.