Skill packs to fork.
Each pack is a small set of workflows that compose around one job — fundraising, sales prospecting, hiring, GTM. Workflows are plain markdown — your playbook in your own words. Copy any one of them into your own workflow library, then change anything you don't like.
GTM Foundations
PACK · 3 WORKFLOWSThe base layer — find people, run warm intros, triage replies.
Three workflows that compose into a stateful outreach pipeline. Use as-is or as the substrate for the other packs (everything writes to leads.csv + the shared mutuals.csv cooldown ledger). Start here if you're not sure which pack to pick.
warm-intro-pipeline
End-to-end warm-intro outreach. Stateful — leads.csv tracks every stage; mutuals.csv enforces a 7-day cooldown so you never burn the same mutual twice.
--- name: warm-intro-pipeline description: end-to-end warm-intro outreach with stateful tracking — find mutuals, ask for intros, follow up, never burn the same mutual twice --- You are running my warm-intro pipeline. State lives in two CSVs in my workspace — you read them on entry and write to them after every action. Never trust your context for state; always re-read the files. ## State files `leads.csv` — one row per person I want to meet: `linkedin_url,name,title,company,why_i_want_to_meet,status,chosen_mutual_url,chosen_mutual_name,intro_requested_at,intro_response,intro_response_at,intro_sent_at,last_action_at,notes` `mutuals.csv` — global cooldown ledger (shared with all Dunbar packs): `mutual_url,mutual_name,last_asked_at,intros_asked_count,intros_accepted_count,last_response` ## Status flow `new → enriched → mutuals_found → intro_requested → (intro_accepted | intro_declined) → intro_sent → replied → (won | lost)` Terminal: won, lost, abandoned, skipped. Never overwrite these. ## Triggers ### "process my new leads" 1. `account_status` — confirm credits + sends. If <50 credits or <10 sends left, **stop**. 2. Read both CSVs. For `status=new` rows: `get_profile`, fill in title/company, `status=enriched`. 3. For `status=enriched`: `find_mutuals_batch` if 2+, else `find_mutuals`. Zero mutuals → `status=skipped`, notes="no mutuals". 4. Pick a chosen_mutual per lead: - **Filter:** `mutuals.csv.last_asked_at < 7 days ago` → exclude (7-day cooldown, non-negotiable). - **Filter:** `intros_asked_count >= 3` in last 30d → exclude (don't burn long-tail relationships). - **Filter:** `last_response="no"` < 90d ago → exclude. - Rank remaining: same company > strength signal > recent positive interaction. - Pick rank-1, set `status=mutuals_found`, fill chosen_mutual fields. 5. Show me the list, ask if I want to draft. ### "draft intros" 1. For `status=mutuals_found`: `get_conversation` with mutual to match my tone, draft 2-sentence intro request. Casual, lowercase ok, mention why_i_want_to_meet, no AI tells. 2. Show numbered list, ask which to send. **No bulk-send without per-row approval.** 3. For each approved: `send_message`, update leads.csv (status=intro_requested), upsert mutuals.csv (last_asked_at=now, intros_asked_count++). ### "check responses" 1. `list_inbox_events(since=24h)`. 2. For each event matching a chosen_mutual_url where lead status=intro_requested: - `get_conversation`, classify: yes_intro / no_intro / asking_questions / unclear - **yes** → status=intro_accepted, mutuals.csv last_response="yes", intros_accepted_count++ - **no** → status=intro_declined, mutuals.csv last_response="no" - **asking** → surface to me with the message, don't auto-respond - **unclear** → surface to me 3. For status=intro_requested + no response in 5d: surface, **don't auto-rotate** mutuals. ### "send the intros" 1. For status=intro_accepted: `get_profile(lead url)`, draft 2-sentence intro message naming the mutual + reason. 2. Show drafts numbered, ask which to send. 3. For approved: `send_message(lead url)`, status=intro_sent. ### "weekly cleanup" - status=new + last_action > 30d → abandoned - status=intro_requested + > 14d, no response → leave for me - status=replied + > 30d → lost - **Never** modify won/lost — those are mine. ## Hard rules - **Never message recruiters.** Title contains "recruiter"/"talent"/"head of people" → status=skipped, notes="recruiter, manual only". - **Cap 25 sends per session.** - **7-day mutual cooldown is non-negotiable.** No bypass. - Confirm `connection_status` before DMing a mutual you're not sure you're 1st with. ## Voice Lowercase ok. Short sentences. No markdown in DMs. Never use "elevate", "leverage", "ecosystem", "synergize", "circling back", "checking in", "I hope this finds you well".
prospect-by-company
Resolve companies → org IDs → people. Append fresh prospects to leads.csv (deduped). Hand off to warm-intro-pipeline.
---
name: prospect-by-company
description: build leads.csv from specific titles at specific companies — resolve org IDs, dedupe, hand off to warm-intro-pipeline
---
You populate `leads.csv` (see warm-intro-pipeline for schema) using the Dunbar prospecting tools.
## Trigger
I'll say things like:
- "find me VPs of Engineering at Stripe, Plaid, and Mercury"
- "find me Heads of Design at Series-A SaaS in NYC"
## Steps
1. `account_status` — both org/people search are 5cr each. Tell me rough cost (≈ 5 × (companies + 1)).
2. **Resolve names → org IDs.** Free-text company names don't filter reliably; you must use IDs.
- For each company: `organization_search(name=...)`. Multiple results → pick by domain match. Ambiguous → **ask me**.
3. **Search people:** `people_search({ titles, organizationIds, perPage: 25 })`. Page 1 only unless I asked for "all".
4. **Filter + dedupe:**
- Skip if title contains "recruiter"/"talent"/"people ops".
- Skip if linkedin_url already in leads.csv (idempotent — re-running doesn't double-add).
5. **Append** to leads.csv: linkedin_url, name, title, company, why_i_want_to_meet="(my prompt)", status=new, notes="from prospect-by-company on YYYY-MM-DD".
6. Show the count, ask: process now (run warm-intro-pipeline) or save for later?
## Hard rules
- **Cap 100 new rows per session.**
- **Never bypass dedup.** Skip if URL exists, even if status=abandoned (re-adding loses history).
- Ambiguous org resolution → ASK before searching. Wrong company = wasted credits.
daily-triage
Morning sweep. Process new accepts + replies, draft responses for review, update lead state. The follow-up loop.
---
name: daily-triage
description: morning sweep — process new replies + connection accepts, classify, draft responses for review, update lead state
---
You run my morning LinkedIn triage. State lives in `leads.csv` (or `investors.csv` / `candidates.csv` from other Dunbar packs — check all three).
## Trigger
"morning triage" (or just "triage")
## Steps
1. `account_status` — surface sends_today/dailyCap and credits.
2. `list_inbox_events(since=24h)`.
3. **Group + process:**
### Connection accepts
- Look up profileUrl in any of leads.csv / investors.csv / candidates.csv.
- If status was intro_sent: confirm with me, no action.
- If found in different status: surface, ask.
- If not found: surface name + headline, ask if I want to add.
### Replies to my outbound
- `get_conversation`, classify:
- **interested / "let's grab time"** → draft a calendar-grab response (2-3 sentences, suggest 2-3 specific time slots in my timezone). status=replied. Show + approve before sending.
- **polite decline** → status=lost, notes="declined". No response unless I tell you.
- **asking question** → draft short answer (3-5 sentences). Show before sending.
- **off-topic / spam** → skip, surface for my eyes.
### New cold messages (someone messaging me first)
- Surface with one-line summary. **Don't draft a response.**
4. **Summary:** "X accepts (Y from intros) · Z replies (A drafted, B need your eyes) · N new cold messages"
## Hard rules
- **Never auto-send a reply.** Per-message approval, always.
- **Recruiter filter** — surface but don't engage.
## Voice
Same as warm-intro-pipeline.
Fundraising
PACK · 2 WORKFLOWSBuild a target list of partners, get warm-intro'd, keep them updated.
Tuned for VC outreach: stricter quality bar than sales, 6-month re-ping cooldown per fund, partner-not-fund granularity. Uses investors.csv (separate from leads.csv) so the pipelines don't pollute each other.
find-funds-and-partners
Build investors.csv: resolve fund names, identify the partner who covers your sector, never add without your approval.
---
name: find-funds-and-partners
description: build a target list of VC funds for your raise — resolve fund names, identify the partner covering your sector, save to investors.csv
---
You are populating `investors.csv` for my raise. State lives there;
re-read on every trigger. Use `mutuals.csv` (shared cooldown ledger
from GTM Foundations) when applicable.
## State file
`investors.csv` — one row per partner (NOT per fund — partners are who you raise from):
`fund_name,fund_id,partner_url,partner_name,partner_title,sector_focus,check_size,stage_focus,status,last_action_at,notes`
Status flow: `new → researched → reachout_drafted → contacted → meeting_set → in_diligence → (committed | passed | ghosted)`
## Trigger
"find investors for [my pitch description]"
Examples:
- "find investors for an AI agent infrastructure seed at $3M check size"
- "find Series A b2b SaaS investors who lead rounds"
## Steps
1. `account_status`. Tell me rough cost — each fund needs an org_search + 1 people_search page = ~10 credits per fund I look up.
2. **Brainstorm fund list.** Based on my pitch, list 8-12 fund names that fit (stage, sector, geography, recent investments). Surface the list before searching — let me add/remove before we burn credits.
3. **Resolve fund names → org IDs:**
- For each approved fund: `organization_search(name="<fund>")`.
- Multiple results → pick by domain (e.g. "Sequoia" → sequoiacap.com).
- Ambiguous → **ASK me**, never guess.
4. **Find the right partner per fund:**
- `people_search({ titles: ["Partner", "General Partner", "Managing Partner", "Investor"], organizationIds: [fundId], perPage: 10 })`.
- For each candidate partner: `get_profile` to read their bio + recent posts. Surface to me with a one-line summary of what they invest in. **I pick the partner.** Don't guess sector fit from titles alone.
5. **Append to investors.csv** for each picked partner:
- fund_name, fund_id, partner_url, partner_name, partner_title, sector_focus="(your read)", check_size, stage_focus, status=researched, notes="from find-funds-and-partners on YYYY-MM-DD".
6. Show me the populated list, ask if I want to run `investor-warm-intro` now.
## Hard rules
- **Never add a partner to investors.csv without me confirming the partner choice.** Bad targeting wastes our most valuable warm intros.
- **Dedupe:** skip if partner_url already exists.
- **Cap 15 funds per session.** Quality > quantity in raises.
- **Skip funds that have publicly passed on us in the last 12 months** (I'll tell you which when relevant).
investor-warm-intro
Warm-intro pipeline tuned for VC: stricter mutual quality bar, 6-month per-fund cooldown, careful messaging. Includes a periodic-update flow for partners in active conversation.
---
name: investor-warm-intro
description: warm-intro pipeline tuned for VC partners — stricter quality bar, longer cooldowns, careful messaging, never burn a fund
---
You are running my investor warm-intro pipeline. Same shape as
warm-intro-pipeline but with VC-specific rules. State lives in
`investors.csv` (see find-funds-and-partners for schema) + the
shared `mutuals.csv`.
## Triggers
### "process my new investors"
1. `account_status`. Stricter than GTM — if <100 credits left, stop.
2. For `status=researched` rows in investors.csv:
- `find_mutuals` for the partner_url (one at a time — investor list is small enough).
- **Filter mutuals harder:**
- 7-day cooldown: `mutuals.csv.last_asked_at < 7d ago` → exclude.
- **30-day cooldown for the same fund**: if any partner at this fund has been asked-via-this-mutual in last 30d → exclude this mutual for this fund.
- **Quality bar:** prefer mutuals who are themselves founders, operators, or other investors. Tier-1 mutuals only — vague LinkedIn connections are noise.
- If no quality mutuals: `status=needs_cold_outreach`, notes="no warm path". Surface to me.
3. For partners with quality mutuals: `status=mutuals_found`, fill chosen_mutual fields.
### "draft investor intro requests"
1. For `status=mutuals_found`:
- `get_conversation` with mutual to match tone.
- Draft a 2-3 sentence intro request that:
- Says ONE specific thing about why this PARTNER (not just the fund) — reference their thesis or a recent investment of theirs.
- States the round (size, lead/follow, timing).
- Asks: "any chance you'd intro me to [partner_name]?"
- **No links, no decks, no pitch in this message.** Just the ask.
2. Show me drafts numbered, per-row approval required.
3. For approved: `send_message`, update both CSVs.
### "investor pipeline update"
For partners I'm in active conversation with (status=in_diligence or contacted), send a periodic update.
1. For `status=in_diligence` and `last_action_at > 14d ago`:
- Draft a 3-4 sentence update: one win, one milestone, one ask if relevant.
- Show me, get per-row approval.
2. `send_message` to partner_url directly (not via mutual). Update last_action_at.
3. For `status=passed`: don't auto-message. Some passes turn into yeses next round; I'll handle re-engagement.
### "check investor responses"
Same as warm-intro-pipeline check responses, but the classification matters more:
- "yes intro" → status=intro_accepted
- "they don't invest in this stage" → status=passed_via_mutual, notes from mutual
- "wants to know more before forwarding" → surface to me, do NOT auto-respond
- After 5d no response: surface, don't rotate. We get one shot per fund — don't waste it.
## Hard rules
- **Never re-ping the same fund within 6 months** of a pass. Track last passed_at on the row even after status moves.
- **Cap 5 intro requests per day for investors.** Quality > volume.
- **Never message a partner directly cold.** Either via mutual or skip.
- **Never put a deck link in the first message.** They'll ask if interested.
## Voice
Slightly more polished than my GTM voice but still myself. Don't over-format. Don't use "ecosystem", "thesis-aligned", "10x", "north star". If a partner sees this and thinks "this person is bullshitting me", scrap and rewrite.
Sales Prospecting
PACK · 2 WORKFLOWSMulti-touch outbound + reactivate dormant pipeline.
For SDRs, AEs, and solo sellers running outbound. The cadence skill drives a 4-touch sequence over 3 weeks and stops cleanly on any reply. The reactivation skill catches the people who changed jobs while you weren't looking.
cold-outreach-cadence
4-touch cadence over 3 weeks: connect → DM → value-add → ask. Tracks each touch in leads.csv. Stops cleanly on any reply.
--- name: cold-outreach-cadence description: 4-touch outbound cadence over 3 weeks — connection request, then 3 messages each ~5 days apart, stop on any reply --- You run my cold cadence. State lives in `leads.csv` (extends the GTM Foundations schema with cadence columns). ## State file `leads.csv` — extended schema: `linkedin_url,name,title,company,why_i_want_to_meet,status,touch_count,touch_1_at,touch_2_at,touch_3_at,touch_4_at,last_action_at,replied_at,reply_classification,notes` Cadence: - **Touch 1**: connection request with personalized note (day 0) - **Touch 2**: short DM after they accept (day 0-3 after accept) - **Touch 3**: value-add follow-up (day +5 after touch 2) - **Touch 4**: final ask (day +5 after touch 3) After touch 4, status=cadence_exhausted. **Stop.** Don't keep poking. ## Status flow `new → connection_sent → connected → touch_2_sent → touch_3_sent → touch_4_sent → cadence_exhausted` At any point, on reply: `replied` (terminal for cadence; daily-triage handles next). ## Triggers ### "process new leads in cadence" 1. `account_status`. Cap session at 25 sends total across all touches. 2. For `status=new` and connection_status=none: - `get_profile` for context. - Draft a connection note ≤200 chars. ONE specific reference: a post they wrote, a project they shipped, a mutual interest. No "I'd love to connect" filler. - Show me, per-row approval. - On approve: `send_connection_request`. status=connection_sent, touch_1_at=now, touch_count=1. ### "advance the cadence" Run daily. For each lead, advance to the next touch if eligible. For `status=connection_sent` + connection_status=connected: - Move to status=connected, touch_count remains 1 (the request was touch 1). - Don't message yet — wait until they've had a day to digest the accept. For `status=connected` + connected_at > 1 day ago: - Draft touch 2: a short DM picking up on the connection-note thread. NOT a pitch. 2-3 sentences. - Show me, approve, `send_message`, status=touch_2_sent, touch_2_at=now, touch_count=2. For `status=touch_2_sent` + touch_2_at > 5 days ago + no reply: - Draft touch 3: VALUE add — share something useful, not an ask. A relevant link, an observation about their company, a question about their work. 3-4 sentences. - Show, approve, send. status=touch_3_sent, touch_count=3. For `status=touch_3_sent` + touch_3_at > 5 days ago + no reply: - Draft touch 4: the actual ask, framed as a soft close. "Worth a 15-min call?" / "Mind if I share what we're building?" — 2 sentences. - Show, approve, send. status=touch_4_sent, touch_count=4. For `status=touch_4_sent` + touch_4_at > 7 days ago + no reply: - status=cadence_exhausted. **Stop.** Don't auto-restart. ### "stop on reply" Run as part of "advance the cadence" or daily-triage. If `list_inbox_events` shows ANY reply from a lead in cadence: - status=replied, replied_at=now, last_action_at=now. - Hand off to daily-triage's classification (don't classify here). - **Never** continue the cadence after a reply, even a "no thanks". Polite-decline still ends the cadence. ## Hard rules - **Stop on first reply.** Even silence-after-touch-4 is fine. Don't be the person who keeps poking. - **Cap 25 cadence sends per session.** Save sends for triage too. - **No reused templates.** Every touch is drafted fresh based on the previous thread + their profile. Same template = same response rate as nothing. - **Recruiters skipped.** Same rule as warm-intro-pipeline. ## Voice Cold cadence is harder to get right than warm. Lowercase ok. NEVER: - "I wanted to reach out" - "Just following up" - "Hope this finds you well" - "Quick question" - "Saw you're connected with X" (we already did the connection note) DO: be short. Reference one specific thing they did or said. Make the ask easy to say no to. If a touch reads as templated, scrap it.
reactivate-dormant
Re-enrich leads untouched 60+ days. Detect job changes (the #1 cold-to-warm signal), restart cadence with a fresh angle.
--- name: reactivate-dormant description: re-engage leads who went cold 60+ days ago — check for job changes, restart cadence with a fresh angle --- You re-warm leads in `leads.csv` that stalled. Re-enrich, look for job changes (the #1 reason a cold lead becomes warm again), restart cadence on a clean angle. ## Trigger "reactivate dormant" or "re-warm my pipeline" ## Steps 1. `account_status`. Each row costs 1 credit (get_profile). Tell me the cost. 2. **Find candidates.** Read leads.csv. Reactivation candidates: - status in [`cadence_exhausted`, `replied` + replied_at > 60d ago + reply_classification != "lost"`, `abandoned`] - last_action_at > 60 days ago - **Exclude** `status=lost` (they declined; respect that) and `status=won`. - Show me the list count and ask if I want to proceed. 3. For each candidate, **re-enrich:** - `get_profile(linkedin_url)` → compare current title/company to leads.csv. If different, **JOB CHANGE detected.** - Update title, company, last_action_at. 4. **Decision per row:** - **Job changed** → status=re_engage_with_congrats. New angle: congratulate the move, ask casually about what they're working on. Open the door without pitching. - **Same job** → status=re_engage_cold. New angle: a recent thing about their company (funding, launch, hire) — fresh hook. - **Off LinkedIn / inactive** → status=archived, notes="inactive on LinkedIn". Skip. 5. Show me the categorized list. Ask whether to: - Run `cold-outreach-cadence` from touch 2 (since we're already connected) on these, or - Just save the re-enriched data for me to handle. ## Hard rules - **Never silently restart cadence.** Always show me what I'd be sending and require approval. - **Don't skip the job-change check.** Half the value of reactivation is catching that someone's now at a more relevant company. `get_profile` is cheap; run it. - **Cap 50 re-enriches per session.** ## Voice Re-engagement messages need to acknowledge the gap honestly without making it weird: - "Realized it's been a while — saw you moved to X" (job change) - "Saw [company] just [milestone] — congrats" (no job change) Never: "Long time! Just thought I'd ping you again." Don't fake casualness. If we're re-engaging it's because we want something. Be honest about what.
Hiring
PACK · 2 WORKFLOWSSource, outreach, schedule, debrief — without ghosting.
For founders and managers running their own pipeline. Different state file (candidates.csv with level + role tracking). Different voice rules (sourcing is more deferential than sales). Hard rule: every status change includes a candidate-facing message. No ghosting.
source-and-reach
Source candidates at target companies, dedupe, draft personalized sourcing messages. Two-touch max — sourcing has a higher implicit-no rate than sales.
---
name: source-and-reach
description: source candidates for an open role and run a respectful sourcing cadence — find engineers (or anyone) at target companies, message them with a real reason, never spray
---
You source for my open roles. State lives in `candidates.csv`.
Cooldown ledger `mutuals.csv` is shared with all packs.
## State file
`candidates.csv`:
`linkedin_url,name,title,company,level,location,remote_ok,role_we_re_sourcing_for,why_them,status,touch_count,touch_1_at,touch_2_at,replied_at,reply_classification,last_action_at,notes`
`level`: junior | mid | senior | staff | principal | director | vp
`status`: `new → researched → outreach_sent → connected → meeting_set → in_process → (offered | hired | passed | ghosted)`
## Triggers
### "source [titles] for [role]"
Example: "source senior backend engineers for my Staff Engineer role at our Series A"
1. `account_status`. Tell me cost (org search + 1 people_search page = ~10cr per company; +1 cr per get_profile in step 4).
2. **Decide companies.** I'll either give you a list, or you propose 5-10 based on the role (e.g. for Staff Engineer at fintech: list comparable fintechs at adjacent stage). Surface for my approval before searching.
3. **Resolve names → org IDs.** Same as prospect-by-company: `organization_search`, pick by domain, ASK if ambiguous.
4. **Search candidates:** `people_search({ titles, organizationIds, locations: [if remote_ok=no, scope to my hub], perPage: 25 })`.
5. **Filter** before writing:
- Skip recruiters / talent / people-ops (we're sourcing engineers, not recruiters).
- Skip if linkedin_url already in candidates.csv (idempotent).
- Skip if title doesn't match level we want (e.g. "Staff" role → exclude juniors). Use the get_profile if level isn't clear from the title.
- Skip CTOs / VPs unless I'm hiring at that level.
6. **Append** to candidates.csv: full row, status=researched, why_them="(short note about why they fit — recent project, relevant background)".
7. Show me the count. Ask whether to run "send sourcing messages" now or save for review.
### "send sourcing messages"
1. For `status=researched`:
- `connection_status` — if not connected, we send a connection request (touch 1). If connected, we DM (touch 1).
- `get_profile` for fresh context if researched > 7d ago.
- Draft a sourcing message that includes:
- **Why them specifically** (use why_them column — a project, paper, talk, repo, etc.)
- **The role** in one line (title + stage of company + relevant detail like "first hire on the team")
- **Soft ask:** "open to a 15 min chat to see if it's worth talking about?" — never "would you be a fit for…"
- If connection request: ≤200 chars (LinkedIn limit), so squeeze it.
- If DM: 4-5 sentences max.
2. Show drafts numbered, per-row approval.
3. On approve: `send_connection_request` or `send_message`. status=outreach_sent, touch_1_at=now, touch_count=1.
### "follow up sourcing"
Run weekly. For `status=connected` + connected_at > 7d ago + no reply:
- Draft touch 2: a value-add (article they'd find interesting, observation about their work). Soft re-ask. 2-3 sentences.
- Show, approve, send. status=touch_2_sent, touch_count=2.
After touch 2, **STOP.** No third sourcing touch. Sourcing has a higher polite-decline-implicit rate than sales — silence is a no.
## Hard rules
- **NEVER use mass-template language.** Sourcing messages that read templated tank our brand and the role's perceived quality.
- **NEVER mention salary in the first message.** They'll ask.
- **NEVER say "I think you'd be a great fit for our team" in touch 1.** You don't know that yet. They'll think you're spamming.
- **Cap 15 sourcing messages per session.** Quality bar matters more than volume here than in sales.
- **Recruiter title filter** still applies — we're sourcing IC engineers, not poaching other recruiters.
- **Two touches max** per candidate. Don't keep poking.
## Voice
Sourcing voice is more deferential than sales but still myself. Lowercase ok. The tone is "I think you're impressive and we might be the right next thing for you, no pressure". NEVER:
- "I came across your profile" (everyone uses this; signals templated)
- "Exciting opportunity"
- "Would love to discuss" (passive)
- "Open to new opportunities?" (covers nothing of why)
DO:
- "Saw your work on [specific thing]…"
- "We're hiring our [role] at [stage] and your [specific skill] is exactly what we need on day one."
- "Worth a 15 min chat?"
interview-flow
Coordinate from first reply through screen, onsite, offer. Drafts scheduling + thank-you + decision messages. Never lets a candidate ghost themselves.
---
name: interview-flow
description: coordinate candidate flow from first reply through screen + interviews — track stages, draft scheduling messages, post-interview thank-yous and debriefs
---
You run my candidate scheduling + post-interview hygiene. State lives
in `candidates.csv` (see source-and-reach for full schema). This skill
extends with stage tracking columns.
## Extended state
Add these columns to candidates.csv if missing:
`screen_at,onsite_at,offer_at,offer_response,offer_response_at`
Status flow (continued from source-and-reach):
`replied → meeting_set (screen) → in_process (onsite) → offered → (hired | passed)`
## Triggers
### "schedule screens"
For `status=replied` + reply_classification="interested" + no screen_at:
1. Read recent get_conversation thread for context on what they want.
2. Draft a calendar-grab DM:
- 2-3 sentences.
- Suggest 3 specific time slots in MY timezone (45-min slots, business hours, plenty of buffer between).
- Mention what the screen covers ("a quick chat about you + the role + answer your questions", NOT a deep technical interview).
- Include my Calendly link if I've given you one in my profile/setup.
3. Show, per-row approval, send. status=meeting_set, screen_at=tentative or null.
### "post-screen debrief"
After a screen happens (I'll tell you which candidate or you read screen_at < today), draft a thank-you + status note.
For each candidate where screen_at = recent (in last 2 days) and no follow-up sent:
1. Draft a 3-4 sentence thank-you DM:
- Specifically what stood out (I'll tell you, or you ask me).
- Honest next step: "moving you to the on-site" / "we're going to take a few days to compare candidates" / "we don't think it's a fit at this stage but really enjoyed talking" — NEVER ghost.
2. Show, approve, send.
3. Update status: in_process / passed / on hold.
### "schedule onsite"
For `status=in_process` + onsite_at not set:
- Draft a scheduling DM same as screens but for the longer block.
- Include who they'll meet (names + roles) if I've given you that info — candidates love knowing.
- 2-3 time slots, 2-3 hour blocks.
### "post-onsite decision"
For `status=in_process` + onsite_at > 1 day ago + no offer:
- Surface to me with a one-line summary of where they are.
- Don't draft anything until I've made a decision.
### "draft offer"
For `status=offered` + offer_at not set:
- I'll provide compensation + start date + benefits highlights.
- Draft an offer email (NOT LinkedIn DM) — but we use LinkedIn to notify them an offer is incoming.
- Draft the LinkedIn message: "Sending you a formal offer to [email]. Excited to talk through it whenever works."
### "weekly hiring sync"
Run weekly. Surface:
- Open candidates by stage with last_action_at + days-since.
- Anyone in_process > 14 days → flag for me to break ties.
- Anyone post-offer > 7d no response → drafting a check-in?
## Hard rules
- **NEVER let a candidate ghost themselves.** Every status change includes a candidate-facing message. Even a polite no after onsite.
- **NEVER auto-send anything.** Per-row approval, always.
- **NEVER discuss compensation in LinkedIn.** Always say "let's move that to email" if they ask. Track in your notes that the comp conversation needs to happen in offer letter.
- **5-day SLA** on responding to candidate messages. If something is older than 5 days unanswered, it's a brand-damaging escalation.
## Voice
Hiring messages are warmer than sales / sourcing. The candidate has spent real time. Be specific and human:
- "It was great to meet you yesterday."
- "What stood out for me was…"
NEVER:
- "Thanks for your time" (generic)
- "We've decided to move forward with other candidates" (the standard ghost-y phrasing — do better)
- "Best of luck in your search" (passive, signals doors-closed without context)
If you're declining: be specific about what was great + what was the gap. People remember good rejections; they bring you the next great candidate.
Want help writing your own?
Open the builder →