@vruum/skills 0.6.53 → 0.6.55
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 +1 -1
- package/.codex-plugin/plugin.json +1 -1
- package/README.md +2 -2
- package/package.json +2 -2
- package/skills/create-content/SKILL.md +1 -1
- package/skills/csv-pipeline-fill/SKILL.md +2 -2
- package/skills/demand-gen-loop/SKILL.md +8 -8
- package/skills/diagnose-reply/SKILL.md +8 -8
- package/skills/engagement-triage/SKILL.md +4 -4
- package/skills/expansion-fill/SKILL.md +1 -1
- package/skills/{campaign-builder → objective-builder}/SKILL.md +27 -24
- package/skills/objective-doctor/SKILL.md +139 -0
- package/skills/outreach-triage/SKILL.md +9 -9
- package/skills/pipeline-fill/RESEARCH-ENGINE.md +29 -30
- package/skills/pipeline-fill/SKILL.md +29 -29
- package/skills/sales-nav-deep-fill/SKILL.md +1 -1
- package/skills/vruum-guide/SKILL.md +11 -11
- package/skills/vruum-skills-upgrade/SKILL.md +2 -2
- package/skills/winback-fill/SKILL.md +1 -1
- package/skills/yc-pipeline-fill/SKILL.md +5 -5
- package/skills/campaign-doctor/SKILL.md +0 -121
|
@@ -11,7 +11,7 @@ You are an outreach queue orchestrator. Your job is to efficiently process pendi
|
|
|
11
11
|
|
|
12
12
|
## Why this skill exists
|
|
13
13
|
|
|
14
|
-
Reviewing outreach messages is context-expensive. Each message with full context (conversation thread,
|
|
14
|
+
Reviewing outreach messages is context-expensive. Each message with full context (conversation thread, objective instructions, match analysis, company research) consumes significant tokens. This skill solves that by dispatching messages to independent subagents, each with their own context window, who do the deep review work and return compact summaries.
|
|
15
15
|
|
|
16
16
|
## Subagent architecture
|
|
17
17
|
|
|
@@ -64,17 +64,17 @@ Once the user says go (or picks a focus area), pull the lightweight message queu
|
|
|
64
64
|
|
|
65
65
|
**Frame by conversation state, not sequence number.** `first_content_touch=true` means this person has NEVER received a content-bearing message — their history is only connection requests (blank or noted). That item is an OPENER no matter its T-number (a T3 email that fell back from a never-accepted LinkedIn connect is still the first thing this person reads from the sender). `first_content_touch=false` means real prior contact exists — follow-up framing is legitimate. If the field is null/absent (rows drafted before this signal shipped, or an older backend), fall back to the sequence_number heuristic: seq 1 = opener, seq ≥2 = follow-up. The full block lives on the `get_outreach_review` item as `conversation_state` (`{v, first_content_touch, prior_context: none|note_only|inbound_only|content, connection_note_text, last_delivered_channel, designed_channel, channel_rewrite_reason, channel_fallback}`) — same absent-block fallback applies when `v` is unrecognized.
|
|
66
66
|
|
|
67
|
-
**Authoring mode (needs_draft items).** The backend no longer writes outreach prose — touches arrive as `needs_draft` items carrying the decision context (channel, touch number, signals) and no content. These are not rewrites; they are blank pages. The full review item includes `manual_notes`, up to 10 recent person notes, newest first. Each note can contain 4,000 characters, each person can contain 8,000, and a full response can contain 30,000 across repeated items. A cut note ends with `...[truncated]`; never treat that incomplete note as an exact saved draft. Use the notes as seller context. When
|
|
67
|
+
**Authoring mode (needs_draft items).** The backend no longer writes outreach prose — touches arrive as `needs_draft` items carrying the decision context (channel, touch number, signals) and no content. These are not rewrites; they are blank pages. The full review item includes `manual_notes`, up to 10 recent person notes, newest first. Each note can contain 4,000 characters, each person can contain 8,000, and a full response can contain 30,000 across repeated items. A cut note ends with `...[truncated]`; never treat that incomplete note as an exact saved draft. Use the notes as seller context. When objective instructions name a complete saved draft in `manual_notes`, treat that named draft as authoritative instead of replacing it with generic researched copy; preserve its wording unless the actual channel limit requires compression. For each needs_draft item the subagent AUTHORS the message: check the person's research freshness from the review item itself — `person_researched_at` / `company_researched_at` / `research_status` are on the payload, no extra fetch needed (older than ~14 days or missing → research first with WebSearch + the research reads, and persist what you learn via `research` action=save_person with `payload.person_id` set to the review item's person UUID (update-only — the response's `updated_fields` confirms what landed; NEVER pass the UUID as the facade `id` argument), plus action=save_company when you learned something about the company, so it compounds), then write the touch in the seller's voice against the same quality standards as any review, then check it — call `check_prose` with `{item_id: <message id>, item_type: "message", content: <draft>}` and treat the `failures[]` as an advisory checklist: fix what you agree with; severity `block` failures are hard stops that bounce at post time — today: channel character caps (cut to fit) and prior-outreach references on a first content touch (rewrite as an opener) — then submit it via `manage_messages` action=edit with the content and the returned `rules_version` as `client_rules_version` — that transitions the item to a normal draft — and approve only what the user's standing instructions allow. (The edit re-runs the same lint server-side; annotations are recorded to the label corpus, never rejected — a `block`-severity draft bounces with `prose_gate_blocked` + `failures[].fix` and a `span` naming the exact offending text.) Inbound replies also arrive as needs_draft (category inbound_reply, with the conversation attached): author the reply with full thread context. If a prospect turns out to be a bad fit at authoring time, skip the item and say why — authoring is the second qualification gate, not an obligation to write.
|
|
68
68
|
|
|
69
69
|
**Dispatch order within the authoring lane.** Pull the WARM lane directly — `search` type=messages with `warm_only=true, status=needs_draft, sort_by=expiring, fields=compact` returns exactly the LinkedIn follow-ups to accepted connections, nearest sweep deadline first, immune to page-1 truncation (warm rows are old by construction, so the default newest-first sort buries them under the cold backlog). Dispatch that lane first — they accepted the invite and the follow-up is the first real message they read. For the remaining lanes, compact rows carry `connection_status` if you need to spot stragglers, and `sort_by=expiring` works on any needs_draft pull.
|
|
70
70
|
|
|
71
71
|
**Expiry and recovery.** The 14-day sweep (see Step 1) means two things at authoring time:
|
|
72
72
|
- Order the authoring lane by `expires_at` (each needs_draft item carries it) — nearest deadline first, warm bucket before cold.
|
|
73
|
-
- An expired touch shows as `rejected` with BLANK content and `ai_decision_context.expired_signal` (reason `needs_draft_ttl_expired`) — GC, not a human rejection; do not read it as an operator verdict when diagnosing
|
|
73
|
+
- An expired touch shows as `rejected` with BLANK content and `ai_decision_context.expired_signal` (reason `needs_draft_ttl_expired`) — GC, not a human rejection; do not read it as an operator verdict when diagnosing an objective. (Rows expired before the marker shipped lack it — the tell there is blank content + `updated_at` near 03:20 UTC.) To revive one, call `manage_messages` action=`regenerate` (returns it to `needs_draft`), then author and `edit` as normal. Do NOT use `draft_followup` to recover an expired touch — it creates a NEW touch row instead of reviving the existing one.
|
|
74
74
|
|
|
75
75
|
**Conversation-state authoring rules** (from the item's `conversation_state`; skip when the block is absent):
|
|
76
76
|
- `first_content_touch=true` → write an OPENER: never reference prior outreach ("my last note", "since I haven't heard back", "bumping this" all bounce at the gate). Referencing THEIR world — posts, news, role — is what openers are made of.
|
|
77
|
-
- `channel_fallback=true` → the step was designed for `designed_channel` (usually a LinkedIn DM that never got the connection accept); the
|
|
77
|
+
- `channel_fallback=true` → the step was designed for `designed_channel` (usually a LinkedIn DM that never got the connection accept); the objective's per-step instructions may describe the wrong channel. Always author for the item's actual `channel`, and make the copy stand alone there.
|
|
78
78
|
- `prior_context='note_only'` → the person got a connection request WITH a note (`connection_note_text` has the text — they may have read it). Don't parrot or duplicate it; referencing the connection event itself ("thanks for connecting") is fine once connected.
|
|
79
79
|
- `prior_context='inbound_only'` → the person has messaged the sender but never received content from them. Continue THEIR conversation — referencing their message is right; referencing "my previous outreach" is not.
|
|
80
80
|
- The block is frozen at draft time: if the item's `conversation_thread` shows an inbound message or delivered content newer than the draft, trust the thread over the block.
|
|
@@ -86,9 +86,9 @@ Categorize into three processing groups:
|
|
|
86
86
|
2. **Follow-ups** (first_content_touch=false; fallback when null: sequence_number >= 2) — need research and quality check
|
|
87
87
|
3. **Openers** (first_content_touch=true; fallback when null: sequence_number = 1) — first real message regardless of T-number; full opener research standards, never "follow-up" framing
|
|
88
88
|
|
|
89
|
-
**Template-authored drafts (VRU-813).** A draft with `ai_decision_context.authored_by = "template"` is the OPERATOR'S OWN template rendered per person (deterministic `{{variable}}` substitution, `generated_by_ai=false`) — it arrives in the review lane already written. Review it for FIT (right person, correct variable values, thread coherence), not style: do NOT rewrite the operator's chosen wording per-row. If the prose itself is weak, that's a template-level problem — flag it once to the user so they fix the template in the
|
|
89
|
+
**Template-authored drafts (VRU-813).** A draft with `ai_decision_context.authored_by = "template"` is the OPERATOR'S OWN template rendered per person (deterministic `{{variable}}` substitution, `generated_by_ai=false`) — it arrives in the review lane already written. Review it for FIT (right person, correct variable values, thread coherence), not style: do NOT rewrite the operator's chosen wording per-row. If the prose itself is weak, that's a template-level problem — flag it once to the user so they fix the template in the objective workbench (or via `manage_outreach` action=objective_update), rather than editing N copies divergently. Conversely, a `needs_draft` row carrying `ai_decision_context.template_fallback_reason` is a person the template could NOT render for (missing variable value, or the channel fell back outside the template's step) — author it normally, and if many rows share one reason, surface the pattern (e.g. "31 people have no title on file") instead of silently hand-writing around a fixable data gap.
|
|
90
90
|
|
|
91
|
-
Present the queue composition before dispatching. For the counts in one call, use `search` with type=messages and `view=breakdown` — it returns grouped counts (by status, category, sequence/touch number, channel, and
|
|
91
|
+
Present the queue composition before dispatching. For the counts in one call, use `search` with type=messages and `view=breakdown` — it returns grouped counts (by status, category, sequence/touch number, channel, and objective) over the whole queue plus a compact items page, so you don't have to tally the pages yourself:
|
|
92
92
|
|
|
93
93
|
"Oaklet: 15 T1s, 5 T2s, 1 T3, 0 replies. How do you want to handle each group?"
|
|
94
94
|
|
|
@@ -131,7 +131,7 @@ You are an outreach review agent.
|
|
|
131
131
|
|
|
132
132
|
Message IDs: {comma_separated_message_ids}
|
|
133
133
|
|
|
134
|
-
Call get_outreach_review with message_ids="{comma_separated_message_ids}" and content_length="full" to load your assigned messages. Inspect `manual_notes`; when
|
|
134
|
+
Call get_outreach_review with message_ids="{comma_separated_message_ids}" and content_length="full" to load your assigned messages. Inspect `manual_notes`; when objective instructions name a saved draft there, treat that note as the authoritative source.
|
|
135
135
|
|
|
136
136
|
For each message:
|
|
137
137
|
1. Check structural compliance (touch sequence, char limits, channel)
|
|
@@ -158,7 +158,7 @@ Prospect: {person_name}, {title} at {company}
|
|
|
158
158
|
Message type: T{sequence_number} — frame by the item's conversation_state, NOT the T-number: first_content_touch=true means this is an OPENER (their history is only connection requests; never reference prior outreach); false means a real follow-up; block absent → treat T{sequence_number}>=2 as follow-up
|
|
159
159
|
|
|
160
160
|
Steps:
|
|
161
|
-
1. Call get_outreach_review with message_ids="{message_id}" and content_length="full" to get the current message, thread context,
|
|
161
|
+
1. Call get_outreach_review with message_ids="{message_id}" and content_length="full" to get the current message, thread context, objective instructions, match analysis, and complete `manual_notes`. When objective instructions name a saved draft there, treat that note as the authoritative source.
|
|
162
162
|
2. Call fetch with type=person_research plus get_person_360 for this person to get everything we know.
|
|
163
163
|
3. Call fetch with type=company_research to understand the company's product, positioning, and what problems it solves.
|
|
164
164
|
4. Search the web for this prospect and their company to understand what they actually do, what challenges they face, what they post about.
|
|
@@ -224,5 +224,5 @@ After outreach messages are processed, ask if the user wants to review the engag
|
|
|
224
224
|
- **User wants to review a specific person:** pull that person's conversation with `fetch` (type=conversation) and review directly. No batch workflow.
|
|
225
225
|
- **Subagent can't reach MCP tools:** fall back to inline review.
|
|
226
226
|
- **Homogeneous T1 pattern:** if the first T1 batch all had the identical issue, fix the remaining in bulk with a single `manage_messages` call passing an id array (same action applied to every id, max 50 per call). Confirm first.
|
|
227
|
-
- **
|
|
227
|
+
- **Objective plan settings drifted:** repair `max_touches` or `allowed_channels` through `manage_outreach` action=`update` with plan-id arrays. This preserves execution state; never update plan rows directly.
|
|
228
228
|
|
|
@@ -4,7 +4,7 @@ This is the canonical research-engine doc referenced by `/pipeline-fill` (orches
|
|
|
4
4
|
|
|
5
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
6
|
|
|
7
|
-
The orchestrator's SKILL.md owns the front-of-flow:
|
|
7
|
+
The orchestrator's SKILL.md owns the front-of-flow: objective 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
8
|
|
|
9
9
|
---
|
|
10
10
|
|
|
@@ -79,7 +79,6 @@ Stable operator-visible codes:
|
|
|
79
79
|
| `person_not_found` | tenant membership points to a missing person | refresh the candidate list |
|
|
80
80
|
| `person_identity_conflict` | fallback identifier belongs to another person | remove the conflicting identifier and review the provider result |
|
|
81
81
|
| `person_research_save_failed` | person research persistence failed with unknown commit status | inspect stored rows before any replay |
|
|
82
|
-
| `source_campaign_forbidden` | caller cannot remove people from their current campaign | ask the source-campaign owner to move them |
|
|
83
82
|
| `research_confirmation_required` | assignment requires explicit approval | pause and show the preview; never self-confirm |
|
|
84
83
|
| `research_partial` | some items failed or were not attempted | resume only `safe_retry_items` |
|
|
85
84
|
|
|
@@ -89,7 +88,7 @@ Backend response details link to `backend/app/domains/people/README.md#named-acc
|
|
|
89
88
|
|
|
90
89
|
## MCP-availability precheck (load-bearing — runs before Step 3)
|
|
91
90
|
|
|
92
|
-
Before any other Step 3 work, call `fetch(type="research_playbook", id=<
|
|
91
|
+
Before any other Step 3 work, call `fetch(type="research_playbook", id=<objective_id>)`. If this fails with "tool not found" / 404 / connection error, abort the run with this exact message:
|
|
93
92
|
|
|
94
93
|
> Vruum MCP not configured as a user-scoped server. Run:
|
|
95
94
|
>
|
|
@@ -105,15 +104,15 @@ The research_playbook fetch also doubles as the ICP load — capture target_titl
|
|
|
105
104
|
|
|
106
105
|
## Step 3 — Pre-flight
|
|
107
106
|
|
|
108
|
-
Per
|
|
107
|
+
Per objective's candidate list:
|
|
109
108
|
|
|
110
109
|
1. **MCP precheck + ICP load** (above) — abort run on failure.
|
|
111
110
|
2. **Batch dedup against existing pipeline.** Call `search(type="people", query=[{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.
|
|
112
111
|
3. **Batch company fixed-field reuse check.** Collect unique company domains from surviving candidates, deriving the apex from `company_website` when necessary. Call `fetch(type="company_research", id=[the domains], filters={"requested_fields":["company_summary","company_stage","current_priorities","funding_data","growth_metrics"]})`.
|
|
113
112
|
- Reuse only values whose field entry has `status="reusable"`.
|
|
114
|
-
- `core_reuse.reusable` means the shared summary core is reusable; it never means the
|
|
113
|
+
- `core_reuse.reusable` means the shared summary core is reusable; it never means the objective brief is complete.
|
|
115
114
|
- Missing, unsourced, stale, invalid, and absent fields remain null inputs. Never carry a raw stored value forward.
|
|
116
|
-
- **Every company still runs Phase A** for
|
|
115
|
+
- **Every company still runs Phase A** for objective-relative outbound motion, ACV class, sales-cycle inference, and triggers. Reusable fixed values are inputs that avoid redundant fetching, not a Phase A skip signal.
|
|
117
116
|
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.
|
|
118
117
|
|
|
119
118
|
**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.)
|
|
@@ -124,18 +123,18 @@ Per campaign's candidate list:
|
|
|
124
123
|
|
|
125
124
|
**Concurrency cap: 10 parallel.** Phase A subagents don't call `research` with action=linkedin_fetch — they hit `fetch` (type=company_research), `research` (action=enrich_company), `WebFetch`, `WebSearch`. No Unipile rate-limit pressure.
|
|
126
125
|
|
|
127
|
-
Dispatch one `vruum-company-deep-researcher` per unique company. Subagent file at `.claude/agents/vruum-company-deep-researcher.md` defines the workflow + tools. Include the reusable fixed-field values and their evidence in the prompt; the researcher must still compute
|
|
126
|
+
Dispatch one `vruum-company-deep-researcher` per unique company. Subagent file at `.claude/agents/vruum-company-deep-researcher.md` defines the workflow + tools. Include the reusable fixed-field values and their evidence in the prompt; the researcher must still compute objective-relative outputs.
|
|
128
127
|
|
|
129
128
|
Dispatch prompt template (fill in placeholders):
|
|
130
129
|
|
|
131
130
|
```
|
|
132
|
-
You are vruum-company-deep-researcher. Research this company against
|
|
131
|
+
You are vruum-company-deep-researcher. Research this company against objective "{objective_name}".
|
|
133
132
|
|
|
134
133
|
company_name: {name}
|
|
135
134
|
domain: {domain}
|
|
136
135
|
website: {company_website or null}
|
|
137
136
|
company_linkedin_url: {company_linkedin_url or null}
|
|
138
|
-
|
|
137
|
+
objective_icp_summary: {one paragraph from the research_playbook fetch}
|
|
139
138
|
acv_floor: {dollars or default $10K}
|
|
140
139
|
|
|
141
140
|
Run your workflow (a–i) and return the structured output block.
|
|
@@ -166,7 +165,7 @@ Dispatch one `vruum-prospect-deep-researcher` per surviving candidate. Subagent
|
|
|
166
165
|
Dispatch prompt template:
|
|
167
166
|
|
|
168
167
|
```
|
|
169
|
-
You are vruum-prospect-deep-researcher. Research this prospect against
|
|
168
|
+
You are vruum-prospect-deep-researcher. Research this prospect against objective "{objective_name}".
|
|
170
169
|
|
|
171
170
|
full_name: {name}
|
|
172
171
|
first_name: {first_name or null}
|
|
@@ -184,7 +183,7 @@ phase_a_signals:
|
|
|
184
183
|
outbound_motion_score: {0|1|2 or null}
|
|
185
184
|
triggers: [list or null]
|
|
186
185
|
|
|
187
|
-
|
|
186
|
+
objective_icp_summary: {one paragraph from the research_playbook fetch}
|
|
188
187
|
acv_floor: {dollars}
|
|
189
188
|
|
|
190
189
|
Run your workflow (a–k) and return the structured output block. Note: do NOT call manage_person action=save_discovered or manage_outreach action=start — those are orchestrator-only and not in your tools list.
|
|
@@ -203,12 +202,12 @@ The Phase B result describes the prospect's **current** employer, not merely the
|
|
|
203
202
|
|
|
204
203
|
## Step 6 — Harness pre-filter gate (orchestrator-side, pre-save)
|
|
205
204
|
|
|
206
|
-
This is the categorical first half of the harness-authoritative gate. It avoids wasted backend saves for obvious dismisses and feeds the deterministic numeric assessment in Step 7c. The backend does not re-score a supplied assessment; it records the harness score and mechanically enforces `match_score >= 70`. `MatchAnalysisAgent` is fallback-only for newly added people when callers omit assessment; duplicates retain their stored score unless a
|
|
205
|
+
This is the categorical first half of the harness-authoritative gate. It avoids wasted backend saves for obvious dismisses and feeds the deterministic numeric assessment in Step 7c. The backend does not re-score a supplied assessment; it records the harness score and mechanically enforces `match_score >= 70`. `MatchAnalysisAgent` is fallback-only for newly added people when callers omit assessment; duplicates retain their stored score unless a objective move enqueues an asynchronous re-score.
|
|
207
206
|
|
|
208
|
-
Per surviving prospect, evaluate four criteria using the
|
|
207
|
+
Per surviving prospect, evaluate four criteria using the objective's playbook ICP and the Phase A + Phase B signals:
|
|
209
208
|
|
|
210
|
-
### 1. ACV class meets
|
|
211
|
-
- `acv_class >= acv_floor_class` → pass this criterion (smb=$5K, mid=$5–50K, ent=$50K+;
|
|
209
|
+
### 1. ACV class meets objective threshold?
|
|
210
|
+
- `acv_class >= acv_floor_class` → pass this criterion (smb=$5K, mid=$5–50K, ent=$50K+; objective's `acv_floor` from playbook maps to a class)
|
|
212
211
|
- If no → dismiss `acv_too_low`. Don't call `manage_person` action=save_discovered.
|
|
213
212
|
|
|
214
213
|
### 2. Outbound motion or hiring signal?
|
|
@@ -217,7 +216,7 @@ Per surviving prospect, evaluate four criteria using the campaign's playbook ICP
|
|
|
217
216
|
|
|
218
217
|
### 3. Decision-maker level senior?
|
|
219
218
|
- `decision_maker_level == senior` → pass
|
|
220
|
-
- If `mid` → pass with a note (
|
|
219
|
+
- If `mid` → pass with a note (objective owner decides if mid is acceptable)
|
|
221
220
|
- 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`.
|
|
222
221
|
|
|
223
222
|
### 4. Trigger event in last 90d?
|
|
@@ -245,8 +244,8 @@ For non-dismiss outcomes, also set `dismiss_reason` to null and `flag` to the re
|
|
|
245
244
|
Apply the requested mode before any persistence:
|
|
246
245
|
|
|
247
246
|
- `research-only`: stop before Step 7a. Return the researched preview and do not call `save_company`, `save_person`, `save_discovered`, or `manage_outreach`.
|
|
248
|
-
- `save`: run Steps 7a–7c, but call `save_discovered` **without** `
|
|
249
|
-
- `save-and-enroll`: run the full chain. Pass `
|
|
247
|
+
- `save`: run Steps 7a–7c, but call `save_discovered` **without** `objective_id`. Pass `assessment_objective_id` so the backend records scoring provenance without assigning an objective or starting outreach.
|
|
248
|
+
- `save-and-enroll`: run the full chain. Pass `objective_id` to `save_discovered`, then include passing prospects in Step 7d.
|
|
250
249
|
|
|
251
250
|
Per surviving prospect:
|
|
252
251
|
|
|
@@ -279,9 +278,9 @@ rejected save persists nothing. There is no create-then-adopt dance anymore.
|
|
|
279
278
|
|
|
280
279
|
### c. Save discovered person — ONE atomic call (authoritative harness score)
|
|
281
280
|
|
|
282
|
-
Build the authoritative `assessment` from the
|
|
281
|
+
Build the authoritative `assessment` from the objective playbook plus Phase A/B evidence. Score mechanically so reruns agree:
|
|
283
282
|
|
|
284
|
-
- Company/ACV fit: 30 points when the known ACV class meets the
|
|
283
|
+
- Company/ACV fit: 30 points when the known ACV class meets the objective floor; a known miss is a harness dismiss and never reaches Step 7.
|
|
285
284
|
- Buying authority: 25 senior, 15 mid; a junior with no senior replacement is dismissed.
|
|
286
285
|
- Outbound/hiring motion: 20 when present, otherwise 0 and tag `warming`.
|
|
287
286
|
- Recent timing trigger: 15 when present, otherwise 0 and tag `low_priority`.
|
|
@@ -293,7 +292,7 @@ The score is the sum (0–100); 70+ passes. Send this exact shape:
|
|
|
293
292
|
```json
|
|
294
293
|
{
|
|
295
294
|
"match_score": 85,
|
|
296
|
-
"match_summary": "Two or three evidence-backed sentences against this
|
|
295
|
+
"match_summary": "Two or three evidence-backed sentences against this objective's ICP.",
|
|
297
296
|
"alignment_points": [
|
|
298
297
|
{
|
|
299
298
|
"point": "Specific alignment",
|
|
@@ -310,7 +309,7 @@ The score is the sum (0–100); 70+ passes. Send this exact shape:
|
|
|
310
309
|
}
|
|
311
310
|
],
|
|
312
311
|
"why_now": "Timing rationale with source",
|
|
313
|
-
"recommended_approach": "
|
|
312
|
+
"recommended_approach": "Objective-relevant approach",
|
|
314
313
|
"overall_confidence": 0.8,
|
|
315
314
|
"scored_by": "harness:pipeline-fill"
|
|
316
315
|
}
|
|
@@ -334,15 +333,15 @@ manage_person(
|
|
|
334
333
|
# topics_of_interest, recent_posts, role_start_date, ...)
|
|
335
334
|
},
|
|
336
335
|
assessment=<object above>, # REQUIRED with person
|
|
337
|
-
|
|
336
|
+
objective_id=... or assessment_objective_id=... # an objective ref is REQUIRED
|
|
338
337
|
}
|
|
339
338
|
)
|
|
340
339
|
```
|
|
341
340
|
|
|
342
341
|
**Person already saved:** `payload={person_id: <uuid>, company_id: <resolved company UUID>, assessment: <object above>, ...}` — applies the score update-in-place and atomically binds/promotes the current employer. If no `company_id` was resolved, pass `company_name` plus at least one top-level anchor instead. Never send a bare `person_id` from this harness.
|
|
343
342
|
|
|
344
|
-
- `mode == save`: add `
|
|
345
|
-
- `mode == save-and-enroll`: add `
|
|
343
|
+
- `mode == save`: add `assessment_objective_id: <objective>` so the score is recorded against the objective ICP, and omit `objective_id` so no assignment or move occurs. New rows remain unassigned; duplicates keep their existing objective assignment.
|
|
344
|
+
- `mode == save-and-enroll`: add `objective_id: <objective>`; the backend uses it for both assessment provenance and assignment. Omit `assessment_objective_id` unless it is the same objective.
|
|
346
345
|
|
|
347
346
|
This:
|
|
348
347
|
- Creates person + research + pipeline membership in ONE transaction (person shape) — a failed or rejected save persists nothing, so there is no orphan window
|
|
@@ -352,11 +351,11 @@ This:
|
|
|
352
351
|
|
|
353
352
|
**Distinguish two failure modes (Codex Finding #9):**
|
|
354
353
|
- **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.
|
|
355
|
-
- **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
|
|
354
|
+
- **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 objectives.
|
|
356
355
|
|
|
357
356
|
### d. Bulk enrollment (only after all prospects saved)
|
|
358
357
|
|
|
359
|
-
Collect all `person_id`s where `harness_gate_status == pass` AND backend `quality_gate_pass == true` AND backend `company_bound == true` AND `mode == save-and-enroll`. Then call `manage_outreach(action="start", id=[those person_ids], payload={
|
|
358
|
+
Collect all `person_id`s where `harness_gate_status == pass` AND backend `quality_gate_pass == true` AND backend `company_bound == true` AND `mode == save-and-enroll`. Then call `manage_outreach(action="start", id=[those person_ids], payload={objective_id: ...})` ONCE at the end of Step 7.
|
|
360
359
|
|
|
361
360
|
- Per-prospect outcomes are returned (enrolled | skipped | failed). Surface per-prospect failures in the report.
|
|
362
361
|
- If `harness_gate_status` is `warming` or `low_priority`, exclude from the bulk enroll list. Operator decides on review.
|
|
@@ -369,7 +368,7 @@ Collect all `person_id`s where `harness_gate_status == pass` AND backend `qualit
|
|
|
369
368
|
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.
|
|
370
369
|
|
|
371
370
|
```
|
|
372
|
-
Pipeline fill complete: {
|
|
371
|
+
Pipeline fill complete: {objective_name} (source: {source}, mode: {harness|platform})
|
|
373
372
|
|
|
374
373
|
Candidates flow:
|
|
375
374
|
source : {N from source skill output}
|
|
@@ -408,7 +407,7 @@ Pool status: healthy | drying up | exhausted ⚠️
|
|
|
408
407
|
Audit log written: .context/runs/pipeline-fill-{timestamp}.md
|
|
409
408
|
```
|
|
410
409
|
|
|
411
|
-
For multi-
|
|
410
|
+
For multi-objective runs, group the report by objective and include a totals summary at the bottom.
|
|
412
411
|
|
|
413
412
|
---
|
|
414
413
|
|
|
@@ -433,7 +432,7 @@ When a HARNESS source skill completes its sourcing flow and has a candidate list
|
|
|
433
432
|
```
|
|
434
433
|
Candidate list ready: {N} prospects from {source}.
|
|
435
434
|
|
|
436
|
-
NEXT: invoke /pipeline-fill Step 3 onward (deep research → harness gate → save) with this list and
|
|
435
|
+
NEXT: invoke /pipeline-fill Step 3 onward (deep research → harness gate → save) with this list and objective {objective_id}.
|
|
437
436
|
|
|
438
437
|
Continue automatically? (y/n)
|
|
439
438
|
```
|
|
@@ -10,21 +10,21 @@ description: >-
|
|
|
10
10
|
---
|
|
11
11
|
# Pipeline Fill
|
|
12
12
|
|
|
13
|
-
You are a source-agnostic pipeline filler. You pick
|
|
13
|
+
You are a source-agnostic pipeline filler. You pick objectives to fill, pick a source per objective (Sales Nav / YC / CSV / account list / discovery), and orchestrate harness deep research that gates against the objective ICP before saving prospects into the backend pipeline.
|
|
14
14
|
|
|
15
15
|
## Why this skill exists
|
|
16
16
|
|
|
17
|
-
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, hand over a list of target accounts (the harness resolves the buying committee per account), 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), scores against
|
|
17
|
+
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, hand over a list of target accounts (the harness resolves the buying committee per account), 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), scores against objective ICP, then lets the backend enforce the fixed `match_score >= 70` gate.
|
|
18
18
|
|
|
19
19
|
## Where the heavy logic lives
|
|
20
20
|
|
|
21
21
|
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:
|
|
22
|
-
- Step 1:
|
|
22
|
+
- Step 1: objective picker (with ETA)
|
|
23
23
|
- Step 2: source picker (conditional PLATFORM block + always-visible HARNESS block)
|
|
24
24
|
- The committee-resolution shared step (companies → people; used by every company-producing source)
|
|
25
25
|
- The account-list handler (company names/domains in, candidate list out)
|
|
26
26
|
- The discovery-mode handler (paste candidates inline OR describe an ICP and source via harness tools)
|
|
27
|
-
- The multi-
|
|
27
|
+
- The multi-objective grammar
|
|
28
28
|
|
|
29
29
|
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.
|
|
30
30
|
|
|
@@ -54,15 +54,15 @@ The orchestrator's MCP precheck at the top of Step 3 (the `fetch` type=research_
|
|
|
54
54
|
|
|
55
55
|
- `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.
|
|
56
56
|
- `source_policy` (optional): machine-readable provider policy matching `contracts/source-policy.schema.json`. It owns `selected_source`, `source_mode`, `prohibited_sources`, ordered `allowed_fallbacks`, bounded wave sizes, and transient retry attempts. Treat prohibited providers as unavailable: do not call status/list/search endpoints for them.
|
|
57
|
-
- `
|
|
58
|
-
- `buyers_per_account` (optional): how many buying-committee members to resolve per company when the source produces companies rather than people. Range 1–5. Per-source defaults: `discovery` → 2 (the surface is unqualified — optimize for reach, spread across more accounts), `account_list` → 3 (the account is already qualified — optimize for depth on the committee). Precedence: explicit operator value > a target stated in the
|
|
57
|
+
- `objective(s)`: target objective(s); multi-objective supported.
|
|
58
|
+
- `buyers_per_account` (optional): how many buying-committee members to resolve per company when the source produces companies rather than people. Range 1–5. Per-source defaults: `discovery` → 2 (the surface is unqualified — optimize for reach, spread across more accounts), `account_list` → 3 (the account is already qualified — optimize for depth on the committee). Precedence: explicit operator value > a target stated in the objective description (e.g. "~2 per account") > the per-source default. Sources that produce people directly (Sales Nav, YC, contact CSVs, discovery Path A) ignore this input.
|
|
59
59
|
- `mode`: `research-only` | `save` | `save-and-enroll` (default: `save-and-enroll`).
|
|
60
60
|
|
|
61
|
-
## Workflow — Step 1: Show pipeline status & pick
|
|
61
|
+
## Workflow — Step 1: Show pipeline status & pick objectives
|
|
62
62
|
|
|
63
|
-
Always call `fetch(type="stats", subtype="outreach")` for queue depth and `search(type="
|
|
63
|
+
Always call `fetch(type="stats", subtype="outreach")` for queue depth and `search(type="objectives")` for objective status. Call `import_prospects(action="sales_nav_searches", payload={action: "list"})` **only** when the operator explicitly selected Sales Nav and `source_policy.prohibited_sources` does not contain `sales_nav` or `linkedin`. A generic status check must never touch Sales Nav.
|
|
64
64
|
|
|
65
|
-
Present a numbered table with **per-
|
|
65
|
+
Present a numbered table with **per-objective ETA**:
|
|
66
66
|
|
|
67
67
|
```
|
|
68
68
|
Pipeline status:
|
|
@@ -72,18 +72,18 @@ Pipeline status:
|
|
|
72
72
|
3. Houston CTOs — 0/20 (20 needed) — harness ETA: ~18m
|
|
73
73
|
4. NYC Partners — 40/40 ✓
|
|
74
74
|
|
|
75
|
-
Which
|
|
75
|
+
Which objectives to fill? (all / 1,3 / skip 2)
|
|
76
76
|
Total if all needing fill: ~37m sequential.
|
|
77
77
|
```
|
|
78
78
|
|
|
79
|
-
ETA estimates: ~2s for batch Step 3 dedup + ~30s/wave Phase A + ~60s/wave Phase B (5-parallel cap on Phase B). Multi-
|
|
79
|
+
ETA estimates: ~2s for batch Step 3 dedup + ~30s/wave Phase A + ~60s/wave Phase B (5-parallel cap on Phase B). Multi-objective ETAs are sequential.
|
|
80
80
|
|
|
81
81
|
**Table rules:**
|
|
82
|
-
- One row per
|
|
82
|
+
- One row per objective, numbered sequentially
|
|
83
83
|
- Show current/target counts and how many are needed
|
|
84
84
|
- Flag searches that are drying up (⚠️) or accounts near capacity
|
|
85
|
-
- Mark
|
|
86
|
-
- Show per-
|
|
85
|
+
- Mark objectives already at target with ✓ and don't number them
|
|
86
|
+
- Show per-objective ETA so operator can budget time
|
|
87
87
|
|
|
88
88
|
**Buyers-per-account line (company-producing sources only).** When the source is already known to produce companies at Step 1 — the operator handed an account list, or named discovery with an ICP brief — append one line under the table so depth is an explicit decision, never a silent default:
|
|
89
89
|
|
|
@@ -91,28 +91,28 @@ ETA estimates: ~2s for batch Step 3 dedup + ~30s/wave Phase A + ~60s/wave Phase
|
|
|
91
91
|
Buyers per account: 3 (account-list default; range 1–5 — reply "buyers N" to change)
|
|
92
92
|
```
|
|
93
93
|
|
|
94
|
-
If the source isn't known yet at Step 1 (the common "fill my pipeline" path), defer this line to source-resolution time — the account-list handler and discovery Path B each confirm it before resolving. Wherever it renders, resolve the shown number via the `buyers_per_account` precedence in Inputs (explicit value >
|
|
94
|
+
If the source isn't known yet at Step 1 (the common "fill my pipeline" path), defer this line to source-resolution time — the account-list handler and discovery Path B each confirm it before resolving. Wherever it renders, resolve the shown number via the `buyers_per_account` precedence in Inputs (explicit value > objective-description target > per-source default), and name which rule produced it (e.g. "2 — objective description says '~2 per account'").
|
|
95
95
|
|
|
96
|
-
**Wait for the user's response.** Parse: "all", "1, 3", "skip 2", "just the CFO ones", "buyers 3", etc. Only proceed with the selected
|
|
96
|
+
**Wait for the user's response.** Parse: "all", "1, 3", "skip 2", "just the CFO ones", "buyers 3", etc. Only proceed with the selected objectives.
|
|
97
97
|
|
|
98
|
-
## Workflow — Step 2: Pick source per
|
|
98
|
+
## Workflow — Step 2: Pick source per objective (only if `prospect_list` not provided)
|
|
99
99
|
|
|
100
|
-
**Default to `discovery`.** Unless the operator named a source (in their prompt or a prior turn), don't lead with the picker — default to the `discovery` source (the describe-an-ICP path: source against the
|
|
100
|
+
**Default to `discovery`.** Unless the operator named a source (in their prompt or a prior turn), don't lead with the picker — default to the `discovery` source (the describe-an-ICP path: source against the objective's own ICP via WebSearch + Vruum MCP + LinkedIn search) and announce it in one line so it stays overridable, e.g.:
|
|
101
101
|
|
|
102
|
-
> Sourcing {
|
|
102
|
+
> Sourcing {objective_name} via discovery (ICP-based, long-tail). Reply `sales-nav`, `yc`, `csv`, `account-list`, or `picker` to switch.
|
|
103
103
|
|
|
104
104
|
**Account-list auto-detect:** if the operator's input is a list of company names or domains with no person identities (no personal names, no `/in/` LinkedIn URLs, no emails — e.g. pasted company lines, or a spreadsheet whose only mappable column is `company`), that is the `account_list` source. Announce it ("Reading this as an account list — {N} companies; I'll resolve the buying committee per account") instead of defaulting to discovery or misreading the rows as contacts.
|
|
105
105
|
|
|
106
106
|
CSV and Sales Nav are fully supported when selected. `source_policy` is a per-run routing contract: an explicit "use CSV" or "use Sales Nav" selects that capability; an explicit "no CSV" or "no Sales Nav" prohibits it only for this run. Never persist a seller's personal source preference as a tenant-wide capability restriction.
|
|
107
107
|
|
|
108
|
-
Why discovery is the default: keyword/Sales-Nav sources keep returning the same marquee names, which collide with already-enrolled prospects as
|
|
108
|
+
Why discovery is the default: keyword/Sales-Nav sources keep returning the same marquee names, which collide with already-enrolled prospects as an objective matures — the Step 3 dedup then throws most of the batch away. Discovery anchors on the objective's own ICP and reaches the long tail, deduping *before* research instead of after. Only render the full picker below when the operator asks to choose (`picker`), names a non-discovery source, or the discovery handler can't proceed.
|
|
109
109
|
|
|
110
|
-
Per selected
|
|
110
|
+
Per selected objective, when the operator wants to choose the source explicitly, prompt:
|
|
111
111
|
|
|
112
112
|
In **public mode** (the package builder strips the PLATFORM block from this skill before publishing), the picker shows only HARNESS modes, renumbered 1–5:
|
|
113
113
|
|
|
114
114
|
```
|
|
115
|
-
Source for {
|
|
115
|
+
Source for {objective_name}?
|
|
116
116
|
HARNESS mode (your compute, in-chat deep research, visible & interruptible):
|
|
117
117
|
1. sales-nav-deep — Sales Nav profiles + harness deep research
|
|
118
118
|
2. yc — scrape YC directory with filters you provide
|
|
@@ -133,18 +133,18 @@ Per source pick, dispatch:
|
|
|
133
133
|
- `account-list` → use the account-list handler below (parse companies → committee resolution → candidate list), then continue to Step 3 with it.
|
|
134
134
|
- `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.
|
|
135
135
|
|
|
136
|
-
**Multi-
|
|
136
|
+
**Multi-objective behavior:** objectives run sequentially. Objective 1's Step 7 (save chain + bulk enroll) completes before objective 2's Step 3 starts. Predictable rate-limit behavior, simple progress narrative. Trade-off: 3-objective fills are ~37min wall-clock vs ~22min if Phase A/B were overlapped across objectives. Cross-objective overlap is a v2.
|
|
137
137
|
|
|
138
138
|
## Committee resolution (shared step: companies → people)
|
|
139
139
|
|
|
140
140
|
The canonical candidate shape in `RESEARCH-ENGINE.md` is person-shaped — a company-only row is invalid by construction. This step is the single route from companies to people. **Every source that ends up holding companies runs it** (`account_list` always; `discovery` Path B after sourcing companies; a company-only CSV redirected from `/csv-pipeline-fill`). Sources that produce people directly skip it, and YC is a deliberate exemption: it is founder-first by design — the founder *is* the buyer, so `/yc-pipeline-fill` keeps its own founder extraction. Never improvise around this step by hand-picking a buyer out of research prose — see the anti-skew rule below.
|
|
141
141
|
|
|
142
142
|
**Contract:**
|
|
143
|
-
- **Input:** a list of companies, each with `company_name` and/or `domain` (at least one), plus any known `company_id`, `company_website`, or `company_linkedin_url`, the
|
|
143
|
+
- **Input:** a list of companies, each with `company_name` and/or `domain` (at least one), plus any known `company_id`, `company_website`, or `company_linkedin_url`, the objective's ICP target titles/seniority, and a resolved `buyers_per_account` (see Inputs).
|
|
144
144
|
- **Output:** the canonical person-shaped candidate list defined in `RESEARCH-ENGINE.md`, ready for Step 3. Copy every trustworthy company anchor onto every resolved person (`company_id`, `company_domain`, `company_website`, `company_linkedin_url`) and set `raw_signals.source_company` so the report can group by account. Do not reduce a strongly identified account back to a name-only company during committee resolution.
|
|
145
145
|
|
|
146
146
|
**Per company:**
|
|
147
|
-
1. Pull up to `buyers_per_account` people matching the
|
|
147
|
+
1. Pull up to `buyers_per_account` people matching the objective's ICP titles/seniority, using the first available provider in this order (same order as discovery sourcing; apply `source_policy` before any call):
|
|
148
148
|
- **Structured B2B provider** — e.g. Clay `find-and-enrich-contacts-at-company` with the title/seniority filter.
|
|
149
149
|
- **Regular LinkedIn** people-at-known-company via `import_prospects action=linkedin_people_search` — fine here because the company is fixed; the marquee-name skew applies to company discovery, not to enumerating a known committee. It needs a connected LinkedIn account, not Sales Navigator, returns at most 25 people, and charges the shared LinkedIn daily activity budget used by reads and sends.
|
|
150
150
|
- **Email finder** — Hunter via `search type=companies {domain, seniority}`.
|
|
@@ -159,7 +159,7 @@ Provider calls run in the standard bounded waves (company-level actions ≤10, p
|
|
|
159
159
|
Input is company names or domains — pasted lines, or a company-only CSV/xlsx redirected from `/csv-pipeline-fill`. This is a thin wrapper around the shared committee-resolution step:
|
|
160
160
|
|
|
161
161
|
1. **Parse companies.** One company per line (or per row). A line that looks like a domain (`acme.com`) sets `domain`; otherwise it's `company_name`. Drop blanks and `#` comments; dedupe case-insensitively. Above 100 accounts, confirm: "{N} accounts — process all, or first M? (a/N)".
|
|
162
|
-
2. **Anchor on
|
|
162
|
+
2. **Anchor on objective ICP.** Read the objective's ICP (via `fetch` type=objective and `fetch` type=settings subtype=profile) to get target titles/seniority. Show a one-line synthesis and the resolved `buyers_per_account` — full precedence per Inputs: an explicit operator value wins, else a objective-description target like "~2 per account", else this source's default of 3 (the accounts are already qualified, go deeper) — and confirm before resolving.
|
|
163
163
|
3. **Run committee resolution** (shared step above) across the account list.
|
|
164
164
|
4. **Show the resolved list** grouped by account — `Company → Name (title) [source]` — and get a "go" / "drop X" before continuing to Step 3.
|
|
165
165
|
|
|
@@ -183,7 +183,7 @@ Drop blank lines and lines starting with `#` (treat as comments).
|
|
|
183
183
|
**Path B — operator describes an ICP** (you want the harness to discover candidates)
|
|
184
184
|
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:
|
|
185
185
|
|
|
186
|
-
1. **Anchor on
|
|
186
|
+
1. **Anchor on objective ICP** — read the objective's existing ICP/company profile (via `fetch` type=objective 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.
|
|
187
187
|
2. **Take a source inventory — use the operator's actual toolbox, don't hardcode one provider.** Different operators have different prospecting tools connected. Take inventory of any MCP servers or CLIs this session can reach (inspect or search your available tools for terms like `clay`, `apollo`, `zoominfo`, `enrich`, `company`, `contacts`) and pick the highest-signal one. Prefer in this order:
|
|
188
188
|
- **Structured B2B data / enrichment provider** (Clay, Apollo, ZoomInfo, Crunchbase, People Data Labs, Clearbit, …) — these firmographic-filter companies AND resolve the buying committee directly, and they reach the long tail, which is the entire point of discovery. If one is connected, it is the primary source. With Clay specifically, that's `find-and-enrich-company` (firmographic company pull) + `find-and-enrich-contacts-at-company` (committee). Mind provider credits / rate limits.
|
|
189
189
|
- **Regular LinkedIn** via `import_prospects action=linkedin_people_search` — fine to *resolve people at a company you already found*, but it over-samples well-known names, so never use it as the primary company-discovery channel. Sales Navigator remains available through the separate `sales_nav_*` actions when its structured filters are necessary.
|
|
@@ -193,7 +193,7 @@ Operator gives a brief like "Series A-C SaaS founders, US, 50-500 ppl" or "direc
|
|
|
193
193
|
Apply `source_policy` before inventorying or calling providers. Validate the entire object against `contracts/source-policy.schema.json` before the first provider call. If `selected_source` is disconnected, stop with code `source_unavailable`; exclusive mode never substitutes, while preferred mode may use only the first connected entry in `allowed_fallbacks`. Announce the resolved policy in one line ("Sourcing via Clay — firmographic pull + committee enrichment; web as allowed backup; Sales Nav prohibited") so the operator can redirect.
|
|
194
194
|
3. **Source companies first, by firmographics — aim past the obvious names** — use the chosen tool to pull companies matching the merged ICP by stage / headcount / vertical / geo, NOT by marquee-name lookup (the saturated set IS the famous names). With a data provider, run the firmographic query directly; with web only, work funding announcements + directories.
|
|
195
195
|
4. **Resolve the buying committee per company** — run the shared **Committee resolution** step above on the sourced companies, with `buyers_per_account` resolved per Inputs (discovery default 2 — the surface is unqualified, so spread it across more accounts rather than going deep on any one).
|
|
196
|
-
5. **Dedup against existing pipeline** — for each discovered person, check `search` type=people with a name/company keyword query so you don't research someone the
|
|
196
|
+
5. **Dedup against existing pipeline** — for each discovered person, check `search` type=people with a name/company keyword query so you don't research someone the objective already has. This is where saturated names drop out, cheaply, before any research spend.
|
|
197
197
|
6. **Show the discovered list to the operator** before handoff. Format: `Name (title) — Company [source] [linkedin]`. Cap the surface at 2x daily_target so we don't over-source. Get a "go" / "drop X" before continuing.
|
|
198
198
|
|
|
199
199
|
Emit progress objects matching `contracts/run-progress.schema.json` after every bounded wave. Company-provider actions run in waves of at most 10; person/LinkedIn/provider contact actions run in waves of at most 5. Never submit a mixed unbounded batch and wait without a progress update.
|
|
@@ -214,5 +214,5 @@ Do not duplicate the engine logic in this skill — link operators back to the e
|
|
|
214
214
|
|
|
215
215
|
- **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).
|
|
216
216
|
- **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).
|
|
217
|
-
- **Harness offload framing**: deep research and the authoritative
|
|
217
|
+
- **Harness offload framing**: deep research and the authoritative objective score run in your IDE (your tokens). The backend validates the payload, records provenance, and mechanically enforces `match_score >= 70`; `MatchAnalysisAgent` is fallback-only for newly added people when a caller omits assessment. Duplicates retain their stored score unless an objective move enqueues an asynchronous re-score.
|
|
218
218
|
- **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?"
|
|
@@ -52,7 +52,7 @@ Emit the canonical handoff prompt (defined in `pipeline-fill/RESEARCH-ENGINE.md`
|
|
|
52
52
|
```
|
|
53
53
|
Candidate list ready: {N} prospects from sales-nav-deep.
|
|
54
54
|
|
|
55
|
-
NEXT: invoke /pipeline-fill Step 3 onward (deep research → harness gate → save) with this list and
|
|
55
|
+
NEXT: invoke /pipeline-fill Step 3 onward (deep research → harness gate → save) with this list and objective {objective_id}.
|
|
56
56
|
|
|
57
57
|
Continue automatically? (y/n)
|
|
58
58
|
```
|
|
@@ -11,7 +11,7 @@ description: >-
|
|
|
11
11
|
|
|
12
12
|
You are the guide to the seller's revenue engine. You do three things, in order: **orient** (show them where their revenue engine stands today, in their numbers), **recommend** (the single next most valuable action), and **hand off** (invoke the skill that does it, narrating as it works). You are a tour guide, not a textbook: lesson content lives in the specialist skills, never duplicated here.
|
|
13
13
|
|
|
14
|
-
**The one rule that overrides everything: every session ends with something real shipped** — a profile completed, a buying hypothesis produced,
|
|
14
|
+
**The one rule that overrides everything: every session ends with something real shipped** — a profile completed, a buying hypothesis produced, an objective created, a post drafted, a queue cleared, a deal advanced, or an account play approved. Never end a session on explanation alone.
|
|
15
15
|
|
|
16
16
|
## Step 0: Load progress
|
|
17
17
|
|
|
@@ -29,7 +29,7 @@ Build "your revenue engine today" from live reads — never from memory or assum
|
|
|
29
29
|
|
|
30
30
|
- `fetch` type=settings subtype=profile → company profile completeness
|
|
31
31
|
- `fetch` type=settings subtype=channel_status → which channels are connected
|
|
32
|
-
- `search` type=
|
|
32
|
+
- `search` type=objectives → objective count + status
|
|
33
33
|
- `search` type=people limit=1 filters={research_status: "all"} → total contacts (read the total, not the rows)
|
|
34
34
|
- `search` type=deals limit=5 → deal pipeline existence
|
|
35
35
|
- `fetch` type=stats subtype=outreach → sends, replies, meetings
|
|
@@ -40,14 +40,14 @@ Present a compact snapshot (5-8 lines, their numbers), positioned on the revenue
|
|
|
40
40
|
|
|
41
41
|
## Step 2: The revenue-motion map
|
|
42
42
|
|
|
43
|
-
Orient recommendations across the full revenue lifecycle. Do not use "outbound" as shorthand for Vruum and do not default to
|
|
43
|
+
Orient recommendations across the full revenue lifecycle. Do not use "outbound" as shorthand for Vruum and do not default to an objective before diagnosing the bottleneck. Vruum today:
|
|
44
44
|
|
|
45
45
|
- **Understand:** website-to-profile/ICP, knowledge grounding, positioning diagnosis, company/prospect research, and evidence-backed match analysis.
|
|
46
46
|
- **Create demand:** organic LinkedIn content, relationship-gated engagement, own-post engager capture, and paid LinkedIn amplification where ad permissions are available.
|
|
47
|
-
- **Select and reach:** source prospects, find warm paths,
|
|
47
|
+
- **Select and reach:** source prospects, find warm paths, cohorts/objectives, run email/LinkedIn outreach, handle inbound replies, and book meetings.
|
|
48
48
|
- **Commit:** deal qualification/review, stakeholder management, proposals, contracts, payment, and close tracking.
|
|
49
49
|
- **Grow and recover:** expansion and win-back are real harness-led motions. Onboarding/adoption are account-state and impact-tracking surfaces today, not autonomous customer-success programs.
|
|
50
|
-
- **Learn and operate:**
|
|
50
|
+
- **Learn and operate:** objective/reply diagnosis, outcome intelligence, HubSpot ingestion, and mailbox health.
|
|
51
51
|
|
|
52
52
|
Important boundaries: outreach/reply/content/comment prose is authored in the harness, not the backend; there is no phone/dialer motion; Google Ads is metrics-only; Salesforce is not wired end to end; the autonomous experiment loop is retired. Use the map to explain WHY a recommendation is next, not as a lecture. One paragraph max per session.
|
|
53
53
|
|
|
@@ -56,20 +56,20 @@ Important boundaries: outreach/reply/content/comment prose is authored in the ha
|
|
|
56
56
|
**Onboarding mode** — when the profile is missing/thin or the account has no executed motion. Land one fast win in the first exchange, then choose the first motion from the seller's actual bottleneck instead of forcing every account through outbound:
|
|
57
57
|
|
|
58
58
|
1. **Profile (the first quick win)**: run `manage_settings` action=auto_fill — Vruum reads their website and builds a starting picture of their ICP, value proposition, and target titles in under a minute. Show that back to them right away: that reveal *is* the first tangible payoff ("here's your revenue engine's starting picture, built from your site"). Then review/correct together and save via action=profile. This grounds every draft the harness authors — worth five careful minutes.
|
|
59
|
-
2. **Choose the first motion**: ask for the near-term revenue outcome and diagnose the constraint. Pipeline gap → sourcing/
|
|
60
|
-
3. **Ship the first result through the specialist skill**. Do not connect channels until the chosen motion needs them. For the common pipeline path: source with `/pipeline-fill`, build with `/
|
|
59
|
+
2. **Choose the first motion**: ask for the near-term revenue outcome and diagnose the constraint. Pipeline gap → sourcing/objective; audience/authority gap → content or demand gen; warm network → warm-path routing; active opportunities → deal triage/close; customer base → expansion; recoverable relationships → win-back. If Vruum is not the right fit, say so.
|
|
60
|
+
3. **Ship the first result through the specialist skill**. Do not connect channels until the chosen motion needs them. For the common pipeline path: source with `/pipeline-fill`, build with `/objective-builder`, let the harness author the `needs_draft` work, then review with `/outreach-triage`. For content, hand off to `/create-content`; for demand gen, `/demand-gen-loop`; for deals, `/deal-triage`; for account growth, `/expansion-fill` or `/winback-fill`.
|
|
61
61
|
4. **Mark the milestone**: save `chosen_motion` and `first_result`; update the legacy campaign/import/draft/review milestones only when that path actually ran.
|
|
62
62
|
|
|
63
63
|
**Next-best-action mode** — when onboarding milestones are done (or the user asks "what's next"). Diagnose from the Step 1 reads, recommend ONE action, hand off:
|
|
64
64
|
|
|
65
65
|
| Signal | Recommendation |
|
|
66
66
|
|---|---|
|
|
67
|
-
| Contacts sitting unenrolled | enroll into
|
|
67
|
+
| Contacts sitting unenrolled | enroll into an objective (`/objective-builder` or `manage_outreach` action=objective_members) |
|
|
68
68
|
| Gmail/provider-scheduled rows or reconciliation exceptions | `/outreach-triage` Gmail/Vruum reconciliation lane; never recreate the email |
|
|
69
69
|
| Outreach queue has pending drafts | `/outreach-triage` |
|
|
70
70
|
| Engagement queue non-empty | `/engagement-triage` |
|
|
71
71
|
| Replies without follow-up | `/diagnose-reply` on the interesting ones, then respond |
|
|
72
|
-
|
|
|
72
|
+
| Objective reply rate sagging vs its history | `/objective-doctor` |
|
|
73
73
|
| Pipeline thin (few researched contacts) | `/pipeline-fill` |
|
|
74
74
|
| Profile clear but no audience/authority motion | `/create-content`; use `/demand-gen-loop` only when paid amplification is appropriate and permitted |
|
|
75
75
|
| Named target with a plausible relationship path | `find_warm_path` before cold enrollment |
|
|
@@ -77,7 +77,7 @@ Important boundaries: outreach/reply/content/comment prose is authored in the ha
|
|
|
77
77
|
| Closed-won customers with no follow-on motion | `/expansion-fill` |
|
|
78
78
|
| Recoverable lost/churned relationships | `/winback-fill` |
|
|
79
79
|
| Offer/ICP is unclear or sellability is questionable | profile auto-fill first; `/positioning-diagnostic` only for the narrower cold-outreach go/no-go |
|
|
80
|
-
| Unclassified personas blocking targeting | `research` action=classify_personas, then `/
|
|
80
|
+
| Unclassified personas blocking targeting | `research` action=classify_personas, then `/objective-builder` |
|
|
81
81
|
| Everything humming | `fetch` type=insights subtype=improve — review what the system learned this week |
|
|
82
82
|
|
|
83
83
|
If several fire, pick the one with the highest expected revenue impact per unit of seller attention. Replies and active deals usually outrank new activity; expansion can outrank cold acquisition when the evidence is strong. Say why in one sentence. Mention the runner-up only if the user asks.
|
|
@@ -85,7 +85,7 @@ If several fire, pick the one with the highest expected revenue impact per unit
|
|
|
85
85
|
## Hard rules
|
|
86
86
|
|
|
87
87
|
- **Hand off, never re-teach.** When a specialist skill exists, invoke it. Do not reproduce its steps here — if you find yourself writing a numbered sub-procedure that exists in another skill, stop and invoke the skill.
|
|
88
|
-
- **Inherit every safety gate.** Launch confirmations, review requirements, and approval modes belong to specialist skills and the platform. Never bypass or pre-approve them. Manual outreach requires review; an explicitly configured `full_auto`
|
|
88
|
+
- **Inherit every safety gate.** Launch confirmations, review requirements, and approval modes belong to specialist skills and the platform. Never bypass or pre-approve them. Manual outreach requires review; an explicitly configured `full_auto` objective may auto-approve harness-authored outreach under backend send/audit guards. Public content and ad spend retain their own explicit approval gates.
|
|
89
89
|
- **Tailor from reads, not stereotypes.** Every recommendation cites their actual numbers from Step 1. If a read fails, say what you couldn't see — don't fill the gap with a guess.
|
|
90
90
|
- **One recommendation at a time.** A menu of five options is how sessions end with nothing shipped.
|
|
91
91
|
- **Update `~/.vruum/guide-state.json` before ending**, and close by naming what shipped this session and what you'd suggest next time.
|
|
@@ -61,7 +61,7 @@ One line: `upgraded @vruum/skills $OLD → $NEW`. Then continue with the origina
|
|
|
61
61
|
|
|
62
62
|
## Standalone mode (user invoked `/vruum-skills-upgrade` directly)
|
|
63
63
|
|
|
64
|
-
Force a fresh check first, then run the flow above starting from Step
|
|
64
|
+
Force a fresh check first, then run the flow above starting from Step 1 so the automatic-upgrade setting is honored:
|
|
65
65
|
|
|
66
66
|
```bash
|
|
67
67
|
~/.vruum/bin/vruum-skills-update-check --force
|
|
@@ -70,7 +70,7 @@ Force a fresh check first, then run the flow above starting from Step 2:
|
|
|
70
70
|
Interpret the forced check's output:
|
|
71
71
|
- Returns nothing → genuinely up to date. Report `already on latest (v$(cat ~/.vruum/VERSION))` and exit.
|
|
72
72
|
- `CHECK_FAILED <local>` → the registry could NOT be reached (network/sandbox), so we do **not** know if you're current. Report `upgrade check failed — couldn't reach the npm registry (network/sandbox); not necessarily on latest. Try again later or from an unsandboxed shell.` Do NOT claim "already on latest." (A forced check emits this whenever the fetch fails; a sandbox that blocks the registry is the common cause.)
|
|
73
|
-
- `UPGRADE_AVAILABLE <old> <new>` → run the upgrade flow from Step
|
|
73
|
+
- `UPGRADE_AVAILABLE <old> <new>` → run the upgrade flow from Step 1.
|
|
74
74
|
|
|
75
75
|
## When something goes wrong
|
|
76
76
|
|