Install
$ agentstack add skill-swan-gtm-gtm-skills-linkedin-dm-signal-classifier ✓ scanned · ✓ verified — works with Claude Code, Cursor, and more.
Security review
✓ PassedNo issues found. Passed automated security review. · v0.1.0 How review works →
- ✓ Prompt-injection patterns
- ✓ Secret / credential exfiltration
- ✓ Dangerous shell & filesystem operations
- ✓ Untrusted network calls
- ✓ Known-malicious package signatures
What it can access
- ✓ Network access No
- ✓ Filesystem access No
- ✓ Shell / process execution No
- ✓ Environment & secrets No
- ✓ Dynamic code execution No
From automated source analysis of v0.1.0. “Used” means the capability is present in the source — more access means more to trust, not that it’s unsafe.
About
Template placeholders
Replace every {{...}} before enabling. See the setup checklist reference for the full setup list.
{{TRIGGER_OWNER}}— Team member whose LinkedIn DMs are scanned (one skill instance per person){{CRM}}— Your CRM (e.g. HubSpot, Attio){{SIGNALS_CHANNEL}}— Slack channel for all DM signal posts + daily digest{{EXPANSION_CHANNEL}}— Slack channel for expansion/churn-risk cross-posts (optional){{MQL_CHANNEL}}— Slack channel where your lead-scoring alerts post qualified accounts{{PARTNERSHIPS_OWNER}}— Person to flag partner/agency signals to{{FUNDRAISING_OWNER}}— Person to flag investor inquiries to (usually a founder){{LEAD_SCORING_SKILL}}— Sub-skill reference: your lead scoring & qualification methodology{{OUTREACH_SKILL}}— Sub-skill reference: your outreach/sequence-building methodology{{HIGH_ACV_THRESHOLD}}— Company profile that suggests medium-to-high ACV (default: >30 employees, funded, or VP+/C-suite/Founder contact){{SELF_SERVE_THRESHOLD}}— Company profile that should route to your self-serve / low-touch motion instead of being scored (default: Global rule — Verbatim message required in all Slack posts:
> Every Slack signal post (all buckets except VENDOR_SPAM) must include the > verbatim text of the last inbound message from the contact, quoted in > a blockquote. Do not paraphrase or summarize as a substitute. If the > thread has multiple inbound messages since the last outbound, include the > most recent one verbatim; you may briefly note context from earlier > messages separately.
VENDOR_SPAM
Skip. No writes, no actions.
CUSTOMER_SUPPORT
- Update account memory:
`` [CUSTOMER SUPPORT DM] [trigger owner name] ↔ [contact name, title] — [date] Issue type: [bug / billing / credits / complaint / at-risk] Summary: [1 sentence] ``
- Create or update the contact in {{CRM}} and log a note:
- Find or create the contact record (name, title, LinkedIn URL, email if
available), associated with the company
- Log a note on the contact: `DM via LinkedIn — [date]. [contact name]
([title]). Support signal. Issue: [type]. Summary: [1 sentence].`
- Post to {{SIGNALS_CHANNEL}}:
🚨 Customer Support DM- Company name + domain
- Contact: name, title
- Issue type: 🐛 bug / 💳 billing/credits / 😤 complaint / ⚠️ at-risk
- What they said (verbatim blockquote)
- ⏰ Awaiting reply line (if applicable)
- Conversation owner: [trigger owner name]
Note: Urgent — route to your support process
CUSTOMER_COMMERCIAL
- Update account memory:
`` [CUSTOMER COMMERCIAL DM] [trigger owner name] ↔ [contact name, title] — [date] Signal: [expansion / renewal / pricing / churn risk] Summary: [1 sentence] ``
- Create or update the contact in {{CRM}} and log a note (same format as
above, with the commercial sub-type).
- Post to {{SIGNALS_CHANNEL}}:
📩 Customer Commercial DM- Company name + domain; contact name, title
- Signal type: 📈 expansion / 🔄 renewal / 💬 pricing question / ⚠️ churn risk
- 1-sentence summary; what they said (verbatim blockquote); ⏰ line if
applicable; conversation owner
- If signal is expansion or churn risk: also cross-post to
{{EXPANSION_CHANNEL}}.
ACTIVE_PIPELINE
- Update account memory:
`` [PIPELINE DM] [trigger owner name] ↔ [contact name, title] — [date] Deal stage: [current stage] Signal: [objection / timeline / pricing / champion re-engagement / positive] Summary: [1-2 sentences] ``
- Load and follow {{LEADSCORINGSKILL}}. A mid-deal DM is strong
first-party intent — do not apply any tier cap that normally limits passive signals.
- Create or update the contact in {{CRM}} and log a note:
- Find or create the contact record (name, title, LinkedIn URL, email if
available), associated with the company
- Log a note on the contact: `DM via LinkedIn — [date]. [contact name]
([title]). Deal stage: [stage]. Signal: [type]. Summary: [1-2 sentences].`
- Post to {{SIGNALS_CHANNEL}}:
📩 Pipeline DM Signal- Company + current deal stage; contact name, title; signal summary;
what they said (verbatim blockquote); tier scored; ⏰ line if applicable; conversation owner
- Continue to Step 6.5.
RE_ENGAGEMENT
- Update account memory:
`` [RE-ENGAGEMENT DM] [trigger owner name] ↔ [contact name, title] — [date] Prior stage: [closed-lost / nurture / self-serve] Summary: [1 sentence] ``
- Treat as first-party intent. Load and follow {{LEADSCORINGSKILL}}.
- Post to {{SIGNALS_CHANNEL}}:
🔁 Re-engagement DM- Company + prior stage; contact name, title; what they said (verbatim
blockquote); tier scored; ⏰ line if applicable; conversation owner
- Continue to Step 6.5.
PROSPECTHIGHACV
- If the company is not yet in the workspace: enrich via research tools and
add it.
- ⛔ SELF-SERVE GATE — Run this before anything else. No exceptions.
Check employee count (take the lower of enrichment vs LinkedIn) and total funding raised.
If the company profile meets {{SELFSERVETHRESHOLD}}:
- Move the account to your self-serve / low-touch stage and tag it
accordingly (sync the stage to {{CRM}} if you mirror stages there)
- Post to {{SIGNALSCHANNEL}} using the PROSPECTHIGH_ACV format but
note: ⛔ Self-serve gate triggered — under threshold. No scoring. Moved to self-serve.
- ⛔ STOP. Do not proceed to memory update, scoring, or MQL routing.
If employee count is unavailable: treat as unknown and apply the gate by default. A strong funding signal alone (well above threshold) may override the gate.
- Update account memory:
`` [PROSPECT DM] [trigger owner name] ↔ [contact name, title] — [date] ACV tier: HIGH Summary: [1 sentence] ``
- Load and follow {{LEADSCORINGSKILL}}. DM = first-party intent.
Promotion rule: A substantive inbound DM (the contact initiated a real business conversation) is strong first-party intent. If the account's current tier is one below your alert tier, instruct the scoring skill that this direct DM interaction justifies promotion one tier up — unless there is contrary evidence in the account memory (explicit disqualification, stated "not now", churned for cause). Never lower an existing tier as a result of this DM run.
- Create or update the contact in {{CRM}}:
- Find or create the contact record with name, title, LinkedIn URL, and
email if available
- Associate with the company record
- Post to {{SIGNALS_CHANNEL}}:
📩 High-ACV Prospect DM- Company + enriched summary (employees, funding, description)
- Contact: name, title, what they said (verbatim blockquote)
- Tier scored; ⏰ line if applicable
- Close with a note that accurately reflects what actually happened:
- Both ran:
Note: Enriched + scored. - Enrichment ran, scoring skipped:
Note: Enriched. Scoring skipped. - Enrichment skipped:
Note: Enrichment skipped. [Reason].
- Continue to Step 6.5.
PROSPECTLOWACV
- Do NOT create a workspace company record. Do NOT run scoring.
- Post to {{SIGNALS_CHANNEL}}:
📩 Low-ACV Prospect DM- Contact: name, title, company (if any); what they said (verbatim
blockquote); ⏰ line if applicable; conversation owner
No company — enrichment skipped(only if no company was identified)Note: Low ACV signal — no workspace record created.
IMPLEMENTATION_PARTNER
- Update account memory (if company record exists):
`` [PARTNER DM] [trigger owner name] ↔ [contact name, title] — [date] Status: ACTIVE / POTENTIAL Summary: [1 sentence on what was discussed] ``
- Post to {{SIGNALS_CHANNEL}}:
🤝 Implementation Partner DM- Company name, domain, what they do; contact name, title
- Status: ACTIVE or POTENTIAL
- Summary of what was discussed; what they said (verbatim blockquote);
⏰ line if applicable; conversation owner
Note: Flagging for {{PARTNERSHIPS_OWNER}}'s awareness
COMMUNITY_SPEAKING
- Post to {{SIGNALS_CHANNEL}}:
🎤 Community / Speaking Opportunity- Contact: name, title, company (if any)
- Opportunity type: podcast / event / community demo / distribution /
co-marketing
- What they proposed (verbatim blockquote); ⏰ line if applicable;
conversation owner
Note: Surface for review — no action taken.
VC_INQUIRY
- Post to {{SIGNALS_CHANNEL}}:
💼 VC Inquiry- Contact: name, title, fund name; what they said (verbatim blockquote);
⏰ line if applicable; conversation owner
Note: Surface for {{FUNDRAISING_OWNER}}'s awareness.
COLLABORATION
- Post to {{SIGNALS_CHANNEL}}:
🤜 Collaboration / Peer Exchange- Contact: name, title, company; brief summary (1 sentence); what they
said (verbatim blockquote); ⏰ line if applicable; conversation owner
UNKNOWN
- Post to {{SIGNALS_CHANNEL}}:
❓ Unclassified DM- Contact: name, title, company (if any); what they said (verbatim
blockquote); ⏰ line if applicable; conversation owner
Note: Could not classify — needs human review.
Step 6.5 — MQL Routing & Suggested Next Move
This step applies whenever {{LEADSCORINGSKILL}} was called in Step 6 — i.e., for ACTIVEPIPELINE, REENGAGEMENT, and PROSPECTHIGHACV signals.
MQL channel routing
Let the scoring skill's standard alert routing run in full — do not suppress it.
⚠️ CRITICAL — Use your standard lead-scoring alert format for {{MQL_CHANNEL}} posts. Do NOT post a custom DM-signal format to that channel. DM context (verbatim last inbound message, conversation owner, days awaiting reply) must be embedded within the standard format — with the verbatim DM woven in as the primary signal (e.g., "Inbound DM from [name] ([title]) — [verbatim quote]") — not posted as a separate custom block. One channel, one format: readers of the MQL channel should never need to learn a second layout.
Decide the next move
After scoring completes, read the full DM thread, account memory, and current deal state, then decide the right next action. Apply these rules:
The person already in the thread — suggest a reply, never enroll in a sequence: The contact is in a live conversation with the trigger owner. Never enroll them in an automated sequence. Instead, draft a suggested reply in the voice of the conversation owner (the trigger owner). If the contact is addressing or asking for someone else on your team, name that person as the right sender in the draft. Include the draft directly in the Slack post so the owner can send it manually.
Other people at the account — outreach is allowed, with checks: Outreach to buying-committee members who are NOT already in the thread is permitted. Before building any sequence:
- Run a duplicate-outreach check: search all existing sequences for each
target contact in any status. If a sequence already exists for them — finished, running, or awaiting approval — do not build a new one; flag for human review instead.
- Follow {{OUTREACH_SKILL}} for sender selection and sequence construction.
- All DM-originated outreach must be queued for approval — never
auto-send, regardless of tier.
Approval always required: A DM thread means a human relationship is in motion. The human decides what goes out. No exceptions.
Slack post closing line
End every ACTIVEPIPELINE, REENGAGEMENT, and PROSPECTHIGHACV signal's Slack post with a Suggested next move: line — one concrete, actionable recommendation. Examples:
Suggested next move: [owner] should reply today — draft above.Suggested next move: Enroll [VP Sales name] in a sequence — queued for approval.Suggested next move: No action — await their reply before following up.Suggested next move: [teammate] to take over — DM context in thread above.
Step 7 — End of Run Summary
After processing all chats, always post a summary to {{SIGNALS_CHANNEL}}. This post is mandatory on every run — including runs where zero signals were found. Silence must mean a failure, never "nothing found."
If at least one non-noise signal was found:
📋 LinkedIn DM Scan — [trigger owner name] — [date]
Chats fetched: [total fetched] | Processed: [N after filter]
Signals found: [N] — support: X, commercial: X, pipeline: X, re-engagement: X,
high-ACV prospect: X, low-ACV prospect: X, partner: X, community/speaking: X,
VC: X, collab: X, unknown: X
Vendor spam / noise skipped: [N] ([breakdown: recruiters, vendors, outbound-only, etc.])
⚠️ Memory-full warnings: [N accounts] (list company names if any)
Omit the memory-full line if count is 0.
If zero non-noise signals were found:
📋 LinkedIn DM Scan — [trigger owner name] — [date]. Chats checked: [N]. No new signals.
Step 8 — Write the Cursor
Only after all chats have been processed successfully, write the cursor to the trigger owner's user memory: LI DM last run: [ISO timestamp of when this run started]
Use the timestamp from when the run started (captured at Step 1), not the end time — this ensures the next run re-checks any chats that became active mid-run.
Important Constraints
- LinkedIn calls are sequential — never parallel
- Never surface noise signals — filter aggressively
- Never auto-send outreach. The only outreach this workflow may produce is a
drafted reply surfaced in Slack, or an approval-queued sequence built per Step 6.5 — a human sends everything.
- If a company is tagged as competitor or vendor: skip, log in memory only,
no Slack post
- Memory-full handling: If an account memory write fails due to capacity,
do NOT delete or trim existing entries to make room. Skip the memory write entirely. Add ⚠️ account memory full — signal not persisted to that signal's Slack post, and count it in the end-of-run digest.
What good looks like
A great run puts every business-relevant DM in exactly one bucket with the right routing: urgent customer-support signals surface same-day with the verbatim complaint quoted in a blockquote, qualified signals get scored and land in {{MQL_CHANNEL}} in the standard alert format, every scored signal's post closes with one concrete Suggested next move: line (often a ready-to-send drafted reply), the 7-day dedup keeps the channel quiet (updates use the short 🔄 format), the end-of-run digest posts even on zero-signal days — silence must mean failure, never "nothing found" — and the cursor only advances after a fully successful run.
Mediocre looks like: vendor spam surfacing as signals, unchanged threads re-posted, a missing digest, awaiting-reply flags on threads where the owner spoke last, custom DM formats polluting the MQL channel, the contact in the thread getting enrolled in an automated sequence, or anything auto-sent without human approval.
Source & license
This open-source skill is cataloged on AgentStack and links to its original source — we do not rehost the code.
- Author: swan-gtm
- Source: swan-gtm/gtm-skills
- License: MIT
Install and usage instructions live in the source repository linked above.
Reviews
No reviews yet — be the first.
Write a review
Versions
- v0.1.0 Imported from the upstream source.