@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.
@@ -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, campaign 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.
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 campaign 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.
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 a campaign. (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.
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 campaign's per-step instructions may describe the wrong channel. Always author for the item's actual `channel`, and make the copy stand alone there.
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 campaign workbench (or via `manage_campaign` action=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.
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 campaign) over the whole queue plus a compact items page, so you don't have to tally the pages yourself:
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 campaign instructions name a saved draft there, treat that note as the authoritative source.
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, campaign instructions, match analysis, and complete `manual_notes`. When campaign instructions name a saved draft there, treat that note as the authoritative source.
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
- - **Campaign 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.
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: campaign 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.
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=<campaign_id>)`. If this fails with "tool not found" / 404 / connection error, abort the run with this exact message:
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 campaign's candidate list:
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 campaign brief is complete.
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 campaign-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.
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 campaign-relative outputs.
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 campaign "{campaign_name}".
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
- campaign_icp_summary: {one paragraph from the research_playbook fetch}
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 campaign "{campaign_name}".
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
- campaign_icp_summary: {one paragraph from the research_playbook fetch}
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 campaign move enqueues an asynchronous re-score.
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 campaign's playbook ICP and the Phase A + Phase B signals:
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 campaign threshold?
211
- - `acv_class >= acv_floor_class` → pass this criterion (smb=$5K, mid=$5–50K, ent=$50K+; campaign's `acv_floor` from playbook maps to a class)
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 (campaign owner decides if mid is acceptable)
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** `campaign_id`. This persists the tenant-visible prospect and gate result without assigning a campaign or starting outreach.
249
- - `save-and-enroll`: run the full chain. Pass `campaign_id` to `save_discovered`, then include passing prospects in Step 7d.
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 campaign playbook plus Phase A/B evidence. Score mechanically so reruns agree:
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 campaign floor; a known miss is a harness dismiss and never reaches Step 7.
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 campaign's ICP.",
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": "Campaign-relevant 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
- campaign_id=... or assessment_campaign_id=... # a campaign ref is REQUIRED
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 `assessment_campaign_id: <campaign>` so the score is recorded against the campaign ICP, and omit `campaign_id` so no assignment or move occurs. New rows remain unassigned; duplicates keep their existing campaign assignment.
345
- - `mode == save-and-enroll`: add `campaign_id: <campaign>`; the backend uses it for both assessment provenance and assignment. Omit `assessment_campaign_id` unless it is the same campaign.
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 campaigns.
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={campaign_id: ...})` ONCE at the end of Step 7.
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: {campaign_name} (source: {source}, mode: {harness|platform})
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-campaign runs, group the report by campaign and include a totals summary at the bottom.
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 campaign {campaign_id}.
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 campaigns to fill, pick a source per campaign (Sales Nav / YC / CSV / account list / discovery), and orchestrate harness deep research that gates against the campaign ICP before saving prospects into the backend pipeline.
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 campaign ICP, then lets the backend enforce the fixed `match_score >= 70` gate.
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: campaign picker (with ETA)
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-campaign grammar
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
- - `campaign(s)`: target campaign(s); multi-campaign 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 campaign 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.
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 campaigns
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="campaigns")` for campaign 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.
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-campaign ETA**:
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 campaigns to fill? (all / 1,3 / skip 2)
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-campaign ETAs are sequential.
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 campaign, numbered sequentially
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 campaigns already at target with ✓ and don't number them
86
- - Show per-campaign ETA so operator can budget time
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 > campaign-description target > per-source default), and name which rule produced it (e.g. "2 — campaign description says '~2 per account'").
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 campaigns.
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 campaign (only if `prospect_list` not provided)
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 campaign's own ICP via WebSearch + Vruum MCP + LinkedIn search) and announce it in one line so it stays overridable, e.g.:
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 {campaign_name} via discovery (ICP-based, long-tail). Reply `sales-nav`, `yc`, `csv`, `account-list`, or `picker` to switch.
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 a campaign matures — the Step 3 dedup then throws most of the batch away. Discovery anchors on the campaign'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.
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 campaign, when the operator wants to choose the source explicitly, prompt:
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 {campaign_name}?
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-campaign behavior:** campaigns run sequentially. Campaign 1's Step 7 (save chain + bulk enroll) completes before campaign 2's Step 3 starts. Predictable rate-limit behavior, simple progress narrative. Trade-off: 3-campaign fills are ~37min wall-clock vs ~22min if Phase A/B were overlapped across campaigns. Cross-campaign overlap is a v2.
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 campaign's ICP target titles/seniority, and a resolved `buyers_per_account` (see Inputs).
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 campaign's ICP titles/seniority, using the first available provider in this order (same order as discovery sourcing; apply `source_policy` before any call):
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 campaign ICP.** Read the campaign's ICP (via `fetch` type=campaign 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 campaign-description target like "~2 per account", else this source's default of 3 (the accounts are already qualified, go deeper) — and confirm before resolving.
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 campaign ICP** — read the campaign's existing ICP/company profile (via `fetch` type=campaign and `fetch` type=settings subtype=profile) and merge with the operator's brief. Show a one-line synthesis ("OK so: Series A-C SaaS, US, 50-500 ppl, founder/CEO/CTO titles") and confirm before sourcing.
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 campaign already has. This is where saturated names drop out, cheaply, before any research spend.
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 campaign 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 a campaign move enqueues an asynchronous re-score.
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 campaign {campaign_id}.
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, a campaign created, a post drafted, a queue cleared, a deal advanced, or an account play approved. Never end a session on explanation alone.
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=campaignscampaign count + status
32
+ - `search` type=objectivesobjective 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 a campaign before diagnosing the bottleneck. Vruum today:
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, build cohorts/campaigns, run email/LinkedIn outreach, handle inbound replies, and book meetings.
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:** campaign/reply diagnosis, outcome intelligence, HubSpot ingestion, and mailbox health.
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/campaign; 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 `/campaign-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`.
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 a campaign (`/campaign-builder` or `manage_campaign` action=members) |
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
- | Campaign reply rate sagging vs its history | `/campaign-doctor` |
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 `/campaign-builder` |
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` campaign may auto-approve harness-authored outreach under backend send/audit guards. Public content and ad spend retain their own explicit approval gates.
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 2:
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 2.
73
+ - `UPGRADE_AVAILABLE <old> <new>` → run the upgrade flow from Step 1.
74
74
 
75
75
  ## When something goes wrong
76
76