@sellable/mcp 0.1.951-wip.sendercontext.20261001.1 → 0.1.952
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tools/bootstrap.d.ts +1 -1
- package/dist/tools/bootstrap.js +2 -2
- package/dist/tools/campaigns.d.ts +2 -0
- package/dist/tools/campaigns.js +4 -1
- package/dist/tools/registry.d.ts +2 -0
- package/package.json +1 -1
- package/skills/create-campaign/references/ai-native-tokens.md +7 -0
- package/skills/create-campaign-v2/core/policy.md +4 -1
- package/skills/create-campaign-v2/references/watch-guide-narration.md +2 -1
- package/skills/create-campaign-v2-tail/SKILL.md +2 -4
- package/skills/first-campaign-onboarding/SKILL.md +218 -13
- package/skills/research-sender/SKILL.md +28 -8
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
import { type AuthStatus } from "./auth.js";
|
|
2
2
|
import { getCampaignContext } from "./context.js";
|
|
3
3
|
import { getCampaignFramework } from "./framework.js";
|
|
4
|
-
import { type ListSubskillPromptsResponse, type SubskillPromptResponse } from "./prompts.js";
|
|
5
4
|
import { type CampaignModelQualityResult } from "./model-quality.js";
|
|
5
|
+
import { type ListSubskillPromptsResponse, type SubskillPromptResponse } from "./prompts.js";
|
|
6
6
|
type BootstrapCreateCampaignInput = {
|
|
7
7
|
campaignId?: string;
|
|
8
8
|
flowVersion?: "v1" | "v2";
|
package/dist/tools/bootstrap.js
CHANGED
|
@@ -2,8 +2,8 @@ import { resetApi } from "../api.js";
|
|
|
2
2
|
import { getAuthStatus } from "./auth.js";
|
|
3
3
|
import { getCampaignContext } from "./context.js";
|
|
4
4
|
import { getCampaignFramework } from "./framework.js";
|
|
5
|
-
import { getSubskillPrompt, listSubskillPrompts, } from "./prompts.js";
|
|
6
5
|
import { evaluateCampaignModelQuality, getCampaignModelMinimumSummary, } from "./model-quality.js";
|
|
6
|
+
import { getSubskillPrompt, listSubskillPrompts, } from "./prompts.js";
|
|
7
7
|
function toErrorMessage(error) {
|
|
8
8
|
return error instanceof Error ? error.message : String(error);
|
|
9
9
|
}
|
|
@@ -307,7 +307,7 @@ export async function bootstrapCreateCampaign(input = {}) {
|
|
|
307
307
|
? resumeDetected
|
|
308
308
|
? `Bootstrap complete.${workspaceNotice}${modelNotice} Resume from campaign state and navigation diagnostics first; treat local draft artifacts as debug-only evidence. Then load ${createCampaignSubskill?.name ?? "create-campaign"} instructions with get_subskill_prompt({ subskillName: "${createCampaignSubskill?.name ?? "create-campaign"}" }); if the response has hasMore=true, continue with nextOffset until hasMore=false.`
|
|
309
309
|
: flowVersion === "v2"
|
|
310
|
-
? `Bootstrap complete.${workspaceNotice}${modelNotice} Load the compact create-campaign-v2 entry prompt once with get_subskill_prompt({ subskillName: "create-campaign-v2" }); Hermes users should start this flow with /sellable-create-campaign. Load flow/reference assets lazily only when that stage needs them. Preserve the pre-intake sequence: confirm auth/workspace status, ask only for the LinkedIn profile URL or handle, normalize handles to a full profile URL, require that profile identity before continuing, run lightweight profile/company lookup, then ask the target, offer, credibility, and prospect-source setup questions. Do not call list_senders or sender discovery during setup; sender availability belongs only to Settings after message approval. Then write the campaign brief, call create_campaign once to mint the watchable shell, surface the returned watch link once before brief approval, and hand off to lead finding without repeating the link.`
|
|
310
|
+
? `Bootstrap complete.${workspaceNotice}${modelNotice} Load the compact create-campaign-v2 entry prompt once with get_subskill_prompt({ subskillName: "create-campaign-v2" }); Hermes users should start this flow with /sellable-create-campaign. Load flow/reference assets lazily only when that stage needs them. Preserve the pre-intake sequence: confirm auth/workspace status, ask only for the LinkedIn profile URL or handle, normalize handles to a full profile URL, require that profile identity before continuing, run lightweight profile/company lookup, then ask the target, offer, credibility, and prospect-source setup questions. Do not call list_senders or sender discovery during setup; sender availability belongs only to Settings after message approval. Then write the campaign brief, call create_campaign once to mint the watchable shell, surface the returned watch link once before brief approval, and hand off to lead finding without repeating the link. Exception: when you entered from the first-campaign-onboarding skill, its first-campaign rules replace the workspace notice, the slash-command note, the setup questions, and the watch-link handoff described here.`
|
|
311
311
|
: `Bootstrap complete.${workspaceNotice}${modelNotice} Load ${createCampaignSubskill?.name ?? "create-campaign"} instructions with get_subskill_prompt({ subskillName: "${createCampaignSubskill?.name ?? "create-campaign"}" }); if the response has hasMore=true, continue with nextOffset until hasMore=false. Follow that flow before calling create_campaign.`
|
|
312
312
|
: "Bootstrap incomplete. Resolve blockingErrors and rerun bootstrap_create_campaign before provider/search/import tools.";
|
|
313
313
|
// Strip prompt body from createCampaignSubskill — it's loaded via the host
|
|
@@ -619,6 +619,7 @@ export declare const campaignToolDefinitions: ({
|
|
|
619
619
|
};
|
|
620
620
|
safety: {
|
|
621
621
|
type: string[];
|
|
622
|
+
description: string;
|
|
622
623
|
};
|
|
623
624
|
progressLabel: {
|
|
624
625
|
type: string[];
|
|
@@ -859,6 +860,7 @@ export declare const campaignToolDefinitions: ({
|
|
|
859
860
|
};
|
|
860
861
|
safety: {
|
|
861
862
|
type: string[];
|
|
863
|
+
description: string;
|
|
862
864
|
};
|
|
863
865
|
progressLabel: {
|
|
864
866
|
type: string[];
|
package/dist/tools/campaigns.js
CHANGED
|
@@ -29,7 +29,10 @@ const WATCH_NARRATION_TOOL_SCHEMA = {
|
|
|
29
29
|
visibleState: { type: "string" },
|
|
30
30
|
agentIntent: { type: "string" },
|
|
31
31
|
nextAction: { type: ["string", "null"] },
|
|
32
|
-
safety: {
|
|
32
|
+
safety: {
|
|
33
|
+
type: ["string", "null"],
|
|
34
|
+
description: 'Required whenever currentStep changes: one short line on what will not happen yet (for example "Rules only. No one is contacted."). Optional otherwise.',
|
|
35
|
+
},
|
|
33
36
|
progressLabel: { type: ["string", "null"] },
|
|
34
37
|
blockedReason: { type: ["string", "null"] },
|
|
35
38
|
workerStatuses: {
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -2702,6 +2702,7 @@ export declare const allTools: ({
|
|
|
2702
2702
|
};
|
|
2703
2703
|
safety: {
|
|
2704
2704
|
type: string[];
|
|
2705
|
+
description: string;
|
|
2705
2706
|
};
|
|
2706
2707
|
progressLabel: {
|
|
2707
2708
|
type: string[];
|
|
@@ -2942,6 +2943,7 @@ export declare const allTools: ({
|
|
|
2942
2943
|
};
|
|
2943
2944
|
safety: {
|
|
2944
2945
|
type: string[];
|
|
2946
|
+
description: string;
|
|
2945
2947
|
};
|
|
2946
2948
|
progressLabel: {
|
|
2947
2949
|
type: string[];
|
package/package.json
CHANGED
|
@@ -53,6 +53,13 @@ The bracket is replaced at generation time by either (a) the rendered
|
|
|
53
53
|
sentence, or (b) nothing (omit). The brackets themselves never appear in
|
|
54
54
|
the final message.
|
|
55
55
|
|
|
56
|
+
**When you save the template in the campaign brief, write each bracket on one
|
|
57
|
+
line.** The examples below wrap a bracket across lines so it is readable here.
|
|
58
|
+
The brief validator reads a bracket that spans three or more lines as message
|
|
59
|
+
prose and rejects the save ("3+ consecutive non-empty prose lines with only
|
|
60
|
+
single newlines"). Keep the DO / DON'T / FALLBACK labels; drop the line breaks
|
|
61
|
+
inside the bracket.
|
|
62
|
+
|
|
56
63
|
## Required clauses inside an AI-native token
|
|
57
64
|
|
|
58
65
|
Every well-specified AI-native token must include four things:
|
|
@@ -90,5 +90,8 @@ messages that pass my quality check and launch for you, then keep your sends fil
|
|
|
90
90
|
Reply review first if you would rather approve." Save the answer with `customer_program`
|
|
91
91
|
`save_context` as `launchConsent: { mode: "autopilot" | "review_first", decidedAt }`.
|
|
92
92
|
A yes, or no objection, is `autopilot`; a wish to approve first is `review_first`.
|
|
93
|
-
|
|
93
|
+
A reply that answers other questions and says nothing about this offer is not an answer to it:
|
|
94
|
+
save nothing and keep every approval. Never ask again; a later "review first" switches to `review_first`, and "pause" sets the saved work and sending pauses with `set_preferences`. If the
|
|
94
95
|
customer has not answered when you reach launch, keep the launch confirmation.
|
|
96
|
+
During a new workspace's first campaign (`first-campaign-onboarding`) do not make this offer at all:
|
|
97
|
+
the first launch always needs the customer's explicit yes.
|
|
@@ -40,7 +40,8 @@ states.
|
|
|
40
40
|
sample you are checking, and why that helps this campaign.
|
|
41
41
|
- At filter choice, do not say the batch is filtering before rubrics are saved.
|
|
42
42
|
- Avoid internal terms: MCP, tool, currentStep, workflow table, scout, debug.
|
|
43
|
-
-
|
|
43
|
+
- Always send a short `safety` line when the step changes; the API rejects a
|
|
44
|
+
step change without one. It is internal context, not a visible disclaimer.
|
|
44
45
|
- Do not repeat long negative lists like "no leads import, no enrichment, no
|
|
45
46
|
messages, no sequence, no sending" in routine watch copy. Prefer one concise
|
|
46
47
|
gate phrase only when it matters: "This only approves me to look for the best
|
|
@@ -198,9 +198,8 @@ import milestone.
|
|
|
198
198
|
`messaging.critique.enabled`, `handoff.autoStart`,
|
|
199
199
|
`handoff.orientation`, `retry.sameToolSameError`,
|
|
200
200
|
`logging.logEveryThresholdTrip`.
|
|
201
|
-
2. Resolve and materialize the approved source after Start Import approval.
|
|
202
|
-
`references/
|
|
203
|
-
discovery or when a legacy no-shell approval fixture must be replayed. If
|
|
201
|
+
2. Resolve and materialize the approved source after Start Import approval.
|
|
202
|
+
`references/step-13-import-leads.md` holds the import steps. If
|
|
204
203
|
CampaignOffer state already attached searches/selections to this campaign,
|
|
205
204
|
reuse them. If there is no `lead-source-intake.json`, replay the approved
|
|
206
205
|
provider recipe from `lead-review.md` with `campaignOfferId` before
|
|
@@ -622,7 +621,6 @@ runs.
|
|
|
622
621
|
| ------------------------------------------------ | ------------------------------------------------------------------------- |
|
|
623
622
|
| `references/approval-gate-framing.md` | Approval gate, before showing the approval packet |
|
|
624
623
|
| `references/watch-link-handoff.md` | First brief handoff + explicit link recovery only |
|
|
625
|
-
| `references/post-mint-source-materialization.md` | Step 13, before importing normal-discovery or legacy campaignless sources |
|
|
626
624
|
| `references/sample-validation-loop.md` | Step 14, before enriching + scoring the sample |
|
|
627
625
|
| `references/escalation-ladder.md` | Any tail step that needs to decide retry / revise / escalate |
|
|
628
626
|
| `references/final-handoff-contract.md` | Step 16, and every Claude greenlight turn |
|
|
@@ -48,11 +48,14 @@ reply to either the same way.)
|
|
|
48
48
|
A reply in that thread is explicit permission to begin preparation, not
|
|
49
49
|
permission to send. It needs no mention of this Agent.
|
|
50
50
|
|
|
51
|
-
- **The reply contains a LinkedIn profile URL.**
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
51
|
+
- **The reply contains a LinkedIn profile URL.** Send one short line first so
|
|
52
|
+
the customer is not left looking at silence: "Got it. Researching you and
|
|
53
|
+
your company now. I'll be back in a few minutes with a first campaign to
|
|
54
|
+
review." Send it before the Live State Check and before loading any other
|
|
55
|
+
skill or reference; those reads take over a minute. Then run the Live State
|
|
56
|
+
Check and enter **Create-Campaign Handoff** in the same turn, with that URL
|
|
57
|
+
as the sender identity. If the Live State Check finds a campaign already
|
|
58
|
+
launched, say so in the next line and offer to help with that campaign.
|
|
56
59
|
- **The reply is a yes, or anything else that agrees, with no profile URL.**
|
|
57
60
|
Ask for it once, in one sentence: "What's your LinkedIn profile URL? I'll
|
|
58
61
|
use it to research you and your company." Ask nothing else.
|
|
@@ -209,18 +212,220 @@ continue in the same conversation; do not ask the user to run another command.
|
|
|
209
212
|
|
|
210
213
|
1. Call `bootstrap_create_campaign({ flowVersion:"v2", host:<current host> })`.
|
|
211
214
|
2. Load `get_subskill_prompt({ subskillName:"create-campaign-v2" })` to
|
|
212
|
-
`hasMore:false`
|
|
215
|
+
`hasMore:false`, then `core/flow.v2.json`. Load every other reference only
|
|
216
|
+
at the stage that needs it. The customer is waiting in silence, so make
|
|
217
|
+
these reads in as few turns as you can: the Live State Check reads together
|
|
218
|
+
in one turn; then bootstrap, the create-campaign-v2 prompt, the flow file,
|
|
219
|
+
`research-sender` and `list_dnc_entries({ limit:1 })` together in the next.
|
|
213
220
|
3. Reuse known identity, company, audience, offer, and any explicit DNC decision.
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
221
|
+
A public LinkedIn profile URL for company research is not a connected
|
|
222
|
+
sending account: the latter belongs at Settings, not before the draft.
|
|
223
|
+
The flow's order and state gates still hold: the campaign exists before
|
|
224
|
+
anyone is imported, the source is chosen before the import, sample rows
|
|
225
|
+
exist before filters and messages run, and the template is saved before
|
|
226
|
+
anything is queued. Who decides each one is set by the next section. Offer
|
|
227
|
+
CRM or meeting integrations only as optional context when helpful.
|
|
219
228
|
4. Reuse an existing draft for the same goal. Never create a duplicate because
|
|
220
229
|
onboarding resumed in a new session.
|
|
221
230
|
5. Agreement authorizes campaign creation/preparation only, not launch. Never
|
|
222
|
-
call `start_campaign` until the
|
|
223
|
-
|
|
231
|
+
call `start_campaign` until the customer has seen who sends and how many a
|
|
232
|
+
day, and explicitly says to launch.
|
|
233
|
+
|
|
234
|
+
### The first campaign in three approvals
|
|
235
|
+
|
|
236
|
+
The welcome promised "a first campaign for you to review". For a new
|
|
237
|
+
workspace's first campaign the customer is asked for three things and nothing
|
|
238
|
+
else: the **direction**, the **sample**, and the **launch**. You decide
|
|
239
|
+
everything in between yourself, using the flow's own hands-off rules
|
|
240
|
+
(`yoloMode.autoSelectsPreLaunchChoices`, except its "brief approval" and
|
|
241
|
+
"generated message review" entries, which are approvals 1 and 2 here), and say
|
|
242
|
+
what you chose in one plain line when you report back. Where the general workflow, a reference file, or a
|
|
243
|
+
tool result says otherwise, this section wins for the first campaign.
|
|
244
|
+
|
|
245
|
+
This is not the standing `launchConsent` autopilot. Do not ask about or save
|
|
246
|
+
`launchConsent` during the first campaign, and never skip one of the three
|
|
247
|
+
approvals. Silence, or a reply that answers something else, is never an
|
|
248
|
+
approval. The daily review offers hands-off sending once the first campaign is
|
|
249
|
+
running.
|
|
250
|
+
|
|
251
|
+
**What you never ask.** Not the four setup questions ("Who should we target
|
|
252
|
+
first?", "What should we pitch?", the credibility question, "How should we
|
|
253
|
+
find prospects?"): giving the profile URL is the customer's instruction to use
|
|
254
|
+
Sellable's researched recommendation and to find prospects for them, which is
|
|
255
|
+
the flow's own skip rule for each question. Not "is this the right company?"
|
|
256
|
+
or "which of your offers should I lead with?": pick the one the profile and
|
|
257
|
+
site lead with and say so in the direction, where they can correct it. Not a
|
|
258
|
+
separate approval for where
|
|
259
|
+
to look, for starting the import, for using filters, for the filter rules, or
|
|
260
|
+
for the message template: those are yours to decide and report.
|
|
261
|
+
|
|
262
|
+
**How you write in Slack.**
|
|
263
|
+
|
|
264
|
+
- Ask in plain words the customer can answer with yes or with the change they
|
|
265
|
+
want. Slack has no question panel: never ask for option letters, and never
|
|
266
|
+
mention a question panel or a config file.
|
|
267
|
+
- Post nothing between "Got it" and the direction: no research progress lines,
|
|
268
|
+
tool names, counts of proof items, or which workspace you are in (this Agent
|
|
269
|
+
is locked to one workspace and the customer knows which).
|
|
270
|
+
- Never print the "WATCH … BUILD THE CAMPAIGN LIVE" block, "Keep this chat
|
|
271
|
+
open", "Brief approved", or any line that tells the customer to run a
|
|
272
|
+
command. Those are written for other hosts.
|
|
273
|
+
- The channel is shared, and the returned `watchUrl` carries a sign-in token.
|
|
274
|
+
Post that URL with only its `token` parameter removed, keeping the rest
|
|
275
|
+
exactly as returned. The customer signed in to Sellable when they set this
|
|
276
|
+
Agent up. This replaces the tool's instruction to print the handoff block
|
|
277
|
+
and its rule against changing the link.
|
|
278
|
+
- Do not paste the full brief into Slack. Save it on the campaign and post the
|
|
279
|
+
five-line summary below.
|
|
280
|
+
|
|
281
|
+
**Approval 1: direction.** After research, write the brief, create the
|
|
282
|
+
campaign with it (`create_campaign`), and post one message in this shape. The
|
|
283
|
+
saved brief is the full brief; this message is how the customer approves it,
|
|
284
|
+
so skip the flow's setup receipt, its "Does this brief look right…" question
|
|
285
|
+
and the inline brief.
|
|
286
|
+
|
|
287
|
+
```text
|
|
288
|
+
Here's the first campaign I'd run for {company}.
|
|
289
|
+
|
|
290
|
+
*Who* {the buyer, one line}
|
|
291
|
+
*Why now* {why these people care right now, one line}
|
|
292
|
+
*Offer* {what you would get to once they reply, and the small ask, one line}
|
|
293
|
+
*Proof* {the proof you would lead with; if you found none, say so and what you would use instead}
|
|
294
|
+
*Where I'll look* {where you will find them first, in plain words}
|
|
295
|
+
|
|
296
|
+
Full brief: {campaign link}
|
|
297
|
+
|
|
298
|
+
Two things from you:
|
|
299
|
+
1. Does this look right? Reply yes, or tell me what to change.
|
|
300
|
+
2. Is there anyone I must never contact: customers, open deals, people who opted out? Paste the list here, or say "no list".
|
|
301
|
+
|
|
302
|
+
Nothing is sent on LinkedIn until you approve the launch.
|
|
303
|
+
```
|
|
304
|
+
|
|
305
|
+
The first message to a prospect opens a conversation; the message rules
|
|
306
|
+
decide its wording, and they keep the pitch and the ask out of it. So write
|
|
307
|
+
the *Offer* line as what comes once the prospect replies, and do not promise,
|
|
308
|
+
here or in the saved brief, that the first message will mention the post
|
|
309
|
+
someone engaged with or carry the ask.
|
|
310
|
+
|
|
311
|
+
If something you assumed could be stale (for example the profile pitches two
|
|
312
|
+
different things), say which one you picked in the line it affects. Leave out
|
|
313
|
+
the second question, and write "One thing from you:", when
|
|
314
|
+
`list_dnc_entries` already shows a list or the saved program context has
|
|
315
|
+
`dncState` `provided` or `explicit_none`. A yes, or "keep going", with no
|
|
316
|
+
answer to the second question is not a "no list" decision, and it is not a
|
|
317
|
+
reason to stop either: carry on with the list as it is, add to your
|
|
318
|
+
acknowledgement "You can send me a do-not-contact list any time before
|
|
319
|
+
launch.", and ask once more in the launch message. Never hold the work
|
|
320
|
+
waiting for a list. Follow the DNC Contract for a pasted list, and save
|
|
321
|
+
the decision with `customer_program` `save_context` (`dncState:
|
|
322
|
+
"explicit_none"` for "no list", `"provided"` once a list is imported) so a
|
|
323
|
+
later session does not ask again. The answer given here settles the flow's
|
|
324
|
+
do-not-contact gate; do not ask again before the import.
|
|
325
|
+
|
|
326
|
+
**Between 1 and 2.** Once the direction and the do-not-contact answer are in,
|
|
327
|
+
reply first, before any tool call: "On it. I'll find the people, check who
|
|
328
|
+
fits and write the message. Your sample will be here in about 15 to 20
|
|
329
|
+
minutes." Then set `interactionMode: "autonomous"` on the campaign with
|
|
330
|
+
`update_campaign` and pass `confirmed: true` where a search or import tool
|
|
331
|
+
asks for it: the direction approval is that confirmation. Do the work without
|
|
332
|
+
asking: pick where to look, find and import the people within the flow's
|
|
333
|
+
caps, use filters and write the rules, write the message, and prepare the
|
|
334
|
+
15-person sample.
|
|
335
|
+
|
|
336
|
+
- **Keep the customer posted.** Whenever about five minutes have passed since
|
|
337
|
+
your last message, post one short line in plain words on where you are
|
|
338
|
+
("Found 370 people who engaged with three posts about this. Checking who
|
|
339
|
+
fits and writing the message now."). No more often than that.
|
|
340
|
+
- **The live builder.** Where the flow's required watch copy tells the
|
|
341
|
+
customer to approve something this section removed, write instead what is
|
|
342
|
+
happening and that nothing is needed from them yet.
|
|
343
|
+
- **Import size.** If `import_leads` says the selected posts cover fewer
|
|
344
|
+
people than the target, select again with the number it says they cover and
|
|
345
|
+
retry once. Do not start a new search for a small gap.
|
|
346
|
+
- **Waits.** A wait tool that stops with `tool_timeout_guard` has only reached
|
|
347
|
+
the host's time limit. Call it again while work is still running.
|
|
348
|
+
- **A sample where nobody fits is not a pause.** Follow the flow's own
|
|
349
|
+
recovery first: a fresh sample from the same list. Stop early only for the
|
|
350
|
+
flow's must-pause reasons: missing data, or a source, filter or message
|
|
351
|
+
that still fails its quality floor after that recovery. Then say what
|
|
352
|
+
failed and what you suggest, as one question.
|
|
353
|
+
- **Let the sample finish.** The flow stops at the first passing message; here
|
|
354
|
+
wait until the 15-person sample has finished scoring and writing, because
|
|
355
|
+
the customer is shown three people and a count.
|
|
356
|
+
- **Check before you show.** For each message you are about to show, compare
|
|
357
|
+
the company it names with the person's own headline. Leave out any that
|
|
358
|
+
disagree and say that you dropped them.
|
|
359
|
+
|
|
360
|
+
**Approval 2: sample.** When the sample is ready, post real people and the
|
|
361
|
+
real message each would receive:
|
|
362
|
+
|
|
363
|
+
```text
|
|
364
|
+
Your sample is ready: {n} real people and the message each would get.
|
|
365
|
+
|
|
366
|
+
{Name} · {title}, {company} · {why they are here, a few words}
|
|
367
|
+
"{the message they would receive}"
|
|
368
|
+
|
|
369
|
+
{two more like this}
|
|
370
|
+
|
|
371
|
+
{passed} of {checked} people I checked fit. {one line on why the rest did not}
|
|
372
|
+
What I chose: {where the people came from and how many are on the list, and the fit rules in a few words}
|
|
373
|
+
The first message has no pitch on purpose. {the offer} comes once they reply.
|
|
374
|
+
Everyone and every message: {campaign link}
|
|
375
|
+
|
|
376
|
+
Reply approve to use these people and this message, or tell me what to change.
|
|
377
|
+
Nothing has been sent.
|
|
378
|
+
```
|
|
379
|
+
|
|
380
|
+
**After "approve".** Reply first: "Approved." plus anything they changed, in
|
|
381
|
+
the same line ("Approved, without Corey."). Mark every person in the sample
|
|
382
|
+
the customer kept as approved in the table, not one or two. A person the
|
|
383
|
+
customer dropped stays unapproved; never approve them later, in bulk or
|
|
384
|
+
otherwise.
|
|
385
|
+
|
|
386
|
+
**Approval 3: launch.** The only thing left is the sending account. Call
|
|
387
|
+
`list_senders`.
|
|
388
|
+
|
|
389
|
+
- **No usable sender.** Ask for that one thing: "One thing left: connect the
|
|
390
|
+
LinkedIn account that will send. https://app.sellable.dev/linkedin-accounts
|
|
391
|
+
It takes about two minutes, and it is only used to send what you approve;
|
|
392
|
+
finding and checking people happened outside your account. Tell me when
|
|
393
|
+
it's done." When they say it is done, call `list_senders` again. If nothing
|
|
394
|
+
usable is there yet, say so plainly and give the link again.
|
|
395
|
+
- **One connected sender that is the person you researched.** Use it without
|
|
396
|
+
asking. If the connected account is someone else, or more than one is
|
|
397
|
+
connected, ask which account should send. That is the one extra question
|
|
398
|
+
this section allows.
|
|
399
|
+
- **Get it ready, then ask.** Attach the sender and the recommended sequence
|
|
400
|
+
to the campaign yourself (`update_campaign` with `senderIds`, then
|
|
401
|
+
`attach_recommended_sequence`); this prepares the launch and sends nothing.
|
|
402
|
+
Read the real numbers for that sender (`refill_v3_world_state` for the next
|
|
403
|
+
two days). Then post:
|
|
404
|
+
|
|
405
|
+
```text
|
|
406
|
+
Ready to launch "{campaign name}".
|
|
407
|
+
|
|
408
|
+
*Sender* {name}
|
|
409
|
+
*First sends* {n} approved messages, {how many a day and which days, from the numbers you read}
|
|
410
|
+
*Replies* I'll post each reply here as a draft for you to approve.
|
|
411
|
+
{only if no do-not-contact answer was ever given: "You haven't sent a do-not-contact list. Paste one now if there is anyone I must not message."}
|
|
412
|
+
|
|
413
|
+
Reply launch to start. You can pause it any time by telling me.
|
|
414
|
+
```
|
|
415
|
+
|
|
416
|
+
State only numbers you read. If you could not read one, leave that part out
|
|
417
|
+
rather than guess.
|
|
418
|
+
- **Start only on an explicit yes** ("launch", "yes, start", "go"). Then call
|
|
419
|
+
`start_campaign` and report what happened: what is scheduled and for when,
|
|
420
|
+
and that scheduled is not sent yet.
|
|
421
|
+
- **After the launch, one question about the rest.** Only the sample has been
|
|
422
|
+
checked and written. Ask: "{remaining} more people are on the list. Want me
|
|
423
|
+
to keep going through them and approve the messages that pass the same
|
|
424
|
+
checks, so your sends stay filled? Reply yes, or review first." Save the
|
|
425
|
+
answer as the standing `launchConsent` (`customer_program` `save_context`:
|
|
426
|
+
`autopilot` for yes, `review_first` otherwise). This is the first and only
|
|
427
|
+
time that question is asked during onboarding, and only an explicit yes is
|
|
428
|
+
`autopilot`.
|
|
224
429
|
|
|
225
430
|
## Hard Boundaries
|
|
226
431
|
|
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: research-sender
|
|
3
|
-
description: "Parallel-first sender research protocol. One round of five batched tool calls (fetch_linkedin_profile + fetch_company + 3× WebSearch), no enrich_sender required, ~30-40s wall time."
|
|
3
|
+
description: "Parallel-first sender research protocol. One round of five batched tool calls (fetch_linkedin_profile + fetch_company + 3× WebSearch), or the profile first and then the rest when only a profile URL is known; no enrich_sender required, ~30-40s wall time."
|
|
4
4
|
visibility: internal
|
|
5
|
-
allowed-tools: mcp__sellable__fetch_linkedin_profile mcp__sellable__fetch_company mcp__sellable__complete_sender_research WebSearch ToolSearch
|
|
5
|
+
allowed-tools: mcp__sellable__fetch_linkedin_profile mcp__sellable__fetch_company mcp__sellable__complete_sender_research WebSearch WebFetch ToolSearch
|
|
6
6
|
---
|
|
7
7
|
|
|
8
8
|
# Research Sender (Parallel-First)
|
|
@@ -64,14 +64,24 @@ Concretely, your next assistant message must contain exactly these five
|
|
|
64
64
|
`tool_use` blocks, in any order, with no leading or trailing prose:
|
|
65
65
|
|
|
66
66
|
1. `fetch_linkedin_profile({ linkedinUrl })` — sender's LinkedIn profile (firstName/lastName, currentCompany, experience, follower count, education) for the brief's "sender background" section.
|
|
67
|
-
2. `fetch_company({
|
|
67
|
+
2. `fetch_company({ companyUrl })` — sender's company LinkedIn page (description, industry, employee range, recent posts). `companyUrl` is the LinkedIn company URL. REQUIRED — replaces what `enrich_sender` used to provide for `companySnapshot`.
|
|
68
68
|
3. `WebSearch({ query: 'site:{companyDomain} ("case study" OR "customer story" OR testimonial OR "success story") "{companyName}"' })` — proof.
|
|
69
69
|
4. `WebSearch({ query: '"{companyName}" {companyDomain} {currentYear} (funding OR raised OR seed OR series OR hiring OR launch OR "press release")' })` — growth/credibility.
|
|
70
70
|
5. `WebSearch({ query: '"{companyName}" about product site:{companyDomain}' })` — positioning.
|
|
71
71
|
|
|
72
|
+
**When only the profile URL is known** (a new customer gave nothing else), the
|
|
73
|
+
company name, domain and LinkedIn company URL come from the profile, so the
|
|
74
|
+
five calls cannot all go in one message. Call `fetch_linkedin_profile` alone
|
|
75
|
+
first, then emit the other four together in the next message. If the profile
|
|
76
|
+
does not show the company's website, call `fetch_company` next on its own and
|
|
77
|
+
take the website from it, then emit the three searches together. Never guess
|
|
78
|
+
the company or its domain from the handle. This order replaces "exactly these
|
|
79
|
+
five … in a single assistant message" above for this case.
|
|
80
|
+
|
|
72
81
|
**Self-check before you reply:** if your reply contains fewer than five
|
|
73
|
-
`tool_use` blocks (after the optional `ToolSearch` setup turn),
|
|
74
|
-
|
|
82
|
+
`tool_use` blocks (after the optional `ToolSearch` setup turn), or fewer than
|
|
83
|
+
four when the profile was fetched first, you are violating the protocol.
|
|
84
|
+
Stop, rewrite the reply with all of them.
|
|
75
85
|
|
|
76
86
|
**Known limitation (`claude -p` headless mode):** Claude often serializes
|
|
77
87
|
these calls one-per-turn even with explicit instructions. That's fine — the
|
|
@@ -137,6 +147,11 @@ signals, AND the campaign fixture/operator told you proof is required, you
|
|
|
137
147
|
MAY issue ONE additional WebFetch on the most promising case-study URL from
|
|
138
148
|
the proof WebSearch. Cap at one WebFetch. No subagents, no second WebSearch round.
|
|
139
149
|
|
|
150
|
+
If the proof and positioning searches returned nothing from the company's own
|
|
151
|
+
site (common when the company name is an ordinary word), you MAY issue that
|
|
152
|
+
ONE WebFetch on the company's homepage instead, to read what it says it does
|
|
153
|
+
and any customers it names. Same cap.
|
|
154
|
+
|
|
140
155
|
If that still yields nothing, set `proofItemsFound: 0` in
|
|
141
156
|
`complete_sender_research` and proceed; the brief can ship without proof.
|
|
142
157
|
|
|
@@ -157,16 +172,21 @@ If no reliable evidence is found, set counts to 0 and include that in `notes`.
|
|
|
157
172
|
|
|
158
173
|
## Progress UX
|
|
159
174
|
|
|
160
|
-
|
|
175
|
+
In a chat host where every line you write is posted to the customer (Slack),
|
|
176
|
+
write nothing here: the customer was already told research is under way, and
|
|
177
|
+
the next thing they should read is the result. Tool names, "in parallel",
|
|
178
|
+
time estimates and counts of proof items are not for customers.
|
|
179
|
+
|
|
180
|
+
In a terminal host, before issuing the batch:
|
|
161
181
|
|
|
162
182
|
```
|
|
163
|
-
|
|
183
|
+
Researching the sender and company…
|
|
164
184
|
```
|
|
165
185
|
|
|
166
186
|
After synthesis:
|
|
167
187
|
|
|
168
188
|
```
|
|
169
|
-
Sender research ready
|
|
189
|
+
Sender research ready.
|
|
170
190
|
```
|
|
171
191
|
|
|
172
192
|
## What Changed From The Previous Protocol
|