@vruum/skills 0.6.4 → 0.6.6
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.claude-plugin/plugin.json +20 -0
- package/.codex-plugin/plugin.json +19 -0
- package/.mcp.json +8 -0
- package/README.md +3 -3
- package/agents/vruum-deal-reviewer.md +5 -8
- package/agents/vruum-engagement-reviewer.md +17 -15
- package/agents/vruum-outreach-reviewer.md +11 -13
- package/package.json +6 -3
- package/skills/campaign-doctor/SKILL.md +13 -13
- package/skills/create-content/SKILL.md +15 -15
- package/skills/csv-pipeline-fill/SKILL.md +3 -3
- package/skills/deal-triage/SKILL.md +14 -14
- package/skills/engagement-triage/SKILL.md +15 -13
- package/skills/engagement-triage/references/content-subagent-instructions.md +1 -1
- package/skills/engagement-triage/references/engagement-subagent-instructions.md +13 -11
- package/skills/enrich-prospect/SKILL.md +5 -5
- package/skills/expansion-fill/COHORT-QUERIES.md +18 -17
- package/skills/expansion-fill/SKILL.md +20 -17
- package/skills/outreach-triage/SKILL.md +10 -10
- package/skills/outreach-triage/references/subagent-instructions.md +5 -5
- package/skills/pipeline-fill/RESEARCH-ENGINE.md +36 -33
- package/skills/pipeline-fill/SKILL.md +10 -10
- package/skills/sales-nav-deep-fill/SKILL.md +4 -4
- package/skills/winback-fill/COHORT-QUERIES.md +9 -9
- package/skills/winback-fill/SKILL.md +19 -16
- package/skills/yc-pipeline-fill/SKILL.md +4 -4
|
@@ -29,7 +29,7 @@ When you reach Step 3, **stop and read** `RESEARCH-ENGINE.md`. That doc is the c
|
|
|
29
29
|
|
|
30
30
|
This skill uses two subagents (defined in `.claude/agents/`):
|
|
31
31
|
- `vruum-company-deep-researcher` — Phase A, one per unique company in the batch (max 10 in parallel)
|
|
32
|
-
- `vruum-prospect-deep-researcher` — Phase B, one per person (max 5 in parallel — Phase B is rate-limited because it calls `
|
|
32
|
+
- `vruum-prospect-deep-researcher` — Phase B, one per person (max 5 in parallel — Phase B is rate-limited because it calls `research` action=linkedin_fetch)
|
|
33
33
|
|
|
34
34
|
### MCP access requirements (load-bearing)
|
|
35
35
|
|
|
@@ -39,7 +39,7 @@ Subagents need access to the Vruum MCP server. Register it once in your AI assis
|
|
|
39
39
|
- **Codex CLI**: `~/.codex/config.toml` → `[mcp_servers.vruum]` with `url = "https://api.vruum.ai/mcp"`
|
|
40
40
|
- **Other**: connect to `https://api.vruum.ai/mcp` (HTTP, OAuth via standard MCP flow)
|
|
41
41
|
|
|
42
|
-
The orchestrator's MCP precheck at the top of Step 3 (`
|
|
42
|
+
The orchestrator's MCP precheck at the top of Step 3 (the `fetch` type=research_playbook call) catches misconfiguration upfront. Don't skip it.
|
|
43
43
|
|
|
44
44
|
### Dispatch methods (in order of preference)
|
|
45
45
|
|
|
@@ -56,7 +56,7 @@ The orchestrator's MCP precheck at the top of Step 3 (`get_research_playbook` ca
|
|
|
56
56
|
|
|
57
57
|
## Workflow — Step 1: Show pipeline status & pick segments
|
|
58
58
|
|
|
59
|
-
Call `
|
|
59
|
+
Call `import_prospects(action="sales_nav_searches", payload={action: "list"})` + `fetch(type="stats", subtype="outreach")` for queue depth + `search(type="campaigns")` for non-Sales-Nav segments. Present a numbered table with **per-segment ETA**:
|
|
60
60
|
|
|
61
61
|
```
|
|
62
62
|
Pipeline status:
|
|
@@ -100,8 +100,8 @@ The conditional rendering happens at package-build time, not at skill-runtime
|
|
|
100
100
|
|
|
101
101
|
Per source pick, dispatch:
|
|
102
102
|
|
|
103
|
-
- `sales-nav-platform` → invoke `/sales-nav-platform-fill` (calls `
|
|
104
|
-
- `csv-platform` → invoke `/csv-platform-fill` (calls `
|
|
103
|
+
- `sales-nav-platform` → invoke `/sales-nav-platform-fill` (calls `import_prospects` action=sales_nav_import; backend handles everything; **skip Steps 3-8 of this skill entirely** — backend agents own the rest).
|
|
104
|
+
- `csv-platform` → invoke `/csv-platform-fill` (calls `import_prospects` action=csv_start; backend handles everything; same — skip Steps 3-8).
|
|
105
105
|
- `sales-nav-deep` → invoke `/sales-nav-deep-fill` to produce a candidate list, then continue to Step 3 with it.
|
|
106
106
|
- `yc` → invoke `/yc-pipeline-fill` to produce a candidate list, then continue to Step 3 with it.
|
|
107
107
|
- `csv` → invoke `/csv-pipeline-fill` to produce a candidate list, then continue to Step 3 with it.
|
|
@@ -116,7 +116,7 @@ Discovery mode covers two paths off the same prompt:
|
|
|
116
116
|
**Path A — operator pastes candidates** (you already know who you want)
|
|
117
117
|
Tolerant line parser, candidates produced directly:
|
|
118
118
|
|
|
119
|
-
- **Line is a LinkedIn URL** (matches `^https?://(www\.)?linkedin\.com/in/[^/?]+/?(\?.*)?$`) → set `linkedin_url`, leave `name` and `company` null. Phase B will fill them via
|
|
119
|
+
- **Line is a LinkedIn URL** (matches `^https?://(www\.)?linkedin\.com/in/[^/?]+/?(\?.*)?$`) → set `linkedin_url`, leave `name` and `company` null. Phase B will fill them via the linkedin_fetch research call.
|
|
120
120
|
- **Line has comma(s)** → split as `name, company[, linkedin_url][, email]`. If 4 fields, last is email. If 3 fields, last is linkedin_url IF it matches the LinkedIn URL pattern, else interpret as email if it has `@`, else treat as a 2-field line + extra junk.
|
|
121
121
|
- **Line is just text** → treat as `full_name`, prompt operator: "what company for {full_name}?". If the operator gets prompted for >3 lines, ask once "set company={X} for all unspecified?" to batch.
|
|
122
122
|
|
|
@@ -127,13 +127,13 @@ Drop blank lines and lines starting with `#` (treat as comments).
|
|
|
127
127
|
**Path B — operator describes an ICP** (you want the harness to discover candidates)
|
|
128
128
|
Operator gives a brief like "Series A-C SaaS founders, US, 50-500 ppl" or "directors of operations at MSPs in DFW, recently posted about hiring". Harness sources candidates from scratch:
|
|
129
129
|
|
|
130
|
-
1. **Anchor on segment ICP** — read the segment's existing ICP/company profile (via `
|
|
130
|
+
1. **Anchor on segment ICP** — read the segment's existing ICP/company profile (via `fetch` type=campaign and `fetch` type=settings subtype=profile) and merge with the operator's brief. Show a one-line synthesis ("OK so: Series A-C SaaS, US, 50-500 ppl, founder/CEO/CTO titles") and confirm before sourcing.
|
|
131
131
|
2. **Source companies first** — use harness tools to find candidate companies matching the brief:
|
|
132
132
|
- `WebSearch` for funding announcements, news, lists ("Series A SaaS 2026", "TechCrunch Series B SaaS announcements")
|
|
133
133
|
- `WebFetch` on Crunchbase / PitchBook / company directories
|
|
134
|
-
- `
|
|
135
|
-
3. **Source people from each company** — for each candidate company, use `
|
|
136
|
-
4. **Dedup against existing pipeline** — for each discovered person, check `
|
|
134
|
+
- `mcp__vruum__import_prospects` with action=sales_nav_search (payload={keywords, title, limit}) for company-fitting roles when the brief is people-shaped (e.g. "VPs of Eng at Series A SaaS")
|
|
135
|
+
3. **Source people from each company** — for each candidate company, use `mcp__vruum__search` with type=companies and filters={domain, seniority} (Unipile-backed; respects LinkedIn rate limits) to find titles matching the segment ICP. Cap at ~5 people per company to spread the discovery surface.
|
|
136
|
+
4. **Dedup against existing pipeline** — for each discovered person, check `mcp__vruum__search` with type=people and a name/company keyword query so you don't research someone the segment already has.
|
|
137
137
|
5. **Show the discovered list to the operator** before handoff. Format: `Name (title) — Company [linkedin]`. Cap the surface at 2x daily_target so we don't over-source. Get a "go" / "drop X" before continuing.
|
|
138
138
|
|
|
139
139
|
Discovery-path candidates produced in either path use the canonical shape in `RESEARCH-ENGINE.md` and feed into Step 3 the same way.
|
|
@@ -10,7 +10,7 @@ description: >-
|
|
|
10
10
|
|
|
11
11
|
You are the Sales Nav harness-mode source for `/pipeline-fill`. You produce a candidate list from a Sales Nav saved search and hand off to the orchestrator for deep research, harness gate, and save chain.
|
|
12
12
|
|
|
13
|
-
**This is the harness counterpart to `/sales-nav-platform-fill`.** The platform skill calls `
|
|
13
|
+
**This is the harness counterpart to `/sales-nav-platform-fill`.** The platform skill calls `import_prospects` with action=sales_nav_import and lets backend agents do everything (Vruum's compute). This skill stops at producing a candidate list — Phase A and Phase B run in your chat session (your harness compute), and `manage_person` with action=save_discovered is called only after the harness pre-filter gate passes. Pick this when you want visibility into the deep research as it happens.
|
|
14
14
|
|
|
15
15
|
## Inputs
|
|
16
16
|
|
|
@@ -21,7 +21,7 @@ You are the Sales Nav harness-mode source for `/pipeline-fill`. You produce a ca
|
|
|
21
21
|
|
|
22
22
|
### Step 1: Preview Sales Nav profiles
|
|
23
23
|
|
|
24
|
-
Call `
|
|
24
|
+
Call `import_prospects(action="sales_nav_preview", payload={segment: ..., count: ...})`. Returns profile data + segment ICP context (target_titles, target_industries, value_proposition, positioning_angle, differentiators).
|
|
25
25
|
|
|
26
26
|
### Step 2: Dispatch `vruum-pipeline-filter` for ICP pre-filter
|
|
27
27
|
|
|
@@ -57,6 +57,6 @@ Continue automatically? (y/n)
|
|
|
57
57
|
|
|
58
58
|
## Notes
|
|
59
59
|
|
|
60
|
-
- **For the operator:** pick this when you want to see the deep research happen, want to interrupt mid-stream, or want zero platform compute cost on the front-half (sourcing + research). The back-half (`
|
|
61
|
-
- **Don't call `
|
|
60
|
+
- **For the operator:** pick this when you want to see the deep research happen, want to interrupt mid-stream, or want zero platform compute cost on the front-half (sourcing + research). The back-half (`manage_person` action=save_discovered, which runs `analyze_person_match` + the `match_score >= 70` gate) still runs on Vruum, intentionally — it's the canonical gate.
|
|
61
|
+
- **Don't call `import_prospects` with action=sales_nav_import directly from this skill.** That triggers the backend research pipeline, which is exactly what we're avoiding by being in harness mode. If you need the backend path, use `/sales-nav-platform-fill` instead.
|
|
62
62
|
- **vruum-pipeline-filter pre-filter is FREE** for the operator (cheap title/company match). The expensive Phase B research only fires on prospects that survive that filter.
|
|
@@ -9,7 +9,7 @@ tenant bind to choose.
|
|
|
9
9
|
|
|
10
10
|
```
|
|
11
11
|
# Step 1: pull closed-lost deals (scoped to your session's tenant)
|
|
12
|
-
lost =
|
|
12
|
+
lost = search(type="deals", filters={"outcome": "lost"}, limit=200)
|
|
13
13
|
|
|
14
14
|
# Step 2: keep deals lost between 90 days and 18 months ago
|
|
15
15
|
ninety_days_ago = now() - 90 days
|
|
@@ -26,7 +26,7 @@ revivable = [
|
|
|
26
26
|
]
|
|
27
27
|
|
|
28
28
|
# Step 4: drop people who currently have an OPEN deal (NULL outcome)
|
|
29
|
-
open_deals =
|
|
29
|
+
open_deals = search(type="deals", filters={"outcome": None}, limit=500)
|
|
30
30
|
open_person_ids = { d.person_id for d in open_deals.deals }
|
|
31
31
|
candidates = [d for d in revivable if d.person_id not in open_person_ids]
|
|
32
32
|
```
|
|
@@ -40,7 +40,7 @@ thanks" buyer from being re-pitched while the rejection is still fresh.
|
|
|
40
40
|
|
|
41
41
|
Why this works without a DB query:
|
|
42
42
|
|
|
43
|
-
- `
|
|
43
|
+
- `search` type=deals is tenant-scoped server-side.
|
|
44
44
|
- The old `company_people` JOIN (to resolve account from person) is replaced
|
|
45
45
|
by `get_person_360` in the skill's Step 3 enrichment — call it per
|
|
46
46
|
surfaced person; `current_positions[0].company_id` is the account.
|
|
@@ -52,8 +52,8 @@ The "warmest cold-outreach possible" play.
|
|
|
52
52
|
|
|
53
53
|
```
|
|
54
54
|
# Step 1: pull historic deals (lost OR won, since won-then-churned applies)
|
|
55
|
-
historic_lost =
|
|
56
|
-
historic_won =
|
|
55
|
+
historic_lost = search(type="deals", filters={"outcome": "lost"}, limit=200).deals
|
|
56
|
+
historic_won = search(type="deals", filters={"outcome": "won"}, limit=200).deals
|
|
57
57
|
former_buyer_person_ids = {
|
|
58
58
|
d.person_id for d in (historic_lost + historic_won)
|
|
59
59
|
if parse(d.stage_changed_at or d.actual_close_date) < (now() - 60 days)
|
|
@@ -80,12 +80,12 @@ funding, press, layoff at a competitor) and use the trigger as the revival
|
|
|
80
80
|
hook.
|
|
81
81
|
|
|
82
82
|
```
|
|
83
|
-
# Per surfaced account (from Variant 1),
|
|
83
|
+
# Per surfaced account (from Variant 1), fetch type=company_research to see
|
|
84
84
|
# if any recent news/event was captured in the last 14 days. The harness
|
|
85
85
|
# does not currently expose a direct "list firing triggers" tool in the
|
|
86
86
|
# public MCP — read recent triggers off the company_research payload.
|
|
87
87
|
for d in variant_1_candidates:
|
|
88
|
-
research =
|
|
88
|
+
research = fetch(type="company_research", id=d.account_company_id)
|
|
89
89
|
fresh_triggers = [
|
|
90
90
|
t for t in (research.recent_signals or [])
|
|
91
91
|
if (now() - parse(t.fired_at)) < 14 days
|
|
@@ -95,13 +95,13 @@ for d in variant_1_candidates:
|
|
|
95
95
|
...
|
|
96
96
|
```
|
|
97
97
|
|
|
98
|
-
If
|
|
98
|
+
If the company_research fetch returns no recent signals, this variant degrades
|
|
99
99
|
to Variant 1 silently — no harm done.
|
|
100
100
|
|
|
101
101
|
## Why these recipes are safe
|
|
102
102
|
|
|
103
103
|
All three variants:
|
|
104
|
-
- Run through `
|
|
104
|
+
- Run through `search` type=deals / `get_person_360` / `fetch` type=company_research, all of
|
|
105
105
|
which derive `user_company_id` from your authenticated session. You CANNOT
|
|
106
106
|
choose a different tenant.
|
|
107
107
|
- Exclude terminal loss reasons (`no_fit`, `no_budget_permanent`) in
|
|
@@ -24,10 +24,10 @@ The impact scoreboard and the `account_stage='churned'`/`'dormant'` tagging let
|
|
|
24
24
|
## Where heavy logic lives
|
|
25
25
|
|
|
26
26
|
[`COHORT-QUERIES.md`](./COHORT-QUERIES.md) — three Vruum-MCP cohort recipes
|
|
27
|
-
(all `
|
|
27
|
+
(all `search` type=deals + post-filter; tenant scope is automatic from your session):
|
|
28
28
|
1. **90-day silent-deal revival** (default): lost deals >90d old, person still at company, loss reason not terminal
|
|
29
29
|
2. **Champion-follows-you**: former champion moved to a new company (re-targeted at new co via `get_person_360.current_positions`)
|
|
30
|
-
3. **Trigger-driven winback**: surfaced via fresh signals on `
|
|
30
|
+
3. **Trigger-driven winback**: surfaced via fresh signals on `fetch` type=company_research (new exec, funding, press)
|
|
31
31
|
|
|
32
32
|
Start with cohort 1 unless you specify otherwise.
|
|
33
33
|
|
|
@@ -35,13 +35,13 @@ Start with cohort 1 unless you specify otherwise.
|
|
|
35
35
|
|
|
36
36
|
Step 1 — Read scoreboard.
|
|
37
37
|
```
|
|
38
|
-
|
|
38
|
+
fetch(type="scoreboard", subtype="impact", filters={"window_days": 90})
|
|
39
39
|
```
|
|
40
40
|
Surface practice rollups. Empty state → fall through to day-1 heuristic.
|
|
41
41
|
|
|
42
42
|
Step 2 — Build the cohort using the recipes in `COHORT-QUERIES.md`. All
|
|
43
|
-
recipes use `
|
|
44
|
-
automatic from your authenticated session. Pick a variant per your intent
|
|
43
|
+
recipes use `search` type=deals + post-filter via the Vruum MCP; tenant scope
|
|
44
|
+
is automatic from your authenticated session. Pick a variant per your intent
|
|
45
45
|
(default: variant 1).
|
|
46
46
|
|
|
47
47
|
For variant 1 (silent-deal revival), the cohort criteria:
|
|
@@ -50,13 +50,13 @@ For variant 1 (silent-deal revival), the cohort criteria:
|
|
|
50
50
|
- `loss_reason NOT IN ('no_fit', 'no_budget_permanent')` (these are terminal — don't re-pitch)
|
|
51
51
|
- Person is still surfaceable via `get_person_360` (still at company)
|
|
52
52
|
- No open deal currently exists on that person (post-filter against
|
|
53
|
-
`
|
|
53
|
+
`search` type=deals filters={outcome: None})
|
|
54
54
|
|
|
55
55
|
Limit 50. Order by `stage_changed_at DESC` (most-recent loss first — freshest memory of the conversation).
|
|
56
56
|
|
|
57
57
|
Step 3 — Per-account enrichment.
|
|
58
58
|
- `get_person_360` — what was the original conversation? `analysis` JSONB on the old deal often captures objection patterns.
|
|
59
|
-
- `
|
|
59
|
+
- `fetch` type=company_research — has anything changed at the company? New exec? Funding? Recent news?
|
|
60
60
|
- `accounts.account_stage` — if `churned`, the account has been flagged as dead. Skip or down-rank unless variant 2/3 applies.
|
|
61
61
|
|
|
62
62
|
Step 4 — Score and rank.
|
|
@@ -84,18 +84,21 @@ If no hook can be generated → defer the row. Warm via marketing/content first,
|
|
|
84
84
|
|
|
85
85
|
Step 6 — Hand off. Two options:
|
|
86
86
|
- **Option A (recommended)**: Approve the list; run `/pipeline-fill` with the prospect_list for harness deep research + outreach. Plans get `outreach_plans.tag = bowtie_pilot:winback`.
|
|
87
|
-
- **Option B**: Direct `
|
|
87
|
+
- **Option B**: Direct `manage_outreach` action=start with a winback-flavored segment (pre-create a `winback_<your-tenant>` segment — tone: empathetic, no apology, lead with what changed since last conversation).
|
|
88
88
|
|
|
89
|
-
Step 7 — Success tracking (auto). The calendar webhook
|
|
89
|
+
Step 7 — Success tracking (auto). The calendar webhook records the impact event, equivalent to:
|
|
90
90
|
```
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
91
|
+
manage_account(
|
|
92
|
+
action="record_impact",
|
|
93
|
+
payload={
|
|
94
|
+
practice: 'winback',
|
|
95
|
+
event_type: 'winback_meeting_booked',
|
|
96
|
+
value_delivered_numeric: deal.estimated_value,
|
|
97
|
+
...
|
|
98
|
+
}
|
|
96
99
|
)
|
|
97
100
|
```
|
|
98
|
-
when a meeting is booked on a plan tagged `bowtie_pilot:winback`. You do NOT manually fire for tagged plans. After 30 days, `
|
|
101
|
+
when a meeting is booked on a plan tagged `bowtie_pilot:winback`. You do NOT manually fire for tagged plans. After 30 days, `fetch` type=scoreboard subtype=impact should show winback `event_count` > 0.
|
|
99
102
|
|
|
100
103
|
## When NOT to use this skill
|
|
101
104
|
|
|
@@ -111,4 +114,4 @@ See `docs/ACCOUNT-LIFECYCLE-VOCABULARY.md` for the 8 canonical account-lifecycle
|
|
|
111
114
|
|
|
112
115
|
## Backend authoritative gate
|
|
113
116
|
|
|
114
|
-
Same pattern as `/expansion-fill`: harness-mode uplift; the backend's `
|
|
117
|
+
Same pattern as `/expansion-fill`: harness-mode uplift; the backend's impact-event write (`manage_account` action=record_impact) is the authoritative surface with dedupe.
|
|
@@ -29,11 +29,11 @@ If the operator runs the skill without filters, prompt: "YC has 5K+ companies
|
|
|
29
29
|
|
|
30
30
|
### Step 1: Load segment ICP
|
|
31
31
|
|
|
32
|
-
Call `
|
|
32
|
+
Call `fetch` type=research_playbook id=<segment_id> to load ICP context. The downstream subagents need it for classification; capture it now to pass forward.
|
|
33
33
|
|
|
34
34
|
### Step 2: Algolia connectivity precheck
|
|
35
35
|
|
|
36
|
-
Issue a one-shot Algolia query against the index with `hitsPerPage: 1` to confirm extraction works. **Do NOT use `
|
|
36
|
+
Issue a one-shot Algolia query against the index with `hitsPerPage: 1` to confirm extraction works. **Do NOT use `import_prospects` action=sales_nav_searches payload={action: "accounts"}** as a precheck — that endpoint checks LinkedIn account state and has nothing to do with YC. False-fails operators who don't have Sales Nav configured (eng review §1B).
|
|
37
37
|
|
|
38
38
|
The precheck happens implicitly in Step 3 (the first real Algolia call); if it fails there, abort with a clear error.
|
|
39
39
|
|
|
@@ -101,7 +101,7 @@ After decoding, regex out:
|
|
|
101
101
|
**Pick primary founder:**
|
|
102
102
|
1. First founder where `title` matches `/CEO|Chief Exec|Co-?founder & CEO/i` (case-insensitive)
|
|
103
103
|
2. Else first founder where `linkedin_url` is non-empty
|
|
104
|
-
3. Else **drop the company entirely.** Do not call `
|
|
104
|
+
3. Else **drop the company entirely.** Do not call `research` action=find_linkedin to guess — produces low-confidence matches and noise.
|
|
105
105
|
|
|
106
106
|
**HTML revision detection:** If >30% of candidates yield zero founders (regex didn't match), abort the run with: "YC company page structure changed — scrape regex needs updating." Don't silently degrade. Single-company misses are tolerable.
|
|
107
107
|
|
|
@@ -161,7 +161,7 @@ Continue automatically? (y/n)
|
|
|
161
161
|
|
|
162
162
|
- **Algolia key extraction fails** (no candidates work): abort, single-line error pointing at the homepage structure.
|
|
163
163
|
- **>30% scrape failure**: abort, point at the company-page structure.
|
|
164
|
-
- **No founder LinkedIn for a company**: drop the company; never guess via
|
|
164
|
+
- **No founder LinkedIn for a company**: drop the company; never guess via the find_linkedin research action.
|
|
165
165
|
- **Pool exhaustion**: flag in candidate list metadata so the orchestrator's report surfaces it.
|
|
166
166
|
- **Operator gives no filters**: prompt for at least one. No silent defaults.
|
|
167
167
|
- **Operator gives `count` of 200+**: still works; expect ~30+ minute wall-clock for the full pipeline-fill flow.
|