1---
2name: agency-outbound
3description: This skill should be used when the user wants to run the Agency Outbound skill, or asks to "pull the people engaging with a competitor's LinkedIn posts", "build a list from a LinkedIn hashtag", "enrich these engagers and keep the ones who fit", "push the leads into my HeyReach, Instantly, Smartlead or lemlist campaign", "how are this workspace's campaigns doing", or "build a dashboard I can share", for outbound run in one superagnt workspace per company. Runs on the superagnt MCP: LinkedIn post and engager data, first-party enrichment, the workspace database, the sequencer connected in this workspace, its synced results and the workspace dashboard. One workspace per chat, named before every write. Leads are staged; starting a campaign is always the human's.
4version: 0.1.0
5---
6
7# Agency Outbound
8
9Outbound for one company, start to finish inside that company's own superagnt
10workspace: a list from the people engaging with competitors' LinkedIn posts
11and with the hashtags its buyers follow, enriched and cut to the ones who fit,
12pushed into the sequencer the workspace already runs, then the results read
13back from the workspace's synced tables and turned into a dashboard view whose
14link you can send, or a report in chat where dashboards aren't on yet.
15
16For the scoring rubric and the outreach playbook in depth, the
17`warm-outbound-engine` skill goes further on each; the base `lead-generation`
18skill covers enrichment and dedup. This skill stands alone without them.
19
20## Start here
21
22Three first runs that work as pasted. When the person sends one of these, run
23it as written, and ask only for what it leaves open: which competitor, which
24hashtag, which campaign.
25
261. "Pull everyone who reacted to or commented on the last 3 posts on my top
27 competitor's LinkedIn page, keep the ones who fit my ideal customer, and
28 save them to a table in this workspace. Ask me which competitor first."
292. "Find this week's most engaged LinkedIn posts under a hashtag my buyers
30 follow, pull the people who commented, keep the ones who fit my ideal
31 customer, and find verified emails for the best 20. Ask me for the hashtag
32 first."
333. "Take the people engaging with my top competitor's LinkedIn page this
34 month, keep the ones who fit my ideal customer, and add them to my campaign
35 in this workspace's sequencer, stopped so I can check the list before it
36 starts."
37
38Each one runs steps 1 to 3 at their smallest, and the third adds step 4. On a
39first run in a workspace, step 1's questions come first, minus whatever the
40prompt already answers.
41
42## The workspace comes first
43
44Each workspace is one company: its own database, sequencer connections,
45synced results and dashboard, and nothing in it is readable from another
46workspace. That separation only holds if the agent keeps it:
47
48- One workspace per chat. If this chat has more than one superagnt
49 connection, ask which workspace it works in before the first call, and use
50 only that connection's tools from then on.
51- Name the workspace before every write: the tables in step 1, every push
52 in step 4, a sync in step 5, the publish in step 6. Use the name the person
53 knows it by: the connection's name when it is named for its workspace (for
54 example `superagnt-acme`), or the name they used in this chat. A bare
55 `superagnt` connection doesn't say which workspace it reaches, so when
56 nothing else does, ask.
57- A second workspace is a second connection, named for its workspace when
58 it's added (the server name in the MCP config, or the name field in
59 Claude's Add custom connector dialog), never a second bare `superagnt`. On
60 the consent screen the person picks that workspace before approving: it may
61 open on another of their workspaces.
62- Never move rows between workspaces. A list built in this workspace goes
63 into this workspace's sequencer and dashboard, never another's.
64
65The platform's own rules on this: `agnt_guidance_load({ "name": "workspaces" })`.
66
67## Step 0: tools
68
69Codex: superagnt's tools can take a few seconds to appear in a new chat.
70If none are listed, wait about 10 seconds and check again.
71
72`agnt_tools_list_enabled`. If the LinkedIn tools aren't served, turn them on:
73`agnt_tools_enable({ "families": ["data:linkedin", "database"] })` (on a
74grouped server, `agnt_tools_write` with `action: "enable"`). Most servers
75serve them already, along with the first-party `data_agnt_*` people and
76company tools, the database, vendor sync, the dashboard and the platform's
77guidance (`agnt_guidance_load`). `requires_upgrade` means a plan limit: give
78the person the `confirm_url`, wait, then retry.
79
80The sequencer's tools appear once the vendor is connected in this workspace;
81no enable step connects one. Not connected yet: ask the person to connect it
82on its page in this workspace in the superagnt app, with that company's own
83key. A sync call for a vendor that isn't connected returns
84`INTEGRATION_NOT_CONNECTED` with a `connect_url` to that page.
85
86### Tool names on grouped servers
87
88This skill writes flat (per-operation) names. New servers list grouped tools
89instead: one tool per resource with an `action` argument. Flat names resolve
90there but are not listed, so most agent apps won't let you call them:
91
92| Flat | Grouped tool, action |
93|---|---|
94| `data_linkedin_get_company_s_post` | `data_linkedin_companies`: `posts` |
95| `data_linkedin_get_company_post_comments` | `data_linkedin_companies`: `post_comments` |
96| `data_linkedin_search_post_by_hashtag` | `data_linkedin_search`: `hashtag_posts` |
97| `data_linkedin_get_post_reactions` | `data_linkedin_posts`: `reactions` |
98| `data_linkedin_get_profile_post_comment` | `data_linkedin_posts`: `comments` |
99| `data_linkedin_get_profile_data_by_url` | `data_linkedin_profiles`: `profile_by_url` |
100| `data_agnt_companies_enrich` | `data_agnt_companies`: `enrich` |
101| `data_agnt_people_email_finder` / `_email_verifier` | `data_agnt_contacts`: `find_email` / `verify_email` |
102| `agnt_db_apply_migration` / `agnt_db_execute_sql` | `agnt_db_sql`: `apply_migration` / `execute_sql` |
103| `agnt_db_select` | `agnt_db_read`: `select` |
104| `agnt_db_insert` / `agnt_db_upsert` / `agnt_db_update` | `agnt_db_write`: `insert` / `upsert` / `update` |
105| `agnt_sync_catalog` / `agnt_sync_list` / `agnt_sync_status` / `agnt_sync_preview` / `agnt_sync_changes` | `agnt_sync_read`: `catalog` / `list` / `status` / `preview` / `changes` |
106| `agnt_sync_enable` | `agnt_sync_write`: `enable` |
107| `agnt_dashboards_guide` / `agnt_dashboards_describe_data` / `agnt_dashboards_preview` / `agnt_dashboards_list_files` | `agnt_dashboards_read`: `guide` / `describe_data` / `preview` / `list_files` |
108| `agnt_dashboards_apply_template` / `agnt_dashboards_write_file` / `agnt_dashboards_publish` | `agnt_dashboards_write`: `apply_template` / `write_file` / `publish` |
109| `agnt_agents_create` / `agnt_schedules_create` | `agnt_agents_write`: `create` / `agnt_schedules_write`: `create` |
110| `agnt_tools_enable` | `agnt_tools_write`: `enable` |
111| `agnt_guidance_load` | `agnt_guidance_read`: `load` |
112
113`agnt_tools_list_enabled` keeps its name on both. The sequencer calls are in
114step 4's table, both ways.
115
116**Grouped data calls return a markdown table that drops fields this skill
117keys on.** Pass `response_format: "json"` on every LinkedIn call in steps 2
118and 3. Posts then carry `urn` (the comments call takes it) and `url` (the
119reactions call takes it); reactions carry `urn` (the reactor's member id) and
120`url` (their profile); comments carry `author_url` (the commenter's profile)
121and `url`, which links the comment itself, never a person. For a field the
122json view leaves out, pass `response_format: "raw"`.
123
124## Step 1: the buyer and the sources (first run in a workspace)
125
126Ask once, in one message, and draft what you can from the company's website
127so the person only corrects it:
128
129- who buys: the roles that own the problem, the kind and size of company, the
130 region, and who looks close but never buys;
131- the competitors whose LinkedIn company pages its buyers follow: 2 to 5, each
132 as the page's slug (the part after `linkedin.com/company/`);
133- 2 to 4 hashtags its buyers post and comment under;
134- who never to contact: competitors and their staff, current customers,
135 partners, the company's own team;
136- which sequencer and campaign the leads go into (step 4).
137
138Create the tables with `agnt_db_apply_migration`. If they exist from an earlier
139run, add missing columns instead of recreating them:
140
141```
142icp_profile(id, company text, icp_description text, target_geography text,
143 do_not_contact text, qualify_threshold int default 50,
144 sequencer text, campaign_id text, updated_at timestamptz)
145sources(source text unique, kind text, name text, added_at timestamptz)
146scraped_posts(post_url unique, post_urn, source text, author text, snippet,
147 reactions int, comments int, scraped_at timestamptz)
148leads(id, person, linkedin_url unique, linkedin_member_urn unique, title,
149 company, company_domain, location, email, email_verified boolean,
150 source text, source_post_url, engagement text, engagement_text text,
151 fit_score int, fit_reason text, status text, campaign_id text,
152 staged_at timestamptz, replied_at timestamptz, meeting_at timestamptz,
153 updated_at timestamptz)
154```
155
156`sources.kind` is `competitor_page` or `hashtag`. Write the answers with
157`agnt_db_insert`, the do-not-contact list included, before the first scrape:
158engager lists are full of competitors' staff.
159
160## Step 2: pull the engagers
161
162Start from competitor pages and hashtags. LinkedIn's keyword post search
163fails often, so use it only when neither source produces, and say so in the
164report.
165
1661. Competitor pages. `data_linkedin_get_company_s_post` with the page's
167 `username` returns its recent posts. Take the 1 to 3 most engaged from the
168 last couple of months.
1692. Hashtags. `data_linkedin_search_post_by_hashtag` with `hashtag` (no
170 `#`). Take the 1 to 3 most engaged recent posts, and skip the ones the
171 competitors or the company itself wrote.
1723. Keep a run to 3 to 5 posts across all sources. That cap is the run's whole
173 data cost. Skip posts already in `scraped_posts`; record the rest as you
174 take them.
1754. Reactors: `data_linkedin_get_post_reactions` with the post's `url`.
176 Commenters: `data_linkedin_get_company_post_comments` with the post's `urn`
177 for a company page's post, `data_linkedin_get_profile_post_comment` for a
178 person's post. Take a reactor's profile from `url` and member id from `urn`,
179 and a commenter's profile from `author_url`.
1805. Record `engagement` (`reacted` or `commented`) and `engagement_text` (the
181 comment, or the reaction type) on each lead, and `source` (the page slug or
182 the hashtag) with `source_post_url`.
1836. Drop anyone whose `linkedin_url` or `linkedin_member_urn` is already in
184 `leads`, the competitors' own staff and anyone on the do-not-contact list.
185 Then `agnt_db_upsert` the rest with `status = 'new'`.
186
187Filter on the enriched profile, never on the post payload: post data leaves
188out the location of plenty of real buyers.
189
190## Step 3: enrich, then keep the fits
191
192**The score gates the spend: a lead under the bar never costs an email
193lookup.**
194
1951. `data_linkedin_get_profile_data_by_url` for the real title, company,
196 location and canonical profile URL. Dedupe again on both keys.
1972. Gate: an empty or fake profile, or a location outside the target region,
198 gets `fit_score = 3` with the reason, and stops.
1993. `data_agnt_companies_enrich` for the domain, size and industry.
2004. Score against `icp_profile.icp_description`: `fit_score` 0 to 100 with a
201 two or three sentence `fit_reason` that cites the evidence. Over
202 `qualify_threshold` (default 50) is a fit. A fit gets `status = 'fit'`;
203 anyone under the bar gets `status = 'disqualified'` and nothing more, ever.
2045. Only for fits, and only when the campaign sends email:
205 `data_agnt_people_email_finder` from name and domain, then always
206 `data_agnt_people_email_verifier`. A LinkedIn-only campaign (HeyReach
207 always, Expandi or La Growth Machine when it sends no email) skips both
208 calls. An unverified email stays `email_verified = false` and never goes
209 into an email campaign.
2106. `agnt_db_update` each row as it moves: `new`, then `fit` or
211 `disqualified`, then `enriched` once it is ready to push (its email
212 checked, or straight away for a LinkedIn-only campaign). A failed run then
213 resumes instead of starting over.
214
215Above about 500 leads a run, the per-lead work belongs in a data job rather
216than a longer chat.
217
218## Step 4: push the fits to the sequencer
219
220Name the workspace, the sequencer and the campaign, and show the person the
221first ten rows before the first push. Then stage, never start: the campaign
222stays stopped, and pressing start is the person's.
223
2241. Read the campaign first with the vendor's campaign read tool: its status,
225 and every variable its steps use. A lead missing a variable gets the
226 step's fallback text, silently. Fill every one, plus first name, last
227 name, company and title. For HeyReach, also load the platform's doctrine
228 once: `agnt_guidance_load({ "name": "linkedin-outreach" })`.
2292. If a step uses a personal first line, write one per lead from what they
230 engaged with: the topic, never the competitor whose post it was. Never
231 invent a detail. Show the person five before the push.
2323. Push only rows with `status = 'enriched'` and, for an email campaign,
233 `email_verified = true`, into a campaign that isn't running:
234
235| Sequencer | Flat | Grouped tool, action | Stays stopped when |
236|---|---|---|---|
237| HeyReach | `connection_heyreach_list_add_leads_v2` | `connection_heyreach_list_members_write`: `add_leads` | you add to the campaign's lead list; never enroll into a running campaign |
238| Instantly | `connection_instantly_bulkaddleads` | `connection_instantly_leads_write`: `add` | the campaign is paused or a draft; pass `skip_if_in_workspace: true` |
239| Smartlead | `connection_smartlead_add_campaign_leads` | `connection_smartlead_leads_write`: `add` | the campaign isn't started; a running one emails new leads on its schedule |
240| lemlist | `connection_lemlist_add_campaign_lead` | `connection_lemlist_campaign_leads_write`: `add` | the campaign isn't running and has `autoReview` off (with it on, an added lead launches at once) |
241| EmailBison | `connection_emailbison_add_leads_to_campaign` | `connection_emailbison_campaign_leads_write`: `add` | the campaign is paused; the leads must exist in EmailBison first |
242| Expandi | `connection_expandi_add_campaign_contact` | `connection_expandi_campaign_contacts_write`: `add` | the campaign is paused |
243| La Growth Machine | `connection_lagrowthmachine_upsert_lead` | `connection_lagrowthmachine_leads_write`: `upsert` | the audience feeds no running campaign |
244| Apollo | `connection_apollo_io_add_contacts_to_sequence` | `connection_apollo_io_sequences_write`: `add_contacts` | the sequence is inactive; enroll saved contacts only |
245
2464. A vendor can skip a lead silently (already in the list or the campaign).
247 Stamp `staged_at` and `campaign_id`, and set `status = 'staged'`, only on
248 rows you can see in the vendor afterwards.
2495. Report what is waiting, where, and that nothing sends until the person
250 starts the campaign.
251
252## Step 5: read the results from the synced tables
253
254The workspace syncs its sequencer into its own database, so replies, accepts
255and meetings are a database query: no vendor rate limit and nothing that
256fails halfway.
257
2581. `agnt_sync_list` shows this workspace's syncs. No sync for the sequencer
259 yet: `agnt_sync_preview` shows the tables and rows it would add against the
260 plan's limits. Name the workspace, get the person's yes, then
261 `agnt_sync_enable`. The first backfill takes a while; `agnt_sync_status`
262 shows when each stream is current.
2632. `agnt_sync_catalog` lists each stream's table and columns. Read it before
264 writing a query, and never guess a column. The tables are named
265 `<vendor>_<stream>`, for example `heyreach_campaign_leads`,
266 `heyreach_messages`, `instantly_replies`, `instantly_campaign_daily_stats`,
267 `smartlead_campaign_daily_stats` and, for meetings,
268 `calendly_scheduled_events` and `calendly_invitees`.
2693. Query with `agnt_db_execute_sql` (`read_only: true`), filtered to
270 `_deleted_at is null`, and join the synced rows to `leads` on the LinkedIn
271 URL or the email. Write what you learn (`replied_at`, `meeting_at`,
272 `status`) into `leads`, the skill's own table. Never write into a synced
273 table: the sync owns it and rewrites it.
2744. A later run reads only what changed: `agnt_sync_changes` with the cursor
275 the last run stored, kept in a `sync_cursors` table with one row per
276 stream.
277
278For the sync's own rules: `agnt_guidance_load({ "name": "sync" })`.
279
280## Step 6: build the dashboard and share its view link
281
282Each workspace has one dashboard, and its views query this workspace's
283database, so the view you build here shows this company's numbers and no
284one else's. No `agnt_dashboards_*` tools listed? The dashboard isn't on for
285this workspace yet: report from the tables in the chat instead, and don't
286build a share link some other way.
287
2881. `agnt_dashboards_guide` once per chat, before the first write. Then
289 `agnt_dashboards_describe_data` for the tables and their columns.
2902. One view for the outbound: leads staged, accepts or opens, replies,
291 interested replies and meetings booked, each compared with the period
292 before; replies per day by channel; and a table of the interested replies
293 (person, title, company, campaign, date). Build it with
294 `agnt_dashboards_apply_template` from a template the guide lists, or
295 `agnt_dashboards_write_file`.
2963. `agnt_dashboards_preview` and read the report: every query returns rows
297 and none errors. Then name the workspace and `agnt_dashboards_publish` with
298 a plain summary.
2994. A view only goes public if it reads no platform data (credits, usage), has
300 no images, lists every table's columns, has no column named like a secret
301 and no web address written in its text. Keep emails and phone numbers out
302 of a view you'll share: anyone with the link can open it.
3035. You can't make the link, and no tool reads it back. Give the person the
304 view's `open_in_app_url` from the publish result (a page in the superagnt
305 app, for people in the workspace, never the link to send), and ask them to
306 press Share there and copy the link the dialog shows: that is the link to
307 send. It shows live numbers, read only, with no login. Afterwards
308 `agnt_dashboards_list_files` shows `public_link` on the view, which says a
309 link is live and since when but carries no URL.
310
311## Step 7: the report
312
313From the tables, never from memory: posts scraped and from which sources,
314new leads, how many fit, how many staged and into which campaign, and once
315results sync back, replies, interested replies and meetings, by source. Every
316report carries the score line ("scored N: X fit, Y under the bar, Z gated").
317When nothing fit, list the five closest calls with their reasons. Name the
318sources that produced fits and the ones that didn't: the next run reads that
319paragraph.
320
321Once a run has worked end to end, offer the standing version: the same
322sweep (steps 2 to 5) every day or two, for this workspace only. Run it on
323this agent's own scheduler when its scheduled runs see the superagnt tools,
324or as a deployed superagnt agent (`agnt_agents_create`, then
325`agnt_schedules_create`; enable the `lifecycle` and `schedules` families
326first if they aren't listed). A scheduled run has nobody to approve a tool
327call, so give it an allowlist: this skill's reads and its one staging call,
328and nothing that starts, activates or resumes a campaign or sends a message.
329Confirm the cadence and the expected data spend with the person before
330scheduling anything.
331
332## Hard rules
333
334- One workspace per chat, named before every write. No row crosses from
335 one workspace into another.
336- Stage, never start. No campaign is started, activated or resumed by this
337 skill. That is the person's call, in the vendor's app.
338- The do-not-contact list is absolute. Nobody on it is looked up, staged
339 or messaged. A lead under the bar never costs an email lookup.
340- Never write into a synced table. Read them; keep the skill's own state
341 in `leads`.
342- Never invent. No made-up detail in a first line, and every number in a
343 report or a dashboard comes from a query.
344- Never scrape a post's engagers twice, and never push a `linkedin_url`
345 already staged.
346
347<!-- skill_id: agency-outbound · source: https://github.com/superagnt/plugins -->