Seven skills. Together they are the agent: it finds who to contact, finds the reason, writes the message, runs the sequence, and routes every reply.
It runs 24/7 because the send half runs on its own infrastructure. Your machine being off changes nothing.
mkdir -p .claude/skills/{list-builder,signal-finder,fit-filter,opener-writer,sequence-builder,merge-prompt-writer,reply-router}
01. list-builder
---
name: list-builder
description: Build a raw prospect list from any LinkedIn source. Use
after outreach-context.md exists.
---
Read .agents/outreach-context.md.
SOURCES, highest intent first:
1. Commenters and likers on a relevant post, yours or a competitor's
2. Attendees of a relevant LinkedIn event
3. Members of a relevant group
4. Search matching the ICP
5. Job boards, for companies hiring the role your offer supports
SOURCE 1 first, always. Someone who commented on a post about your
exact topic has done something. Someone who matches a search filter
has done nothing.
Capture: name, headline, company, size, location, LinkedIn URL,
source.
Write to output/prospects.csv with a linkedin_url column. That
header name is required by the import later, so do not rename it.
Enrich nothing. Look up no emails. Most of this list gets discarded
in two steps and enrichment is priced per lookup.
02. signal-finder
The skill that separates this from every automated message they already ignore.
---
name: signal-finder
description: Find a dated, verifiable reason to contact each lead now
rather than later.
---
ONE signal per lead, ranked by how close it sits to a budget
decision.
TIER 1, budget is already moving
- Hiring for the role your offer replaces or supports
- Raised in the last 90 days
- New leader in post, last 60 days
- Publicly named the exact problem, last 30 days
TIER 2, the problem is visible
- Their site or content shows the specific gap
- Commented on a competitor's post
- Expanding into a new market
TIER 3, fit only, no observable trigger
Every signal must be VERIFIABLE and DATED. Record the date and the
URL, because it appears in the first message and they will check it.
No signal means write NONE. Never stretch an attribute into a
trigger. "Works at a SaaS company" is fit, not intent, and using it
as intent is the single clearest tell that a message was generated.
Add signal, tier, date and source_url columns to the CSV.
03. fit-filter
Run on the cheapest model. It touches every row, so it is the highest-volume call in the agent and has the least thinking in it.
---
name: fit-filter
description: Filter the raw list before any enrichment or writing.
---
Per row, return exactly one of KEEP, DROP or UNSURE, with a reason
of 12 words maximum.
KEEP Matches the job title band and the company size band
DROP Fails either, or is a competitor, or is too small to afford
the price band in outreach-context
UNSURE Headline is ambiguous. Do not guess.
KEEP and UNSURE to output/filtered.csv.
DROP to output/dropped.csv, so the filter can be audited when you
later wonder why a company was excluded.
Report the drop rate. Below 40% usually means the search was too
narrow rather than the list being unusually good.
THEN GRADE what survives, 1 to 10 across: ICP fit, title accuracy,
affordability, data completeness, deduplication, recency,
reachability, signal strength. Average into a letter.
Below B, stop and rebuild. Name the two weakest dimensions.
Signal strength predicts reply rate better than any other
dimension. A B-grade list where most rows carry a tier 1 signal
beats an A-grade list with none.
04. opener-writer
---
name: opener-writer
description: Write the first message from a signal.
---
STRUCTURE
Line 1 the signal, dated and specific
Line 2 the inference you drew from it
Line 3 the question
RULES
- Under 300 characters. Longer reads as a template.
- Line one references the signal SPECIFICALLY, not its category.
"Saw you're hiring a RevOps lead" beats "saw you're growing".
- One ask, answerable in under ten words.
- Never mention the product in message one.
- No compliments. No "hope this finds you well".
- No em dashes.
ONE VERSION PER TIER
Tier 1 lead with the signal. It is recent and they know it.
Tier 2 lead with the observable problem. There is no event.
Tier 3 lead with the segment-level pattern. Claim no
personalisation you cannot back.
BANNED, these are burnt:
"I came across your profile"
"I help [vertical] with [service]"
"Are you open to a quick chat"
"Quick question for you"
05. sequence-builder
---
name: sequence-builder
description: Turn the copy into a node-by-node spec ready to build.
---
Output a node list, not prose. Per node: action, delay, exact copy,
exit condition.
THE FIVE NODES
1 Import from source
2 Condition: lead is 1st level
3 YES branch: message, using the merge prompt
4 NO branch: connection request, then message on acceptance
5 Wait 3 days, follow-up on a different angle, then add tag
RULES
- Four touches maximum. After that the answer is no.
- Reply stops the sequence for that lead, always.
- A connection request auto-detects acceptance over two weeks, so
NO WAIT NODE after one. This is the most common unnecessary node
people add.
- Every touch a different angle. Never "just following up".
- Any voice note pairs with a written message, never sent alone.
STATE the daily send split against the ceiling in
outreach-context.md.
FAIL CLOSED if the requested volume exceeds the ceiling. Give the
split rather than writing a sequence that will restrict the account.
This spec maps node for node onto the Prosp builder. The actions available are connection request, message, voice note, InMail, message to open profile, comment on last post, reply comment, like last post, visit profile, wait X days and add tag. The conditions are has LinkedIn URL, lead is 1st level, opened message, lead is open profile, and check data in column.
06. merge-prompt-writer
The handoff. This is how one sequence personalises across a whole list without you writing a hundred drafts.
---
name: merge-prompt-writer
description: Convert message copy into prompts that personalise per
lead at send time.
---
SYNTAX, keep the two distinct:
{{double curly}} the platform's own profile data
[square brackets] instructions to the AI
Variables: first name, last name, headline, biography, job title,
company, last post. Plus any custom variable from the CSV, so the
signal column is usable here.
RULES
- ONLY the opening line varies. Keep the ask identical across leads
so results are comparable and failure is diagnosable.
- State length explicitly, in characters.
- Say what to do when a variable is empty. Never allow "Hi ,".
- Ban the burnt openers inside the prompt itself.
- One instruction per bracket. Compound instructions get
half-followed.
SHAPE
Hi {{first name}},
[One sentence referencing {{signal}}. Specific about the actual
detail, not the category. Under 90 characters. If {{signal}} is
empty use {{headline}}. Never open with "I came across".]
[Then this line unchanged:] Worth a quick look at how [X] handle
this?
Always preview against real contacts before sending. That catches
the prompt that reads fine on lead one and produces something odd
on lead five.
07. reply-router
The skill that makes 24/7 mean something. Replies arrive at any hour; this sorts them before you read one.
---
name: reply-router
description: Classify every inbound reply and route it. Runs on the
cheapest model, continuously.
---
Exactly one category:
INTERESTED Wants more, asks about the offer
TIMING Interested, not now. Capture the date named.
OBJECTION Price, fit, incumbent, capacity
WRONG_PERSON Not their remit. Ask who owns it.
NOT_INTERESTED Clear no. Thank them, stop, never pitch again.
AUTO_REPLY Out of office. Reschedule to their return date.
ESCALATE Anything below 8 confidence
Per reply: category, confidence 1 to 10, the sentence that decided
it, and the recommended next action.
Below 8 confidence, always ESCALATE. Do not guess at tone. Sarcasm,
brevity and politeness all read the same in text.
DRAFT the reply for INTERESTED and OBJECTION, but never auto-send.
A human presses send on anything that goes out under their name.
FOR INTERESTED, decide IF before HOW:
Ask for the meeting only when they have named a problem, asked
about price or timeline, or two substantive messages have gone each
way. On a one-line reply, ask one more question instead.
THE ASK: name the length and the agenda, offer the link and two
concrete times. "15 minutes, three questions, no deck." Never "let
me know what works."
After sending, tag the lead so the sequence stops. A follow-up
landing after someone has booked is the most avoidable bad
impression in the system.
The replies land in one place. Prosp has a unified inbox spanning every connected account with filters for campaign, replied and unread, so this runs over one queue rather than several logins. Add tag nodes write the classification back against the lead.
Running the whole agent in one go
Take ownership of this from start to finish.
Read .agents/outreach-context.md.
GOAL: a launch-ready campaign, with every output in output/.
Run in order: list-builder, signal-finder, fit-filter,
opener-writer, sequence-builder, merge-prompt-writer.
Stop and wait for me at two points only:
- After fit-filter, so I can see the grade before you write copy
- After merge-prompt-writer, before anything is built in the sender
Between those, keep working. Make intermediate decisions and note
them rather than checking in.
Hard rules:
- Never invent a number, client name or result. Only what is in
outreach-context.md.
- Grade the list before writing. Below B, rebuild rather than
proceed.
- Never propose a send volume above my daily ceiling.
End with the two things I need to review and the daily send split.
Two stops, not six. The list grade and the final copy are the only two decisions worth interrupting for. Everything between them is reversible by editing a file.