@sellable/mcp 0.1.154 → 0.1.156
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/agents/post-find-leads-filter-scout.md +6 -6
- package/agents/post-find-leads-message-scout.md +12 -11
- package/agents/source-scout-linkedin-engagement.md +29 -24
- package/agents/source-scout-prospeo-contact.md +3 -3
- package/agents/source-scout-sales-nav.md +3 -3
- package/dist/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/tools/cells.js +1 -1
- package/dist/tools/leads.d.ts +1 -0
- package/dist/tools/leads.js +43 -25
- package/dist/tools/navigation.js +2 -2
- package/dist/tools/prompts.js +7 -7
- package/dist/tools/readiness.js +2 -2
- package/dist/tools/rubrics.js +1 -1
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +37 -28
- package/skills/create-campaign-v2/SKILL.md +39 -50
- package/skills/create-campaign-v2/SOUL.md +4 -4
- package/skills/create-campaign-v2/core/auto-execute.README.md +9 -9
- package/skills/create-campaign-v2/core/flow.v2.json +7 -7
- package/skills/create-campaign-v2/core/policy.md +1 -1
- package/skills/create-campaign-v2/references/approval-gate-framing.md +2 -2
- package/skills/create-campaign-v2/references/filter-leads.md +11 -11
- package/skills/create-campaign-v2/references/final-handoff-contract.md +4 -4
- package/skills/create-campaign-v2/references/sample-validation-loop.md +8 -8
- package/skills/create-campaign-v2/references/step-13-import-leads.md +19 -18
- package/skills/create-campaign-v2/references/step-15-re-cascade.md +2 -2
- package/skills/create-campaign-v2/references/watch-guide-narration.md +15 -15
- package/skills/create-campaign-v2-tail/SKILL.md +27 -27
- package/skills/create-rubric/SKILL.md +1 -1
- package/skills/find-leads/SKILL.md +2 -2
- package/skills/providers/prospeo.md +1 -1
- package/skills/providers/sales-nav.md +1 -1
- package/skills/providers/signal-discovery.md +31 -27
- package/skills/research/config.json +9 -0
|
@@ -2,7 +2,7 @@ You are Lead Fit Builder for Sellable create-campaign-v2.
|
|
|
2
2
|
|
|
3
3
|
Your job starts only after the lead source has been approved or auto-confirmed,
|
|
4
4
|
the confirmed source list has been copied into the campaign table, and the first
|
|
5
|
-
|
|
5
|
+
campaign-table execution slice exists.
|
|
6
6
|
Work only on the lead filter branch. Do not source new leads, draft messages,
|
|
7
7
|
import leads, create campaigns, or ask the user questions. Your only live
|
|
8
8
|
campaign mutation is calling `save_rubrics` after the production rubrics are
|
|
@@ -16,7 +16,7 @@ Required inputs:
|
|
|
16
16
|
- selected source decision and provider/list state
|
|
17
17
|
- `selectedLeadListId`
|
|
18
18
|
- `workflowTableId`
|
|
19
|
-
-
|
|
19
|
+
- initial campaign-table execution slice rows, including row ids/hash when available
|
|
20
20
|
- filter choice
|
|
21
21
|
|
|
22
22
|
Required first steps:
|
|
@@ -25,7 +25,7 @@ Required first steps:
|
|
|
25
25
|
campaign context.
|
|
26
26
|
2. Load the filter-leads reference before designing rubrics:
|
|
27
27
|
`get_subskill_asset({ subskillName: "create-campaign-v2", assetPath: "references/filter-leads.md" })`.
|
|
28
|
-
3. Treat campaign state and the campaign
|
|
28
|
+
3. Treat campaign state and the campaign-table execution slice as the input of record.
|
|
29
29
|
Do not require or hunt for local markdown/json artifacts.
|
|
30
30
|
|
|
31
31
|
Owned outputs:
|
|
@@ -40,9 +40,9 @@ via `save_rubrics` plus the parent-thread summary.
|
|
|
40
40
|
|
|
41
41
|
Process:
|
|
42
42
|
|
|
43
|
-
1. Preserve the approved source decision
|
|
44
|
-
by the parent; do not re-run sourcing.
|
|
45
|
-
2. Turn the
|
|
43
|
+
1. Preserve the approved source decision, source math, and campaign-table slice
|
|
44
|
+
evidence supplied by the parent; do not re-run sourcing.
|
|
45
|
+
2. Turn the slice's good-fit and false-positive patterns into a strict but
|
|
46
46
|
campaign-native filter.
|
|
47
47
|
3. Include keep rules, exclude rules, sample false positives, pass-rate /
|
|
48
48
|
expected-yield impact, and a recommendation.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
You are Message Draft Builder for Sellable create-campaign-v2.
|
|
2
2
|
|
|
3
3
|
Your job starts only after the source is approved, the confirmed source list has
|
|
4
|
-
been copied into the campaign table, the first
|
|
5
|
-
the parent has recorded the filter choice. Work only on the
|
|
4
|
+
been copied into the campaign table, the first campaign-table execution slice
|
|
5
|
+
exists, and the parent has recorded the filter choice. Work only on the
|
|
6
|
+
message-draft branch.
|
|
6
7
|
Do not source leads, create lead filters, import leads, confirm lead lists, queue
|
|
7
8
|
cells, attach sequences, start campaigns, ask the user questions, or mutate live
|
|
8
9
|
campaign state. The main thread owns approval and campaign writes.
|
|
@@ -17,7 +18,7 @@ Use the live campaign inputs supplied by the parent thread:
|
|
|
17
18
|
- selected source decision and provider state
|
|
18
19
|
- `selectedLeadListId` or selected source list context
|
|
19
20
|
- `workflowTableId`
|
|
20
|
-
-
|
|
21
|
+
- initial campaign-table execution slice rows from that selected list, including row IDs
|
|
21
22
|
and a sample row hash when available
|
|
22
23
|
- filter basis at branch start: `pending`, `use-filters`, or `skip-filters`
|
|
23
24
|
- any already-saved fit/rubric result summaries supplied by the parent
|
|
@@ -31,7 +32,7 @@ All live reads must come from scoped MCP/product tools by campaign and
|
|
|
31
32
|
workspace, such as `get_campaign`, `get_campaign_context`, and
|
|
32
33
|
`get_rows_minimal({ tableId: workflowTableId })`, or from equivalent parent
|
|
33
34
|
thread payloads. Reject the task as `blocked` if the campaign id, workspace,
|
|
34
|
-
`selectedLeadListId`, `workflowTableId`, or
|
|
35
|
+
`selectedLeadListId`, `workflowTableId`, or execution-slice row ids do not match
|
|
35
36
|
the branch input.
|
|
36
37
|
|
|
37
38
|
## Required First Steps
|
|
@@ -43,8 +44,8 @@ the branch input.
|
|
|
43
44
|
|
|
44
45
|
2. Use that prompt as the drafting contract. Do not use create-campaign
|
|
45
46
|
safety/checklist instructions as a substitute for the full prompt.
|
|
46
|
-
3. Draft only from the campaign brief, selected source context, and
|
|
47
|
-
|
|
47
|
+
3. Draft only from the campaign brief, selected source context, and initial
|
|
48
|
+
campaign-table execution slice rows supplied by the parent.
|
|
48
49
|
4. Keep the work provisional until the user chooses `Use Template` in Messages.
|
|
49
50
|
|
|
50
51
|
## Owned Output
|
|
@@ -53,12 +54,12 @@ Return the following to the parent thread:
|
|
|
53
54
|
|
|
54
55
|
- proposed first-message template using supported `{{...}}` tokens
|
|
55
56
|
- token fill rules and fallbacks
|
|
56
|
-
- one rendered good-fill sample for a plausible passing
|
|
57
|
+
- one rendered good-fill sample for a plausible passing campaign-table row
|
|
57
58
|
- one omit/fallback sample when the row signal is not safe
|
|
58
59
|
- pass/fail notes against the generate-messages quality gates
|
|
59
60
|
- compact runtime status: `ready`, `blocked`, `retry-needed`, or `stale`
|
|
60
61
|
- basis token containing campaign revision/updatedAt, brief hash,
|
|
61
|
-
`selectedLeadListId`, `workflowTableId`,
|
|
62
|
+
`selectedLeadListId`, `workflowTableId`, execution-slice row ids/hash, filter
|
|
62
63
|
choice, and rubric/filter basis when present
|
|
63
64
|
- output timestamp/hash and any retry/error detail
|
|
64
65
|
|
|
@@ -79,7 +80,7 @@ When reporting branch runtime proof, use this shape under
|
|
|
79
80
|
- optional `compactOutputRef`, `compactOutput`, and `error`
|
|
80
81
|
|
|
81
82
|
Do not tell the UI to show Message Draft Builder as running unless this proof
|
|
82
|
-
exists and points at the current non-empty
|
|
83
|
+
exists and points at the current non-empty campaign-table execution slice.
|
|
83
84
|
|
|
84
85
|
## Basis Changes And Rewrites
|
|
85
86
|
|
|
@@ -90,7 +91,7 @@ row data became available after this branch started.
|
|
|
90
91
|
|
|
91
92
|
Treat later filter/enrichment data as optional rewrite context. If campaign id,
|
|
92
93
|
brief hash, selected source, `selectedLeadListId`, `workflowTableId`, and
|
|
93
|
-
|
|
94
|
+
execution-slice row ids/hash still match, keep the initial recommendation usable
|
|
94
95
|
and report `status: ready` with `basisStatus: "usable_initial"` or
|
|
95
96
|
`"enriched_rewrite_available"`. The parent thread may offer the user a choice
|
|
96
97
|
to keep the initial draft or rewrite with enriched/filter data, but the rewrite
|
|
@@ -99,7 +100,7 @@ must be explicit user opt-in.
|
|
|
99
100
|
Retry or regenerate without asking only when the initial recommendation is
|
|
100
101
|
missing, failed, structurally invalid, unsafe, or mismatched on campaign id,
|
|
101
102
|
brief hash, selected source, `selectedLeadListId`, `workflowTableId`, or
|
|
102
|
-
|
|
103
|
+
execution-slice rows. Filter/rubric/enrichment basis drift alone is not a stale
|
|
103
104
|
blocker.
|
|
104
105
|
|
|
105
106
|
## Hard Rules
|
|
@@ -51,14 +51,15 @@ currentStep: "signal-discovery" })` before sampling so the watched Signal
|
|
|
51
51
|
visible headline/display-name cues only. Do not enrich people during
|
|
52
52
|
viability estimation.
|
|
53
53
|
7. Compute capacity before recommending the source: source target good-fit
|
|
54
|
-
leads (default
|
|
55
|
-
reachable engagers,
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
leads (default 300 for Signal Discovery unless the parent supplies a target),
|
|
55
|
+
reachable engagers, sampled headline-fit rate as `n/N` plus an easy
|
|
56
|
+
percentage/range, expected headline-fit prospects per 100 engagers, required
|
|
57
|
+
engagers to scrape (`source target / sampled headline-fit rate`), average
|
|
58
|
+
reachable engagers per right-content post, expected headline-fit prospects
|
|
59
|
+
per right-content post, posts needed to hit the target, and whether
|
|
60
|
+
sampled/projected headline-fit rate clears the 10% planning floor. Treat the
|
|
61
|
+
10% floor as a reject threshold, not as the scrape-count denominator when the
|
|
62
|
+
actual sample rate is higher.
|
|
62
63
|
8. Select/promote enough right-content posts to plausibly hit the target. If the
|
|
63
64
|
warm Signals pool is useful but too small, return the expected warm range and
|
|
64
65
|
recommend Sales Nav/Prospeo for scale instead of padding with noisy posts.
|
|
@@ -72,12 +73,12 @@ Return a concise structured result with:
|
|
|
72
73
|
- `selected_posts` with URL/title, author/topic, age, engager count, sampled engagers, good fits as n/N, estimated usable prospects per post, use/discard
|
|
73
74
|
- `sample_leads`, if any
|
|
74
75
|
- `approval_math` with eligible posts, source target good-fit leads, sampled
|
|
75
|
-
engagers,
|
|
76
|
-
100 engagers, required engagers to scrape, average reachable
|
|
77
|
-
post, expected
|
|
78
|
-
target, whether the 10% planning floor clears
|
|
79
|
-
|
|
80
|
-
fallback
|
|
76
|
+
engagers, headline-fit rate as `n/N` plus percentage/range, headline-fit
|
|
77
|
+
prospects per 100 engagers, required engagers to scrape, average reachable
|
|
78
|
+
engagers per post, expected headline-fit prospects per post, posts needed for
|
|
79
|
+
target, whether the 10% planning floor clears, selected post count, internal
|
|
80
|
+
campaign-table execution-slice size, expected headline-fit lead range, and
|
|
81
|
+
scale fallback
|
|
81
82
|
- `estimated_good_fit_range`
|
|
82
83
|
- `message_context_strength`, directional and source-specific
|
|
83
84
|
- `false_positive_patterns`
|
|
@@ -93,14 +94,18 @@ Evidence standards:
|
|
|
93
94
|
GTM/outbound/buyer pain, workflow, or role context that makes the campaign
|
|
94
95
|
relevant.
|
|
95
96
|
- Do not make the user infer capacity. Say, plainly, how many eligible posts
|
|
96
|
-
exist, how many sampled engagers
|
|
97
|
-
that implies per 100 engagers, how many
|
|
98
|
-
post should yield
|
|
99
|
-
300-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
the
|
|
97
|
+
exist, how many sampled engagers passed the headline ICP rubric, what
|
|
98
|
+
headline-fit rate that implies per 100 engagers, how many headline-fit
|
|
99
|
+
prospects one right-content post should yield, how many engagers must be
|
|
100
|
+
scraped for the 300 headline-fit source target using the sampled pass rate
|
|
101
|
+
(or the 20% working assumption only when there is no stronger sample), how
|
|
102
|
+
many posts are needed for that source target, and which posts you would use.
|
|
103
|
+
Also say the source list is copied into the campaign and only the first
|
|
104
|
+
campaign-table execution slice is processed internally for filter and message
|
|
105
|
+
setup.
|
|
103
106
|
- If `fetch_post_engagers` is unavailable or fails, report that explicitly and mark the estimate lower-confidence.
|
|
104
|
-
- Keep LinkedIn Engagement viable when selected posts can produce roughly 300+
|
|
105
|
-
-
|
|
106
|
-
|
|
107
|
+
- Keep LinkedIn Engagement viable when selected posts can produce roughly 300+
|
|
108
|
+
headline-fit warm prospects before final filtering, even if Sales Nav is more
|
|
109
|
+
scalable.
|
|
110
|
+
- If sampled/projected headline-fit rate is below 10%, reject the Signals
|
|
111
|
+
scrape path and recommend Sales Nav recent activity as the next source.
|
|
@@ -28,7 +28,7 @@ Process:
|
|
|
28
28
|
2. Identify whether this is domain/account targeting, hiring-led targeting, or broad persona expansion.
|
|
29
29
|
3. For domain targeting, use or create the standalone `domainFilterId` before searching; never pass raw domains directly into `search_prospeo`.
|
|
30
30
|
4. For hiring-led targeting, use `company_job_posting_hiring_for` for the target open-role themes and `company_job_posting_quantity` when the brief needs an active hiring floor. Pair those company hiring filters with buyer/referrer person filters; do not treat hiring-led targeting as Sales Nav-only.
|
|
31
|
-
5. Run the narrowest useful Prospeo people preview and 1-2 refinements if quality or scale is unclear. Check scale against the source target good-fit lead count (default about 300 usable prospects unless the parent supplies a different target) and cap source candidates at the provider limit. Use the first-page sample to compute projected good fits from a source-list export, not to recommend
|
|
31
|
+
5. Run the narrowest useful Prospeo people preview and 1-2 refinements if quality or scale is unclear. Check scale against the source target good-fit lead count (default about 300 usable prospects unless the parent supplies a different target) and cap source candidates at the provider limit. Use the first-page sample to compute projected good fits from a source-list export, not to recommend importing only the internal campaign-table execution slice.
|
|
32
32
|
6. If `raw_result_count * projected_fit_rate_after_cleanup` is below the source target, do not recommend import yet. Tighten or broaden filters and retry until the projected usable pool clears target, or clearly report that the lane is too constrained.
|
|
33
33
|
7. Call out that Prospeo gives contact/account and hiring-signal coverage but usually weaker LinkedIn intent than LinkedIn Engagement or Sales Nav activity slices.
|
|
34
34
|
|
|
@@ -58,6 +58,6 @@ Evidence standards:
|
|
|
58
58
|
direction rather than switching providers again.
|
|
59
59
|
- Never recommend "import 25 leads" as the Prospeo source action. Recommend
|
|
60
60
|
exporting/materializing the approved source list; the parent thread later
|
|
61
|
-
copies the confirmed source rows into the campaign and
|
|
62
|
-
|
|
61
|
+
copies the confirmed source rows into the campaign and internally uses the
|
|
62
|
+
first campaign-table execution slice for filter and message setup.
|
|
63
63
|
- Treat Prospeo as an account/contact and company hiring-signal lane, not as proof of fresh LinkedIn intent.
|
|
@@ -44,7 +44,7 @@ Process:
|
|
|
44
44
|
cannot plausibly reach the target after loosening.
|
|
45
45
|
7. Use the first-page sample to compute projected good fits from the source-list
|
|
46
46
|
export. The recommendation should name the source-list `targetLeadCount` for
|
|
47
|
-
`import_leads`, not
|
|
47
|
+
`import_leads`, not the internal campaign-table execution-slice size.
|
|
48
48
|
8. Verify filters actually applied: returned search URL contains filters, first-page rows match the intended lane, and result count does not look like an unfiltered pool.
|
|
49
49
|
|
|
50
50
|
Return a concise structured result with:
|
|
@@ -82,7 +82,7 @@ Evidence standards:
|
|
|
82
82
|
as the winning source; recommend Prospeo as the next provider.
|
|
83
83
|
- Never recommend "import 25 leads" as the Sales Nav source action. Recommend
|
|
84
84
|
exporting/materializing the approved source list; the parent thread later
|
|
85
|
-
copies the confirmed source rows into the campaign and
|
|
86
|
-
|
|
85
|
+
copies the confirmed source rows into the campaign and internally uses the
|
|
86
|
+
first campaign-table execution slice for filter and message setup.
|
|
87
87
|
- Do not hand-wave missing filter IDs.
|
|
88
88
|
- If Sales Nav returns a giant unfiltered pool, discard that result and retry with valid filters before recommending it.
|
package/dist/index-dev.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/tools/cells.js
CHANGED
|
@@ -20,7 +20,7 @@ export const cellToolDefinitions = [
|
|
|
20
20
|
},
|
|
21
21
|
{
|
|
22
22
|
name: "queue_cells",
|
|
23
|
-
description: "Queue explicit cells for processing. In create-campaign-v2 Filter Leads, pass only
|
|
23
|
+
description: "Queue explicit cells for processing. In create-campaign-v2 Filter Leads, pass only initial campaign-table execution-slice enrichCellId values from get_rows_minimal; do not pass icpCellId values or full-table cells because the workflow cascade handles downstream ICP scoring.",
|
|
24
24
|
inputSchema: {
|
|
25
25
|
type: "object",
|
|
26
26
|
properties: {
|
package/dist/tools/leads.d.ts
CHANGED
package/dist/tools/leads.js
CHANGED
|
@@ -53,6 +53,7 @@ const defaultCampaignSourceDefaults = {
|
|
|
53
53
|
},
|
|
54
54
|
};
|
|
55
55
|
const defaultProviderSourceListTarget = 1000;
|
|
56
|
+
export const MAX_SIGNAL_DISCOVERY_POSTS = 10;
|
|
56
57
|
// Mirror the web app's Signal Discovery Harvest caps. The MCP tool selects and
|
|
57
58
|
// describes the scrape before the web API runs, so it must use the same capped
|
|
58
59
|
// capacity math instead of raw visible engagement counts.
|
|
@@ -353,7 +354,12 @@ function estimateScrapableSignalEngagers(post) {
|
|
|
353
354
|
export function selectSignalPostsForImport(posts, options) {
|
|
354
355
|
const normalizedTargetEngagers = normalizePositiveInteger(options.targetEngagerCount);
|
|
355
356
|
const normalizedMaxPosts = normalizePositiveInteger(options.maxPostsToScrape);
|
|
356
|
-
|
|
357
|
+
const effectiveMaxPosts = normalizedMaxPosts
|
|
358
|
+
? Math.min(normalizedMaxPosts, MAX_SIGNAL_DISCOVERY_POSTS)
|
|
359
|
+
: MAX_SIGNAL_DISCOVERY_POSTS;
|
|
360
|
+
if (!normalizedTargetEngagers &&
|
|
361
|
+
!normalizedMaxPosts &&
|
|
362
|
+
posts.length <= MAX_SIGNAL_DISCOVERY_POSTS) {
|
|
357
363
|
const availableEngagers = posts.reduce((sum, post) => sum + estimateScrapableSignalEngagers(post), 0);
|
|
358
364
|
return {
|
|
359
365
|
posts,
|
|
@@ -369,7 +375,7 @@ export function selectSignalPostsForImport(posts, options) {
|
|
|
369
375
|
const selected = [];
|
|
370
376
|
let estimatedEngagers = 0;
|
|
371
377
|
for (const post of ranked) {
|
|
372
|
-
if (
|
|
378
|
+
if (selected.length >= effectiveMaxPosts) {
|
|
373
379
|
break;
|
|
374
380
|
}
|
|
375
381
|
selected.push(post);
|
|
@@ -527,7 +533,7 @@ function buildSourceImportWatchNarration({ provider, selectedPostCount, estimate
|
|
|
527
533
|
: ""}`
|
|
528
534
|
: `the approved ${providerLabel} source`;
|
|
529
535
|
const targetDetail = typeof targetLeadCount === "number"
|
|
530
|
-
? ` Targeting ${targetLeadCount.toLocaleString("en-US")} source leads before
|
|
536
|
+
? ` Targeting ${targetLeadCount.toLocaleString("en-US")} source leads before campaign setup starts.`
|
|
531
537
|
: "";
|
|
532
538
|
return {
|
|
533
539
|
stage: "find-leads",
|
|
@@ -536,8 +542,8 @@ function buildSourceImportWatchNarration({ provider, selectedPostCount, estimate
|
|
|
536
542
|
: "Importing source leads",
|
|
537
543
|
visibleState: `The browser is showing ${providerLabel} import progress for ${sourceDetail}.${targetDetail}`,
|
|
538
544
|
agentIntent: "Codex is materializing the approved source into a lead list before copying the confirmed list into the campaign.",
|
|
539
|
-
nextAction: "Wait for source leads, then confirm the list and
|
|
540
|
-
safety: "This step materializes the source list; the
|
|
545
|
+
nextAction: "Wait for source leads, then confirm the list and configure campaign setup",
|
|
546
|
+
safety: "This step materializes the source list; the internal campaign-table execution slice is processed only after the later filter and message approvals.",
|
|
541
547
|
progressLabel: "Source scouting",
|
|
542
548
|
};
|
|
543
549
|
}
|
|
@@ -550,11 +556,11 @@ function buildSourceImportRecoveryWatchNarration(args) {
|
|
|
550
556
|
? "Prospeo"
|
|
551
557
|
: "source";
|
|
552
558
|
const reasonCopy = args.reason === "failed"
|
|
553
|
-
? `${providerLabel} import failed before
|
|
559
|
+
? `${providerLabel} import failed before campaign rows were ready.`
|
|
554
560
|
: args.reason === "zero"
|
|
555
|
-
? `${providerLabel} import finished without usable
|
|
561
|
+
? `${providerLabel} import finished without usable campaign rows.`
|
|
556
562
|
: args.reason === "timeout"
|
|
557
|
-
? `${providerLabel} import has not produced
|
|
563
|
+
? `${providerLabel} import has not produced campaign rows within the wait window.`
|
|
558
564
|
: `${providerLabel} import is still materializing source rows.`;
|
|
559
565
|
return {
|
|
560
566
|
stage: "review-batch",
|
|
@@ -771,10 +777,10 @@ function buildSignalDiscoverySourceRecommendation({ selectedPosts, }) {
|
|
|
771
777
|
|
|
772
778
|
Use Signal Discovery first.
|
|
773
779
|
|
|
774
|
-
**Goal:** ~${targetGoodFitLeads.toLocaleString("en-US")}
|
|
775
|
-
**Working assumption:** ~${Math.round(defaultFitRate * 100)}% of raw post engagers
|
|
780
|
+
**Goal:** ~${targetGoodFitLeads.toLocaleString("en-US")} headline-fit prospects that pass the Signal Discovery headline criteria<br>
|
|
781
|
+
**Working assumption:** ~${Math.round(defaultFitRate * 100)}% of raw post engagers pass headline filtering unless a real sample supports a different rate<br>
|
|
776
782
|
**Engagers needed:** ~${sourceCandidateTarget.toLocaleString("en-US")} raw engagers<br>
|
|
777
|
-
**Planning floor:** continue with Signal Discovery only when sampled/projected fit is at least ${Math.round(minPlanningFitRate * 100)}
|
|
783
|
+
**Planning floor:** continue with Signal Discovery only when sampled/projected headline-fit rate is at least ${Math.round(minPlanningFitRate * 100)}%; below that, switch to Sales Nav recent activity<br>
|
|
778
784
|
**Review checkpoint:** copy the confirmed source list into the campaign, then process the first ${reviewBatchSize.toLocaleString("en-US")} leads for fit and message review before scaling
|
|
779
785
|
|
|
780
786
|
### Selected posts
|
|
@@ -784,17 +790,17 @@ Use Signal Discovery first.
|
|
|
784
790
|
${tableRows || "| Selected posts | Campaign-matched public engagement | - |"}
|
|
785
791
|
|
|
786
792
|
**Total visible pool:** ${formatApproxInteger(totalEngagement)} engagers<br>
|
|
787
|
-
**Estimated
|
|
793
|
+
**Estimated headline-fit pool at ${Math.round(defaultFitRate * 100)}%:** ${formatApproxInteger(estimatedGoodFit)} prospects before enrichment and deeper fit review
|
|
788
794
|
|
|
789
795
|
### Recommendation
|
|
790
796
|
|
|
791
797
|
Approve scraping these ${selectedCount} posts.
|
|
792
798
|
|
|
793
|
-
This gives enough volume to target ~${targetGoodFitLeads.toLocaleString("en-US")}
|
|
799
|
+
This gives enough volume to target ~${targetGoodFitLeads.toLocaleString("en-US")} headline-fit prospects, while keeping the source tied to people already engaging with the campaign's strongest public buying signals.
|
|
794
800
|
|
|
795
|
-
**First pass:** build the source list, copy it into the campaign, then
|
|
801
|
+
**First pass:** build the source list, copy it into the campaign, then use the first ${reviewBatchSize.toLocaleString("en-US")} campaign rows as the internal setup slice for filters and messages before scaling.
|
|
796
802
|
|
|
797
|
-
**Fallback:** if the sampled/projected fit rate is below ${Math.round(minPlanningFitRate * 100)}%, or if the
|
|
803
|
+
**Fallback:** if the sampled/projected headline-fit rate is below ${Math.round(minPlanningFitRate * 100)}%, or if the source sample is too vendor-heavy, agency-heavy, or off-ICP, switch to Sales Nav recent activity.
|
|
798
804
|
|
|
799
805
|
Approval card should say:
|
|
800
806
|
|
|
@@ -1335,7 +1341,7 @@ export const leadToolDefinitions = [
|
|
|
1335
1341
|
},
|
|
1336
1342
|
{
|
|
1337
1343
|
name: "import_leads",
|
|
1338
|
-
description: "Create/select a source lead list and start the provider import/export job. Requires provider prompt preflight via get_provider_prompt for the active provider. For Sales Nav/Prospeo, targetLeadCount is the approved source-list export count computed from projected good-fit math, not the later
|
|
1344
|
+
description: "Create/select a source lead list and start the provider import/export job. Requires provider prompt preflight via get_provider_prompt for the active provider. For Sales Nav/Prospeo, targetLeadCount is the approved source-list export count computed from projected good-fit math, not the later internal campaign-table execution-slice size. On success, this tool owns moving the watched campaign to confirm-lead-list with import/progress narration after a leadListId/jobId exists; do not call update_campaign to fix that step. After the user confirms the source list, call confirm_lead_list to copy the confirmed source rows into the campaign table and use the initial campaign-table execution slice for setup.",
|
|
1339
1345
|
inputSchema: {
|
|
1340
1346
|
type: "object",
|
|
1341
1347
|
properties: {
|
|
@@ -1362,7 +1368,7 @@ export const leadToolDefinitions = [
|
|
|
1362
1368
|
},
|
|
1363
1369
|
targetLeadCount: {
|
|
1364
1370
|
type: "number",
|
|
1365
|
-
description: "Provider source-list target count (max per provider from config). For Sales Nav/Prospeo this should be the export/materialization count needed to hit projected good-fit goals, not the
|
|
1371
|
+
description: "Provider source-list target count (max per provider from config). For Sales Nav/Prospeo this should be the export/materialization count needed to hit projected good-fit goals, not the internal campaign-table execution-slice size.",
|
|
1366
1372
|
},
|
|
1367
1373
|
mode: {
|
|
1368
1374
|
type: "string",
|
|
@@ -1384,11 +1390,11 @@ export const leadToolDefinitions = [
|
|
|
1384
1390
|
},
|
|
1385
1391
|
targetEngagerCount: {
|
|
1386
1392
|
type: "number",
|
|
1387
|
-
description: "Signal Discovery: target number of post engagers/source candidates to scrape. Default planning target is about 300
|
|
1393
|
+
description: "Signal Discovery: target number of post engagers/source candidates to scrape. Default planning target is about 300 headline-fit prospects at a 20% raw-engager headline-pass assumption, or about 1500 engagers. Use real sample math when available: target headline-fit prospects divided by sampled headline-pass rate. If the sampled/projected headline-fit rate is below the 10% planning floor, switch to the next provider instead of scaling noisy engagers. Limits selected posts before starting scrape, with a backend hard cap of 10 posts.",
|
|
1388
1394
|
},
|
|
1389
1395
|
maxPostsToScrape: {
|
|
1390
1396
|
type: "number",
|
|
1391
|
-
description: "Signal Discovery: optional hard cap on selected posts to scrape after ranking selected posts by engagement.",
|
|
1397
|
+
description: "Signal Discovery: optional hard cap on selected posts to scrape after ranking selected posts by engagement. Values above 10 are clamped to the backend hard cap.",
|
|
1392
1398
|
},
|
|
1393
1399
|
rubricGuidelines: {
|
|
1394
1400
|
type: "array",
|
|
@@ -1428,7 +1434,7 @@ export const leadToolDefinitions = [
|
|
|
1428
1434
|
},
|
|
1429
1435
|
{
|
|
1430
1436
|
name: "confirm_lead_list",
|
|
1431
|
-
description: "After the user confirms the lead list looks good, copy the confirmed source list into the campaign table and mark the
|
|
1437
|
+
description: "After the user confirms the lead list looks good, copy the confirmed source list into the campaign table and mark the initial campaign-table execution slice for the flow. This tool owns moving the watched campaign to filter-choice with campaign-setup narration only after non-empty campaign rows exist; do not call update_campaign to fix filter-choice afterward. selectedLeadListId remains the source lead list; workflowTableId is the campaign table.",
|
|
1432
1438
|
inputSchema: {
|
|
1433
1439
|
type: "object",
|
|
1434
1440
|
properties: {
|
|
@@ -1484,7 +1490,7 @@ export const leadToolDefinitions = [
|
|
|
1484
1490
|
},
|
|
1485
1491
|
selections: {
|
|
1486
1492
|
type: "array",
|
|
1487
|
-
description: "Array of promising posts to select for scraping. Prefer the recommendedPostIds from search_signals.",
|
|
1493
|
+
description: "Array of promising posts to select for scraping. Prefer the recommendedPostIds from search_signals. Backend hard cap: 10 posts.",
|
|
1488
1494
|
items: {
|
|
1489
1495
|
type: "object",
|
|
1490
1496
|
properties: {
|
|
@@ -1500,7 +1506,7 @@ export const leadToolDefinitions = [
|
|
|
1500
1506
|
required: ["postId", "reason"],
|
|
1501
1507
|
},
|
|
1502
1508
|
minItems: 1,
|
|
1503
|
-
maxItems:
|
|
1509
|
+
maxItems: MAX_SIGNAL_DISCOVERY_POSTS,
|
|
1504
1510
|
},
|
|
1505
1511
|
headlineICPCriteria: {
|
|
1506
1512
|
type: "array",
|
|
@@ -2468,6 +2474,9 @@ export async function importLeads(input) {
|
|
|
2468
2474
|
}
|
|
2469
2475
|
}
|
|
2470
2476
|
const uniqueSelectedPosts = Array.from(uniqueByUrl.values());
|
|
2477
|
+
if (uniqueSelectedPosts.length > MAX_SIGNAL_DISCOVERY_POSTS) {
|
|
2478
|
+
throw new Error(`Maximum ${MAX_SIGNAL_DISCOVERY_POSTS} Signal Discovery posts can be imported for scraping. ${uniqueSelectedPosts.length} unique posts are currently selected; reduce the selected posts to the strongest ${MAX_SIGNAL_DISCOVERY_POSTS} before calling import_leads.`);
|
|
2479
|
+
}
|
|
2471
2480
|
const importSelection = selectSignalPostsForImport(uniqueSelectedPosts, {
|
|
2472
2481
|
targetEngagerCount: effectiveTargetEngagerCount,
|
|
2473
2482
|
maxPostsToScrape,
|
|
@@ -2872,14 +2881,14 @@ export async function confirmLeadList(input) {
|
|
|
2872
2881
|
remainingRowCount <= 0 &&
|
|
2873
2882
|
importedRowCount > 0);
|
|
2874
2883
|
if (!campaignTableReady) {
|
|
2875
|
-
throw new Error("Campaign source rows are still copying and no
|
|
2884
|
+
throw new Error("Campaign source rows are still copying and no campaign-table rows are available yet. Stay on lead import until the campaign table has rows, then retry confirm_lead_list or call wait_for_campaign_table_ready.");
|
|
2876
2885
|
}
|
|
2877
2886
|
if (importedRowCount <= 0) {
|
|
2878
2887
|
const recoveryNarration = buildSourceImportRecoveryWatchNarration({
|
|
2879
2888
|
reason: "zero",
|
|
2880
2889
|
provider: resolvedProvider,
|
|
2881
2890
|
});
|
|
2882
|
-
throw new Error(`${recoveryNarration.headline}: No usable
|
|
2891
|
+
throw new Error(`${recoveryNarration.headline}: No usable campaign rows were kept for the campaign table. Retry the import or change the approved source before continuing. ${recoveryNarration.safety}`);
|
|
2883
2892
|
}
|
|
2884
2893
|
let reviewSampleRows = [];
|
|
2885
2894
|
if (campaignTableId && effectiveCurrentStep === "filter-choice") {
|
|
@@ -2939,7 +2948,7 @@ export async function confirmLeadList(input) {
|
|
|
2939
2948
|
"brief hash",
|
|
2940
2949
|
"selectedLeadListId",
|
|
2941
2950
|
"workflowTableId",
|
|
2942
|
-
"
|
|
2951
|
+
"initial campaign-table execution slice row ids/hash",
|
|
2943
2952
|
"filter choice at branch start",
|
|
2944
2953
|
],
|
|
2945
2954
|
promptRequired: 'Load get_subskill_prompt({ subskillName: "generate-messages", offset, limit }) until hasMore=false before drafting.',
|
|
@@ -3004,6 +3013,15 @@ export async function selectPromisingPosts(input) {
|
|
|
3004
3013
|
const api = getApi();
|
|
3005
3014
|
const { campaignOfferId, selections, headlineICPCriteria, selectionMode, mode, } = input;
|
|
3006
3015
|
const effectiveMode = selectionMode ?? mode ?? "add";
|
|
3016
|
+
if (selections.length > MAX_SIGNAL_DISCOVERY_POSTS) {
|
|
3017
|
+
return {
|
|
3018
|
+
success: false,
|
|
3019
|
+
selectedCount: 0,
|
|
3020
|
+
unselectedCount: 0,
|
|
3021
|
+
criteriaCount: headlineICPCriteria.length,
|
|
3022
|
+
message: `Maximum ${MAX_SIGNAL_DISCOVERY_POSTS} Signal Discovery posts can be selected for scraping. Narrow the post set to the strongest ${MAX_SIGNAL_DISCOVERY_POSTS} before calling select_promising_posts again.`,
|
|
3023
|
+
};
|
|
3024
|
+
}
|
|
3007
3025
|
// Update post selections via API
|
|
3008
3026
|
const postIds = selections.map((s) => s.postId);
|
|
3009
3027
|
let unselectedIds = [];
|
package/dist/tools/navigation.js
CHANGED
|
@@ -324,9 +324,9 @@ function describeVisibleStep(step) {
|
|
|
324
324
|
case "leads":
|
|
325
325
|
return {
|
|
326
326
|
label: "Lead import",
|
|
327
|
-
summary: "The lead preview or imported
|
|
327
|
+
summary: "The lead preview or imported campaign rows are visible.",
|
|
328
328
|
nextMilestone: "Fit filtering and message review",
|
|
329
|
-
guidance: "Confirm the browser-visible campaign state and
|
|
329
|
+
guidance: "Confirm the browser-visible campaign state and imported rows before filter/message work.",
|
|
330
330
|
checkpoint: "import",
|
|
331
331
|
};
|
|
332
332
|
case "filter-choice":
|
package/dist/tools/prompts.js
CHANGED
|
@@ -260,7 +260,7 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
260
260
|
"campaignBrief",
|
|
261
261
|
"source decision and selectedLeadList/source state",
|
|
262
262
|
"workflowTableId",
|
|
263
|
-
"
|
|
263
|
+
"initial campaign-table execution slice rows from selectedLeadList with row ids/hash",
|
|
264
264
|
"filter choice and filter/rubric basis when present",
|
|
265
265
|
],
|
|
266
266
|
scouts: scouts.map((agent) => ({
|
|
@@ -297,11 +297,11 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
297
297
|
nextStage: "message-review",
|
|
298
298
|
},
|
|
299
299
|
messageDraftBranchContract: {
|
|
300
|
-
firstAllowedStart: "after confirm_lead_list copies source rows and the
|
|
300
|
+
firstAllowedStart: "after confirm_lead_list copies source rows and the initial campaign-table execution slice exists",
|
|
301
301
|
forbiddenStarts: [
|
|
302
302
|
"source recommendation",
|
|
303
303
|
"provider import job alone",
|
|
304
|
-
"zero-row
|
|
304
|
+
"zero-row campaign-table execution slice",
|
|
305
305
|
],
|
|
306
306
|
runtimeProofTransport: "CampaignOffer.watchNarration.workerDetails.messageDraftBuilder",
|
|
307
307
|
runtimeProofRequiredFields: [
|
|
@@ -321,7 +321,7 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
321
321
|
"brief hash",
|
|
322
322
|
"selectedLeadListId",
|
|
323
323
|
"workflowTableId",
|
|
324
|
-
"
|
|
324
|
+
"initial campaign-table execution slice row ids/hash",
|
|
325
325
|
"filter choice",
|
|
326
326
|
"filter/rubric basis when present",
|
|
327
327
|
],
|
|
@@ -340,9 +340,9 @@ export function getPostFindLeadsScoutRegistry() {
|
|
|
340
340
|
reusePolicy: "The first completed Message Draft Builder recommendation remains the default review candidate. Later Lead Fit Builder, Filter Leads, enrichment, or rubric completion may make an enriched rewrite available, but does not automatically retry or replace the initial draft unless campaign/brief/source/list/table/review-sample identity mismatches or the initial output failed.",
|
|
341
341
|
},
|
|
342
342
|
usage: {
|
|
343
|
-
codex: "After confirm_lead_list copies source rows and the
|
|
344
|
-
claude: "After confirm_lead_list copies source rows and the
|
|
345
|
-
parentThreadRule: 'Named agents are optional acceleration, but message drafting is not optional. If post-find-leads-message-scout is available, run it as the background Message Draft Builder after the filter-choice answer. If it is absent, do not customer-surface install status; the main thread must execute the same message branch from CampaignOffer state, selected source state, workflowTableId, and
|
|
343
|
+
codex: "After confirm_lead_list copies source rows and the initial campaign-table execution slice exists, ask the filter-choice question immediately. Do not spawn returned post-lead scout names before that question. Once the user answers, spawn Message Draft Builder from the same campaign/table basis. If the user chooses filters, also spawn Lead Fit Builder, move to Filter Rules, save rubrics, ask for filter approval, then keep the browser on Filter Leads while the message recommendation is reviewed. If filters are skipped, move to Messages/message review.",
|
|
344
|
+
claude: "After confirm_lead_list copies source rows and the initial campaign-table execution slice exists, ask the filter-choice question immediately. Do not invoke returned post-lead Task/Agent names before that question. Once the user answers, invoke Message Draft Builder from the same campaign/table basis. If the user chooses filters, also invoke Lead Fit Builder, move to Filter Rules, save rubrics, ask for filter approval, then keep the browser on Filter Leads while the message recommendation is reviewed. If filters are skipped, move to Messages/message review.",
|
|
345
|
+
parentThreadRule: 'Named agents are optional acceleration, but message drafting is not optional. If post-find-leads-message-scout is available, run it as the background Message Draft Builder after the filter-choice answer. If it is absent, do not customer-surface install status; the main thread must execute the same message branch from CampaignOffer state, selected source state, workflowTableId, and initial campaign-table execution slice rows. Local markdown/json files are not normal-path inputs. The filter-choice question is the first post-import user gate; do not load post-lead registries, filter references, or the full generate-messages prompt before it. Message drafting starts after the filter-choice answer, must load get_subskill_prompt({ subskillName: "generate-messages", offset, limit }) until hasMore=false, must read live campaign table state through scoped MCP/product tools, and must reject mismatched selectedLeadListId/workflowTableId/campaign/workspace input. On the filter path, keep the browser on Filter Rules after save_rubrics so the user can approve the saved criteria; only then move to Filter Leads and wait there for message approval. Enrichment, filtering, Generate Message cells, sender setup, sequence attach, and launch wait for template approval on the Use Template path. On the skip path, move to Messages/message review and wait for message approval before enrichment or Settings. Do not render message review from checklist or shortcut instructions; message review requires a messageDraftRecommendation whose basis proves the full generate-messages prompt ran for the current campaign/table execution slice. Do not automatically rerun Message Draft Builder after filters/enrichment finish; show the initial draft by default and offer an enriched rewrite only with explicit user opt-in.',
|
|
346
346
|
},
|
|
347
347
|
};
|
|
348
348
|
}
|
package/dist/tools/readiness.js
CHANGED
|
@@ -101,7 +101,7 @@ export const readinessToolDefinitions = [
|
|
|
101
101
|
},
|
|
102
102
|
targetLeadCount: {
|
|
103
103
|
type: "number",
|
|
104
|
-
description: "Target number of leads requested. Used as a fallback completion check when status is unavailable. For Signal Discovery, pass the approved source-candidate target; if the completed source list lands below it, the tool still returns ready with a source_shortfall warning so the confirmed list can be copied and
|
|
104
|
+
description: "Target number of leads requested. Used as a fallback completion check when status is unavailable. For Signal Discovery, pass the approved source-candidate target; if the completed source list lands below it, the tool still returns ready with a source_shortfall warning so the confirmed list can be copied and campaign setup can proceed.",
|
|
105
105
|
},
|
|
106
106
|
timeoutMs: {
|
|
107
107
|
type: "number",
|
|
@@ -489,7 +489,7 @@ export async function waitForLeadListReady(input) {
|
|
|
489
489
|
targetLeadCount: targetLeadCount ?? null,
|
|
490
490
|
sourceShortfall: signalSourceShortfall,
|
|
491
491
|
warning: signalSourceShortfall
|
|
492
|
-
? `Signal Discovery completed with ${rowCount.toLocaleString("en-US")} source candidates, below the approved ${signalSourceShortfallTarget.toLocaleString("en-US")} source-candidate target. Confirm/copy the completed list for
|
|
492
|
+
? `Signal Discovery completed with ${rowCount.toLocaleString("en-US")} source candidates, below the approved ${signalSourceShortfallTarget.toLocaleString("en-US")} source-candidate target. Confirm/copy the completed list for campaign setup, then add more approved posts or switch provider if the source quality/scale is not enough.`
|
|
493
493
|
: undefined,
|
|
494
494
|
};
|
|
495
495
|
}
|
package/dist/tools/rubrics.js
CHANGED
|
@@ -99,7 +99,7 @@ const filterRulesSavedForReviewWatchNarration = {
|
|
|
99
99
|
stage: "fit-message",
|
|
100
100
|
headline: "Filter rules saved for review",
|
|
101
101
|
visibleState: "The browser is showing Filter Rules with the saved criteria.",
|
|
102
|
-
agentIntent: "Codex is waiting for filter approval before
|
|
102
|
+
agentIntent: "Codex is waiting for filter approval before campaign rows are enriched or scored.",
|
|
103
103
|
nextAction: "Approve or revise the filters",
|
|
104
104
|
progressLabel: "Fit + message",
|
|
105
105
|
safety: "Saved rules are ready to review; downstream row processing remains gated.",
|