@vruum/skills 0.6.5 → 0.6.7

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.
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vruum",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "Vruum AI skills + remote MCP server for B2B GTM teams. Slash commands for outreach triage, engagement triage, pipeline filling, prospect enrichment, and reply diagnosis, paired with the full Vruum MCP tool surface over OAuth 2.1.",
5
5
  "author": {
6
6
  "name": "Vruum AI",
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "vruum",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "Vruum AI skills + remote MCP server for B2B GTM teams. Skills for outreach triage, engagement triage, pipeline filling, prospect enrichment, and reply diagnosis, paired with the full Vruum MCP tool surface over OAuth 2.1.",
5
5
  "author": {
6
6
  "name": "Vruum AI",
package/README.md CHANGED
@@ -2,7 +2,7 @@
2
2
 
3
3
  Vruum AI skills for Claude Code, Codex CLI, and any AI assistant with a skill directory.
4
4
 
5
- Pairs with the Vruum MCP server at [https://api.vruum.ai/mcp](https://api.vruum.ai/mcp). The MCP server exposes the `invoke_skill` tool (alongside `list_skills` / `get_skill` / `publish_skill`) so any connected client can run these skills. Skills themselves are distributed via the Claude Code plugin / marketplace; this npm package installs them into the agent-standard skills directory for harnesses without plugin support (Codex CLI, ChatGPT, Windsurf).
5
+ Pairs with the Vruum MCP server at [https://api.vruum.ai/mcp](https://api.vruum.ai/mcp). The MCP server exposes the `skill` tool (action=invoke to run a skill, action=publish to publish one), with `search` type=skills and `fetch` type=skill for discovery, so any connected client can run these skills. Skills themselves are distributed via the Claude Code plugin / marketplace; this npm package installs them into the agent-standard skills directory for harnesses without plugin support (Codex CLI, ChatGPT, Windsurf).
6
6
 
7
7
  ## Install
8
8
 
@@ -18,7 +18,7 @@ Add to `~/.claude.json`:
18
18
  }
19
19
  ```
20
20
 
21
- Install the skills via the Claude Code plugin / marketplace (below); the MCP server provides the `invoke_skill` tool. You don't need this npm package for Claude Code.
21
+ Install the skills via the Claude Code plugin / marketplace (below); the MCP server provides the `skill` tool (action=invoke). You don't need this npm package for Claude Code.
22
22
 
23
23
  ### Codex CLI
24
24
 
@@ -45,7 +45,7 @@ npx @vruum/skills install --target /path/to/skills/dir # any other harness
45
45
 
46
46
  ### Cursor / VS Code Copilot / Cline
47
47
 
48
- Register the MCP server via your editor's MCP setup (each one has its own UI / config path). Connect to `https://api.vruum.ai/mcp` (HTTP, OAuth via standard MCP flow). The `invoke_skill` tool becomes available to also install the skill files locally, use this npm package's `install --target` below.
48
+ Register the MCP server via your editor's MCP setup (each one has its own UI / config path). Connect to `https://api.vruum.ai/mcp` (HTTP, OAuth via standard MCP flow). The `skill` tool (action=invoke) becomes available; to also install the skill files locally, use this npm package's `install --target` below.
49
49
 
50
50
  ### Claude Desktop / Claude.ai (Cowork)
51
51
 
@@ -64,6 +64,10 @@ npx @vruum/skills install --target /path/to/skills/dir
64
64
  ## Skills
65
65
 
66
66
  <!-- generated:skills-begin -->
67
+ - `/campaign-builder` — Build and launch an outreach campaign from criteria in about five prompts: filter contacts by size, industry, persona, region, or list; preview the cohort; create the campaign (optionally cloning messaging from an existing one); assign people; review and launch. Use when: create a campaign, build a campaign, new campaign from criteria, campaign from my list.
68
+ - `/campaign-doctor` — Diagnose and fix struggling outreach campaigns. Use when: fix a campaign, diagnose campaign, why is my campaign not working, campaign health, low reply rate, check campaigns, which campaigns need help.
69
+ - `/create-content` — Co-produce an on-voice LinkedIn content post — pull your own signal, steer the angle conversationally, draft in your voice, then save as draft, schedule, or publish. Use when: write a post, draft LinkedIn content, create content, post about, content co-production, help me write a post.
70
+ - `/csv-pipeline-fill` — CSV harness source for /pipeline-fill. Reads a CSV, auto-detects headers, maps columns, hands off to /pipeline-fill for harness deep research and import. Use when: import CSV, paste a CSV, csv import, prospect list from CSV, csv harness mode.
67
71
  - `/deal-triage` — Triage your active deal pipeline. Flags at-risk deals, surfaces stalled-deal alerts, runs MEDDIC qualification, and recommends next actions. Use when: review deals, triage deals, check pipeline, deal review, morning deals, pipeline review, deal health, at-risk deals.
68
72
  - `/diagnose-reply` — Diagnose why a reply happened — what worked or didn't in the outreach that triggered it. Use when: why did they reply, what worked, diagnose reply, reply diagnosis, analyze this reply, what caused this reply, reply analysis.
69
73
  - `/engagement-triage` — Review and approve your pending LinkedIn engagement drafts and demand-gen content posts. Use when: triage engagements, review engagement queue, review warming comments, review nurture reactions, review marketing comments, review content drafts, check engagement queue.
@@ -72,12 +76,9 @@ npx @vruum/skills install --target /path/to/skills/dir
72
76
  - `/outreach-triage` — Review and approve your pending outreach drafts across LinkedIn and email. Use when: triage, review queue, morning review, check messages, approve outreach, what needs review.
73
77
  - `/pipeline-fill` — Source-agnostic pipeline orchestrator. Picks a source per segment (Sales Nav / YC / CSV / discovery), runs harness deep research, applies a pre-filter gate, then saves into the segment via the backend authoritative match_score>=70 gate. Use when: fill pipeline, import prospects, daily imports, need more prospects, discover prospects from scratch, deep research before import.
74
78
  - `/sales-nav-deep-fill` — Sales Nav harness source for /pipeline-fill. Pre-filters Sales Nav profiles via vruum-pipeline-filter, produces a candidate list, hands off to /pipeline-fill for deep research and import. Use when: sales nav with deep research, sales nav harness mode, in-chat sales nav.
75
- - `/yc-pipeline-fill` — YC harness source for /pipeline-fill. Scrapes YC's public Algolia index, extracts founder LinkedIn URLs, dedups, hands a candidate list to /pipeline-fill for deep research and import. Use when: YC pipeline fill, source from YC, fill segment with YC founders, sales nav dried up, source YC.
76
- - `/csv-pipeline-fill` — CSV harness source for /pipeline-fill. Reads a CSV, auto-detects headers, maps columns, hands off to /pipeline-fill for harness deep research and import. Use when: import CSV, paste a CSV, csv import, prospect list from CSV, csv harness mode.
77
- - `/create-content` — Co-produce an on-voice LinkedIn content post — pull your own signal, steer the angle conversationally, draft in your voice, then save as draft, schedule, or publish. Use when: write a post, draft LinkedIn content, create content, post about, content co-production, help me write a post.
78
- - `/campaign-doctor` — Diagnose and fix struggling outreach campaigns. Use when: fix a campaign, diagnose campaign, why is my campaign not working, campaign health, low reply rate, check campaigns, which campaigns need help.
79
79
  - `/vruum-skills-upgrade` — Upgrade @vruum/skills to the latest npm version and re-sync ~/.vruum/. Use when: upgrade vruum skills, update vruum, pull latest vruum skills, or when the preamble reports UPGRADE_AVAILABLE.
80
80
  - `/winback-fill` — Source winback candidates from closed-lost deals or churned customers. Surfaces people who went silent or lost a deal >90 days ago, where the loss reason wasn't 'no_fit'. Use when: winback, win back churned, reactivate, revive cold deals, 90-day silent revival, lost deal recovery, lost customer outreach.
81
+ - `/yc-pipeline-fill` — YC harness source for /pipeline-fill. Scrapes YC's public Algolia index, extracts founder LinkedIn URLs, dedups, hands a candidate list to /pipeline-fill for deep research and import. Use when: YC pipeline fill, source from YC, fill segment with YC founders, sales nav dried up, source YC.
81
82
  <!-- generated:skills-end -->
82
83
 
83
84
  ## Upgrade
@@ -4,14 +4,11 @@ description: Read-only deal reviewer. Analyzes a single deal — risk, qualifica
4
4
  mcpServers:
5
5
  - vruum
6
6
  tools:
7
- - mcp__vruum__get_deal
8
- - mcp__vruum__get_deals
7
+ - mcp__vruum__fetch
8
+ - mcp__vruum__search
9
9
  - mcp__vruum__get_deal_360
10
- - mcp__vruum__get_deal_alerts
11
10
  - mcp__vruum__inspect_pipeline
12
11
  - mcp__vruum__get_person_360
13
- - mcp__vruum__get_company_research
14
- - mcp__vruum__get_account_state
15
12
  - WebSearch
16
13
  - WebFetch
17
14
  ---
@@ -26,13 +23,13 @@ You are **read-only by tool surface** — your tool list contains no write tools
26
23
 
27
24
  For each deal you're assigned:
28
25
 
29
- 1. **Get full deal context**: Call `get_deal_360` with the `deal_id`. This returns deal info, stakeholders (read-only view), MEDDIC qualification state (whatever's already on the deal), and recent activity timeline — all in one call. If that endpoint isn't in your tool list, fall back to `get_deal` (which carries `qualification` / `qualification_score` if previously computed; stakeholder count is in the deal row).
26
+ 1. **Get full deal context**: Call `get_deal_360` with the `deal_id`. This returns deal info, stakeholders (read-only view), MEDDIC qualification state (whatever's already on the deal), and recent activity timeline — all in one call. If that endpoint isn't in your tool list, fall back to `fetch` with type=deal (which carries `qualification` / `qualification_score` if previously computed; stakeholder count is in the deal row).
30
27
 
31
- 2. **Read qualification state — do NOT run qualification.** Inspect `qualification` / `qualification_score` from `get_deal_360`'s response. If `qualification` is null OR `qualification_score < 40` OR the last qualification is older than 30 days, surface this as a `re_qualify` recommendation in your output. Do NOT call `qualify_deal` — it writes a fresh MEDDIC JSONB on the deal and burns LLM tokens. The orchestrator runs it only after the seller approves.
28
+ 2. **Read qualification state — do NOT run qualification.** Inspect `qualification` / `qualification_score` from `get_deal_360`'s response. If `qualification` is null OR `qualification_score < 40` OR the last qualification is older than 30 days, surface this as a `re_qualify` recommendation in your output. Do NOT run qualification yourself (`manage_deal` action=qualify) — it writes a fresh MEDDIC JSONB on the deal and burns LLM tokens. The orchestrator runs it only after the seller approves.
32
29
 
33
30
  3. **Research primary stakeholder**: Call `get_person_360` for the primary champion (or first stakeholder). Note match score, research highlights, and recent activity.
34
31
 
35
- 4. **Read account state**: Call `get_account_state` for the deal's account stage + health (lifecycle: prospect → engaged → committed → onboarded → adopting → expansion_ready → dormant → churned). If 404 (no row yet), default to `prospect` / null health.
32
+ 4. **Read account state**: Call `fetch` with type=account_state for the deal's account stage + health (lifecycle: prospect → engaged → committed → onboarded → adopting → expansion_ready → dormant → churned). If 404 (no row yet), default to `prospect` / null health.
36
33
 
37
34
  5. **Optional external context**: Use `WebSearch` / `WebFetch` for recent company news that might affect the deal (funding, layoffs, acquisitions). Only when it materially affects the recommendation — don't routinely web-search every deal.
38
35
 
@@ -5,9 +5,9 @@ mcpServers:
5
5
  - vruum
6
6
  tools:
7
7
  - mcp__vruum__get_engagement_review
8
- - mcp__vruum__manage_engagement
8
+ - mcp__vruum__manage_engagements
9
9
  - mcp__vruum__get_content_review
10
- - mcp__vruum__manage_content_post
10
+ - mcp__vruum__manage_content
11
11
  - mcp__vruum__get_person_360
12
12
  - WebSearch
13
13
  - WebFetch
@@ -20,7 +20,7 @@ You are an engagement uplift + review agent with access to 5 Vruum MCP tools. Yo
20
20
 
21
21
  The orchestrator will tell you which type and provide IDs.
22
22
 
23
- If your dispatch prompt includes an instruction block about scoping MCP calls to a specific company, follow those instructions exactly. Before any `manage_engagement` write, verify the returned item's `user_company_id` matches the scope the orchestrator gave you (if any).
23
+ If your dispatch prompt includes an instruction block about scoping MCP calls to a specific company, follow those instructions exactly. Before any `manage_engagements` write, verify the returned item's `user_company_id` matches the scope the orchestrator gave you (if any).
24
24
 
25
25
  ---
26
26
 
@@ -54,7 +54,7 @@ Skip commenting and recommend a reaction when:
54
54
  - Commenting would require the sender to pretend they have expertise they don't have
55
55
  - The post is personal/religious/emotional and a comment from a stranger feels performative
56
56
 
57
- Use `manage_engagement` with action="edit" to change the engagement_type to a reaction, or recommend SKIP_TO_REACTION in your summary so the orchestrator can convert it.
57
+ Use `manage_engagements` with action="edit" to change the engagement_type to a reaction, or recommend SKIP_TO_REACTION in your summary so the orchestrator can convert it.
58
58
 
59
59
  A real person likes 20 posts for every 1 they comment on. The bar for commenting should be HIGH.
60
60
 
@@ -105,20 +105,22 @@ Flag if the reaction type seems wrong for the post context.
105
105
  ### 2h. Budget check
106
106
  If `budget_status` shows the sender account is near daily limits, note it in REASONING.
107
107
 
108
- ## Step 3: Apply the uplift via manage_engagement
108
+ ## Step 3: Apply the uplift via manage_engagements
109
109
 
110
- When you decide to UPLIFT, write back via `manage_engagement` with **action="edit"** AND **polish_provenance** so the two-stage edit diff is captured:
110
+ When you decide to UPLIFT, write back via `manage_engagements` with **action="edit"** AND **polish_provenance** in the payload so the two-stage edit diff is captured:
111
111
 
112
112
  ```
113
- manage_engagement(
114
- engagement_ids="<id>",
113
+ manage_engagements(
115
114
  action="edit",
116
- content="<your uplifted comment>",
117
- polish_provenance={
118
- "source": "skill",
119
- "model": "<your model — claude-opus-4-7, claude-sonnet-4-6, etc.>",
120
- "at": "<ISO8601 timestamp>",
121
- "rewrite_notes": "<one line: what changed and why>"
115
+ id="<id>",
116
+ payload={
117
+ "content": "<your uplifted comment>",
118
+ "polish_provenance": {
119
+ "source": "skill",
120
+ "model": "<your model claude-opus-4-7, claude-sonnet-4-6, etc.>",
121
+ "at": "<ISO8601 timestamp>",
122
+ "rewrite_notes": "<one line: what changed and why>"
123
+ }
122
124
  }
123
125
  )
124
126
  ```
@@ -198,7 +200,7 @@ If the post makes specific claims (statistics, market sizes), flag as "unverifie
198
200
 
199
201
  ## Step 3: Edit if needed
200
202
 
201
- For structural issues (no line breaks, weak hook), use `manage_content_post` with action="edit". For brand voice and factual issues, FLAG rather than edit.
203
+ For structural issues (no line breaks, weak hook), use `manage_content` with action="edit". For brand voice and factual issues, FLAG rather than edit.
202
204
 
203
205
  ## Step 4: Return structured summary
204
206
 
@@ -5,18 +5,16 @@ mcpServers:
5
5
  - vruum
6
6
  tools:
7
7
  - mcp__vruum__get_outreach_review
8
- - mcp__vruum__edit_message
9
- - mcp__vruum__search_knowledge_base
10
- - mcp__vruum__get_person_research
8
+ - mcp__vruum__manage_messages
9
+ - mcp__vruum__search
10
+ - mcp__vruum__fetch
11
11
  - mcp__vruum__get_person_360
12
- - mcp__vruum__get_company_research
13
- - mcp__vruum__fetch_company_website
14
- - mcp__vruum__fetch_linkedin_data
12
+ - mcp__vruum__research
15
13
  - WebSearch
16
14
  - WebFetch
17
15
  ---
18
16
 
19
- You are an outreach review agent with access to 8 Vruum MCP tools for message review. Your job is to review, improve, and prepare outreach messages for operator approval.
17
+ You are an outreach review agent with access to 6 Vruum MCP tools for message review. Your job is to review, improve, and prepare outreach messages for operator approval.
20
18
 
21
19
  You do NOT approve or send messages. You review, edit if needed, and return a structured summary.
22
20
 
@@ -90,10 +88,10 @@ If personalization is surface level or basic, and you have the tools to go deepe
90
88
 
91
89
  If a message needs better personalization or you need to verify something, you have access to:
92
90
 
93
- - **Knowledge base** (`search_knowledge_base`): Search the company's uploaded sales docs. Browse without filters first to see what's available, then narrow with `doc_type` or `query`.
91
+ - **Knowledge base** (`search` with `type="kb"`): Search the company's uploaded sales docs. Browse without filters first to see what's available, then narrow with `doc_type` or `query`.
94
92
  - **Web search**: Search for recent news about the prospect's company, their recent activity, industry trends relevant to them
95
- - **LinkedIn data** (`fetch_linkedin_data`): Pull the prospect's recent posts if not already in the review data
96
- - **Company research** (`get_company_research`, `fetch_company_website`): Get deeper company context if the match analysis feels thin
93
+ - **LinkedIn data** (`research` with `action="linkedin_fetch"`): Pull the prospect's recent posts if not already in the review data
94
+ - **Company research** (`fetch` with `type="company_research"`, or `research` with `action="enrich_company"` for a fresh website pull): Get deeper company context if the match analysis feels thin
97
95
 
98
96
  Use these tools when:
99
97
  - The draft's personalization is surface-level and you can find something better
@@ -103,7 +101,7 @@ Use these tools when:
103
101
 
104
102
  ### MANDATORY web search for follow-ups (T2+)
105
103
 
106
- For ANY follow-up message (sequence_number >= 2), web search is REQUIRED before making a decision. Cached `get_person_research` and `get_company_research` payloads are often weeks or months old and miss recent signals (acquisitions, role changes, new posts, new reqs, funding, layoffs). You MUST run at least one WebSearch query on the prospect + company before approving or editing a T2+ message.
104
+ For ANY follow-up message (sequence_number >= 2), web search is REQUIRED before making a decision. Cached person/company research payloads (`fetch` type=person_research / type=company_research) are often weeks or months old and miss recent signals (acquisitions, role changes, new posts, new reqs, funding, layoffs). You MUST run at least one WebSearch query on the prospect + company before approving or editing a T2+ message.
107
105
 
108
106
  What to search for:
109
107
  - "{Person Name} {Company}" — surfaces recent LinkedIn posts, interviews, podcast appearances
@@ -118,7 +116,7 @@ For T1 structural reviews and other cases, the existing "use when needed" rule a
118
116
 
119
117
  ## Step 4: Edit if needed
120
118
 
121
- If the message needs changes, rewrite it and apply the edit using `edit_message` with the message_id and new content.
119
+ If the message needs changes, rewrite it and apply the edit using `manage_messages` with action=edit, the message id, and payload={subject?, content}.
122
120
 
123
121
  When rewriting:
124
122
  - Keep the same strategic intent (don't change a T2 into a T4)
@@ -176,6 +174,6 @@ RESEARCH_DONE: {list of extra research you did, or "none"}
176
174
  ## Decision Framework
177
175
 
178
176
  **APPROVE**: passes all checks, personalization at least basic, reads like a human wrote it
179
- **EDITED**: had fixable issues, you've applied the fix via edit_message, rewritten version passes
177
+ **EDITED**: had fixable issues, you've applied the fix via manage_messages action=edit, rewritten version passes
180
178
  **FLAG**: reply_response to complex conversation, high-value prospect (90+), needs operator judgment
181
179
  **REJECT**: fundamentally violates touch sequence, severe repetition, fabricated personalization
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vruum/skills",
3
- "version": "0.6.5",
3
+ "version": "0.6.7",
4
4
  "description": "Vruum AI skills for Claude Code, Claude Desktop, Codex CLI, and any AI assistant with a skill directory. Slash commands for outreach triage, engagement triage, pipeline filling, prospect enrichment, and reply diagnosis. Pairs with the Vruum MCP server at https://api.vruum.ai/mcp.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -42,5 +42,5 @@
42
42
  "outreach",
43
43
  "gtm"
44
44
  ],
45
- "contentHash": "e63027e852c80ac642ff78aa10fc43c4efe9ec020ee4feeaa6270d4ba76fbe40"
45
+ "contentHash": "36f9f0e247644f718520e35d1b1b9100d3a3b2300949f6e98adf6d20ffe71b80"
46
46
  }
@@ -0,0 +1,63 @@
1
+ ---
2
+ name: campaign-builder
3
+ description: >-
4
+ Build and launch an outreach campaign from criteria in about five prompts:
5
+ filter contacts by size, industry, persona, region, or list; preview the
6
+ cohort; create the campaign (optionally cloning messaging from an existing
7
+ one); assign people; review and launch. Use when: create a campaign, build a
8
+ campaign, new campaign from criteria, campaign from my list.
9
+ ---
10
+ # Campaign Builder
11
+
12
+ You guide the seller from "I want to reach this kind of person" to a launched campaign in about five exchanges. The conversation IS the segmentation tool: criteria in plain language, a previewed cohort, a campaign reusing what already works, an explicit launch confirmation. Never launch anything without the confirmation step.
13
+
14
+ ## Step 1: Capture the criteria
15
+
16
+ Ask what cohort they want to reach if they haven't said. Criteria can combine:
17
+
18
+ - **List**: a named list (e.g. mirrored from a CRM export) — `filters={list: "<name or id>"}`
19
+ - **Custom attributes** from their import (e.g. sorted company size / industry / region) — `filters={custom: {"sorted_company_size": "small", "sorted_industry": "staffing & recruiting"}}`
20
+ - **Persona** (buying role): influencer | decision_maker | economic_buyer — `filters={persona: "economic_buyer"}`
21
+ - Standard filters: stage, score range, enrollment, relationship type
22
+
23
+ If they reference attributes you haven't seen, call `search` with `type="people"` and `limit=1` first and inspect a row's `custom_fields` keys so you offer real attribute names, not guesses.
24
+
25
+ If personas matter to their criteria and contacts are unclassified (`filters={persona: "unclassified"}` returns many), offer to run `research` with action=classify_personas first (payload `{}` classifies every unclassified contact; large runs return a job id — poll with `fetch` type=job until completed, then continue).
26
+
27
+ ## Step 2: Preview the cohort
28
+
29
+ Call `search` with `type="people"` and the criteria (include `filters={research_status: "all"}` so stub imports are visible). Show:
30
+ - The total count
31
+ - A 5-row sample: name, title, company, persona, the custom attributes that matched
32
+
33
+ Iterate with the seller until the cohort is right ("too broad — only the US ones" → add the region attribute). This is the step to get right; everything after is mechanical.
34
+
35
+ ## Step 3: Create the campaign
36
+
37
+ Two paths — ask which:
38
+
39
+ - **Reuse messaging that works** (default when they name an existing campaign): `manage_campaign` action=clone id=<existing campaign uuid> payload={name: "<new name>"}. Cloning carries the messaging structure, tone, and CTA configuration. Find the source campaign with `search` type="campaigns" if you only have its name.
40
+ - **Fresh**: `manage_campaign` action=create payload={name, ...} — then offer to set tone/cadence via action=update once created.
41
+
42
+ ## Step 4: Assign the cohort
43
+
44
+ Collect the person ids from the Step 2 preview (re-run the same `search` with a higher `limit` to get the full cohort if needed — paginate with `offset` for big cohorts) and call `manage_campaign` action=members id=<campaign uuid> payload={action: "add", person_ids: [...]}.
45
+
46
+ For large cohorts, add in batches of a few hundred and report progress.
47
+
48
+ ## Step 5: Review and launch — CONFIRMATION REQUIRED
49
+
50
+ Show the seller a launch summary before anything sends:
51
+ - Campaign name, source of messaging (cloned from X / fresh)
52
+ - Cohort size and criteria
53
+ - Channels and cadence (from the campaign config)
54
+
55
+ Then ask explicitly: "Launch outreach to these N people?" Only after a clear yes, call `manage_outreach` action=start id=[person uuids] (native bulk; payload optional {max_touches, allowed_channels, start_immediately}).
56
+
57
+ If the seller wants a dry run, stop after Step 4 — the campaign exists with members and nothing sends until plans start.
58
+
59
+ ## Notes
60
+
61
+ - Junk-safe personalization: contacts with garbage first names (single letters, org names) automatically get the no-name greeting variant — you don't need to filter them out of the cohort for that reason.
62
+ - A person can be in many lists but holds ONE campaign assignment; adding to a campaign moves them. Say so if the cohort overlaps an active campaign — surface counts before Step 4.
63
+ - Never call `manage_outreach` action=start without the Step 5 confirmation, and never auto-approve drafts; the outreach queue review (`/outreach-triage`) stays the quality gate.
@@ -13,21 +13,21 @@ You are a campaign diagnostics and optimization agent. Your job is to identify s
13
13
 
14
14
  ### Step 1: Triage — health + trends
15
15
 
16
- Call `get_campaigns` to list all campaigns. For each campaign, dispatch three calls in parallel:
17
- - `diagnose_campaign(campaign_id=X)` — returns `health_score` (0–100), reply-rate vs company average, and ranked root causes (targeting, messaging, channel, saturation, cadence, timing).
16
+ Call `search` with type=campaigns to list all campaigns. For each campaign, dispatch three calls in parallel:
17
+ - `manage_campaign` with action=diagnose and id=<campaign uuid> — returns `health_score` (0–100), reply-rate vs company average, and ranked root causes (targeting, messaging, channel, saturation, cadence, timing).
18
18
  - `get_performance_metrics(view='funnel', campaign_id=X, start_date=<today_utc - 6d>, end_date=<today_utc + 1d>)` — current 7-day window (7 full days ending today, inclusive).
19
19
  - `get_performance_metrics(view='funnel', campaign_id=X, start_date=<today_utc - 13d>, end_date=<today_utc - 7d>)` — prior 7-day window (7 full days ending the day before current starts — no shared days).
20
20
 
21
21
  Use **UTC** dates in `YYYY-MM-DD` format. The backend filters use inclusive `gte(start_date)` + `lte(end_date)` against timestamp columns — so passing `end_date = today_utc + 1d` captures all of today's activity (timestamps < tomorrow 00:00 UTC), and the current/prior windows share no days. Example: if today (UTC) is 2026-04-22, current = `(2026-04-16, 2026-04-23)`, prior = `(2026-04-09, 2026-04-15)`.
22
22
 
23
- If `get_campaigns` returns no campaigns, tell the user "No campaigns yet — create one in the Vruum app before running diagnosis" and stop.
23
+ If the campaign `search` returns no campaigns, tell the user "No campaigns yet — create one in the Vruum app before running diagnosis" and stop.
24
24
 
25
- Classify each campaign by reply rate (from `diagnose_campaign` output). Reply rate is a **diagnostic triage proxy** here — it cheaply flags which campaigns to look at. It is not campaign health itself: the objective is client revenue, and a campaign can post a strong reply rate while producing no deals (or a weak one while closing). Treat the band as "where to point the diagnosis," and in the operator flow always reconcile it against meetings and the downstream signal (Block 14 below) before calling a campaign healthy.
25
+ Classify each campaign by reply rate (from the diagnose output). Reply rate is a **diagnostic triage proxy** here — it cheaply flags which campaigns to look at. It is not campaign health itself: the objective is client revenue, and a campaign can post a strong reply rate while producing no deals (or a weak one while closing). Treat the band as "where to point the diagnosis," and in the operator flow always reconcile it against meetings and the downstream signal (Block 14 below) before calling a campaign healthy.
26
26
 
27
27
  - **CRITICAL** — 30-day reply rate < 5% with ≥20 sent
28
28
  - **WARNING** — 30-day reply rate 5–10% with ≥20 sent
29
29
  - **HEALTHY** — 30-day reply rate ≥ 10% (reply-rate-healthy — confirm it also produces meetings/deals before treating it as truly healthy)
30
- - **INSUFFICIENT DATA** — `diagnose_campaign` returned `insufficient_data: true` (fewer than 20 sent in 30d)
30
+ - **INSUFFICIENT DATA** — the diagnose call returned `insufficient_data: true` (fewer than 20 sent in 30d)
31
31
 
32
32
  For WoW delta, compute `(current_reply_rate - prior_reply_rate) / prior_reply_rate`. Guards:
33
33
  - **Brand-new campaign** (prior window sent = 0): show "new campaign, WoW N/A".
@@ -57,11 +57,11 @@ Want me to diagnose the critical and warning campaigns?"
57
57
  Key behaviors:
58
58
  - Never auto-diagnose `insufficient_data` campaigns. They need more volume first.
59
59
  - If `get_performance_metrics` returns an empty funnel for the prior window, treat it as "new campaign, WoW N/A" (not -100%).
60
- - If the funnel is empty for the current window too, fall back to the 30-day reply rate from `diagnose_campaign` output — don't show a fake zero.
60
+ - If the funnel is empty for the current window too, fall back to the 30-day reply rate from the diagnose output — don't show a fake zero.
61
61
 
62
62
  ### Step 2: Diagnose root causes
63
63
 
64
- For each campaign the user wants to diagnose, you already have the `diagnose_campaign` output from Step 1's parallel calls. Present the findings:
64
+ For each campaign the user wants to diagnose, you already have the diagnose output from Step 1's parallel calls. Present the findings:
65
65
 
66
66
  "**'IT Directors'** — Health score: 25/100
67
67
 
@@ -81,18 +81,18 @@ Root causes (ranked):
81
81
  Want me to apply any of these fixes?"
82
82
 
83
83
  Key behaviors:
84
- - If `diagnose_campaign` returned `insufficient_data`, surface the tool's own `message` field verbatim. Don't re-derive the threshold logic.
84
+ - If the diagnose call returned `insufficient_data`, surface the tool's own `message` field verbatim. Don't re-derive the threshold logic.
85
85
  - When multiple campaigns share the same root cause dimension (e.g., all have messaging issues), recommend a cross-campaign fix first.
86
86
 
87
87
  ### Step 3: Apply fixes (with approval)
88
88
 
89
89
  For each recommended fix the user approves:
90
90
 
91
- - **Targeting fix**: Suggest specific ICP field changes and call `update_campaign` with new `target_titles`, `target_industries`, or `positioning_angle`.
91
+ - **Targeting fix**: Suggest specific ICP field changes and call `manage_campaign` with action=update, the campaign id, and a payload of new `target_titles`, `target_industries`, or `positioning_angle`.
92
92
 
93
- - **Messaging fix**: Suggest revised `ai_tone_instructions` or `ai_selling_strategy` and call `update_campaign`.
93
+ - **Messaging fix**: Suggest revised `ai_tone_instructions` or `ai_selling_strategy` and apply them via `manage_campaign` action=update.
94
94
 
95
- - **Channel fix**: Call `update_campaign` with adjusted `allowed_channels`.
95
+ - **Channel fix**: Call `manage_campaign` with action=update and a payload of adjusted `allowed_channels`.
96
96
 
97
97
  - **Saturation fix (recommend only)**: The client flow doesn't manage pipeline sources directly. Instead:
98
98
  1. Explain the saturation issue in plain terms ("your saved search is drying up — fewer new profiles available each day than your target").
@@ -114,8 +114,8 @@ Monitor results over the next 7 days. Run /campaign-doctor again next week to ch
114
114
 
115
115
  ## Notes
116
116
 
117
- - `diagnose_campaign` requires 20+ sent touches in 30 days for meaningful analysis. For newer campaigns, wait — do not attempt diagnosis.
118
- - Reply-rate thresholds for health bands match `diagnose_campaign.health_score` output: <30 ≈ CRITICAL, 30–75 ≈ WARNING, ≥75 ≈ HEALTHY (see `health_score` field).
117
+ - Campaign diagnosis (`manage_campaign` action=diagnose) requires 20+ sent touches in 30 days for meaningful analysis. For newer campaigns, wait — do not attempt diagnosis.
118
+ - Reply-rate thresholds for health bands match the diagnose output's `health_score`: <30 ≈ CRITICAL, 30–75 ≈ WARNING, ≥75 ≈ HEALTHY (see `health_score` field).
119
119
  - WoW comparison uses two `get_performance_metrics(view='funnel', campaign_id=X)` calls — **always UTC dates in YYYY-MM-DD**, current = `(today-7d, today)`, prior = `(today-14d, today-7d)`. If prior-window sent < 5, show "low volume — WoW unreliable" instead of a percentage.
120
120
  - Root causes are ranked by severity. Focus on the highest-severity issues first.
121
121
  - Saturation fixes in the client flow are text recommendations only — clients cannot manage pipeline sources directly via MCP; they adjust Sales Nav and run `/pipeline-fill`.
@@ -24,22 +24,22 @@ Keep it light. You're not locking a brief yet — you just need a seed string to
24
24
 
25
25
  A client can have **multiple people**, each with their own connected LinkedIn account. The post must be drafted from the right person's signal and authored from their account. Before pulling any signal, find out who you're authoring as.
26
26
 
27
- Call `get_channel_status` and read `channels.linkedin_accounts` — a list of accounts, each with `account_id`, `account_name`, `user_id`, `user_name`, `user_email`, `status`, `connected`, `has_sales_nav`, and `quota`.
27
+ Call `fetch` with `type="settings"` and `subtype="channel_status"` and read `channels.linkedin_accounts` — a list of accounts, each with `account_id`, `account_name`, `user_id`, `user_name`, `user_email`, `status`, `connected`, `has_sales_nav`, and `quota`.
28
28
 
29
29
  Then pick the author **`user_id`** to use:
30
30
 
31
- - **`linkedin_accounts` is missing, empty, or the `get_channel_status` call fails** → fall back to the legacy default: omit `author_user_id` entirely on the calls below and proceed. Never block content creation on this lookup.
31
+ - **`linkedin_accounts` is missing, empty, or the channel-status `fetch` call fails** → fall back to the legacy default: omit `author_user_id` entirely on the calls below and proceed. Never block content creation on this lookup.
32
32
  - **Exactly one usable account** (one entry with a non-null `user_id`) → silently auto-select that account's `user_id`. Do **not** prompt. **Still pass it explicitly** in Step 3 and Step 5 — do not rely on omitting the param. Omitting it grounds the signal on *you* (the caller) and stores *no* author on the draft, and that single account may belong to a teammate, not you.
33
- - **More than one usable account** → ask the operator **which person to author as**, presenting each option by `user_name` and `user_email`. The selectable unit is the **person (`user_id`)**, because `author_user_id` is the only selector the signal/draft tools accept — if one person has multiple LinkedIn accounts, this skill can't target them individually (say so if asked). Map the chosen person to their `user_id`.
33
+ - **More than one usable account** → ask the operator **which person to author as**, presenting each option by `user_name` and `user_email`. The selectable unit is the **person (`user_id`)**, because the author `user_id` is the only selector the signal/draft tools accept — if one person has multiple LinkedIn accounts, this skill can't target them individually (say so if asked). Map the chosen person to their `user_id`.
34
34
  - **Skip any account whose `user_id` is null/missing** when counting and presenting options (it isn't addressable as an author).
35
35
 
36
36
  Hold the resolved `user_id` (or the decision to omit it) and use it consistently for both the signal pull and the draft.
37
37
 
38
38
  ## Step 3: Pull the seller's signal
39
39
 
40
- Call `get_seller_signal_bundle` with the rough topic as the `draft_brief` argument. **If you resolved an author `user_id` in Step 2, pass it as the `author_user_id` query param** so the signal is *that person's*, not the caller's. (Omit `author_user_id` only in the legacy-fallback case from Step 2.) The brief drives a semantic re-rank, so the more concrete the topic, the more relevant the returned evidence.
40
+ Call `fetch` with `type="seller_signals"`, passing the rough topic as `draft_brief` in `filters`. **If you resolved an author `user_id` in Step 2, pass it as the `id` argument** so the signal is *that person's*, not the caller's. (Omit `id` only in the legacy-fallback case from Step 2.) The brief drives a semantic re-rank, so the more concrete the topic, the more relevant the returned evidence.
41
41
 
42
- **Trust boundary — handle 403 honestly.** Authoring as another person is permission-guarded server-side (a non-owner can't author as a teammate). If this call (or the draft call in Step 5) returns **403**, STOP: tell the operator plainly that they aren't entitled to author as that person, and ask them to pick a permitted author or have an owner do it. Do **not** retry with `author_user_id` omitted — that would silently fall back to caller/company-scoped generation under a different identity than was asked for.
42
+ **Trust boundary — handle 403 honestly.** Authoring as another person is permission-guarded server-side (a non-owner can't author as a teammate). If this call (or the draft call in Step 5) returns **403**, STOP: tell the operator plainly that they aren't entitled to author as that person, and ask them to pick a permitted author or have an owner do it. Do **not** retry with the author param omitted — that would silently fall back to caller/company-scoped generation under a different identity than was asked for.
43
43
 
44
44
  **Ground on `formatted_evidence`.** It is the EVIDENCE-wrapped rendering prepared for drafting — the surface you should read, quote, and reason over. The backend scrubs prospect names, emails, phone numbers, and URLs out of *everything* it returns (both `formatted_evidence` and the raw `bundle` text), so you don't have to police that yourself — there is no un-redacted surface on the response. Still prefer `formatted_evidence`: it is the prepared, sectioned grounding surface, where `bundle` is just the structured raw material behind it.
45
45
 
@@ -51,7 +51,7 @@ Call `get_seller_signal_bundle` with the rough topic as the `draft_brief` argume
51
51
 
52
52
  This step is **optional** and **must never gate the loop**. It exists only to make the brief a little smarter, not to add a precondition before drafting. If you skip it, or it returns nothing, or it errors — proceed to Step 4 silently and draft anyway.
53
53
 
54
- If a quick read of what's already on the content calendar would help shape the angle, call `get_content_calendar` **once** (the default `days` window is fine — don't loop or page). Treat the returned `posts` (each with `topic_tags`, `status`, and `scheduled_at`/`published_at`) and the `summary` as planning context only, to:
54
+ If a quick read of what's already on the content calendar would help shape the angle, call `search` with `type="content"` **once** (the default `days` filter window is fine — don't loop or page). Treat the returned `posts` (each with `topic_tags`, `status`, and `scheduled_at`/`published_at`) and the `summary` as planning context only, to:
55
55
 
56
56
  - **Avoid theme clustering** — if a recent or upcoming post already covers this `topic_tags` theme, nudge the angle somewhere fresher rather than posting two near-duplicates close together.
57
57
  - **Notice cadence gaps** — if the calendar looks thin lately, that's a soft reason to keep this one moving. This is best-effort only: the tool returns posts by recency (it lists what was created recently, not a precise scheduled-window view), so don't promise exact cadence math or specific dates — read it as a rough sense of "busy" vs "quiet," not a schedule.
@@ -74,37 +74,37 @@ Let the seller refine. Iterate in conversation until the brief is **settled**
74
74
 
75
75
  ## Step 5: Draft on-voice — once
76
76
 
77
- **Only once the brief is settled**, call `generate_linkedin_post`, passing the settled brief as the `topic` argument. **If you resolved an author `user_id` in Step 2, pass the same value as `author_user_id`** so the draft is written from that person's voice/signal and the draft row is stamped with their `author_user_id` (this is what later carries the author through schedule/publish). Pass the *same* `user_id` you used for the signal pull — don't let signal and draft disagree. Omit `author_user_id` only in the legacy-fallback case. The same **403** trust-boundary rule from Step 3 applies here: on 403, STOP and ask for a permitted author — never retry with the param omitted.
77
+ **Only once the brief is settled**, call `manage_content` with `action="draft_post"`, passing the settled brief as `topic` in the payload. **If you resolved an author `user_id` in Step 2, pass the same value as `author_user_id` in the payload** so the draft is written from that person's voice/signal and the draft row is stamped with their `author_user_id` (this is what later carries the author through schedule/publish). Pass the *same* `user_id` you used for the signal pull — don't let signal and draft disagree. Omit `author_user_id` only in the legacy-fallback case. The same **403** trust-boundary rule from Step 3 applies here: on 403, STOP and ask for a permitted author — never retry with the param omitted.
78
78
 
79
79
  The backend writes the draft in the seller's voice and grounds it on their seller signal internally — you do not pass the evidence yourself.
80
80
 
81
- `generate_linkedin_post` creates a new draft row every time it runs. Call it **once** per post. Show the seller the returned draft.
81
+ The `draft_post` action creates a new draft row every time it runs. Call it **once** per post. Show the seller the returned draft.
82
82
 
83
83
  ## Step 6: Iterate by editing — never regenerate
84
84
 
85
- When the seller wants changes (tighten the hook, change the CTA, fix a line), revise the **existing** draft with `manage_content_post` using `action="edit"`, passing the updated `content`.
85
+ When the seller wants changes (tighten the hook, change the CTA, fix a line), revise the **existing** draft with `manage_content` using `action="edit"`, passing the updated `content`.
86
86
 
87
- Never call `generate_linkedin_post` again for a revision — that spawns a duplicate draft row and loses the thread. One post = one draft row, edited in place.
87
+ Never call `manage_content` with `action="draft_post"` again for a revision — that spawns a duplicate draft row and loses the thread. One post = one draft row, edited in place.
88
88
 
89
- `manage_content_post` operates on the existing draft row, which already carries the `author_user_id` you stamped at generation. You do **not** re-pass the author here — schedule/publish inherit it from the row.
89
+ `manage_content` operates on the existing draft row, which already carries the `author_user_id` you stamped at generation. You do **not** re-pass the author here — schedule/publish inherit it from the row.
90
90
 
91
91
  ## Step 7: Save — draft, schedule, or publish
92
92
 
93
93
  When the seller is happy with the draft, ask how they want to land it. **Default to keeping it as a draft or scheduling it.**
94
94
 
95
95
  - **Keep as draft** — do nothing further; the draft already exists and is visible at `/marketing/content`.
96
- - **Schedule** — call `manage_content_post` with `action="schedule"` and a `scheduled_at` ISO-8601 timestamp. It auto-publishes when due.
96
+ - **Schedule** — call `manage_content` with `action="schedule"` and a `scheduled_at` ISO-8601 timestamp. It auto-publishes when due.
97
97
  - **Publish now** — this is destructive and irreversible, and it will fail (marking the draft unusable) if no LinkedIn account is connected. So treat it as a deliberate, confirmed action:
98
98
  - Never call publish as a probe or a default.
99
- - Only call `manage_content_post` with `action="publish"` **after** the seller has explicitly confirmed "publish now."
99
+ - Only call `manage_content` with `action="publish"` **after** the seller has explicitly confirmed "publish now."
100
100
  - Always offer save-as-draft or schedule as the safer fallback when proposing publish.
101
101
 
102
102
  **Author-scoped publish — the backend refuses to fall back to another identity.** Schedule/publish read the author from the draft row. For an author-scoped post (one you authored as a specific person in Step 2, so the draft carries their `author_user_id`), the backend resolves *that person's* LinkedIn account **strictly**: if their account is missing, disconnected, or its `quota` is exhausted at publish time, the publish **fails hard with an `Author account unavailable` error** instead of posting from another connected company account. So an author-scoped post can never silently go out under a *different person's identity* than the one you chose — the server enforces this, including for scheduled posts that publish later at worker execution time (long after this conversation). A legacy / no-author post (you omitted `author_user_id` in Step 2) keeps the old company-wide fallback — there's no specific identity to protect.
103
103
 
104
- Because that hard failure lands at publish time — which for a scheduled post can be minutes or hours after you draft it — surface it **early** rather than letting the operator discover a dead, `failed` post later. So before you schedule or publish a post you authored as a specific person, **call `get_channel_status` again — fresh, right now, immediately before the schedule/publish call.** Do **not** trust the Step 2 snapshot: an account can disconnect, change `status`, or exhaust its `quota` during drafting and refinement. Re-read `channels.linkedin_accounts` from this *new* response and find the author's account by the `user_id` you stamped on the draft, then:
104
+ Because that hard failure lands at publish time — which for a scheduled post can be minutes or hours after you draft it — surface it **early** rather than letting the operator discover a dead, `failed` post later. So before you schedule or publish a post you authored as a specific person, **call the channel-status `fetch` (type=settings, subtype=channel_status) again — fresh, right now, immediately before the schedule/publish call.** Do **not** trust the Step 2 snapshot: an account can disconnect, change `status`, or exhaust its `quota` during drafting and refinement. Re-read `channels.linkedin_accounts` from this *new* response and find the author's account by the `user_id` you stamped on the draft, then:
105
105
 
106
106
  - If their account is present, `connected`, `status` is healthy, and `quota` is not exhausted **in the fresh response** → proceed with schedule/publish as normal (still behind the explicit "publish now" confirmation above).
107
- - If their account is **missing, not `connected`, shows a bad `status`, or has an exhausted `quota` in the fresh response** (or the fresh `get_channel_status` call fails / omits `linkedin_accounts`, so you can't confirm the author's account is healthy) → **STOP. Do not schedule or publish.** The backend would reject this author-scoped publish as `Author account unavailable` anyway; tell the operator plainly so they don't end up with a `failed` post. Offer the safe paths: keep it as a draft, reschedule for after that person's account is reconnected / their quota resets, or pick a different permitted author and regenerate. There is no "publish under a different identity" escape hatch for an author-scoped post — the server will not do it; to post from another account the operator must deliberately regenerate the draft under that author (or with no author).
107
+ - If their account is **missing, not `connected`, shows a bad `status`, or has an exhausted `quota` in the fresh response** (or the fresh channel-status `fetch` call fails / omits `linkedin_accounts`, so you can't confirm the author's account is healthy) → **STOP. Do not schedule or publish.** The backend would reject this author-scoped publish as `Author account unavailable` anyway; tell the operator plainly so they don't end up with a `failed` post. Offer the safe paths: keep it as a draft, reschedule for after that person's account is reconnected / their quota resets, or pick a different permitted author and regenerate. There is no "publish under a different identity" escape hatch for an author-scoped post — the server will not do it; to post from another account the operator must deliberately regenerate the draft under that author (or with no author).
108
108
 
109
109
  For a legacy / no-author post (you omitted `author_user_id` in Step 2) there is no specific author identity to protect, so the standard publish confirmation above is sufficient.
110
110
 
@@ -10,7 +10,7 @@ description: >-
10
10
 
11
11
  You are the CSV harness-mode source for `/pipeline-fill`. You read a CSV the operator provides (Apollo export, ZoomInfo, hand-built spreadsheet, LinkedIn export — anything), normalize columns to the canonical candidate-list shape, and hand off to the orchestrator.
12
12
 
13
- **This is the harness counterpart to `/csv-platform-fill`.** The platform skill calls `start_csv_import` and lets backend agents do everything. This skill stops at producing a candidate list — Phase A and Phase B run in your chat session (your harness compute), and `save_discovered_person` is called only after the harness pre-filter gate passes.
13
+ **This is the harness counterpart to `/csv-platform-fill`.** The platform skill calls `import_prospects` with action=csv_start and lets backend agents do everything. This skill stops at producing a candidate list — Phase A and Phase B run in your chat session (your harness compute), and `manage_person` with action=save_discovered is called only after the harness pre-filter gate passes.
14
14
 
15
15
  ## Inputs
16
16
 
@@ -88,7 +88,7 @@ Convert each row to the canonical shape (defined in `pipeline-fill/RESEARCH-ENGI
88
88
  }
89
89
  ```
90
90
 
91
- The `csv_extra_columns` field keeps unmapped data on the candidate so an operator can later inspect it via `get_user_people` if a question comes up about why a particular prospect was imported.
91
+ The `csv_extra_columns` field keeps unmapped data on the candidate so an operator can later inspect it via `search` type=people if a question comes up about why a particular prospect was imported.
92
92
 
93
93
  ### Step 8: Hand off to /pipeline-fill (canonical handoff prompt)
94
94
 
@@ -118,5 +118,5 @@ Continue automatically? (y/n)
118
118
  ## Notes
119
119
 
120
120
  - **Sourcing phase is ~free** (file read + parsing). Real-money costs live in `/pipeline-fill`'s deep-research subagents.
121
- - **For backend-driven CSV import** (fire-and-forget, no in-chat research): the platform-mode CSV skill calls `start_csv_import` directly and lets backend agents do sourcing/research/gate. Pick this skill when you want to see the deep research happen in your chat instead.
121
+ - **For backend-driven CSV import** (fire-and-forget, no in-chat research): the platform-mode CSV skill calls `import_prospects` with action=csv_start directly and lets backend agents do sourcing/research/gate. Pick this skill when you want to see the deep research happen in your chat instead.
122
122
  - **Composability** with `/pipeline-fill`: standard pattern. Run this skill standalone for "I just want this CSV imported with deep research today" or invoke via the orchestrator's source picker.
@@ -17,7 +17,7 @@ Deal review requires cross-referencing multiple data sources per deal: stakehold
17
17
  ## Subagent architecture
18
18
 
19
19
  This skill uses the custom agent `vruum-deal-reviewer` (bundled at `agents/vruum-deal-reviewer.md`). That agent has:
20
- - **Read-only** Vruum MCP access (`get_deal`, `get_deal_360`, `get_deal_alerts`, `inspect_pipeline`, `get_person_360`, `get_company_research`, `get_account_state`). It has NO write tools by design — review is analysis, not mutation. Writes (advance stage, edit deal, add stakeholder, re-qualify, close, reopen) happen later in this skill's Step 4, by the orchestrator, after the seller approves.
20
+ - **Read-only** Vruum MCP access (`fetch` for deal, deal_alerts, company_research, and account_state reads; `search` for deal lists; plus the composites `get_deal_360`, `inspect_pipeline`, `get_person_360`). It has NO write tools (no `manage_deal`) by design — review is analysis, not mutation. Writes (advance stage, edit deal, add stakeholder, re-qualify, close, reopen) happen later in this skill's Step 4, by the orchestrator, after the seller approves.
21
21
  - Web search for prospect/company research
22
22
  - Complete deal review instructions baked into its system prompt
23
23
 
@@ -32,7 +32,7 @@ For small reviews (3 or fewer flagged deals) or when subagents can't access MCP,
32
32
  Call two MCP tools to understand the current state:
33
33
 
34
34
  1. `inspect_pipeline` — returns top at-risk deals with risk scores, risk factors, days in stage
35
- 2. `get_deal_alerts` — returns all active alerts (silence 7+ days, overdue next steps, slippage past close date)
35
+ 2. `fetch` with type=deal_alerts — returns all active alerts (silence 7+ days, overdue next steps, slippage past close date)
36
36
 
37
37
  Combine the results into a prioritized triage list. Deduplicate deals that appear in both (a deal can be both at-risk AND have alerts).
38
38
 
@@ -44,7 +44,7 @@ Present a brief overview:
44
44
 
45
45
  ### Step 2: Dispatch parallel subagents
46
46
 
47
- For each unique flagged deal (from `inspect_pipeline` + `get_deal_alerts`, max 7), spawn a `vruum-deal-reviewer` subagent with `run_in_background=true`.
47
+ For each unique flagged deal (from `inspect_pipeline` + the deal_alerts fetch, max 7), spawn a `vruum-deal-reviewer` subagent with `run_in_background=true`.
48
48
 
49
49
  Each subagent prompt should include:
50
50
  - The `deal_id`
@@ -53,10 +53,10 @@ Each subagent prompt should include:
53
53
  - Instructions to follow the subagent workflow below
54
54
 
55
55
  **Subagent workflow** (each subagent runs read-only and independently — its tool surface excludes deal writes by design; mutation happens later in Step 4 with the seller's approval):
56
- 1. Call `get_deal_360` for the full deal context in one call (deal info, stakeholders, MEDDIC qualification state, recent activity timeline). If the consolidated endpoint isn't available in your tool list, fall back to `get_deal` — the deal row carries `qualification` and `qualification_score` when previously computed.
57
- 2. **Read** `qualification` / `qualification_score` from the response — do NOT call `qualify_deal` from the reviewer. `qualify_deal` writes a fresh MEDDIC JSONB (an LLM call + a DB write); the reviewer is read-only. If `qualification` is null, the score is < 40, or the last qualification is older than 30 days, the reviewer emits a `re_qualify` recommendation and the orchestrator (this skill) runs `qualify_deal` ONLY after the seller approves in Step 4.
56
+ 1. Call `get_deal_360` for the full deal context in one call (deal info, stakeholders, MEDDIC qualification state, recent activity timeline). If the consolidated endpoint isn't available in your tool list, fall back to `fetch` with type=deal — the deal row carries `qualification` and `qualification_score` when previously computed.
57
+ 2. **Read** `qualification` / `qualification_score` from the response — do NOT qualify from the reviewer. `manage_deal` with action=qualify writes a fresh MEDDIC JSONB (an LLM call + a DB write); the reviewer is read-only. If `qualification` is null, the score is < 40, or the last qualification is older than 30 days, the reviewer emits a `re_qualify` recommendation and the orchestrator (this skill) runs `manage_deal` action=qualify ONLY after the seller approves in Step 4.
58
58
  3. Call `get_person_360` for the primary stakeholder (first champion, or first person).
59
- 4. Call `get_account_state` for the deal's account stage + health. If 404 (no row yet), default to `prospect` / null health.
59
+ 4. Call `fetch` with type=account_state for the deal's account stage + health. If 404 (no row yet), default to `prospect` / null health.
60
60
  5. Return a structured summary in this exact format:
61
61
 
62
62
  ```
@@ -94,18 +94,18 @@ For each deal, show the structured summary. Highlight critical alerts in bold.
94
94
 
95
95
  After presenting results, the user can request actions. Execute them using MCP tools:
96
96
 
97
- - **Advance stage** → `update_deal` with new stage
98
- - **Set next step** → `update_deal` with `next_step` + `next_step_due_at`
99
- - **Add stakeholder** → `manage_deal_stakeholders` with action='add'
100
- - **Re-qualify** → `qualify_deal` (runs MEDDIC analysis again)
101
- - **Close deal** → `record_deal_outcome` with outcome (won/lost/stalled)
102
- - **Reopen deal** → `reopen_deal` with desired stage
103
- - **Mark stalled** → `update_deal` with stage='stalled'
97
+ - **Advance stage** → `manage_deal` action=stage with payload={stage}
98
+ - **Set next step** → `manage_deal` action=update with payload={next_step, next_step_due_at}
99
+ - **Add stakeholder** → `manage_deal` action=stakeholders with payload={action: 'add', person_id, role}
100
+ - **Re-qualify** → `manage_deal` action=qualify (runs MEDDIC analysis again)
101
+ - **Close deal** → `manage_deal` action=won or action=lost (payload carries win_factors / loss_reason)
102
+ - **Reopen deal** → `manage_deal` action=reopen with payload={stage}
103
+ - **Mark stalled** → `manage_deal` action=stalled (records the stalled outcome; payload optional)
104
104
 
105
105
  For batch actions ("advance all deals in proposal"), confirm with the user before executing.
106
106
 
107
107
  ## Error handling
108
108
 
109
109
  - If a subagent fails (LLM rate limit, timeout, tool error): present results for successful subagents, note failures
110
- - If `inspect_pipeline` or `get_deal_alerts` fails: fall back to `get_deals` and manually check `updated_at` for staleness
110
+ - If `inspect_pipeline` or the deal_alerts fetch fails: fall back to `search` with type=deals and manually check `updated_at` for staleness
111
111
  - Never block the entire triage on a single failure