@vruum/skills 0.4.7 → 0.4.9
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/README.md +4 -1
- package/package.json +2 -2
- package/skills/csv-pipeline-fill/SKILL.md +132 -0
- package/skills/pipeline-fill/RESEARCH-ENGINE.md +288 -0
- package/skills/pipeline-fill/SKILL.md +97 -65
- package/skills/sales-nav-deep-fill/SKILL.md +72 -0
- package/skills/segment-doctor/SKILL.md +1 -1
- package/skills/yc-pipeline-fill/SKILL.md +184 -0
package/README.md
CHANGED
|
@@ -41,7 +41,10 @@ npx @vruum/skills install --target /path/to/skills/dir
|
|
|
41
41
|
- `/engagement-triage` — Review and approve your pending LinkedIn engagement drafts and demand-gen content posts. Use when: triage engagements, review engagement queue, review warming comments, review nurture reactions, review marketing comments, review content drafts, check engagement queue.
|
|
42
42
|
- `/enrich-prospect` — Deep prospect diarization — synthesize everything known about a person into a structured intelligence profile. Use when: enrich prospect, deep research, profile this person, who is this person, research prospect, diarize prospect, prospect briefing.
|
|
43
43
|
- `/outreach-triage` — Review and approve your pending outreach drafts across LinkedIn and email. Use when: triage, review queue, morning review, check messages, approve outreach, what needs review.
|
|
44
|
-
- `/pipeline-fill` —
|
|
44
|
+
- `/pipeline-fill` — Source-agnostic pipeline orchestrator. Picks a source per segment (Sales Nav / YC / CSV / discovery), runs harness deep research, applies a pre-filter gate, then saves into the segment via the backend authoritative match_score>=70 gate. Use when: fill pipeline, import prospects, daily imports, need more prospects, discover prospects from scratch, deep research before import.
|
|
45
|
+
- `/sales-nav-deep-fill` — Sales Nav harness source for /pipeline-fill. Pre-filters Sales Nav profiles via vruum-pipeline-filter, produces a candidate list, hands off to /pipeline-fill for deep research and import. Use when: sales nav with deep research, sales nav harness mode, in-chat sales nav.
|
|
46
|
+
- `/yc-pipeline-fill` — YC harness source for /pipeline-fill. Scrapes YC's public Algolia index, extracts founder LinkedIn URLs, dedups, hands a candidate list to /pipeline-fill for deep research and import. Use when: YC pipeline fill, source from YC, fill segment with YC founders, sales nav dried up, source YC.
|
|
47
|
+
- `/csv-pipeline-fill` — CSV harness source for /pipeline-fill. Reads a CSV, auto-detects headers, maps columns, hands off to /pipeline-fill for harness deep research and import. Use when: import CSV, paste a CSV, csv import, prospect list from CSV, csv harness mode.
|
|
45
48
|
- `/segment-doctor` — Diagnose and fix struggling outreach segments. Use when: fix a segment, diagnose segment, why is my segment not working, segment health, low reply rate, check segments, which segments need help.
|
|
46
49
|
- `/vruum-skills-upgrade` — Upgrade @vruum/skills to the latest npm version and re-sync ~/.vruum/. Use when: upgrade vruum skills, update vruum, pull latest vruum skills, or when the preamble reports UPGRADE_AVAILABLE.
|
|
47
50
|
<!-- generated:skills-end -->
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@vruum/skills",
|
|
3
|
-
"version": "0.4.
|
|
3
|
+
"version": "0.4.9",
|
|
4
4
|
"description": "Vruum AI skills for Claude Code, Claude Desktop, Codex CLI, and any AI assistant with a skill directory. Slash commands for outreach triage, engagement triage, pipeline filling, prospect enrichment, and reply diagnosis. Pairs with the Vruum MCP server at https://api.vruum.ai/mcp.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"repository": {
|
|
@@ -36,5 +36,5 @@
|
|
|
36
36
|
"outreach",
|
|
37
37
|
"gtm"
|
|
38
38
|
],
|
|
39
|
-
"contentHash": "
|
|
39
|
+
"contentHash": "fe89953973a2a52b8a68248751cb90fa8beb8d71fa74c6b0c365798e78d154b5"
|
|
40
40
|
}
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: csv-pipeline-fill
|
|
3
|
+
description: >-
|
|
4
|
+
CSV harness source for /pipeline-fill. Reads a CSV, auto-detects headers, maps
|
|
5
|
+
columns, hands off to /pipeline-fill for harness deep research and import. Use
|
|
6
|
+
when: import CSV, paste a CSV, csv import, prospect list from CSV, csv harness
|
|
7
|
+
mode.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Auto-update check
|
|
11
|
+
|
|
12
|
+
Before starting, run `~/.vruum/bin/vruum-skills-update-check` (path relative to this repo). Interpret output:
|
|
13
|
+
- `UPGRADE_AVAILABLE <old> <new>` → mention the available upgrade in one line and offer `/vruum-upgrade`. Then continue.
|
|
14
|
+
- `JUST_UPGRADED <old> <new>` → acknowledge in one line, then continue.
|
|
15
|
+
- Empty → proceed silently.
|
|
16
|
+
|
|
17
|
+
Never block skill execution on this check.
|
|
18
|
+
|
|
19
|
+
# CSV Pipeline Fill (harness source)
|
|
20
|
+
|
|
21
|
+
You are the CSV harness-mode source for `/pipeline-fill`. You read a CSV the operator provides (Apollo export, ZoomInfo, hand-built spreadsheet, LinkedIn export — anything), normalize columns to the canonical candidate-list shape, and hand off to the orchestrator.
|
|
22
|
+
|
|
23
|
+
**This is the harness counterpart to `/csv-platform-fill`.** The platform skill calls `start_csv_import` and lets backend agents do everything. This skill stops at producing a candidate list — Phase A and Phase B run in your chat session (your harness compute), and `save_discovered_person` is called only after the harness pre-filter gate passes.
|
|
24
|
+
|
|
25
|
+
## Inputs
|
|
26
|
+
|
|
27
|
+
- `file_path`: absolute path to the CSV. If not provided, ask for it. Common locations: `~/Downloads/`, `.context/attachments/` in the workspace.
|
|
28
|
+
- `segment`: target segment (single)
|
|
29
|
+
- `column_mapping` (optional): explicit `{header_name: canonical_field}` mapping if headers don't auto-resolve. Default: auto-detect.
|
|
30
|
+
|
|
31
|
+
## Workflow
|
|
32
|
+
|
|
33
|
+
### Step 1: Read the file
|
|
34
|
+
|
|
35
|
+
Use the `Read` tool on the absolute path. If the file doesn't exist, surface a clear error: "File not found at `{path}`. If it's in your Downloads folder, the path is typically `/Users/<you>/Downloads/<file>.csv`." Don't guess.
|
|
36
|
+
|
|
37
|
+
### Step 2: Detect delimiter + encoding
|
|
38
|
+
|
|
39
|
+
- **Delimiter:** read the first line. If it has more `;` than `,`, use `;`. If more tabs than commas, use `\t`. Default: `,`.
|
|
40
|
+
- **Encoding:** assume UTF-8. If decoding fails, fall back to Latin-1 and note the encoding in the report.
|
|
41
|
+
- **BOM:** if the first 3 bytes are `\xef\xbb\xbf` (UTF-8 BOM), strip them before parsing.
|
|
42
|
+
- **Header row offset:** if the first row has only one non-empty cell (likely a title or disclaimer), skip it and treat row 2 as headers.
|
|
43
|
+
|
|
44
|
+
### Step 3: Detect headers + map columns
|
|
45
|
+
|
|
46
|
+
The first non-empty data row after the header offset is treated as the header. Lowercase + strip whitespace from each header. Match against canonical fields:
|
|
47
|
+
|
|
48
|
+
- `name` ← `full_name`, `name`, `contact name`, `prospect`, `contact`
|
|
49
|
+
- `first_name` ← `first_name`, `first name`, `firstname`, `given name`
|
|
50
|
+
- `last_name` ← `last_name`, `last name`, `lastname`, `surname`, `family name`
|
|
51
|
+
- `company` ← `company`, `company name`, `account`, `organization`, `org`, `employer`
|
|
52
|
+
- `linkedin_url` ← `linkedin`, `linkedin url`, `linkedin_url`, `profile`, `linkedin profile`, `linkedin_profile`, `li_url`
|
|
53
|
+
- `email` ← `email`, `email_address`, `work_email`, `business email`
|
|
54
|
+
- `title` ← `title`, `job_title`, `position`, `role`
|
|
55
|
+
|
|
56
|
+
If `name` is mapped, defer the first/last split to the engine doc's Step 7 (canonical heuristic). If `first_name` + `last_name` are both mapped, use them directly.
|
|
57
|
+
|
|
58
|
+
### Step 4: Resolve ambiguous mappings
|
|
59
|
+
|
|
60
|
+
If any required field can't be auto-mapped (`name`/(first+last) AND `company`, OR `linkedin_url`), show the operator the detected headers + sample row and ask which column maps to which field. Don't guess silently — silent guessing is the source of "why did my CSV import 50 prospects with the wrong company" bugs.
|
|
61
|
+
|
|
62
|
+
For multi-column ambiguity (e.g. two columns matching `email`), pick the leftmost match and note it in the operator output.
|
|
63
|
+
|
|
64
|
+
### Step 5: Normalize each row
|
|
65
|
+
|
|
66
|
+
Per row:
|
|
67
|
+
- Strip whitespace from all fields.
|
|
68
|
+
- Lowercase emails.
|
|
69
|
+
- Validate `linkedin_url` matches `^https?://(www\.)?linkedin\.com/in/[^/?]+/?(\?.*)?$`. If invalid (e.g. `https://linkedin.com/company/...`), set to null and log.
|
|
70
|
+
- Strip query strings from LinkedIn URLs (`?utm_source=...` etc.) — canonicalize to `https://linkedin.com/in/<slug>/`.
|
|
71
|
+
- **Skip rows** where neither `linkedin_url` nor (`name` AND `company`) is present. Log the skipped count.
|
|
72
|
+
- **In-CSV dedup**: dedup the candidate list by lowercased `linkedin_url` (preferred), else by `<lowercased name> + <lowercased company>`. Log duplicates dropped.
|
|
73
|
+
|
|
74
|
+
### Step 6: Pre-filter on size
|
|
75
|
+
|
|
76
|
+
If the CSV has >200 rows after normalize+dedup, ask the operator: "CSV has {N} rows after dedup. Process all, or first M? (a/N)". Big CSVs eat real time + LinkedIn API quota in the deep-research stage.
|
|
77
|
+
|
|
78
|
+
### Step 7: Build candidate list
|
|
79
|
+
|
|
80
|
+
Convert each row to the canonical shape (defined in `pipeline-fill/RESEARCH-ENGINE.md`):
|
|
81
|
+
|
|
82
|
+
```
|
|
83
|
+
{
|
|
84
|
+
full_name: <name field if mapped, else null>,
|
|
85
|
+
first_name: <first_name field if mapped, else null>,
|
|
86
|
+
last_name: <last_name field if mapped, else null>,
|
|
87
|
+
company: <company>,
|
|
88
|
+
linkedin_url: <canonicalized URL or null>,
|
|
89
|
+
email: <email or null>,
|
|
90
|
+
person_id: null,
|
|
91
|
+
title: <title or null>,
|
|
92
|
+
raw_signals: {
|
|
93
|
+
source: "csv",
|
|
94
|
+
csv_path: "<file_path>",
|
|
95
|
+
csv_row_number: <row index, 1-based>,
|
|
96
|
+
csv_extra_columns: { <header>: <value>, ... } // any unmapped columns, preserved for ops
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
```
|
|
100
|
+
|
|
101
|
+
The `csv_extra_columns` field keeps unmapped data on the candidate so an operator can later inspect it via `get_user_people` if a question comes up about why a particular prospect was imported.
|
|
102
|
+
|
|
103
|
+
### Step 8: Hand off to /pipeline-fill (canonical handoff prompt)
|
|
104
|
+
|
|
105
|
+
Emit the canonical handoff prompt (defined in `pipeline-fill/RESEARCH-ENGINE.md`):
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
Candidate list ready: {N} prospects from csv (after {dedup_count} dedup).
|
|
109
|
+
|
|
110
|
+
NEXT: invoke /pipeline-fill Step 3 onward (deep research → harness gate → save) with this list and segment {segment_id}.
|
|
111
|
+
|
|
112
|
+
Continue automatically? (y/n)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
- Operator answers `y` → continue into the engine doc's Step 3.
|
|
116
|
+
- Operator answers `n` → exit cleanly with the candidate list visible in chat.
|
|
117
|
+
|
|
118
|
+
## Edge cases
|
|
119
|
+
|
|
120
|
+
- **Tabs/semicolons** as delimiter — auto-detected from first line.
|
|
121
|
+
- **BOM/encoding** — UTF-8 BOM stripped; Latin-1 fallback if UTF-8 fails.
|
|
122
|
+
- **Headers in row 2** — auto-detected when row 1 has only one non-empty cell.
|
|
123
|
+
- **Duplicate rows** — in-CSV dedup before handoff.
|
|
124
|
+
- **LinkedIn URLs with tracking params** — query string stripped during canonicalization.
|
|
125
|
+
- **Mixed name format** (some rows have full_name, others have first/last) — engine handles both via Step 7's identity-resolution flow.
|
|
126
|
+
- **Empty CSV** — orchestrator says "CSV has no data rows after dedup; nothing to research" and exits.
|
|
127
|
+
|
|
128
|
+
## Notes
|
|
129
|
+
|
|
130
|
+
- **Sourcing phase is ~free** (file read + parsing). Real-money costs live in `/pipeline-fill`'s deep-research subagents.
|
|
131
|
+
- **For backend-driven CSV import** (fire-and-forget, no in-chat research): the platform-mode CSV skill calls `start_csv_import` directly and lets backend agents do sourcing/research/gate. Pick this skill when you want to see the deep research happen in your chat instead.
|
|
132
|
+
- **Composability** with `/pipeline-fill`: standard pattern. Run this skill standalone for "I just want this CSV imported with deep research today" or invoke via the orchestrator's source picker.
|
|
@@ -0,0 +1,288 @@
|
|
|
1
|
+
# Pipeline Fill — Research Engine
|
|
2
|
+
|
|
3
|
+
This is the canonical research-engine doc referenced by `/pipeline-fill` (orchestrator) and all harness-mode source skills (`/sales-nav-deep-fill`, `/yc-pipeline-fill`, `/csv-pipeline-fill`, plus inline manual-list mode).
|
|
4
|
+
|
|
5
|
+
When a source skill produces a candidate list, it hands off to this engine via the canonical handoff prompt at the bottom of this doc. The engine then runs Steps 3–8: pre-flight → Phase A → Phase B → harness gate → save → report.
|
|
6
|
+
|
|
7
|
+
The orchestrator's SKILL.md owns the front-of-flow: segment picker (Step 1), source picker (Step 2), and the inline manual-list parser. Everything from Step 3 onward is defined here. **Don't duplicate this doc in source skills** — link to it.
|
|
8
|
+
|
|
9
|
+
---
|
|
10
|
+
|
|
11
|
+
## Candidate List Shape (canonical, single source of truth — load-bearing)
|
|
12
|
+
|
|
13
|
+
All harness source skills produce candidate lists matching this shape exactly. The orchestrator consumes only this shape. Adding a field requires updating this section, then auditing each source skill.
|
|
14
|
+
|
|
15
|
+
```
|
|
16
|
+
[
|
|
17
|
+
{
|
|
18
|
+
full_name: string | null, // "Jane Smith" — convenience; engine splits to first/last in Step 7
|
|
19
|
+
first_name: string | null, // optional if full_name set; one of these MUST be present
|
|
20
|
+
last_name: string | null, // optional if full_name set
|
|
21
|
+
company: string | null, // "Acme Co" — null OK if linkedin_url is set
|
|
22
|
+
linkedin_url: string | null, // canonical /in/ URL — null OK if name+company set
|
|
23
|
+
email: string | null, // null = pending lookup; engine doesn't gate on email presence
|
|
24
|
+
person_id: string | null, // pre-existing Vruum person UUID; null for new (resolved in Step 7)
|
|
25
|
+
title: string | null, // optional, fills via Phase B if missing
|
|
26
|
+
raw_signals: object | null // source-specific opaque blob (yc batch, csv row#, etc.)
|
|
27
|
+
}
|
|
28
|
+
]
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
**Rules:**
|
|
32
|
+
- At minimum, each candidate needs **either** `linkedin_url` **or** (`name`-fields + `company`). Candidates with neither are skipped at Step 3.
|
|
33
|
+
- `full_name` is a convenience for sources that don't pre-split. Engine's Step 7 splits via last-space heuristic (`Jane van der Merwe` → first=`Jane`, last=`van der Merwe`). Multi-token surnames like `Maria Del Carmen Garcia` may split imperfectly — Phase B's `fetch_linkedin_data` returns canonical first/last when `linkedin_url` is present and overrides the heuristic.
|
|
34
|
+
- Field additions are additive only. Removing a field is a breaking change for source skills.
|
|
35
|
+
|
|
36
|
+
---
|
|
37
|
+
|
|
38
|
+
## MCP-availability precheck (load-bearing — runs before Step 3)
|
|
39
|
+
|
|
40
|
+
Before any other Step 3 work, call `get_research_playbook(segment_id=<id>)`. If this fails with "tool not found" / 404 / connection error, abort the run with this exact message:
|
|
41
|
+
|
|
42
|
+
> Vruum MCP not configured as a user-scoped server. Run:
|
|
43
|
+
>
|
|
44
|
+
> `claude mcp add --transport http --scope user vruum https://api.vruum.ai/mcp`
|
|
45
|
+
>
|
|
46
|
+
> and retry. (The cloud `claude.ai Vruum` connector doesn't propagate to subagents — they need `vruum` configured at the user scope in `~/.claude.json`.)
|
|
47
|
+
|
|
48
|
+
This catches the common silent-failure mode: deep-research subagents dispatch, all return `STATUS: failed` because they can't reach MCP, and the operator gets a confusing "0 enrolled, no errors" report. One MCP call upfront vs an hour of debugging.
|
|
49
|
+
|
|
50
|
+
The `get_research_playbook` call also doubles as the ICP load — capture target_titles, target_industries, value_proposition, positioning_angle, ACV floor, signals_to_watch, exclusions for use in subagent dispatch prompts.
|
|
51
|
+
|
|
52
|
+
---
|
|
53
|
+
|
|
54
|
+
## Step 3 — Pre-flight
|
|
55
|
+
|
|
56
|
+
Per segment's candidate list:
|
|
57
|
+
|
|
58
|
+
1. **MCP precheck + ICP load** (above) — abort run on failure.
|
|
59
|
+
2. **Batch dedup against existing pipeline.** Call `batch_search_existing_people(queries=[{name, company, linkedin_url} for each candidate])`. Returns one match record per candidate (in input order). Drop candidates with non-null `match` — they're already in pipeline.
|
|
60
|
+
3. **Batch company-cache check.** Collect unique company domains from surviving candidates. Call `batch_get_company_research(domains=[...])`. Returns `[{domain, cached_research, age_days}]`.
|
|
61
|
+
- Cache hit (`cached_research != null` AND `age_days <= 90`) → company skips Phase A; the cached research carries forward.
|
|
62
|
+
- Cache miss or stale (`age_days > 90`) → company joins the Phase A research queue.
|
|
63
|
+
4. **Operator confirmation gate (CSV / large lists only).** If the original candidate list was >200 (CSV) or >100 (manual list), confirm count to process before continuing.
|
|
64
|
+
|
|
65
|
+
**Latency:** ~2s for batch dedup + ~1s for batch company cache, regardless of list size. (Per-prospect iteration was ~12s for 60 prospects pre-batch primitives.)
|
|
66
|
+
|
|
67
|
+
---
|
|
68
|
+
|
|
69
|
+
## Step 4 — Phase A: company research
|
|
70
|
+
|
|
71
|
+
**Concurrency cap: 10 parallel.** Phase A subagents don't call `fetch_linkedin_data` — they hit `get_company_research`, `fetch_company_website`, `WebFetch`, `WebSearch`. No Unipile rate-limit pressure.
|
|
72
|
+
|
|
73
|
+
Dispatch one `vruum-company-deep-researcher` per unique uncached company. Subagent file at `.claude/agents/vruum-company-deep-researcher.md` defines the workflow + tools.
|
|
74
|
+
|
|
75
|
+
Dispatch prompt template (fill in placeholders):
|
|
76
|
+
|
|
77
|
+
```
|
|
78
|
+
You are vruum-company-deep-researcher. Research this company against segment "{segment_name}".
|
|
79
|
+
|
|
80
|
+
company_name: {name}
|
|
81
|
+
domain: {domain}
|
|
82
|
+
segment_icp_summary: {one paragraph from get_research_playbook}
|
|
83
|
+
acv_floor: {dollars or default $10K}
|
|
84
|
+
|
|
85
|
+
Run your workflow (a–i) and return the structured output block.
|
|
86
|
+
```
|
|
87
|
+
|
|
88
|
+
Each subagent returns: `company_id`, `funding_data`, `growth_metrics`, `current_priorities`, `outbound_motion_score` (0/1/2), `acv_class` (smb/mid/ent), `sales_cycle_inference` (short/medium/long), `triggers[]`, `STATUS: ok | failed`, `CACHE_HIT`.
|
|
89
|
+
|
|
90
|
+
**Wait for the wave to complete before Phase B.** Phase B inputs depend on Phase A's signals (or null if failed).
|
|
91
|
+
|
|
92
|
+
**Subagent timeout cascade (load-bearing):** when STATUS=failed for a company, the orchestrator does NOT skip the prospects from that company. Phase B still runs for them with `null` company signals. The harness pre-filter gate then tags them `harness_gate_status: gate_inconclusive` (a fourth status alongside pass/warming/low_priority/dismiss). `save_discovered_person` is still called — the backend's `MatchAnalysisAgent` may have cached company research from earlier runs and gates them appropriately. Surface gate-inconclusive prospects in the final report so the operator can re-run the failed companies later.
|
|
93
|
+
|
|
94
|
+
**Inter-wave progress line.** After each wave (5–10 subagents):
|
|
95
|
+
```
|
|
96
|
+
[PROGRESS] Phase A: {done}/{total} companies researched, {failed} failed, elapsed {M}m, eta {N}m
|
|
97
|
+
```
|
|
98
|
+
Helps operators distinguish "still working" from "stuck."
|
|
99
|
+
|
|
100
|
+
---
|
|
101
|
+
|
|
102
|
+
## Step 5 — Phase B: prospect research
|
|
103
|
+
|
|
104
|
+
**Concurrency cap: 5 parallel** (lowered from Phase A's 10 because Phase B subagents call `fetch_linkedin_data` and the Unipile rate limiter throws over cap — see `backend/app/domains/channels/services/unipile/rate_limiter.py:36`. Lower concurrency keeps us under the per-account window.)
|
|
105
|
+
|
|
106
|
+
Dispatch one `vruum-prospect-deep-researcher` per surviving candidate. Subagent file at `.claude/agents/vruum-prospect-deep-researcher.md`.
|
|
107
|
+
|
|
108
|
+
Dispatch prompt template:
|
|
109
|
+
|
|
110
|
+
```
|
|
111
|
+
You are vruum-prospect-deep-researcher. Research this prospect against segment "{segment_name}".
|
|
112
|
+
|
|
113
|
+
full_name: {name}
|
|
114
|
+
first_name: {first_name or null}
|
|
115
|
+
last_name: {last_name or null}
|
|
116
|
+
company: {company}
|
|
117
|
+
linkedin_url: {url or null}
|
|
118
|
+
email: {email or null}
|
|
119
|
+
|
|
120
|
+
phase_a_signals:
|
|
121
|
+
acv_class: {smb|mid|ent or null if Phase A failed}
|
|
122
|
+
outbound_motion_score: {0|1|2 or null}
|
|
123
|
+
triggers: [list or null]
|
|
124
|
+
|
|
125
|
+
segment_icp_summary: {one paragraph from get_research_playbook}
|
|
126
|
+
acv_floor: {dollars}
|
|
127
|
+
|
|
128
|
+
Run your workflow (a–k) and return the structured output block. Note: do NOT call save_discovered_person or start_outreach — those are orchestrator-only and not in your tools list.
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
Each subagent returns: `topics_of_interest`, `recent_posts`, `opening_hooks[]` (2–3, with source URLs), `decision_maker_level` (junior/mid/senior), `email_status` (found/pending), `role_start_date`, per-prospect `triggers[]`, `STATUS`. Note: `person_id` is NOT returned here — identity resolution happens in Step 7.
|
|
132
|
+
|
|
133
|
+
**Inter-wave progress line:**
|
|
134
|
+
```
|
|
135
|
+
[PROGRESS] Phase B: {done}/{total} prospects researched, {dismissed_for_linkedin_unavailable} skipped, elapsed {M}m, eta {N}m
|
|
136
|
+
```
|
|
137
|
+
|
|
138
|
+
---
|
|
139
|
+
|
|
140
|
+
## Step 6 — Harness pre-filter gate (orchestrator-side, pre-save)
|
|
141
|
+
|
|
142
|
+
This is a **coarse pre-filter** — its job is to avoid wasted backend `save_discovered_person` calls on obvious dismisses. The **authoritative** gate is server-side `MatchAnalysisAgent.match_score >= 70` and runs inside `save_discovered_person`. The harness gate cannot override the backend gate; it can only dismiss before reaching it.
|
|
143
|
+
|
|
144
|
+
Per surviving prospect, evaluate four criteria using the segment's playbook ICP and the Phase A + Phase B signals:
|
|
145
|
+
|
|
146
|
+
### 1. ACV class meets segment threshold?
|
|
147
|
+
- `acv_class >= acv_floor_class` → pass this criterion (smb=$5K, mid=$5–50K, ent=$50K+; segment's `acv_floor` from playbook maps to a class)
|
|
148
|
+
- If no → dismiss `acv_too_low`. Don't call `save_discovered_person`.
|
|
149
|
+
|
|
150
|
+
### 2. Outbound motion or hiring signal?
|
|
151
|
+
- `outbound_motion_score > 0` OR explicit hiring trigger present → pass
|
|
152
|
+
- If no → flag `warming_candidate` (still call `save_discovered_person` — operator may want to warm-track them; backend match analysis tells us if the segment fit is real)
|
|
153
|
+
|
|
154
|
+
### 3. Decision-maker level senior?
|
|
155
|
+
- `decision_maker_level == senior` → pass
|
|
156
|
+
- If `mid` → pass with a note (segment owner decides if mid is acceptable)
|
|
157
|
+
- If `junior` → look for a more-senior person at the same `company_id` in the Phase B output set. If found, swap and rerun. If not, dismiss `decision_maker_junior`.
|
|
158
|
+
|
|
159
|
+
### 4. Trigger event in last 90d?
|
|
160
|
+
- 1+ trigger from Phase A (`funding`, `exec_hire`, `launch`, `m_and_a`, `partnership`) OR Phase B (`new_role`, `topical_post`, `press_mention`, `promotion`) → pass
|
|
161
|
+
- If no → flag `low_priority` (still call `save_discovered_person`)
|
|
162
|
+
|
|
163
|
+
### Tag each prospect:
|
|
164
|
+
- `harness_gate_status: pass` — all four criteria passed
|
|
165
|
+
- `harness_gate_status: warming` — failed criterion 2 (no outbound motion)
|
|
166
|
+
- `harness_gate_status: low_priority` — failed criterion 4 (no recent trigger)
|
|
167
|
+
- `harness_gate_status: gate_inconclusive` — Phase A failed for this prospect's company (degraded mode)
|
|
168
|
+
- `harness_gate_status: dismiss` — failed criterion 1 (acv) or 3 (junior, no senior swap available); skip backend call entirely
|
|
169
|
+
|
|
170
|
+
For non-dismiss outcomes, also set `dismiss_reason` to null and `flag` to the relevant reason (warming|low_priority|gate_inconclusive|null).
|
|
171
|
+
|
|
172
|
+
**The gate is declarative prose — not a hardcoded function.** The orchestrator follows the rules above and tags each candidate. If a future criterion changes, edit this section.
|
|
173
|
+
|
|
174
|
+
---
|
|
175
|
+
|
|
176
|
+
## Step 7 — Save chain (everyone except harness-gate dismisses)
|
|
177
|
+
|
|
178
|
+
Per surviving prospect:
|
|
179
|
+
|
|
180
|
+
### a. Save company research (once per company)
|
|
181
|
+
If the prospect's company isn't already cached and Phase A produced fresh research, call `save_company_research(company_name=..., domain=..., funding_data=..., growth_metrics=..., current_priorities=...)`. Skip if `CACHE_HIT: true` for that company.
|
|
182
|
+
|
|
183
|
+
### b. Identity resolution + person research (load-bearing — corrects Codex Finding #6)
|
|
184
|
+
|
|
185
|
+
`save_person_research` requires `first_name` + `last_name`, NOT `name`. `save_discovered_person` requires `person_id` from a prior save step. So Step 7 is a 2-step backend dance:
|
|
186
|
+
|
|
187
|
+
1. **Split full_name** if `first_name`/`last_name` aren't already set:
|
|
188
|
+
- Last-space heuristic: split on the last space. `Jane Smith` → first=`Jane`, last=`Smith`. `Jane van der Merwe` → first=`Jane`, last=`van der Merwe`.
|
|
189
|
+
- **Override with Phase B canonical names** if `fetch_linkedin_data` returned them. LinkedIn's `first_name`/`last_name` fields are authoritative; the heuristic is a fallback for candidates without `linkedin_url`.
|
|
190
|
+
|
|
191
|
+
2. **Call `save_person_research(first_name=..., last_name=..., company=..., linkedin_url=..., email=..., topics_of_interest=..., recent_posts=..., opening_hooks=..., role_start_date=...)`.**
|
|
192
|
+
- If the prospect already had `person_id` set on the candidate (e.g. operator pasted a Vruum person UUID), pass it explicitly: `save_person_research(person_id=..., ...)` — backend updates rather than creating a new record.
|
|
193
|
+
- The response includes the `person_id`. Capture it for step c.
|
|
194
|
+
|
|
195
|
+
### c. Save discovered person (the backend authoritative gate runs here)
|
|
196
|
+
|
|
197
|
+
Call `save_discovered_person(person_id=<from b>, segment_id=...)`. This:
|
|
198
|
+
- Runs server-side `analyze_person_match` + signal eval
|
|
199
|
+
- Returns `match_score` (0–100) and `quality_gate_pass` (bool, true iff `match_score >= 70`)
|
|
200
|
+
- Writes the `company_people` row that puts the prospect into the segment
|
|
201
|
+
|
|
202
|
+
**Distinguish two failure modes (Codex Finding #9):**
|
|
203
|
+
- **Request failure (5xx, timeout, network):** retry once with 2s backoff. If still failing, leave the prospect in `discovery_failed` status and surface in the final report. **Don't** claim "saved as gate-fail" — the row was never written.
|
|
204
|
+
- **Request success + low score (`quality_gate_pass: false`):** the prospect IS saved with research; backend marks gate-fail; surface for operator review. This is a soft-fail. The prospect is on file with full research, useful for future segments.
|
|
205
|
+
|
|
206
|
+
### d. Bulk enrollment (only after all prospects saved)
|
|
207
|
+
|
|
208
|
+
Collect all `person_id`s where `harness_gate_status == pass` AND backend `quality_gate_pass == true` AND `mode == save-and-enroll`. Then call `bulk_start_outreach(person_ids=[...], segment_id=...)` ONCE at the end of Step 7.
|
|
209
|
+
|
|
210
|
+
- Per-prospect outcomes are returned (enrolled | skipped | failed). Surface per-prospect failures in the report.
|
|
211
|
+
- If `harness_gate_status` is `warming` or `low_priority`, exclude from the bulk enroll list. Operator decides on review.
|
|
212
|
+
- If `mode != save-and-enroll`, skip enrollment entirely; operator handles via `/outreach-triage` later.
|
|
213
|
+
|
|
214
|
+
---
|
|
215
|
+
|
|
216
|
+
## Step 8 — Aggregate report (chat + audit log)
|
|
217
|
+
|
|
218
|
+
Print to chat AND write to `.context/runs/pipeline-fill-{ISO-timestamp}.md` (workspace-local; `.context/` is gitignored per CLAUDE.md). Format identical for both surfaces.
|
|
219
|
+
|
|
220
|
+
```
|
|
221
|
+
Pipeline fill complete: {segment_name} (source: {source}, mode: {harness|platform})
|
|
222
|
+
|
|
223
|
+
Candidates flow:
|
|
224
|
+
source : {N from source skill output}
|
|
225
|
+
pre-flight : {after dedup, after company-cache hit}
|
|
226
|
+
phase A : {company subagents fired} ({cached_skip} skipped via cache)
|
|
227
|
+
phase B : {prospect subagents fired} ({linkedin_unavailable} dismissed)
|
|
228
|
+
|
|
229
|
+
Harness pre-filter gate:
|
|
230
|
+
pass : {N}
|
|
231
|
+
warming : {N}
|
|
232
|
+
low_priority : {N}
|
|
233
|
+
gate_inconclusive : {N}
|
|
234
|
+
dismiss : {N} (top reasons: acv_too_low={N}, decision_maker_junior={N})
|
|
235
|
+
|
|
236
|
+
Backend authoritative gate (match_score >= 70):
|
|
237
|
+
passed : {N}
|
|
238
|
+
failed : {N} (saved with research; operator can review via /enrich-prospect)
|
|
239
|
+
request_failed : {N} (retry candidates — surface in next run)
|
|
240
|
+
|
|
241
|
+
Enrolled (both gates pass + auto-enroll mode): {N}
|
|
242
|
+
Saved but not enrolled: {N}
|
|
243
|
+
|
|
244
|
+
Triggers detected (top 5):
|
|
245
|
+
- "raised $5M Series A" — {N} prospects
|
|
246
|
+
- "hired Head of Sales" — {N} prospects
|
|
247
|
+
...
|
|
248
|
+
|
|
249
|
+
Pool status: healthy | drying up | exhausted ⚠️
|
|
250
|
+
|
|
251
|
+
Audit log written: .context/runs/pipeline-fill-{timestamp}.md
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
For multi-segment runs, group the report by segment and include a totals summary at the bottom.
|
|
255
|
+
|
|
256
|
+
---
|
|
257
|
+
|
|
258
|
+
## Edge cases + failure handling reference
|
|
259
|
+
|
|
260
|
+
- **Source returns empty after dedup** — orchestrator says "All {N} candidates already in pipeline, nothing to research" and exits cleanly.
|
|
261
|
+
- **Mid-flight cancellation** (operator Ctrl+C between Phase A and Phase B) — Phase A research is saved server-side. Re-running `/pipeline-fill` for the same segment + source picks up via batch dedup; no re-research of cached companies. Note this in the cancellation message.
|
|
262
|
+
- **Subagent timeout cascade** — Phase A failed for a company → Phase B runs degraded → harness gate marks `gate_inconclusive` → backend decides via cached company research. See Step 4.
|
|
263
|
+
- **Two-gate disagreement** — harness pass + backend fail (or vice versa) → see Step 7c. Stricter outcome wins for enrollment; both states surfaced in the report.
|
|
264
|
+
- **Cached company research >90 days old** — Phase A re-runs the company subagent. Don't trust stale signals for an active fill.
|
|
265
|
+
- **Manual-list cap** — if >100 lines pasted, orchestrator asks "{N} prospects pasted — process all, or first M? (a/N)".
|
|
266
|
+
- **CSV >200 rows** — same prompt at Step 5 of csv-pipeline-fill.
|
|
267
|
+
- **LinkedIn rate-limit (Unipile 429)** — Phase B subagent dismisses with `linkedin_data_unavailable`; orchestrator surfaces in report; operator reruns later.
|
|
268
|
+
- **Unicode multi-token surnames** — `Maria Del Carmen Garcia`: heuristic splits to first=`Maria Del Carmen`, last=`Garcia` (last space wins). When `linkedin_url` is present, `fetch_linkedin_data` overrides with canonical names. Imperfect for candidates without LinkedIn URL — operator can edit via `update_person_contact` post-import.
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Canonical Handoff Prompt (for source skills)
|
|
273
|
+
|
|
274
|
+
When a HARNESS source skill completes its sourcing flow and has a candidate list ready, it ends with **this exact confirmation gate** (do not paraphrase — predictable behavior matters):
|
|
275
|
+
|
|
276
|
+
```
|
|
277
|
+
Candidate list ready: {N} prospects from {source}.
|
|
278
|
+
|
|
279
|
+
NEXT: invoke /pipeline-fill Step 3 onward (deep research → harness gate → save) with this list and segment {segment_id}.
|
|
280
|
+
|
|
281
|
+
Continue automatically? (y/n)
|
|
282
|
+
```
|
|
283
|
+
|
|
284
|
+
- Operator answers `y` → orchestrator continues into Step 3 (this engine's flow).
|
|
285
|
+
- Operator answers `n` → source skill exits cleanly with the candidate list printed in chat for inspection. The list can be passed back later by re-running `/pipeline-fill` with the list pasted.
|
|
286
|
+
- Operator answers anything else → repeat the prompt; treat ambiguous responses as "ask again," not as silent default.
|
|
287
|
+
|
|
288
|
+
This pattern eliminates the implicit "now follow Step 3" hand-off that would otherwise depend on attention drift. Source skills that don't end with this exact prompt are the source of "the skill stopped halfway" bug reports.
|
|
@@ -1,9 +1,11 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: pipeline-fill
|
|
3
3
|
description: >-
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
4
|
+
Source-agnostic pipeline orchestrator. Picks a source per segment (Sales Nav /
|
|
5
|
+
YC / CSV / discovery), runs harness deep research, applies a pre-filter gate,
|
|
6
|
+
then saves into the segment via the backend authoritative match_score>=70
|
|
7
|
+
gate. Use when: fill pipeline, import prospects, daily imports, need more
|
|
8
|
+
prospects, discover prospects from scratch, deep research before import.
|
|
7
9
|
---
|
|
8
10
|
|
|
9
11
|
## Auto-update check
|
|
@@ -17,116 +19,146 @@ Never block skill execution on this check.
|
|
|
17
19
|
|
|
18
20
|
# Pipeline Fill
|
|
19
21
|
|
|
20
|
-
You are a pipeline
|
|
22
|
+
You are a source-agnostic pipeline filler. You pick segments to fill, pick a source per segment (Sales Nav / YC / CSV / discovery), and orchestrate harness deep research that gates against the segment ICP before saving prospects into the backend pipeline.
|
|
21
23
|
|
|
22
24
|
## Why this skill exists
|
|
23
25
|
|
|
24
|
-
Filling your pipeline
|
|
26
|
+
Filling your pipeline by source-of-the-day is normal. Sales Nav drying up doesn't mean you're stuck — pick YC, paste a CSV, or run discovery (paste candidates OR describe an ICP and the harness sources them via WebSearch + Vruum MCP + LinkedIn search). This skill orchestrates deep research per prospect in your IDE (your compute), pre-filters against segment ICP, then saves the qualified ones into the segment via the backend's canonical gate.
|
|
27
|
+
|
|
28
|
+
## Where the heavy logic lives
|
|
29
|
+
|
|
30
|
+
Steps 3–8 (pre-flight, Phase A research, Phase B research, harness gate, save chain, audit-log report) are defined in `RESEARCH-ENGINE.md` (in this same skill directory). This skill owns:
|
|
31
|
+
- Step 1: segment picker (with ETA)
|
|
32
|
+
- Step 2: source picker (conditional PLATFORM block + always-visible HARNESS block)
|
|
33
|
+
- The discovery-mode handler (paste candidates inline OR describe an ICP and source via harness tools)
|
|
34
|
+
- The multi-segment grammar
|
|
35
|
+
|
|
36
|
+
When you reach Step 3, **stop and read** `RESEARCH-ENGINE.md`. That doc is the canonical source for the candidate-list shape, the harness gate criteria, the identity-resolution save chain, and the canonical handoff prompt that source skills use.
|
|
25
37
|
|
|
26
38
|
## Subagent architecture
|
|
27
39
|
|
|
28
|
-
This skill uses
|
|
29
|
-
-
|
|
30
|
-
-
|
|
40
|
+
This skill uses two subagents (defined in `.claude/agents/`):
|
|
41
|
+
- `vruum-company-deep-researcher` — Phase A, one per unique company in the batch (max 10 in parallel)
|
|
42
|
+
- `vruum-prospect-deep-researcher` — Phase B, one per person (max 5 in parallel — Phase B is rate-limited because it calls `fetch_linkedin_data`)
|
|
31
43
|
|
|
32
|
-
### MCP access requirements
|
|
44
|
+
### MCP access requirements (load-bearing)
|
|
33
45
|
|
|
34
|
-
Subagents can only access Vruum MCP if `vruum` is configured as a user-scoped HTTP MCP server in `~/.claude.json`. Cloud MCP servers (`claude.ai Vruum`) are NOT inherited by subagents.
|
|
46
|
+
Subagents can only access Vruum MCP if `vruum` is configured as a **user-scoped HTTP MCP server** in `~/.claude.json`. Cloud MCP servers (`claude.ai Vruum`) are NOT inherited by subagents.
|
|
35
47
|
|
|
36
48
|
To set up: `claude mcp add --transport http --scope user vruum https://api.vruum.ai/mcp` (one-time, requires OAuth).
|
|
37
49
|
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
1. **Agent tool** (primary): Spawn subagents via the Agent tool. Subagents inherit local MCP servers from `~/.claude.json` and can call Vruum tools directly. Use `run_in_background=true` for parallelism.
|
|
50
|
+
The orchestrator's MCP precheck at the top of Step 3 (`get_research_playbook` call) catches this misconfiguration upfront with a clear error message. Don't skip it.
|
|
41
51
|
|
|
42
|
-
|
|
52
|
+
### Dispatch methods (in order of preference)
|
|
43
53
|
|
|
44
|
-
|
|
54
|
+
1. **Agent tool** (primary): Spawn subagents via the Agent tool with `subagent_type: vruum-company-deep-researcher` or `vruum-prospect-deep-researcher`. Subagents inherit local MCP servers from `~/.claude.json` and call Vruum tools directly. Use `run_in_background=true` for waves; wait for the wave to complete before dispatching the next.
|
|
45
55
|
|
|
46
|
-
|
|
56
|
+
2. **CLI dispatch**: `claude --agent vruum-{company,prospect}-deep-researcher --allowedTools "mcp__vruum-local__*" -p "<prompt>" --output-format text --max-turns 20`. Use only if Agent tool subagents can't reach MCP.
|
|
47
57
|
|
|
48
|
-
|
|
49
|
-
1. How many people are already in "new" stage (queued but not yet contacted)?
|
|
50
|
-
2. What is the daily connection limit for the segment?
|
|
51
|
-
3. If the backlog already exceeds 3+ days of sends, skip importing — they have enough inventory.
|
|
58
|
+
3. **Inline**: For very small fills (1–3 prospects) where dispatching subagents is overhead, run the research inline in the main session.
|
|
52
59
|
|
|
53
|
-
|
|
60
|
+
## Inputs
|
|
54
61
|
|
|
55
|
-
|
|
62
|
+
- `prospect_list` (optional): pre-built candidate list matching the canonical shape in `RESEARCH-ENGINE.md`. If provided, skip the source-picker step and go straight to Step 3 (pre-flight). This is how source skills hand off.
|
|
63
|
+
- `segment(s)`: target segment(s); multi-segment supported.
|
|
64
|
+
- `mode`: `research-only` | `save` | `save-and-enroll` (default: `save-and-enroll`).
|
|
65
|
+
- `gate_threshold`: minimum backend `match_score` to enroll (default: segment's existing quality_gate).
|
|
56
66
|
|
|
57
|
-
|
|
67
|
+
## Workflow — Step 1: Show pipeline status & pick segments
|
|
58
68
|
|
|
59
|
-
Call `manage_sales_nav_searches
|
|
69
|
+
Call `manage_sales_nav_searches(action="list")` + `get_outreach_stats` for queue depth + `get_segments` for non-Sales-Nav segments. Present a numbered table with **per-segment ETA**:
|
|
60
70
|
|
|
61
71
|
```
|
|
62
72
|
Pipeline status:
|
|
63
73
|
|
|
64
|
-
1. DFW CFOs
|
|
65
|
-
2. Austin VPs
|
|
66
|
-
3. Houston CTOs
|
|
67
|
-
4. NYC Partners
|
|
74
|
+
1. DFW CFOs — 12/30 (18 needed) — harness ETA: ~16m
|
|
75
|
+
2. Austin VPs — 28/30 (2 needed) — harness ETA: ~3m
|
|
76
|
+
3. Houston CTOs — 0/20 (20 needed) — harness ETA: ~18m
|
|
77
|
+
4. NYC Partners — 40/40 ✓
|
|
68
78
|
|
|
69
|
-
Which segments to fill? (all / 1,3
|
|
79
|
+
Which segments to fill? (all / 1,3 / skip 2)
|
|
80
|
+
Total if all needing fill: ~37m sequential.
|
|
70
81
|
```
|
|
71
82
|
|
|
83
|
+
ETA estimates: ~2s for batch Step 3 dedup + ~30s/wave Phase A + ~60s/wave Phase B (5-parallel cap on Phase B). Multi-segment ETAs are sequential.
|
|
84
|
+
|
|
72
85
|
**Table rules:**
|
|
73
86
|
- One row per segment, numbered sequentially
|
|
74
87
|
- Show current/target counts and how many are needed
|
|
75
88
|
- Flag searches that are drying up (⚠️) or accounts near capacity
|
|
76
89
|
- Mark segments already at target with ✓ and don't number them
|
|
90
|
+
- Show per-segment ETA so operator can budget time
|
|
91
|
+
|
|
92
|
+
**Wait for the user's response.** Parse: "all", "1, 3", "skip 2", "just the CFO ones", etc. Only proceed with the selected segments.
|
|
77
93
|
|
|
78
|
-
|
|
79
|
-
- "all" — fill every segment that needs it
|
|
80
|
-
- Segment numbers: "1, 3, 4" or "1 and 4"
|
|
81
|
-
- Exclusions: "skip 2"
|
|
82
|
-
- Natural language: "just the CFO ones", "skip anything under 10 needed"
|
|
94
|
+
## Workflow — Step 2: Pick source per segment (only if `prospect_list` not provided)
|
|
83
95
|
|
|
84
|
-
|
|
96
|
+
Per selected segment, prompt:
|
|
85
97
|
|
|
86
|
-
|
|
98
|
+
In **public mode** (the package builder strips the PLATFORM block from this skill before publishing), the picker shows only HARNESS modes, renumbered 1–4:
|
|
87
99
|
|
|
88
|
-
|
|
100
|
+
```
|
|
101
|
+
Source for {segment_name}?
|
|
102
|
+
HARNESS mode (your compute, in-chat deep research, visible & interruptible):
|
|
103
|
+
1. sales-nav-deep — Sales Nav profiles + harness deep research
|
|
104
|
+
2. yc — scrape YC directory with filters you provide
|
|
105
|
+
3. csv — read a CSV file (path next), harness deep research
|
|
106
|
+
4. discovery — paste candidates inline OR describe an ICP and I'll source them via WebSearch + Vruum MCP + LinkedIn search
|
|
107
|
+
```
|
|
89
108
|
|
|
90
|
-
|
|
109
|
+
The conditional rendering happens at package-build time, not at skill-runtime — when the orchestrator runs in operator mode it sees the 6-option block; when it runs in public mode (stripped package) it sees only the 4-option block. Source-skill dispatch logic below uses option labels (`sales-nav-platform`, `yc`, etc.), not numbers, so the renumbering is cosmetic.
|
|
91
110
|
|
|
92
|
-
|
|
93
|
-
- Search name or ID
|
|
94
|
-
- Segment ID or name
|
|
95
|
-
- Number of profiles to import
|
|
96
|
-
- Instructions to call `preview_sales_nav_search`, evaluate profiles against ICP, dismiss poor fits, and import the good ones with `auto_enroll=true`
|
|
111
|
+
Per source pick, dispatch:
|
|
97
112
|
|
|
98
|
-
**
|
|
113
|
+
- `sales-nav-platform` → invoke `/sales-nav-platform-fill` (calls `import_from_sales_nav`; backend handles everything; **skip Steps 3-8 of this skill entirely** — backend agents own the rest).
|
|
114
|
+
- `csv-platform` → invoke `/csv-platform-fill` (calls `start_csv_import`; backend handles everything; same — skip Steps 3-8).
|
|
115
|
+
- `sales-nav-deep` → invoke `/sales-nav-deep-fill` to produce a candidate list, then continue to Step 3 with it.
|
|
116
|
+
- `yc` → invoke `/yc-pipeline-fill` to produce a candidate list, then continue to Step 3 with it.
|
|
117
|
+
- `csv` → invoke `/csv-pipeline-fill` to produce a candidate list, then continue to Step 3 with it.
|
|
118
|
+
- `discovery` → use the discovery-mode handler below to produce a candidate list (handler branches: paste-shaped input → parse, prose ICP brief → harness sources via WebSearch + Vruum MCP + LinkedIn search), then continue to Step 3 with it.
|
|
99
119
|
|
|
100
|
-
|
|
120
|
+
**Multi-segment behavior:** segments run sequentially. Segment 1's Step 7 (save chain + bulk enroll) completes before segment 2's Step 3 starts. Predictable rate-limit behavior, simple progress narrative. Trade-off: 3-segment fills are ~37min wall-clock vs ~22min if Phase A/B were overlapped across segments. Cross-segment overlap is a v2.
|
|
101
121
|
|
|
102
|
-
|
|
122
|
+
## Discovery-mode handler (for `discovery` source)
|
|
103
123
|
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
124
|
+
Discovery mode covers two paths off the same prompt:
|
|
125
|
+
|
|
126
|
+
**Path A — operator pastes candidates** (you already know who you want)
|
|
127
|
+
Tolerant line parser, candidates produced directly:
|
|
128
|
+
|
|
129
|
+
- **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 `fetch_linkedin_data`.
|
|
130
|
+
- **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.
|
|
131
|
+
- **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.
|
|
132
|
+
|
|
133
|
+
Drop blank lines and lines starting with `#` (treat as comments).
|
|
134
|
+
|
|
135
|
+
**Cap at 100 lines** by default. Above that, ask: "{N} prospects pasted — process all, or first M? (a/N)". Keeps operators from accidentally kicking off a 1,000-prospect harness fill.
|
|
107
136
|
|
|
108
|
-
|
|
137
|
+
**Path B — operator describes an ICP** (you want the harness to discover candidates)
|
|
138
|
+
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:
|
|
109
139
|
|
|
110
|
-
|
|
140
|
+
1. **Anchor on segment ICP** — read the segment's existing ICP/company profile (via `get_segment` and `get_company_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.
|
|
141
|
+
2. **Source companies first** — use harness tools to find candidate companies matching the brief:
|
|
142
|
+
- `WebSearch` for funding announcements, news, lists ("Series A SaaS 2026", "TechCrunch Series B SaaS announcements")
|
|
143
|
+
- `WebFetch` on Crunchbase / PitchBook / company directories
|
|
144
|
+
- `mcp__vruum-local__search_linkedin_people` for company-fitting roles when the brief is people-shaped (e.g. "VPs of Eng at Series A SaaS")
|
|
145
|
+
3. **Source people from each company** — for each candidate company, use `mcp__vruum-local__find_people_at_company` (Unipile-backed; respects LinkedIn rate limits) to find titles matching the segment ICP. Cap at ~5 people per company to spread the discovery surface.
|
|
146
|
+
4. **Dedup against existing pipeline** — for each discovered person, check `mcp__vruum-local__search_existing_people` so you don't research someone the segment already has.
|
|
147
|
+
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.
|
|
111
148
|
|
|
112
|
-
|
|
149
|
+
Discovery-path candidates produced in either path use the canonical shape in `RESEARCH-ENGINE.md` and feed into Step 3 the same way.
|
|
113
150
|
|
|
114
|
-
"
|
|
115
|
-
- DFW CFOs: Previewed 45 profiles, approved 32, dismissed 13. Imported 18 new.
|
|
116
|
-
- Austin VPs: 2 more imported.
|
|
117
|
-
- Houston CTOs: Search drying up — returned only 7 profiles. Consider broadening the search or creating a new one.
|
|
118
|
-
Total: 27 imported, 13 dismissed, ~$3.40 saved by pre-filtering."
|
|
151
|
+
**Path detection:** if the first non-comment line looks like a URL or has commas (paste-shaped), use Path A. If it's prose without URLs/commas and >40 chars, use Path B. If ambiguous, ask: "paste, or describe the ICP and I discover?"
|
|
119
152
|
|
|
120
|
-
##
|
|
153
|
+
## Workflow — Steps 3 onward
|
|
121
154
|
|
|
122
|
-
|
|
155
|
+
**Switch to `RESEARCH-ENGINE.md` here.** Read that doc and follow Step 3 (pre-flight) → Step 4 (Phase A) → Step 5 (Phase B) → Step 6 (harness gate) → Step 7 (save chain with identity resolution) → Step 8 (aggregate report + audit log to `.context/runs/`).
|
|
123
156
|
|
|
124
|
-
|
|
157
|
+
Do not duplicate the engine logic in this skill — link operators back to the engine doc when they ask "what does the gate check?" or "how does the save chain work?"
|
|
125
158
|
|
|
126
159
|
## Notes
|
|
127
160
|
|
|
128
|
-
-
|
|
129
|
-
-
|
|
130
|
-
-
|
|
131
|
-
-
|
|
132
|
-
- If subagent MCP connections fail, fall back to inline: preview in the main session, filter manually, import directly. This typically means `vruum` isn't in `~/.claude.json`.
|
|
161
|
+
- **Composability** with source skills: source skills produce candidate lists; this orchestrator runs the research engine. Both directions allowed (operator can run a source skill standalone or run /pipeline-fill as the front door).
|
|
162
|
+
- **Real money costs** are in Phase B (LinkedIn API + Hunter calls + OpenAI tokens for the prospect subagent). Phase A is mostly WebFetch/WebSearch which is operator-network. The batch primitives in Step 3 keep dedup latency low (~2s vs 12s pre-batch).
|
|
163
|
+
- **Harness offload framing**: deep research runs in your IDE (your tokens). The backend `MatchAnalysisAgent` runs the canonical gate (~$0.02/prospect on Vruum's bill). This split is intentional — see memory `project_harness_offload_strategy.md`.
|
|
164
|
+
- **Audit trail**: every run writes to `.context/runs/pipeline-fill-{ISO-timestamp}.md`. Useful weeks later for "what did the YC fill on Apr 12 import?"
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: sales-nav-deep-fill
|
|
3
|
+
description: >-
|
|
4
|
+
Sales Nav harness source for /pipeline-fill. Pre-filters Sales Nav profiles
|
|
5
|
+
via vruum-pipeline-filter, produces a candidate list, hands off to
|
|
6
|
+
/pipeline-fill for deep research and import. Use when: sales nav with deep
|
|
7
|
+
research, sales nav harness mode, in-chat sales nav.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Auto-update check
|
|
11
|
+
|
|
12
|
+
Before starting, run `~/.vruum/bin/vruum-skills-update-check` (path relative to this repo). Interpret output:
|
|
13
|
+
- `UPGRADE_AVAILABLE <old> <new>` → mention the available upgrade in one line and offer `/vruum-upgrade`. Then continue.
|
|
14
|
+
- `JUST_UPGRADED <old> <new>` → acknowledge in one line, then continue.
|
|
15
|
+
- Empty → proceed silently.
|
|
16
|
+
|
|
17
|
+
Never block skill execution on this check.
|
|
18
|
+
|
|
19
|
+
# Sales Nav Deep Fill (harness source)
|
|
20
|
+
|
|
21
|
+
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.
|
|
22
|
+
|
|
23
|
+
**This is the harness counterpart to `/sales-nav-platform-fill`.** The platform skill calls `import_from_sales_nav` 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 `save_discovered_person` is called only after the harness pre-filter gate passes. Pick this when you want visibility into the deep research as it happens.
|
|
24
|
+
|
|
25
|
+
## Inputs
|
|
26
|
+
|
|
27
|
+
- `segment`: target segment (single — multi-segment lives in `/pipeline-fill`)
|
|
28
|
+
- `count`: how many profiles to pre-filter (default 30)
|
|
29
|
+
|
|
30
|
+
## Workflow
|
|
31
|
+
|
|
32
|
+
### Step 1: Preview Sales Nav profiles
|
|
33
|
+
|
|
34
|
+
Call `preview_sales_nav_search(segment=..., count=...)`. Returns profile data + segment ICP context (target_titles, target_industries, value_proposition, positioning_angle, differentiators).
|
|
35
|
+
|
|
36
|
+
### Step 2: Dispatch `vruum-pipeline-filter` for ICP pre-filter
|
|
37
|
+
|
|
38
|
+
Spawn the existing `vruum-pipeline-filter` subagent (defined at `.claude/agents/vruum-pipeline-filter.md`). It evaluates each profile against the segment ICP using cheap title/company matching — no LinkedIn API calls, no deep research. APPROVE / DISMISS per profile.
|
|
39
|
+
|
|
40
|
+
This is the cheap pre-filter — it removes obvious mismatches (titled-wrong, industry-wrong) before they enter Phase A/B deep research. Saves ~8 minutes per fill on a 30-profile preview that has 10 mismatches.
|
|
41
|
+
|
|
42
|
+
### Step 3: Build candidate list
|
|
43
|
+
|
|
44
|
+
Take the APPROVED profiles from Step 2 and convert them to the canonical candidate-list shape. From each profile, capture:
|
|
45
|
+
- `full_name` (from profile name)
|
|
46
|
+
- `company` (from profile current company)
|
|
47
|
+
- `linkedin_url` (canonicalize via the LinkedIn URL in the profile)
|
|
48
|
+
- `title` (current title — useful for downstream classification but Phase B re-fetches authoritatively)
|
|
49
|
+
- `email`: null (Phase B finds it)
|
|
50
|
+
- `person_id`: null (resolved in Step 7 of engine flow)
|
|
51
|
+
- `raw_signals`: `{source: "sales-nav-deep", search_id: ..., preview_metadata: {...}}`
|
|
52
|
+
|
|
53
|
+
### Step 4: Hand off to /pipeline-fill (canonical handoff prompt)
|
|
54
|
+
|
|
55
|
+
Emit the canonical handoff prompt (defined in `pipeline-fill/RESEARCH-ENGINE.md` — the canonical handoff section):
|
|
56
|
+
|
|
57
|
+
```
|
|
58
|
+
Candidate list ready: {N} prospects from sales-nav-deep.
|
|
59
|
+
|
|
60
|
+
NEXT: invoke /pipeline-fill Step 3 onward (deep research → harness gate → save) with this list and segment {segment_id}.
|
|
61
|
+
|
|
62
|
+
Continue automatically? (y/n)
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
- Operator answers `y` → continue into the engine doc's Step 3.
|
|
66
|
+
- Operator answers `n` → exit cleanly with the candidate list visible in chat.
|
|
67
|
+
|
|
68
|
+
## Notes
|
|
69
|
+
|
|
70
|
+
- **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 (`save_discovered_person`'s `analyze_person_match` + `match_score >= 70` gate) still runs on Vruum, intentionally — it's the canonical gate.
|
|
71
|
+
- **Don't call `import_from_sales_nav` 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.
|
|
72
|
+
- **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.
|
|
@@ -85,7 +85,7 @@ Root causes (ranked):
|
|
|
85
85
|
→ Recommendation: Shift channel mix to prioritize LinkedIn
|
|
86
86
|
|
|
87
87
|
4. **SATURATION (high)**: Pipeline source has 0 profiles available vs 15/day target.
|
|
88
|
-
→ Recommendation: Broaden your Sales Navigator saved search criteria, then run `/pipeline-fill` to add volume.
|
|
88
|
+
→ Recommendation: Broaden your Sales Navigator saved search criteria, then run `/pipeline-fill` to add volume. (`/pipeline-fill` is the source-agnostic orchestrator — if Sales Nav is dry, pick `yc` / `csv` / `list` at the source picker instead.)
|
|
89
89
|
|
|
90
90
|
Want me to apply any of these fixes?"
|
|
91
91
|
|
|
@@ -0,0 +1,184 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: yc-pipeline-fill
|
|
3
|
+
description: >-
|
|
4
|
+
YC harness source for /pipeline-fill. Scrapes YC's public Algolia index,
|
|
5
|
+
extracts founder LinkedIn URLs, dedups, hands a candidate list to
|
|
6
|
+
/pipeline-fill for deep research and import. Use when: YC pipeline fill,
|
|
7
|
+
source from YC, fill segment with YC founders, sales nav dried up, source YC.
|
|
8
|
+
---
|
|
9
|
+
|
|
10
|
+
## Auto-update check
|
|
11
|
+
|
|
12
|
+
Before starting, run `~/.vruum/bin/vruum-skills-update-check` (path relative to this repo). Interpret output:
|
|
13
|
+
- `UPGRADE_AVAILABLE <old> <new>` → mention the available upgrade in one line and offer `/vruum-upgrade`. Then continue.
|
|
14
|
+
- `JUST_UPGRADED <old> <new>` → acknowledge in one line, then continue.
|
|
15
|
+
- Empty → proceed silently.
|
|
16
|
+
|
|
17
|
+
Never block skill execution on this check.
|
|
18
|
+
|
|
19
|
+
# YC Pipeline Fill (harness source)
|
|
20
|
+
|
|
21
|
+
You are the YCombinator harness-mode source for `/pipeline-fill`. You scrape YC's public directory, extract founder LinkedIn URLs, dedup, and hand a candidate list to the orchestrator. No deep research, no harness gate, no save chain in this skill — that's all in `pipeline-fill/RESEARCH-ENGINE.md`.
|
|
22
|
+
|
|
23
|
+
This is the answer to "DFW CFOs Sales Nav is dead, what now?" — pivot to YC, get 30 founders into the segment in ~15 minutes.
|
|
24
|
+
|
|
25
|
+
## Inputs
|
|
26
|
+
|
|
27
|
+
- `segment`: target segment (single)
|
|
28
|
+
- `count`: target number of imports (default 50)
|
|
29
|
+
- `filters` (REQUIRED — no defaults; operator must specify at least one):
|
|
30
|
+
- `team_size_min` / `team_size_max` (proxy for funding stage)
|
|
31
|
+
- `launched_at_after` / `launched_at_before` (Unix ts; YC batch recency)
|
|
32
|
+
- `industries` (e.g. ["B2B"])
|
|
33
|
+
- `tags` (e.g. ["SaaS", "Fintech", "DevTools"])
|
|
34
|
+
- `regions` (e.g. ["United States"])
|
|
35
|
+
|
|
36
|
+
If the operator runs the skill without filters, prompt: "YC has 5K+ companies — give me at least one filter (e.g. 'last 18 months, team 11-50, B2B SaaS')." Don't apply silent defaults — defaults shape the funnel invisibly.
|
|
37
|
+
|
|
38
|
+
## Workflow
|
|
39
|
+
|
|
40
|
+
### Step 1: Load segment ICP
|
|
41
|
+
|
|
42
|
+
Call `get_research_playbook(segment_id=<id>)` to load ICP context. The downstream subagents need it for classification; capture it now to pass forward.
|
|
43
|
+
|
|
44
|
+
### Step 2: Algolia connectivity precheck
|
|
45
|
+
|
|
46
|
+
Issue a one-shot Algolia query against the index with `hitsPerPage: 1` to confirm extraction works. **Do NOT use `manage_sales_nav_searches(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).
|
|
47
|
+
|
|
48
|
+
The precheck happens implicitly in Step 3 (the first real Algolia call); if it fails there, abort with a clear error.
|
|
49
|
+
|
|
50
|
+
### Step 3: Fetch rotating Algolia API key
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
curl -sL https://www.ycombinator.com/companies > /tmp/yc-home.html
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
Extract the Algolia API key with regex `[A-Za-z0-9]{200,}` from the HTML — there's typically one match (a 256-char base64 string). The key is a scoped key with embedded YC restrictions:
|
|
57
|
+
- `analyticsTags=ycdc`
|
|
58
|
+
- `restrictIndices=YCCompany_production%2CYCCompany_By_Launch_Date_production`
|
|
59
|
+
- `tagFilters=%5B%22ycdc_public%22%5D`
|
|
60
|
+
|
|
61
|
+
Try the extracted key against the Algolia endpoint (Step 4). If multiple `[A-Za-z0-9]{200,}` matches exist, try each in order; the working one returns `hits`, others return `{"message":"Invalid API key"}` or 403.
|
|
62
|
+
|
|
63
|
+
**If all candidates fail**, abort with: "YC homepage structure changed — Algolia key extractor needs updating. Inspect `/tmp/yc-home.html` and adjust the regex." Don't silently fall back. The key rotates on every server-side render — don't cache across runs.
|
|
64
|
+
|
|
65
|
+
### Step 4: Query Algolia
|
|
66
|
+
|
|
67
|
+
```
|
|
68
|
+
POST https://45bwzj1sgc-dsn.algolia.net/1/indexes/YCCompany_By_Launch_Date_production/query
|
|
69
|
+
Headers:
|
|
70
|
+
x-algolia-api-key: <extracted key>
|
|
71
|
+
x-algolia-application-id: 45BWZJ1SGC
|
|
72
|
+
content-type: application/json
|
|
73
|
+
Body:
|
|
74
|
+
{
|
|
75
|
+
"query": "",
|
|
76
|
+
"hitsPerPage": <count * 3>,
|
|
77
|
+
"page": 0,
|
|
78
|
+
"facetFilters": [<derived from filters>],
|
|
79
|
+
"numericFilters": [<derived from filters>]
|
|
80
|
+
}
|
|
81
|
+
```
|
|
82
|
+
|
|
83
|
+
**Filter mapping:**
|
|
84
|
+
- `team_size_min/max` → numericFilters: `team_size>=N` / `team_size<=N`
|
|
85
|
+
- `launched_at_after/before` → numericFilters: `launched_at>=ts` / `launched_at<=ts`
|
|
86
|
+
- `industries` → facetFilters array: `[["industries:Fintech", "industries:SaaS"]]` (OR within one filter)
|
|
87
|
+
- `tags` → facetFilters: `[["tags:B2B"]]`
|
|
88
|
+
- `regions` → facetFilters: `[["regions:United States"]]`
|
|
89
|
+
|
|
90
|
+
Request `count * 3` hits to allow for dedup + research-stage drops. Capture per hit: `slug`, `name`, `website`, `team_size`, `batch`, `one_liner`, `long_description`, `tags`, `all_locations`.
|
|
91
|
+
|
|
92
|
+
### Step 5: Per-company page scrape (parallelizable, free)
|
|
93
|
+
|
|
94
|
+
For each candidate:
|
|
95
|
+
|
|
96
|
+
```
|
|
97
|
+
curl -sL https://www.ycombinator.com/companies/{slug}
|
|
98
|
+
```
|
|
99
|
+
|
|
100
|
+
The page is HTML-entity-encoded. Decode entities first:
|
|
101
|
+
|
|
102
|
+
```
|
|
103
|
+
curl -sL https://www.ycombinator.com/companies/{slug} | python3 -c "import html,sys;print(html.unescape(sys.stdin.read()))"
|
|
104
|
+
```
|
|
105
|
+
|
|
106
|
+
After decoding, regex out:
|
|
107
|
+
|
|
108
|
+
- **Founders array**: `"founders":\[(.*?)\]` — extract per-founder `"full_name":"([^"]+)"`, `"title":"([^"]*)"`, `"linkedin_url":"([^"]+)"`
|
|
109
|
+
- **News items**: `"newsItems":\[(.*?)\]` — extract per-item `"title":"..."`, `"url":"..."`, `"date":"..."`. Filter to last 12 months by parsing the date strings (format like "May 09, 2023"). Useful triggers for the orchestrator's harness gate.
|
|
110
|
+
|
|
111
|
+
**Pick primary founder:**
|
|
112
|
+
1. First founder where `title` matches `/CEO|Chief Exec|Co-?founder & CEO/i` (case-insensitive)
|
|
113
|
+
2. Else first founder where `linkedin_url` is non-empty
|
|
114
|
+
3. Else **drop the company entirely.** Do not call `find_person_linkedin` to guess — produces low-confidence matches and noise.
|
|
115
|
+
|
|
116
|
+
**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.
|
|
117
|
+
|
|
118
|
+
### Step 6: Build candidate list
|
|
119
|
+
|
|
120
|
+
Convert each surviving company into a candidate matching the canonical shape (defined in `pipeline-fill/RESEARCH-ENGINE.md`):
|
|
121
|
+
|
|
122
|
+
```
|
|
123
|
+
{
|
|
124
|
+
full_name: <founder full_name>,
|
|
125
|
+
first_name: null, // engine resolves in Step 7
|
|
126
|
+
last_name: null,
|
|
127
|
+
company: <yc company name>,
|
|
128
|
+
linkedin_url: <founder linkedin_url, canonicalized>,
|
|
129
|
+
email: null, // Phase B finds it
|
|
130
|
+
person_id: null, // engine resolves in Step 7
|
|
131
|
+
title: <founder title>,
|
|
132
|
+
raw_signals: {
|
|
133
|
+
source: "yc",
|
|
134
|
+
yc_slug: <slug>,
|
|
135
|
+
yc_batch: <batch>,
|
|
136
|
+
yc_team_size: <team_size>,
|
|
137
|
+
yc_tags: <tags>,
|
|
138
|
+
yc_locations: <all_locations>,
|
|
139
|
+
yc_one_liner: <one_liner>,
|
|
140
|
+
yc_news: <filtered news items, last 12mo, with title/url/date>
|
|
141
|
+
}
|
|
142
|
+
}
|
|
143
|
+
```
|
|
144
|
+
|
|
145
|
+
The `yc_news` entries are pre-loaded triggers — Phase A's company subagent uses them in lieu of an extra WebSearch.
|
|
146
|
+
|
|
147
|
+
### Step 7: Pool exhaustion check
|
|
148
|
+
|
|
149
|
+
If the Algolia query returned `< count` total hits even before dedup:
|
|
150
|
+
- < 5 hits left → flag: "YC pool for these filters is exhausted — broaden filters or fall back to Sales Nav."
|
|
151
|
+
- < 20% pass-rate after dedup + founder-filter → flag: "YC pool drying up — consider broadening filters next run."
|
|
152
|
+
|
|
153
|
+
Embed the pool status in the candidate-list metadata so the orchestrator's report can surface it.
|
|
154
|
+
|
|
155
|
+
### Step 8: Hand off to /pipeline-fill (canonical handoff prompt)
|
|
156
|
+
|
|
157
|
+
Emit the canonical handoff prompt (defined in `pipeline-fill/RESEARCH-ENGINE.md`):
|
|
158
|
+
|
|
159
|
+
```
|
|
160
|
+
Candidate list ready: {N} prospects from yc.
|
|
161
|
+
|
|
162
|
+
NEXT: invoke /pipeline-fill Step 3 onward (deep research → harness gate → save) with this list and segment {segment_id}.
|
|
163
|
+
|
|
164
|
+
Continue automatically? (y/n)
|
|
165
|
+
```
|
|
166
|
+
|
|
167
|
+
- Operator answers `y` → continue into the engine doc's Step 3.
|
|
168
|
+
- Operator answers `n` → exit cleanly with the candidate list visible in chat.
|
|
169
|
+
|
|
170
|
+
## Edge cases
|
|
171
|
+
|
|
172
|
+
- **Algolia key extraction fails** (no candidates work): abort, single-line error pointing at the homepage structure.
|
|
173
|
+
- **>30% scrape failure**: abort, point at the company-page structure.
|
|
174
|
+
- **No founder LinkedIn for a company**: drop the company; never guess via `find_person_linkedin`.
|
|
175
|
+
- **Pool exhaustion**: flag in candidate list metadata so the orchestrator's report surfaces it.
|
|
176
|
+
- **Operator gives no filters**: prompt for at least one. No silent defaults.
|
|
177
|
+
- **Operator gives `count` of 200+**: still works; expect ~30+ minute wall-clock for the full pipeline-fill flow.
|
|
178
|
+
|
|
179
|
+
## Notes
|
|
180
|
+
|
|
181
|
+
- **Sourcing phase is purely scrape-based** — no MCP/LinkedIn calls. Cheap to run repeatedly with different filters to find the right batch before committing to deep research downstream.
|
|
182
|
+
- **YC batch recency**: filter `launched_at_after` to the past 18 months gives a Series-A-shaped pool — most likely to clear ACV gates downstream. But don't apply silently; the operator should choose.
|
|
183
|
+
- **Algolia key rotation**: the key in YC's homepage HTML rotates on every server-side render. Don't cache across runs. The single-fetch-per-run pattern is correct.
|
|
184
|
+
- **Composability with /pipeline-fill**: this skill produces a candidate list. The orchestrator runs the engine. Operator can also run this skill standalone for "I just want YC, today" — answer `y` at the handoff prompt to continue into research.
|