@sellable/mcp 0.1.195 → 0.1.196
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/source-scout-linkedin-engagement.md +1 -1
- package/dist/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/tools/campaigns.js +13 -7
- package/dist/tools/leads.d.ts +23 -0
- package/dist/tools/leads.js +23 -9
- package/dist/tools/registry.d.ts +18 -0
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +7 -2
- package/skills/create-campaign-v2/SKILL.md +36 -41
- package/skills/create-campaign-v2/core/flow.v2.json +1 -1
- package/skills/create-campaign-v2-tail/SKILL.md +2 -1
- package/skills/providers/signal-discovery.md +18 -4
- package/skills/research/config.json +0 -9
|
@@ -43,7 +43,7 @@ sales Claude`; do not treat broad anchor-only lanes like `Claude Code`, `MCP`,
|
|
|
43
43
|
5. Promote the first narrow sample set when campaign-attached. If a
|
|
44
44
|
`campaignOfferId` was supplied, call `select_promising_posts({
|
|
45
45
|
campaignOfferId, selectionMode: "replace", selections, headlineICPCriteria,
|
|
46
|
-
currentStep: "signal-discovery" })` before sampling so the watched Signal
|
|
46
|
+
scrapePlanMode: "all-selected", currentStep: "signal-discovery" })` before sampling so the watched Signal
|
|
47
47
|
Discovery table shows the promoted posts and the exact posts being tested.
|
|
48
48
|
Do not move the campaign to `confirm-lead-list`; `import_leads` owns that
|
|
49
49
|
visible transition after source approval.
|
package/dist/index-dev.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/tools/campaigns.js
CHANGED
|
@@ -798,23 +798,29 @@ export async function createCampaign(input) {
|
|
|
798
798
|
isAnchor: true,
|
|
799
799
|
},
|
|
800
800
|
};
|
|
801
|
-
//
|
|
802
|
-
//
|
|
803
|
-
//
|
|
801
|
+
// Create-campaign v2 mints the watchable brief-review shell. Source choice is
|
|
802
|
+
// a later approved transition, so source fields passed during shell creation
|
|
803
|
+
// are treated as intent only and are not persisted as state.
|
|
804
|
+
const briefReviewShellStep = "create-offer";
|
|
805
|
+
const effectiveCurrentStep = currentStep ?? "create-offer";
|
|
806
|
+
const isCreatingBriefReviewShell = effectiveCurrentStep === briefReviewShellStep;
|
|
807
|
+
const shouldPersistSourceStateOnCreate = !isCreatingBriefReviewShell;
|
|
804
808
|
const normalizedLeadSourceProvider = input.leadSourceProvider === undefined
|
|
805
809
|
? undefined
|
|
806
810
|
: normalizeLeadSourceProvider(input.leadSourceProvider);
|
|
807
|
-
const { messageGenerationMode: _messageGenerationMode, ...apiInput } = input;
|
|
811
|
+
const { messageGenerationMode: _messageGenerationMode, leadSourceProvider: _leadSourceProvider, leadSourceType: _leadSourceType, ...apiInput } = input;
|
|
808
812
|
const formattedInput = {
|
|
809
813
|
...apiInput,
|
|
810
814
|
name,
|
|
811
815
|
clientProspectId,
|
|
812
816
|
offerPositioning,
|
|
813
|
-
...(input.leadSourceProvider !== undefined
|
|
817
|
+
...(shouldPersistSourceStateOnCreate && input.leadSourceProvider !== undefined
|
|
814
818
|
? { leadSourceProvider: normalizedLeadSourceProvider }
|
|
815
819
|
: {}),
|
|
816
|
-
currentStep:
|
|
817
|
-
|
|
820
|
+
currentStep: effectiveCurrentStep,
|
|
821
|
+
...(shouldPersistSourceStateOnCreate
|
|
822
|
+
? { leadSourceType: input.leadSourceType ?? "new" }
|
|
823
|
+
: {}),
|
|
818
824
|
senderLinkedinUrl,
|
|
819
825
|
campaignBrief: {
|
|
820
826
|
name, // Use campaign name as brief name
|
package/dist/tools/leads.d.ts
CHANGED
|
@@ -165,6 +165,7 @@ export type SelectPromisingPostsInput = {
|
|
|
165
165
|
currentStep?: string | null;
|
|
166
166
|
selectionMode?: "add" | "replace";
|
|
167
167
|
mode?: "add" | "replace";
|
|
168
|
+
scrapePlanMode?: "all-selected" | "capacity-target";
|
|
168
169
|
targetEngagerCount?: number;
|
|
169
170
|
maxPostsToScrape?: number;
|
|
170
171
|
};
|
|
@@ -250,6 +251,7 @@ export declare const leadToolDefinitions: ({
|
|
|
250
251
|
includeRawImportResult?: undefined;
|
|
251
252
|
selections?: undefined;
|
|
252
253
|
selectionMode?: undefined;
|
|
254
|
+
scrapePlanMode?: undefined;
|
|
253
255
|
};
|
|
254
256
|
required: string[];
|
|
255
257
|
};
|
|
@@ -428,6 +430,7 @@ export declare const leadToolDefinitions: ({
|
|
|
428
430
|
includeRawImportResult?: undefined;
|
|
429
431
|
selections?: undefined;
|
|
430
432
|
selectionMode?: undefined;
|
|
433
|
+
scrapePlanMode?: undefined;
|
|
431
434
|
};
|
|
432
435
|
required: never[];
|
|
433
436
|
};
|
|
@@ -505,6 +508,7 @@ export declare const leadToolDefinitions: ({
|
|
|
505
508
|
includeRawImportResult?: undefined;
|
|
506
509
|
selections?: undefined;
|
|
507
510
|
selectionMode?: undefined;
|
|
511
|
+
scrapePlanMode?: undefined;
|
|
508
512
|
};
|
|
509
513
|
required: string[];
|
|
510
514
|
};
|
|
@@ -654,6 +658,7 @@ export declare const leadToolDefinitions: ({
|
|
|
654
658
|
includeRawImportResult?: undefined;
|
|
655
659
|
selections?: undefined;
|
|
656
660
|
selectionMode?: undefined;
|
|
661
|
+
scrapePlanMode?: undefined;
|
|
657
662
|
};
|
|
658
663
|
required: string[];
|
|
659
664
|
};
|
|
@@ -745,6 +750,7 @@ export declare const leadToolDefinitions: ({
|
|
|
745
750
|
includeRawImportResult?: undefined;
|
|
746
751
|
selections?: undefined;
|
|
747
752
|
selectionMode?: undefined;
|
|
753
|
+
scrapePlanMode?: undefined;
|
|
748
754
|
};
|
|
749
755
|
required: string[];
|
|
750
756
|
};
|
|
@@ -845,6 +851,7 @@ export declare const leadToolDefinitions: ({
|
|
|
845
851
|
includeRawImportResult?: undefined;
|
|
846
852
|
selections?: undefined;
|
|
847
853
|
selectionMode?: undefined;
|
|
854
|
+
scrapePlanMode?: undefined;
|
|
848
855
|
};
|
|
849
856
|
required: string[];
|
|
850
857
|
};
|
|
@@ -927,6 +934,7 @@ export declare const leadToolDefinitions: ({
|
|
|
927
934
|
includeRawImportResult?: undefined;
|
|
928
935
|
selections?: undefined;
|
|
929
936
|
selectionMode?: undefined;
|
|
937
|
+
scrapePlanMode?: undefined;
|
|
930
938
|
};
|
|
931
939
|
required: never[];
|
|
932
940
|
};
|
|
@@ -1548,6 +1556,7 @@ export declare const leadToolDefinitions: ({
|
|
|
1548
1556
|
includeRawImportResult?: undefined;
|
|
1549
1557
|
selections?: undefined;
|
|
1550
1558
|
selectionMode?: undefined;
|
|
1559
|
+
scrapePlanMode?: undefined;
|
|
1551
1560
|
};
|
|
1552
1561
|
required: string[];
|
|
1553
1562
|
};
|
|
@@ -1682,6 +1691,7 @@ export declare const leadToolDefinitions: ({
|
|
|
1682
1691
|
includeRawImportResult?: undefined;
|
|
1683
1692
|
selections?: undefined;
|
|
1684
1693
|
selectionMode?: undefined;
|
|
1694
|
+
scrapePlanMode?: undefined;
|
|
1685
1695
|
};
|
|
1686
1696
|
required: never[];
|
|
1687
1697
|
};
|
|
@@ -1804,6 +1814,7 @@ export declare const leadToolDefinitions: ({
|
|
|
1804
1814
|
includeRawImportResult?: undefined;
|
|
1805
1815
|
selections?: undefined;
|
|
1806
1816
|
selectionMode?: undefined;
|
|
1817
|
+
scrapePlanMode?: undefined;
|
|
1807
1818
|
};
|
|
1808
1819
|
required: string[];
|
|
1809
1820
|
};
|
|
@@ -1884,6 +1895,7 @@ export declare const leadToolDefinitions: ({
|
|
|
1884
1895
|
includeRawImportResult?: undefined;
|
|
1885
1896
|
selections?: undefined;
|
|
1886
1897
|
selectionMode?: undefined;
|
|
1898
|
+
scrapePlanMode?: undefined;
|
|
1887
1899
|
};
|
|
1888
1900
|
required: string[];
|
|
1889
1901
|
};
|
|
@@ -1987,6 +1999,7 @@ export declare const leadToolDefinitions: ({
|
|
|
1987
1999
|
tableId?: undefined;
|
|
1988
2000
|
selections?: undefined;
|
|
1989
2001
|
selectionMode?: undefined;
|
|
2002
|
+
scrapePlanMode?: undefined;
|
|
1990
2003
|
};
|
|
1991
2004
|
required: string[];
|
|
1992
2005
|
};
|
|
@@ -2034,6 +2047,11 @@ export declare const leadToolDefinitions: ({
|
|
|
2034
2047
|
enum: string[];
|
|
2035
2048
|
description: string;
|
|
2036
2049
|
};
|
|
2050
|
+
scrapePlanMode: {
|
|
2051
|
+
type: string;
|
|
2052
|
+
enum: string[];
|
|
2053
|
+
description: string;
|
|
2054
|
+
};
|
|
2037
2055
|
targetEngagerCount: {
|
|
2038
2056
|
type: string;
|
|
2039
2057
|
description: string;
|
|
@@ -2184,6 +2202,7 @@ export declare const leadToolDefinitions: ({
|
|
|
2184
2202
|
includeRawImportResult?: undefined;
|
|
2185
2203
|
selections?: undefined;
|
|
2186
2204
|
selectionMode?: undefined;
|
|
2205
|
+
scrapePlanMode?: undefined;
|
|
2187
2206
|
};
|
|
2188
2207
|
required: string[];
|
|
2189
2208
|
};
|
|
@@ -2724,11 +2743,15 @@ export declare function selectPromisingPosts(input: SelectPromisingPostsInput):
|
|
|
2724
2743
|
message: string;
|
|
2725
2744
|
recommendedPostCount?: undefined;
|
|
2726
2745
|
recommendedTargetEngagerCount?: undefined;
|
|
2746
|
+
selectionMode?: undefined;
|
|
2747
|
+
scrapePlanMode?: undefined;
|
|
2727
2748
|
} | {
|
|
2728
2749
|
success: boolean;
|
|
2729
2750
|
selectedCount: number;
|
|
2730
2751
|
recommendedPostCount: number;
|
|
2731
2752
|
recommendedTargetEngagerCount: number | null;
|
|
2753
|
+
selectionMode: "replace" | "add";
|
|
2754
|
+
scrapePlanMode: "all-selected" | "capacity-target";
|
|
2732
2755
|
unselectedCount: number;
|
|
2733
2756
|
criteriaCount: number;
|
|
2734
2757
|
message: string;
|
package/dist/tools/leads.js
CHANGED
|
@@ -1314,7 +1314,7 @@ export const leadToolDefinitions = [
|
|
|
1314
1314
|
},
|
|
1315
1315
|
{
|
|
1316
1316
|
name: "search_signals",
|
|
1317
|
-
description: 'Search LinkedIn posts for signal discovery. Requires get_provider_prompt({ provider: "signal-discovery" }) first. Supports keyword search, profile posts, company posts, or a single post URL. Use campaignless preview mode for directional discovery; include `campaignOfferId` when the search should persist into campaign state. Post-mint create-campaign-v2 watch runs MUST pass campaignOfferId and currentStep: "signal-discovery"
|
|
1317
|
+
description: 'Search LinkedIn posts for signal discovery. Requires get_provider_prompt({ provider: "signal-discovery" }) first. Supports keyword search, profile posts, company posts, or a single post URL. Use campaignless preview mode for directional discovery; include `campaignOfferId` when the search should persist into campaign state. Post-mint create-campaign-v2 watch runs MUST pass campaignOfferId and currentStep: "signal-discovery" only after the visible source-plan gate has been approved and leadSourceProvider has been persisted. Omitting campaignOfferId after that approval orphans the search from the UI. Returns a compact summary with recommended posts to avoid context bloat.',
|
|
1318
1318
|
inputSchema: {
|
|
1319
1319
|
type: "object",
|
|
1320
1320
|
properties: {
|
|
@@ -1533,7 +1533,7 @@ export const leadToolDefinitions = [
|
|
|
1533
1533
|
},
|
|
1534
1534
|
{
|
|
1535
1535
|
name: "select_promising_posts",
|
|
1536
|
-
description:
|
|
1536
|
+
description: 'Select/promote LinkedIn posts for lead scraping AND provide headline ICP criteria. selectionMode is required: use "replace" for a fresh/absolute promoted set that clears previous promoted posts; use "add" only when intentionally expanding the current promoted set. Use the selectionTarget returned by search_signals (default 3) for the sampling/promoted set. scrapePlanMode controls the import recommendation: use "all-selected" when approval should scrape every promoted post; use "capacity-target" with targetEngagerCount when sample math should recommend the smallest right-content post subset that covers the source-candidate target. If selectedCount and recommendedPostCount differ, user-facing copy must describe the recommended/importable scrape set, not the broader selected sample.',
|
|
1537
1537
|
inputSchema: {
|
|
1538
1538
|
type: "object",
|
|
1539
1539
|
properties: {
|
|
@@ -1571,7 +1571,12 @@ export const leadToolDefinitions = [
|
|
|
1571
1571
|
selectionMode: {
|
|
1572
1572
|
type: "string",
|
|
1573
1573
|
enum: ["add", "replace"],
|
|
1574
|
-
description: 'How to apply selections. "
|
|
1574
|
+
description: 'Required. How to apply selections. "replace" is the fresh/absolute set mode: it clears previously promoted posts not included in this call. "add" keeps existing promoted posts and adds these; use only when intentionally expanding a current selection.',
|
|
1575
|
+
},
|
|
1576
|
+
scrapePlanMode: {
|
|
1577
|
+
type: "string",
|
|
1578
|
+
enum: ["all-selected", "capacity-target"],
|
|
1579
|
+
description: 'How to recommend the scrape/import set after promotion. "all-selected" means approve/import every promoted post. "capacity-target" means use targetEngagerCount/maxPostsToScrape to recommend only enough promoted posts to cover the source-candidate target.',
|
|
1575
1580
|
},
|
|
1576
1581
|
targetEngagerCount: {
|
|
1577
1582
|
type: "number",
|
|
@@ -1586,7 +1591,12 @@ export const leadToolDefinitions = [
|
|
|
1586
1591
|
description: "Headless workflow step ID",
|
|
1587
1592
|
},
|
|
1588
1593
|
},
|
|
1589
|
-
required: [
|
|
1594
|
+
required: [
|
|
1595
|
+
"campaignOfferId",
|
|
1596
|
+
"selections",
|
|
1597
|
+
"headlineICPCriteria",
|
|
1598
|
+
"selectionMode",
|
|
1599
|
+
],
|
|
1590
1600
|
},
|
|
1591
1601
|
},
|
|
1592
1602
|
{
|
|
@@ -3159,8 +3169,10 @@ export function getProviderPrompt(input) {
|
|
|
3159
3169
|
}
|
|
3160
3170
|
export async function selectPromisingPosts(input) {
|
|
3161
3171
|
const api = getApi();
|
|
3162
|
-
const { campaignOfferId, selections, headlineICPCriteria, selectionMode, mode, targetEngagerCount, maxPostsToScrape, } = input;
|
|
3163
|
-
const effectiveMode = selectionMode ?? mode ?? "
|
|
3172
|
+
const { campaignOfferId, selections, headlineICPCriteria, selectionMode, mode, scrapePlanMode, targetEngagerCount, maxPostsToScrape, } = input;
|
|
3173
|
+
const effectiveMode = selectionMode ?? mode ?? "replace";
|
|
3174
|
+
const effectiveScrapePlanMode = scrapePlanMode ??
|
|
3175
|
+
(targetEngagerCount || maxPostsToScrape ? "capacity-target" : "all-selected");
|
|
3164
3176
|
if (selections.length > MAX_SIGNAL_DISCOVERY_POSTS) {
|
|
3165
3177
|
return {
|
|
3166
3178
|
success: false,
|
|
@@ -3232,8 +3244,8 @@ export async function selectPromisingPosts(input) {
|
|
|
3232
3244
|
}
|
|
3233
3245
|
const recommendation = buildSignalDiscoverySourceRecommendation({
|
|
3234
3246
|
selectedPosts: Array.from(selectedByUrl.values()),
|
|
3235
|
-
targetEngagerCount,
|
|
3236
|
-
maxPostsToScrape,
|
|
3247
|
+
targetEngagerCount: effectiveScrapePlanMode === "capacity-target" ? targetEngagerCount : null,
|
|
3248
|
+
maxPostsToScrape: effectiveScrapePlanMode === "capacity-target" ? maxPostsToScrape : null,
|
|
3237
3249
|
});
|
|
3238
3250
|
sourceRecommendation = recommendation.message;
|
|
3239
3251
|
recommendedPostCount = recommendation.recommendedPostCount;
|
|
@@ -3262,11 +3274,13 @@ Approval card should say:
|
|
|
3262
3274
|
selectedCount: selectionResult.selectedCount,
|
|
3263
3275
|
recommendedPostCount,
|
|
3264
3276
|
recommendedTargetEngagerCount: recommendationTargetEngagerCount,
|
|
3277
|
+
selectionMode: effectiveMode,
|
|
3278
|
+
scrapePlanMode: effectiveScrapePlanMode,
|
|
3265
3279
|
unselectedCount: selectionResult.unselectedCount,
|
|
3266
3280
|
criteriaCount: selectionResult.criteriaCount,
|
|
3267
3281
|
message: `${sourceRecommendation}
|
|
3268
3282
|
|
|
3269
|
-
|
|
3283
|
+
Selection mode: ${effectiveMode === "replace" ? "replace/fresh absolute set; previous promoted posts not included here were cleared" : "add; existing promoted posts were preserved"}. Selection persisted: ${selectionResult.selectedCount} selected post${selectionResult.selectedCount === 1 ? "" : "s"} with ${selectionResult.criteriaCount} ICP criteria. Import plan: ${recommendedPostCount} recommended post${recommendedPostCount === 1 ? "" : "s"} (${effectiveScrapePlanMode}). Ask the user to approve that recommended scrape set once; after approval, ${effectiveScrapePlanMode === "all-selected" ? "call import_leads without targetEngagerCount/maxPostsToScrape so all promoted posts are scraped" : `call import_leads with targetEngagerCount ${recommendationTargetEngagerCount ?? "from the approved source math"} immediately`} and do not claim a larger post count than recommendedPostCount.`,
|
|
3270
3284
|
};
|
|
3271
3285
|
}
|
|
3272
3286
|
export async function setHeadlineICPCriteria(input) {
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -1608,6 +1608,7 @@ export declare const allTools: ({
|
|
|
1608
1608
|
includeRawImportResult?: undefined;
|
|
1609
1609
|
selections?: undefined;
|
|
1610
1610
|
selectionMode?: undefined;
|
|
1611
|
+
scrapePlanMode?: undefined;
|
|
1611
1612
|
};
|
|
1612
1613
|
required: string[];
|
|
1613
1614
|
};
|
|
@@ -1786,6 +1787,7 @@ export declare const allTools: ({
|
|
|
1786
1787
|
includeRawImportResult?: undefined;
|
|
1787
1788
|
selections?: undefined;
|
|
1788
1789
|
selectionMode?: undefined;
|
|
1790
|
+
scrapePlanMode?: undefined;
|
|
1789
1791
|
};
|
|
1790
1792
|
required: never[];
|
|
1791
1793
|
};
|
|
@@ -1863,6 +1865,7 @@ export declare const allTools: ({
|
|
|
1863
1865
|
includeRawImportResult?: undefined;
|
|
1864
1866
|
selections?: undefined;
|
|
1865
1867
|
selectionMode?: undefined;
|
|
1868
|
+
scrapePlanMode?: undefined;
|
|
1866
1869
|
};
|
|
1867
1870
|
required: string[];
|
|
1868
1871
|
};
|
|
@@ -2012,6 +2015,7 @@ export declare const allTools: ({
|
|
|
2012
2015
|
includeRawImportResult?: undefined;
|
|
2013
2016
|
selections?: undefined;
|
|
2014
2017
|
selectionMode?: undefined;
|
|
2018
|
+
scrapePlanMode?: undefined;
|
|
2015
2019
|
};
|
|
2016
2020
|
required: string[];
|
|
2017
2021
|
};
|
|
@@ -2103,6 +2107,7 @@ export declare const allTools: ({
|
|
|
2103
2107
|
includeRawImportResult?: undefined;
|
|
2104
2108
|
selections?: undefined;
|
|
2105
2109
|
selectionMode?: undefined;
|
|
2110
|
+
scrapePlanMode?: undefined;
|
|
2106
2111
|
};
|
|
2107
2112
|
required: string[];
|
|
2108
2113
|
};
|
|
@@ -2203,6 +2208,7 @@ export declare const allTools: ({
|
|
|
2203
2208
|
includeRawImportResult?: undefined;
|
|
2204
2209
|
selections?: undefined;
|
|
2205
2210
|
selectionMode?: undefined;
|
|
2211
|
+
scrapePlanMode?: undefined;
|
|
2206
2212
|
};
|
|
2207
2213
|
required: string[];
|
|
2208
2214
|
};
|
|
@@ -2285,6 +2291,7 @@ export declare const allTools: ({
|
|
|
2285
2291
|
includeRawImportResult?: undefined;
|
|
2286
2292
|
selections?: undefined;
|
|
2287
2293
|
selectionMode?: undefined;
|
|
2294
|
+
scrapePlanMode?: undefined;
|
|
2288
2295
|
};
|
|
2289
2296
|
required: never[];
|
|
2290
2297
|
};
|
|
@@ -2906,6 +2913,7 @@ export declare const allTools: ({
|
|
|
2906
2913
|
includeRawImportResult?: undefined;
|
|
2907
2914
|
selections?: undefined;
|
|
2908
2915
|
selectionMode?: undefined;
|
|
2916
|
+
scrapePlanMode?: undefined;
|
|
2909
2917
|
};
|
|
2910
2918
|
required: string[];
|
|
2911
2919
|
};
|
|
@@ -3040,6 +3048,7 @@ export declare const allTools: ({
|
|
|
3040
3048
|
includeRawImportResult?: undefined;
|
|
3041
3049
|
selections?: undefined;
|
|
3042
3050
|
selectionMode?: undefined;
|
|
3051
|
+
scrapePlanMode?: undefined;
|
|
3043
3052
|
};
|
|
3044
3053
|
required: never[];
|
|
3045
3054
|
};
|
|
@@ -3162,6 +3171,7 @@ export declare const allTools: ({
|
|
|
3162
3171
|
includeRawImportResult?: undefined;
|
|
3163
3172
|
selections?: undefined;
|
|
3164
3173
|
selectionMode?: undefined;
|
|
3174
|
+
scrapePlanMode?: undefined;
|
|
3165
3175
|
};
|
|
3166
3176
|
required: string[];
|
|
3167
3177
|
};
|
|
@@ -3242,6 +3252,7 @@ export declare const allTools: ({
|
|
|
3242
3252
|
includeRawImportResult?: undefined;
|
|
3243
3253
|
selections?: undefined;
|
|
3244
3254
|
selectionMode?: undefined;
|
|
3255
|
+
scrapePlanMode?: undefined;
|
|
3245
3256
|
};
|
|
3246
3257
|
required: string[];
|
|
3247
3258
|
};
|
|
@@ -3345,6 +3356,7 @@ export declare const allTools: ({
|
|
|
3345
3356
|
tableId?: undefined;
|
|
3346
3357
|
selections?: undefined;
|
|
3347
3358
|
selectionMode?: undefined;
|
|
3359
|
+
scrapePlanMode?: undefined;
|
|
3348
3360
|
};
|
|
3349
3361
|
required: string[];
|
|
3350
3362
|
};
|
|
@@ -3392,6 +3404,11 @@ export declare const allTools: ({
|
|
|
3392
3404
|
enum: string[];
|
|
3393
3405
|
description: string;
|
|
3394
3406
|
};
|
|
3407
|
+
scrapePlanMode: {
|
|
3408
|
+
type: string;
|
|
3409
|
+
enum: string[];
|
|
3410
|
+
description: string;
|
|
3411
|
+
};
|
|
3395
3412
|
targetEngagerCount: {
|
|
3396
3413
|
type: string;
|
|
3397
3414
|
description: string;
|
|
@@ -3542,6 +3559,7 @@ export declare const allTools: ({
|
|
|
3542
3559
|
includeRawImportResult?: undefined;
|
|
3543
3560
|
selections?: undefined;
|
|
3544
3561
|
selectionMode?: undefined;
|
|
3562
|
+
scrapePlanMode?: undefined;
|
|
3545
3563
|
};
|
|
3546
3564
|
required: string[];
|
|
3547
3565
|
};
|
package/package.json
CHANGED
|
@@ -270,8 +270,13 @@ only an internal approval proof.
|
|
|
270
270
|
|
|
271
271
|
For campaign-attached Signal Discovery sampling, promote/select the exact posts
|
|
272
272
|
with `select_promising_posts` before `fetch_post_engagers` so the user can see
|
|
273
|
-
which posts are being sampled in the watched app.
|
|
274
|
-
|
|
273
|
+
which posts are being sampled in the watched app. Use
|
|
274
|
+
`selectionMode: "replace"` for a fresh absolute promoted set and
|
|
275
|
+
`selectionMode: "add"` only when intentionally expanding existing promoted
|
|
276
|
+
posts. Use `scrapePlanMode: "all-selected"` when the approval/import should use
|
|
277
|
+
every promoted post; use `scrapePlanMode: "capacity-target"` only when source
|
|
278
|
+
math should import the smallest set that covers a target. The watch guide should
|
|
279
|
+
say that we are checking people from these posts to confirm the right people are
|
|
275
280
|
actually engaging and the source is viable.
|
|
276
281
|
|
|
277
282
|
After confirmed source rows exist in the campaign table, use the same registry pattern for
|
|
@@ -126,17 +126,16 @@ created; append the one watch-link handoff, then ask approve/revise.
|
|
|
126
126
|
|
|
127
127
|
## YOLO Mode
|
|
128
128
|
|
|
129
|
-
Enable YOLO
|
|
130
|
-
`mode=yolo`, selects `Approve + YOLO autopilot setup`, or
|
|
131
|
-
guesses
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
source, filters, and message direction from company evidence plus user
|
|
135
|
-
directions; newest directions win.
|
|
129
|
+
Enable YOLO when the user asks for yolo/autopilot, passes `--yolo` or
|
|
130
|
+
`mode=yolo`, selects `Approve + YOLO autopilot setup`, or asks you to use best
|
|
131
|
+
guesses. If identity is missing, ask only for the LinkedIn profile URL; never
|
|
132
|
+
continue from a website/domain alone. Infer buyer, offer/CTA, proof, source,
|
|
133
|
+
filters, and message direction from evidence plus user directions; newest wins.
|
|
136
134
|
Set `interactionMode: "autonomous"` once `campaignId` exists. Auto-select
|
|
137
|
-
pre-launch choices
|
|
138
|
-
|
|
139
|
-
launch.
|
|
135
|
+
confident pre-launch choices, show the assumption, and pause for missing data,
|
|
136
|
+
quality-floor failures, or final launch. Never call `start_campaign` without
|
|
137
|
+
explicit launch confirmation. YOLO still advances one visible checkpoint at a
|
|
138
|
+
time: brief approval moves only to source plan, never source/action approval.
|
|
140
139
|
|
|
141
140
|
## Structured Questions
|
|
142
141
|
|
|
@@ -155,18 +154,18 @@ when a bounded choice needs an escape hatch.
|
|
|
155
154
|
|
|
156
155
|
## Watch Link And Narration
|
|
157
156
|
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
`
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
`
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
157
|
+
Create the shell before lead import so the user can watch. When
|
|
158
|
+
`create_campaign` returns `watchUrl`, print that exact value once, directly
|
|
159
|
+
before brief approval. It must be a direct
|
|
160
|
+
`/campaign-builder/{campaignId}?mode={claude|codex}` URL with `workspaceId` and
|
|
161
|
+
`token`; never derive, shorten, reconstruct, or print a bare campaign URL. If
|
|
162
|
+
missing/broken, recover with `create_campaign({ campaignId })` or `get_campaign`
|
|
163
|
+
before approval. Initial shell state is only brief review:
|
|
164
|
+
`currentStep: "create-offer"` plus brief/identity. Do not pass `leadSourceType`,
|
|
165
|
+
`leadSourceProvider`, selected-list, workflow-table, or provider/search state
|
|
166
|
+
until later approval transitions. Later turns must not print `Watch link:`;
|
|
167
|
+
update `currentStep` and `watchNarration` so the open app changes live. Never
|
|
168
|
+
open a browser or use automation just because a watch link exists.
|
|
170
169
|
|
|
171
170
|
Every watched step switch must call:
|
|
172
171
|
|
|
@@ -188,10 +187,10 @@ when not hiring-led. For hiring-by-role signals, start with Prospeo because
|
|
|
188
187
|
`company_job_posting_quantity`; Sales Nav does not provide hiring-by-role filters.
|
|
189
188
|
|
|
190
189
|
Before any provider prompt/search/scout call, move the watched campaign to
|
|
191
|
-
source selection, show `## Find Buyers Plan`, then open
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
190
|
+
source selection, show `## Find Buyers Plan`, then open `request_user_input`
|
|
191
|
+
without repeating the URL. The plan must appear before the question; never ask
|
|
192
|
+
first or reuse that approval. This only authorizes where to look; it does not
|
|
193
|
+
add anyone yet. Write:
|
|
195
194
|
|
|
196
195
|
- the buyer groups or places we could check
|
|
197
196
|
- the best place to start
|
|
@@ -207,21 +206,17 @@ scouting", or "not importing leads" in customer-facing copy.
|
|
|
207
206
|
Terms: buyers=market; people to check=raw reactions/comments; prospects=likely
|
|
208
207
|
after fit; leads=rows.
|
|
209
208
|
|
|
210
|
-
Do not surface blanket
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
"
|
|
214
|
-
"
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
`search_sales_nav`,
|
|
222
|
-
`search_prospeo`,
|
|
223
|
-
`fetch_post_engagers`, or provider-scoped subagents until the user approves this
|
|
224
|
-
source plan or explicitly chooses a different source.
|
|
209
|
+
Do not surface blanket heuristics. Make the recommendation campaign-specific.
|
|
210
|
+
If LinkedIn engagement is recommended, name exact post themes in plain language.
|
|
211
|
+
Avoid chat terms "Signal Discovery", "lead-source scouting", "source scouting",
|
|
212
|
+
"lane", "provider", "precision/scale tradeoff", "evidence quality", "pilot
|
|
213
|
+
volume", "workflow pain", or "ICP". If public conversations look thin, recommend
|
|
214
|
+
Sales Nav or Prospeo in plain words once. Only the approval question shown after
|
|
215
|
+
that visible plan, or an explicit source choice after seeing it, satisfies this
|
|
216
|
+
gate. Do not ask a second source-plan question or call `search_signals`,
|
|
217
|
+
`search_sales_nav`, `search_prospeo`, `fetch_post_engagers`, or provider
|
|
218
|
+
subagents until approved.
|
|
219
|
+
Only then persist `leadSourceType`, `leadSourceProvider`, and provider `currentStep`.
|
|
225
220
|
|
|
226
221
|
Call `get_source_scout_registry` before source scouting. Source scouting is
|
|
227
222
|
sequential by default. Run `source-scout-linkedin-engagement`,
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"v2.1-compact","workflow":"create-campaign-v2","principle":"CampaignOffer state and watch link are canonical. Create the watched shell, approve source, confirm the source list, process the first campaign-table slice, approve filters/message, then run the bounded cascade before Settings and explicit start.","normalCustomerPath":"Use campaign state, MCP responses, and concise watchNarration. Do not create, read, link, or surface local draft files. Print the exact tokenized create_campaign.watchUrl only once at the initial brief handoff.","legacyCompatibility":{"validationSubskill":"create-campaign-v2-validation","tailSubskill":"create-campaign-v2-tail","rule":"Legacy validation/rehearsal files are opt-in diagnostics only and are not active flow gates."},"commitGateChoices":["approve","revise-brief","revise-leads","revise-rubric","revise-messaging","abort"],"canonicalStateFields":["campaignId","watchUrl","campaignBrief","currentStep","watchNarration","interactionMode","providerSearchAssociation","selectedLeadListId","workflowTableId","filterChoice","leadScoringRubrics","messageDraftRecommendation","approvedMessageTemplate","senderIds","sequenceTemplate","runningState"],"watchNarrationTransitionContract":{"rule":"Every watched currentStep switch must include fresh watchNarration.","requiredFields":["stage","headline","visibleState","agentIntent","nextAction"],"copyMustInclude":["what just happened","current visible page/state","next user action"],"appliesToTools":["create_campaign","update_campaign","attach_recommended_sequence","start_campaign"],"oneTimeWatchLinkPolicy":"After the initial brief handoff, customer-facing turns must not print another watch link unless the user asks or link recovery is needed."},"lazyReferences":{"watch":["references/watch-link-handoff.md","references/watch-guide-narration.md"],"source":["references/lead-validation-preview.md","references/step-13-import-leads.md"],"filter":["references/filter-leads.md"],"message":[],"tail":["references/sample-validation-loop.md","references/step-15-re-cascade.md","references/final-handoff-contract.md"]},"safetyBoundaries":["Do not call list_senders before Settings after message approval.","Do not import leads until the source decision is approved.","Do not queue cells until confirmed source rows exist in the campaign and the message/filter gates are satisfied.","Do not call start_campaign until the user explicitly confirms launch.","Do not use local files as durable state in normal customer runs."],"yoloMode":{"triggerPhrases":["yolo","--yolo","mode=yolo","autopilot","use best guesses","use best estimates","answer for me","just run it","yolo autopilot"],"identityRequestOnlyWhenMissing":true,"operatorDirectionsRule":"Treat freeform directions supplied at invocation or later as durable operator directions; newest conflicting direction wins.","setInteractionModeAfterCampaignCreate":"autonomous","autoSelectsPreLaunchChoices":["campaign focus","brief approval","source plan","source review/import approval","filters vs skip filters","filter rubric approval","message template approval when recommendation is approve-message","generated message review when quality floor passes","sender selection when exactly one connected sender is safe"],"mustPauseFor":["missing LinkedIn profile URL or handle","missing credentials or required data","ambiguous choice with no reasonable estimate","source/message/filter quality floor failure","final live launch confirmation"],"neverAutoStart":true},"steps":[{"id":"bootstrap","label":"Bootstrap","onEnter":[{"tool":"bootstrap_create_campaign","requiredValues":{"flowVersion":"v2"}},{"tool":"get_subskill_prompt","requiredValues":{"subskillName":"create-campaign-v2"},"purpose":"load the entry prompt once"}],"allowedTools":["bootstrap_create_campaign","get_auth_status","get_active_workspace","get_subskill_prompt","AskUserQuestion","request_user_input"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","update_campaign","queue_cells","start_campaign"],"waitFor":"bootstrap_complete","transitions":{"bootstrap_complete":"brief-interview"}},{"id":"brief-interview","label":"Client, offer, research, and brief","onEnter":[{"action":"resolve_campaign_identity_before_strategy_packet","allowedTools":["fetch_company","fetch_linkedin_profile","WebFetch","WebSearch"],"mustNotInferFromNameOnly":true,"doNotPresentSenderPickerBeforeIdentityInference":true,"fallback":"Ask only: \"What is your LinkedIn profile URL or handle?\" Normalize bare handles or `/in/...` paths to `https://www.linkedin.com/in/{handle}/`. If the input is not a profile, ask again for the person profile URL before strategy questions.","requiresLinkedInProfileUrl":true,"doNotAcceptAsIdentityInput":["non-profile URL","company page","company/name-only input without a profile handle"],"acceptsLinkedInProfileHandle":true,"normalizeLinkedInProfileHandleToUrl":true},{"action":"run_campaign_identity_research_before_strategy_packet","target":"research-sender","requiredCompletion":"complete_sender_research","allowedTools":["get_subskill_prompt","fetch_linkedin_profile","fetch_company","fetch_linkedin_posts","fetch_company_posts","WebFetch","WebSearch","complete_sender_research"],"requiredInput":"LinkedIn profile URL or handle"},{"action":"confirm_target_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"Who should we target first?","options":["Use Sellable's researched recommendation","Choose a different buyer","I'm not sure yet"],"copyMustInclude":["research basis","recommendation is editable","who to target"],"requiredOption":"Other / custom","neverCollectOpenTextWithStructuredQuestion":true,"skipIf":"user already supplied a clear target or yolo_mode with any reasonable best-estimate target"},{"action":"confirm_current_offer_before_strategy_packet","uses":"request_user_input","question":"What should we pitch to prospects first?","options":["Use Sellable's researched recommendation","Use my current pitch","Shape the pitch together"],"copyMustInclude":["research basis","public LinkedIn research may be stale","recommendation is editable","pitch to prospects"],"skipIf":"user already supplied a clear current offer or yolo_mode with any reasonable researched recommendation"},{"action":"confirm_trust_proof_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"What is the strongest credibility signal we can lead with?","options":["Use Sellable's recommended proof","Use customer proof or a case study","Use founder or company credibility"],"copyMustInclude":["build trust with prospects","recommendation is editable","proof to use or avoid"],"requiredOption":"Other / custom","skipIf":"user already supplied clear proof to use or avoid, or yolo_mode with any reasonable researched proof"},{"action":"choose_prospect_source_path_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"How should we find prospects?","options":["Find prospects for me","Use a CSV of LinkedIn profiles","Use a CSV of company domains"],"copyMustInclude":["prospects","no one added yet","find prospects for me"],"skipIf":"user already supplied a source path or yolo_mode with any reasonable source path"},{"action":"infer_strategy_packet_in_yolo_mode","when":"yolo_mode","skipStructuredSetupQuestions":true,"inferFields":["buyer segment","offer/CTA","proof to use or avoid","lead source","filter choice","message direction"],"sourceOfTruth":"identity/company lookup plus operator directions","mustStateAssumptionsInBrief":true},{"action":"render_supplied_setup_receipt_before_brief_when_setup_questions_skip","when":"any setup question is skipped because the user already supplied or yolo mode inferred identity, target, offer, proof, or source","output":"normal chat receipt before the campaign brief or watch-link handoff","copyMustInclude":["Accepted LinkedIn input: normalized to the required LinkedIn profile URL.","Offer path: supplied current offer or Sellable's researched recommendation; you can replace it with your current offer."],"copyMustNotInclude":["Campaign Identity"]},{"action":"create_watchable_campaign_shell_with_v1_brief","tool":"create_campaign","requiredFields":["name","campaignBrief","clientProspectId or senderLinkedinUrl","currentStep","watchNarration"],"requiredValues":{"currentStep":"create-offer","watchNarration.stage":"brief"},"capture":["campaignId","watchUrl"],"canonicalStateWrites":["campaignId","watchUrl","campaignBrief","currentStep:create-offer"]},{"action":"surface_campaign_shell_watch_link","watchUrlSource":"create_campaign.watchUrl","requiredWatchUrlShape":"exact create_campaign.watchUrl; direct /campaign-builder/{campaignId}?mode={claude|codex} URL with workspaceId and token","copyMustInclude":["We'll keep building the campaign in this chat.","You can watch it being built in real time in the app here:","I'll ask for your approval whenever I need your expertise or taste before moving forward.","Send changes here."],"immediateNextMainChatLine":"Ask for brief approval now. After approval, say: \"Brief approved. Next, we'll choose where to find buyers. I won't add anyone yet.\"","codexBrowserHandoff":{"openWhenAvailable":false,"printWatchLinkOnly":true,"tellUserCommandEnterOrClick":false,"mustNotUseBrowserAutomation":true,"fallbackMustNotClaimInspection":true},"copyMustAvoid":["bare /campaign-builder/{campaignId} URL","derived watch URL","second Watch link:","Next, we'll choose where to find buyers"],"oneTimeOnly":true}],"allowedTools":["get_subskill_prompt","fetch_company","fetch_company_posts","fetch_linkedin_profile","fetch_linkedin_posts","complete_sender_research","create_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["campaign_shell_created","brief_ready","confirm_with_user"],"transitions":{"campaign_shell_created":"brief-review","brief_ready":"brief-review","confirm_with_user":"brief-review"}},{"id":"brief-review","label":"Brief review","onEnter":[{"action":"render_brief_inline","minimumVisibleDetail":"campaign direction, buyer, offer, proof, source plan, safety gates","copyMustInclude":["This brief is based on Sellable's research and your answers.","If your site or LinkedIn is stale","Do you agree with this researched campaign direction?"],"copyMustNotInclude":["quoted first-message copy","Message Angle as final proof","This approval covers","does not approve adding people","does not approve scraping posts","selecting a sender","attaching a sequence","Campaign Identity","what should this campaign sell first","sell first","[from you]","[from LinkedIn]","[from website]","[from case study]","[Sellable recommendation]"],"messageHintRule":"If any message-shape hint remains, keep it as non-final bullets and say real examples come after leads are found and filtered.","approvalBoundaryRule":"Keep this gate positive and short. Do not list later steps this approval does not cover; ask approve/revise and then move to choosing where to find buyers.","sectionLabelRule":"Use ## Sender and Company for the person/company context; never render ## Campaign Identity.","skipIf":"the full brief was already rendered in the current shell-creation handoff before create_campaign returned","renderOnceRule":"The brief must be visible exactly once before approval. Do not render it again immediately after shell creation; append the one watch-link handoff and ask the approval question.","copyMustAvoid":["duplicate brief render after shell creation","second Watch link:"]},{"action":"ask_brief_choice","uses":"request_user_input","choices":["Approve brief","Revise brief","Approve + YOLO autopilot setup"],"skipIf":"yolo_mode; auto_continue after rendering assumptions unless brief quality floor fails","question":"Do you agree with this researched campaign direction?","autonomousChoice":"Approve + YOLO autopilot setup","choiceDescriptions":{"Approve + YOLO autopilot setup":"Use best guesses to finish setup and stop before final launch."}}],"requiredCampaignState":["campaignId","watchUrl","campaignBrief","currentStep"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign"],"doNotAllow":["create_campaign","list_senders","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["user_brief_confirmed","revise_brief","auto_continue"],"transitions":{"user_brief_confirmed":"find-leads","revise_brief":"brief-interview","auto_continue":"find-leads"}},{"id":"find-leads","label":"Find leads","sourceSelectionFunnel":{"preScoutRecommendationGate":{"required":true,"label":"Find buyers plan approval","mustHappenBefore":["get_provider_prompt","search_signals","search_sales_nav","search_prospeo","fetch_post_engagers","source-scout dispatch"],"show":["buyer groups or places we could check","best place to start","why the right buyers are likely to be there","what signs the next search will check","where to look next if the first place is too thin","what approval authorizes"],"approvalAuthorizes":"looking for the best places to find buyers; no one added yet","explanationMustInclude":["where the right buyers are already talking","why that place fits this campaign","enough likely prospects","what counts as a good sign","where to look next if thin","I won't add anyone yet","choose where to find buyers"],"yoloMode":{"autoApproveRecommendedLane":true,"mustShowAssumedChoice":true,"pauseIfConfidenceLow":true},"customerLanguage":{"readability":"grade-5","requiredHeading":"Find Buyers Plan","prefer":["place to look","best place to start","people to check","prospects","I won't add anyone yet"],"avoid":["lane","source scouting","provider","precision/scale tradeoff","evidence quality","pilot volume","ICP","workflow pain","lead-source scouting","not importing leads"],"example":"Brief approved. Next, we'll choose where to find buyers. I won't add anyone yet. I recommend starting with LinkedIn posts about [topic]; fallback is [place].","termRule":{"buyers":"target market","peopleToCheck":"raw reactions/comments before fit is known","prospects":"likely usable people after fit","leads":"campaign rows"}},"questionOrder":"update_campaign to pick-provider, render ## Find Buyers Plan in normal chat without repeating the watch link, then open the structured approval question; never ask first or show the plan after approval","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"approvalFreshnessRule":"Only an approval question opened after the visible Find Buyers Plan, or an explicit source choice made after that plan, can set source_lane_approved. Do not reuse brief approval or pre-plan provider state."},"defaultWhenSourceUnspecified":["signal-discovery","sales-nav-recent-active","sales-nav-general","prospeo"],"userFacingFallbackOrder":["LinkedIn posts where the right buyers are already talking","people with the right titles who recently posted on LinkedIn","people with the right titles from a broader LinkedIn search","a broader company and contact search"],"parallelAllowedOnlyWhen":["user explicitly requested source comparison","resuming already-started parallel scouts","first viable source is borderline and one cheap fallback check is needed"]},"onEnter":[{"tool":"update_campaign","requiredValues":{"currentStep":"pick-provider","watchNarration.stage":"find-leads","watchNarration.headline":"Review where to find buyers","watchNarration.visibleState":"The app is showing source selection before lead finding starts.","watchNarration.agentIntent":"Codex is explaining where it recommends looking first and where it will look next if that is too thin.","watchNarration.nextAction":"Approve where to look first or choose another place","watchNarration.safety":"Only deciding where to look. No one is added yet."},"purpose":"show the visible source-plan approval checkpoint before provider lanes"},{"tool":"get_source_scout_registry","purpose":"load canonical source scout names before optional branch launch"},{"action":"render_find_buyers_plan_inline","oneShot":true,"requiredPrecondition":"currentStep=pick-provider has been saved with update_campaign","mustHappenAfter":["update_campaign currentStep=pick-provider"],"mustHappenBefore":["ask_find_buyers_plan_choice","get_provider_prompt","search_signals","search_sales_nav","search_prospeo","fetch_post_engagers","source-scout dispatch"],"output":"normal chat text before any request_user_input approval panel","requiredHeading":"Find Buyers Plan","requiredInlineFields":["plain-language buyer groups or places for this campaign","best place to start","why the right buyers are likely to be there","what signs the next search will check","where to look next if the first place is too thin","what approval authorizes"],"copyMustInclude":["Find Buyers Plan","best place to start","I won't add anyone","choose where to find buyers","I won't add anyone yet"],"copyMustAvoid":["lane","source scouting","provider","precision/scale tradeoff","evidence quality","pilot volume","ICP","workflow pain","lead-source scouting","not importing leads","Watch link:","campaign-builder URL","repeat the watch URL"],"approvalBoundary":"Explain what approval authorizes before asking: looking for the best places to find buyers; no one is added yet.","linkPolicy":"Do not include another watch link. The first brief handoff already gave the user the live app URL."},{"action":"ask_find_buyers_plan_choice","uses":"request_user_input","oneShot":true,"requiredPrecondition":"render_find_buyers_plan_inline completed in normal chat after pick-provider state was saved; no earlier approval can satisfy this","skipIf":"source_lane_approved was set by ask_find_buyers_plan_choice after the visible Find Buyers Plan, leadSourceProvider was explicitly chosen by the user after that plan, or yolo_mode auto-selected source_lane_approved after showing the assumed choice","choices":["Start with LinkedIn posts","Start with active LinkedIn profiles","Start with company/contact search","Choose a different place","Pause here"],"approvalChoiceLabelsByProvider":{"signal-discovery":"Start with LinkedIn posts","sales-nav":"Start with active LinkedIn profiles","prospeo":"Start with company/contact search"},"approvalState":"source_lane_approved","copyMustInclude":["Approve this plan for where to find buyers?","Start with LinkedIn posts"],"mustNotHappenBefore":["render_find_buyers_plan_inline"],"approvalStateRule":"Set source_lane_approved only from this question after the visible plan, never from brief approval, generic Approve plan, or pre-plan provider state."},{"action":"persist_provider_search_step_before_search","tool":"update_campaign","requiredPrecondition":"source_lane_approved set after the visible Find Buyers Plan approval question","providerCurrentStepMap":{"signal-discovery":"signal-discovery","sales-nav":"sales-nav","prospeo":"prospeo"},"requiredValues":{"leadSourceType":"new","leadSourceProvider":"approved provider","currentStep":"provider-specific current step","watchNarration.stage":"find-leads","watchNarration.headline":"Searching the approved place","watchNarration.safety":"Looking only. No one is added yet."},"mustRunBefore":["search_signals","search_sales_nav","search_prospeo","fetch_post_engagers"]},{"action":"run_sequential_source_funnel","requiredPrecondition":"source_lane_approved set after the visible Find Buyers Plan approval question","defaultOrder":["source-scout-linkedin-engagement","source-scout-sales-nav","source-scout-prospeo-contact"],"campaignOfferIdRequired":true,"stopOnFirstViableUnlessComparisonRequested":true}],"requiredCampaignState":["campaignId","campaignBrief","currentStep"],"allowedTools":["get_source_scout_registry","get_provider_prompt","lookup_sales_nav_filter","search_sales_nav","search_prospeo","search_signals","select_promising_posts","fetch_post_engagers","fetch_company","fetch_linkedin_profile","load_csv_linkedin_leads","load_csv_domains","get_rows_minimal","update_campaign","AskUserQuestion","request_user_input","get_campaign_navigation_state"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["lead_review_ready","revise_brief","confirm_with_user"],"transitions":{"lead_review_ready":"lead-review","revise_brief":"brief-interview","confirm_with_user":"lead-review"}},{"id":"lead-review","label":"Source approval","onEnter":[{"action":"show_source_decision_card","requiredInlineFields":["primary source and exact filters/recipe","specific source action awaiting approval","for Signal Discovery: compact Source Recommendation in plain language with selected posts, people to check, likely prospects, first review, and fallback","for Signal Discovery: recommended scrape post count and target people-to-check volume","first campaign review size, clearly separate from source sampling","runner-up and why it lost","raw volume","sampled people","sampled fits as n/N plus percentage/range","estimated usable prospects","cleanup risk","what will happen after approval"],"copyMustAvoid":["lead-source scouting","source scouting","headline-fit","engagers needed","execution slice","ICP","good buyers","real buyers","This approval covers","It does not approve","does not approve filters","filters, messages, sender selection","sender selection, sequence, or sending","Watch link:","campaign-builder URL","repeat the watch URL"],"approvalBoundaryRule":"Use positive source-action copy only. Include: \"After approval, I will build the source list, add it to the campaign, and review the first 15 leads before we scale.\" Never list future non-approvals.","linkPolicy":"Do not include another watch link in Source Recommendation; describe the source decision only."},{"action":"ask_source_review_choice","uses":"request_user_input","choices":["Approve scraping N recommended LinkedIn posts","Run the approved source import","Revise source","Pause here"],"approvalChoiceLabelsByProvider":{"signal-discovery":"Approve scraping {scrapePostCount} recommended LinkedIn posts?","sales-nav":"Import the approved Sales Nav source list","prospeo":"Import the approved Prospeo source list"},"postApprovalContract":{"singleUseApproval":true,"doNotRepeatAfterApproval":["Source Recommendation","show_source_decision_card","ask_source_review_choice"],"requiredNextActionAfterApproval":"ack once; call import_leads immediately","signalDiscoveryNextTool":"import_leads({ provider: \"signal-discovery\", targetEngagerCount, maxPostsToScrape, confirmed: true })"},"autoSelectIf":"yolo_mode and projected usable pool clears the source quality floor","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"requiredCampaignState":["campaignId","campaignBrief","providerSearchAssociation"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["lead_review_confirmed","revise_leads","confirm_with_user","auto_continue"],"transitions":{"lead_review_confirmed":"auto-execute-leads","revise_leads":"find-leads","confirm_with_user":"auto-execute-leads","auto_continue":"auto-execute-leads"}},{"id":"auto-execute-leads","label":"Materialize confirmed source list","currentStepValue":"auto-execute-leads","reference":"references/step-13-import-leads.md","onEnter":[{"tool":"get_subskill_prompt","requiredValues":{"subskillName":"create-campaign-v2-tail"},"purpose":"load tail contract before execution tools"},{"tool":"import_leads","requiredFields":["campaignOfferId","selected source/list","sourceListTarget or targetEngagerCount"],"requiredValues":{"salesNavProspeoDefaultSourceListTarget":1000,"signalDiscoveryDefaultEngagerTarget":1500},"modeAddHandshake":{"firstCallReturns":"needsModeSelection when adding to an existing campaign-attached list","requiredFollowup":"retry with mode=add after explicit user/source-state compatibility is confirmed"},"surfaceDedupRatio":true},{"tool":"wait_for_lead_list_ready","purpose":"poll until the source lead-list import reaches a terminal complete/failed/cancelled state before copying rows into the campaign table","repeatUntil":"ready_true_or_cancelled_or_import_failed","requiredValues":{"requireComplete":true},"onImportStillRunning":"re-run wait_for_lead_list_ready; do not call confirm_lead_list just because rows exist","partialOverrideRule":"Only if the user explicitly asks to keep going early may the next confirm_lead_list call pass allowPartialSourceList: true."},{"tool":"confirm_lead_list","requiredFields":["campaignOfferId","selectedLeadListId","reviewBatchLimit"],"requiredValues":{"reviewBatchLimit":15},"capture":["workflowTableId","reviewBatchRowIds"],"requiredPrecondition":"wait_for_lead_list_ready returned ready:true, unless the user explicitly asked to keep going early with a partial list","partialOverrideField":"allowPartialSourceList:true only after explicit user early-continue instruction","mustNotRunWhen":["wait_for_lead_list_ready reason=import_still_running and no explicit early-continue instruction","wait_for_lead_list_ready reason=cancelled","wait_for_lead_list_ready reason=import_failed","missing import job metadata"]},{"tool":"wait_for_campaign_table_ready"},{"tool":"get_rows_minimal","requiredValues":{"tableId":"{workflowTableId}","limit":15},"capture":["reviewBatchRowHash"]},{"action":"summarize_review_batch_and_advance_to_filter_choice","purpose":"ask filter choice immediately; no post-lead registries, filter refs, or message prompts first","maxCustomerCopyLines":4,"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"linkPolicy":"Ask add filters vs skip filters without repeating the watch link."}],"requiredCampaignState":["campaignId","providerSearchAssociation","selectedLeadListId"],"allowedTools":["get_subskill_prompt","import_leads","wait_for_lead_list_ready","confirm_lead_list","wait_for_campaign_table_ready","get_rows_minimal","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["get_post_find_leads_scout_registry","Task","spawn_agent","list_senders","queue_cells","start_campaign","enrich_with_prospeo","bulk_enrich_with_prospeo","save_rubrics"],"waitFor":"source_list_confirmed_and_review_sample_ready","transitions":{"source_list_confirmed_and_review_sample_ready":"filter-choice","escalation_triggered":"escalation"},"hardRules":["import_leads_then_repeated_wait_for_lead_list_ready_then_confirm_lead_list_then_wait_for_campaign_table_ready","partial_source_rows_do_not_unblock_confirm_lead_list_without_explicit_user_early_continue","cancelled_or_failed_source_import_stops_before_campaign_table_copy"]},{"id":"filter-choice","label":"Filter choice","currentStepValue":"filter-choice","onEnter":[{"tool":"update_campaign","requiredValues":{"currentStep":"filter-choice","watchNarration.stage":"fit-message"}},{"action":"ask_filter_choice","uses":"request_user_input","choices":["Use filters","Skip filters","Revise source"],"autoSelectIf":"yolo_mode; choose filters unless source is tightly curated or user directed otherwise","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"copyMustInclude":["source rows are in the campaign","add fit filters before message generation or skip filters"]}],"hardRules":["ask_filter_choice_immediately_after_review_batch_import","do_not_call_get_subskill_prompt_before_filter_choice","do_not_call_get_subskill_asset_before_filter_choice","do_not_call_get_post_find_leads_scout_registry_before_filter_choice","do_not_spawn_post_lead_agents_before_filter_choice"],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign","get_campaign_navigation_state"],"doNotAllow":["get_subskill_prompt","get_subskill_asset","get_post_find_leads_scout_registry","Task","spawn_agent","create_campaign","list_senders","import_leads","confirm_lead_list","queue_cells","start_campaign","generate_messages"],"waitFor":["filters_enabled","filters_skipped","revise_leads"],"transitions":{"filters_enabled":"post-lead-workstreams","filters_skipped":"message-generation","revise_leads":"find-leads"}},{"id":"post-lead-workstreams","label":"Filter workstream","onEnter":[{"action":"persist_add_filters_approval","tool":"update_campaign","when":"filters_enabled","requiredFields":["campaignId","enableICPFilters","currentStep","watchNarration"],"requiredValues":{"enableICPFilters":true,"currentStep":"create-icp-rubric","watchNarration.stage":"fit-message","watchNarration.headline":"Create filter rules","watchNarration.visibleState":"Filters are enabled and the browser is showing Filter Rules while Codex defines the rubric.","watchNarration.nextAction":"Review saved filter rules"},"mustRunBefore":["get_post_find_leads_scout_registry","launch_lead_fit_builder_after_filter_choice","save_rubrics"]},{"tool":"get_post_find_leads_scout_registry","purpose":"load canonical post-lead worker names only after the user has chosen filters"},{"action":"launch_lead_fit_builder_after_filter_choice","mode":"parallel_when_host_supports_subagents","target":"post-find-leads-filter-scout","inputs":["campaignId","campaignBrief","selectedLeadListId","workflowTableId","reviewBatchRowIds/hash","filterChoice"],"stateSource":"live campaign/table","debugFilesOptionalOnly":true},{"action":"launch_message_draft_builder_after_filter_decision","mode":"parallel_when_host_supports_subagents","target":"post-find-leads-message-scout","when":"filters_enabled","doesNotQueueCells":true,"customerNarration":"Say message agent is drafting."},{"action":"save_filter_rubrics_to_campaign","tool":"save_rubrics","when":"filters_enabled and rubrics are production-shaped","requiredFields":["campaignOfferId","leadScoringRubrics"],"writesCampaignState":"leadScoringRubrics","requiredSideEffects":{"enableICPFilters":true,"currentStep":"create-icp-rubric","watchNarration.headline":"Filter rules saved for review"}},{"action":"ask_filter_rubric_review_choice","uses":"request_user_input","choices":["Approve filters","Revise filters","Pause"],"purpose":"let the user read saved rubrics before Filter Leads or enrichment","copyMustInclude":["These rules prevent wasted sends before I score/import the list.","Recommended because of the researched ICP, source sample, and repeated false-positive patterns.","one pass example and one block example when available","approval is for the filter rules","Approve these filter rules."],"autoSelectIf":"yolo_mode and rubrics are production-shaped","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["get_subskill_prompt","get_subskill_asset","get_post_find_leads_scout_registry","save_rubrics","get_campaign","get_rows_minimal","update_campaign","get_campaign_navigation_state","AskUserQuestion","request_user_input","Task","spawn_agent"],"doNotAllow":["create_campaign","list_senders","import_leads","confirm_lead_list","queue_cells","start_campaign","check_rubric","generate_messages"],"waitFor":["filter_rubrics_approved","revise_leads","revise_rubric","revise_messaging"],"hardRules":["after_save_rubrics_currentStep_must_stay_create-icp-rubric_until_filter_approval","filter_approval_required_before_apply-icp-rubric_or_queue_campaign_cells","do_not_move_browser_to_messages_until_filter_leads_step_is_current_or_filters_are_explicitly_skipped","no_post_lead_worker_or_deep_prompt_before_filter_choice","lead_fit_builder_starts_only_after_filters_enabled","msg_draft_after_filter_choice","msg_draft_no_cells"],"transitions":{"filter_rubrics_approved":"message-generation","revise_leads":"find-leads","revise_rubric":"filter-rubric","revise_messaging":"message-generation","confirm_with_user":"message-review"}},{"id":"filter-rubric","label":"Rubric revision","onEnter":[{"tool":"get_subskill_asset","requiredValues":{"subskillName":"create-campaign-v2","assetPath":"references/filter-leads.md"}},{"tool":"save_rubrics","requiredFields":["campaignOfferId","leadScoringRubrics"],"writesCampaignState":"leadScoringRubrics","requiredSideEffects":{"enableICPFilters":true,"currentStep":"create-icp-rubric","watchNarration.headline":"Filter rules saved for review"}},{"action":"ask_filter_rubric_review_choice","uses":"request_user_input","choices":["Approve filters","Revise filters","Pause"],"copyMustInclude":["These rules prevent wasted sends before I score/import the list.","approval object is filter rules only","approval is for the filter rules","Approve these filter rules."],"autoSelectIf":"yolo_mode and rubrics are production-shaped","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"requiredCampaignState":["campaignId","workflowTableId"],"allowedTools":["get_subskill_asset","save_rubrics","AskUserQuestion","request_user_input"],"doNotAllow":["create_campaign","list_senders","import_leads","confirm_lead_list","update_campaign","queue_cells","start_campaign","check_rubric"],"waitFor":["filter_rubrics_approved","revise_leads","confirm_with_user","auto_continue"],"transitions":{"filter_rubrics_approved":"message-generation","revise_leads":"find-leads","confirm_with_user":"message-generation","auto_continue":"message-generation"}},{"id":"message-generation","label":"Message generation","onEnter":[{"action":"set_message_review_visible_step_by_filter_choice","tool":"update_campaign","branchRules":["yes after filter approval: currentStep=apply-icp-rubric; wait on Filter Leads","no: currentStep=messages; message review"],"watchNarration.stage":"fit-message"},{"action":"run_or_reconcile_message_draft_builder","target":"post-find-leads-message-scout","toolCallRequiredBeforeDraft":["run background post-find-leads-message-scout when available","get_subskill_prompt({ subskillName: \"generate-messages\" }) for the required normal-path contract","do not use any alternate or examples-only message prompt"],"stateSource":"campaignBrief, source, selectedLeadListId, workflowTableId, execution-slice row ids/hash","outputState":"messageDraftRecommendation","revisionMode":{"when":"revise_messaging or latest user message requests template changes","requiredInputs":["latest user feedback","current messageDraftRecommendation","campaign/table execution-slice state"],"output":"revised messageDraftRecommendation rendered before request_user_input"}}],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["get_subskill_prompt","get_campaign","get_rows_minimal","update_campaign"],"toolRules":["Run post-find-leads-message-scout when available; otherwise load get_subskill_prompt({ subskillName: \"generate-messages\" }) from live state.","brief.md, lead-review.md, and lead-sample.json are optional debug context only.","messageDraftRecommendation returns templateRecommendation, tokenFillRules, renderedSample, concerns, status, basisToken, outputAt, outputHash, and error/retry detail.","If campaign/source/table/execution-slice basis does not match, classify the output stale or blocked."],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign","generate_messages","AskUserQuestion","request_user_input"],"waitFor":["message_validation_ready","revise_rubric","revise_messaging"],"transitions":{"message_validation_ready":"message-review","revise_rubric":"filter-rubric","revise_messaging":"message-generation"},"revisionLoop":{"trigger":"user gives copy feedback, asks for an update, or chooses revise-messaging","required":["apply latest user feedback to current messageDraftRecommendation and live campaign/table state","produce a new messageDraftRecommendation with revisionNotes","render revised ## Message Template, ## Rendered Example, and What changed before asking approval"],"blocked":["asking whether an unseen new version is better","reusing the old template after acknowledging feedback","update_campaign_brief before approve-message","request_user_input or AskUserQuestion during message-generation"],"outputState":"messageDraftRecommendation"},"hardRules":["message_revision_must_render_new_template_before_approval_question","message_revision_saves_only_after_approve_message","message_generation_must_not_call_request_user_input","message_generation_must_transition_to_message_review_only_after_renderable_draft_state"]},{"id":"message-review","label":"Message review","onEnter":[{"action":"render_message_review_from_state","requiredState":["campaignBrief","selectedLeadListId","workflowTableId","messageDraftRecommendation"],"requiredVisibleLabels":["## Message Template","## Rendered Example","Good token fill:","My take:","Question: approve-message or revise-messaging?","Recommendation:"],"doNotShowByDefault":["Token Notes","Good omit / fallback","Bad fill to avoid","Token Adherence Table"],"internalPersistenceOnly":["Token Fill Rules","Token Fill Examples","fallback guidance","bad-fill avoidance notes"],"copyMustInclude":["QA receipt before approval","tokens resolved","company/person match","proof claim","prospect angle","language/tone when known","obvious bad fits","Would I take this call?","weak personalization can burn the sender's reputation","full list remains paused"],"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"revisionRenderRule":"After revise_messaging, show revised template, rendered example, and What changed before ask_message_review_choice; never ask if an unseen version is better."},{"action":"ask_message_review_choice","uses":"request_user_input","choices":["approve-message","revise-messaging"],"copyMustInclude":["approve this message template and continue","nothing sends from this approval"],"autoSelectIf":"yolo_mode and Recommendation is approve-message; revise autonomously when Recommendation is revise-messaging","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"requiredPrecondition":"current or revised template and rendered example are visible in this turn","revisionPrecondition":"after revise_messaging, revised template is visible before this question"},{"action":"sync_approved_message_set_to_campaign_brief","tool":"update_campaign_brief","when":"after approve-message","requiredFields":["campaignId","approvedMessageTemplate","Token Fill Rules","Token Fill Examples"],"writesCampaignState":"approvedMessageTemplate","revisionApprovalRule":"If the user requested revisions, write only the latest approved revised template and token rules to campaignBrief."}],"requiredCampaignState":["campaignId","workflowTableId","messageDraftRecommendation"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign_brief","update_campaign","get_rows_minimal"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","start_campaign"],"waitFor":["message_approved","revise_messaging"],"transitions":{"message_approved":"validate-sample","revise_messaging":"message-generation"}},{"id":"validate-sample","label":"Validate campaign-table execution slice","currentStepValue":"apply-icp-rubric","reference":"references/sample-validation-loop.md","visibleStepRule":"Filter Leads is reached only after saved-filter approval; on approve-message, save the template and queue bounded Enrich Prospect cells through selector-based campaign processing.","onEnter":[{"tool":"update_campaign","requiredValues":{"currentStep":"apply-icp-rubric","watchNarration.stage":"fit-message"},"purpose":"keep the watched UI on Filter Leads while the approved template starts the bounded cascade","watchNarrationRule":"Say the template is saved and Filter Leads is now running the bounded enrichment and scoring pass."},{"tool":"queue_campaign_cells","purpose":"queue Enrich Prospect cells for the stored review batch without fetching bulky row payloads","requiredFields":["workflowTableId","columnRole","rowSelector"],"requiredValues":{"columnRole":"enrich","rowSelector":{"type":"reviewBatch"},"forceRerun":false},"targetCountSource":"WorkflowTable.config.mcp.reviewBatch.rowCount (default 15)"},{"tool":"wait_for_campaign_processing","purpose":"wait_for_first_passed_review_batch_row","requiredFields":["workflowTableId","minPassedCount"],"requiredValues":{"minPassedCount":1},"readVia":"stats_only_tool_result","timeoutGuidance":"If this times out, do not repoll identical args; use partial diagnostics to revise filters or surface blocked sample state.","customerSummaryPattern":["{checked} leads checked","{passed} passed fit scoring","{blocked} blocked before sending","full list remains paused until approval"]},{"action":"handle_partial_or_timeout_sample","customerStatus":"sample-needs-revision","rule":"Do not repeat waits indefinitely; surface partial status and route to revision.","copyMustInclude":"completed, passed, pending, blocked before sending, and full list remains paused"}],"requiredCampaignState":["campaignId","workflowTableId","approvedMessageTemplate","filterRubricsApproved when enableICPFilters=true"],"allowedTools":["get_subskill_asset","queue_campaign_cells","select_campaign_cells","get_campaign_table_schema","wait_for_campaign_processing","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","enrich_with_prospeo","bulk_enrich_with_prospeo","check_rubric"],"hardRules":["campaign_processing_waits_are_stats_only_by_default","queue_review_batch_by_selector_never_fetch_rows_for_cell_ids","timeout_never_repeats_without_customer_handoff","timeout_or_underfloor_sample_never_advances_to_settings"],"waitFor":["sample_validated","sample_revision_required"],"transitions":{"sample_validated":"auto-execute-messaging","sample_revision_required":"lead-review","revise_leads":"find-leads","revise_rubric":"filter-rubric","escalation_triggered":"escalation"}},{"id":"auto-execute-messaging","label":"Generate initial campaign-row messages","currentStepValue":"auto-execute-messaging","references":["references/parallel-critique-protocol.md","references/thomas-variant-selection.md","references/thomas-revision-filters.md","references/step-15-re-cascade.md"],"onEnter":[{"tool":"queue_campaign_cells","batchSize":100,"when":"any passing row has pending, empty, or stale Generate Message cell","requiredFields":["workflowTableId","columnRole","rowSelector"],"requiredValues":{"columnRole":"generateMessage","rowSelector":{"type":"needsGeneratedMessage"},"forceRerun":false},"cellSource":"selector-engine needsGeneratedMessage rows using approved campaign brief template"},{"tool":"wait_for_campaign_processing","purpose":"wait_for_first_current_revision_generated_message","requiredValues":{"minGeneratedMessages":1,"templateRevision":"current"},"readVia":"stats_only_tool_result","invariant":"A generated message implies the pass gate; stale or wrong-revision messages are excluded from readiness."},{"action":"observe_generate_message_results","reference":"references/step-15-re-cascade.md"},{"action":"enforce_token_contract","modeFromConfig":"messaging.tokenContract"},{"action":"optional_critique_pass","enabledFromConfig":"messaging.critique.enabled","plan":"85-03","protocol":"references/parallel-critique-protocol.md","sampleSizeFromConfig":"messaging.critique.sampleSize","budgetUsdCapFromConfig":"messaging.critique.budgetUsdCap","perCriticTimeoutFromConfig":"messaging.critique.perCriticTimeoutSeconds","totalTimeoutFromConfig":"messaging.critique.totalTimeoutSeconds","criticsFromConfig":"messaging.critique.critics","enforceFinalizerPassFromConfig":"messaging.critique.synthesis.enforceFinalizerPass","rejectOnFakeProofFromConfig":"messaging.critique.rejectOnFakeProof","rejectOnUnsupportedTokenFromConfig":"messaging.critique.rejectOnUnsupportedToken","onBudgetTrip":"halt_critique_continue_plain_tail","onPerCriticTimeout":"drop_critic_proceed_with_remaining","onTotalTimeout":"persist_plain_message_for_row","onTokenContractViolation":"persist_plain_message_for_row","onFakeProof":"persist_plain_message_for_row"},{"action":"optional_opus_subset","enabledFromConfig":"messaging.critique.opus.enabled","selection":"references/thomas-variant-selection.md","maxMessagesPerPassFromConfig":"messaging.critique.opus.maxMessagesPerPass","budgetUsdCapFromConfig":"messaging.critique.opus.budgetUsdCap","onOpusBudgetTrip":"halt_opus_continue_non_opus","onOpusTokenContractViolation":"fallback_to_non_opus_rewrite"},{"tool":"update_campaign","requiredValues":{"currentStep":"auto-execute-messaging","watchNarration.stage":"review-ready"},"watchNarrationRule":"Say the first passing generated message is ready in Messages; next is review before Settings."},{"action":"ask_generated_message_review_choice","uses":"request_user_input","choices":["Approve reviewed draft rows and continue to Settings","Revise filters","Revise message template","Pause here"],"copyMustInclude":["approve the reviewed draft rows and continue to Settings","tokens resolved","company/person match","proof claim","prospect angle","language/tone when known","obvious bad fits","Would I take this call?","weak personalization can burn the sender's reputation","full list remains paused","nothing sends from this approval"],"autoSelectIf":"yolo_mode and the first passing generated message clears the quality floor","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"allowedTools":["get_subskill_asset","get_campaign_table_schema","select_campaign_cells","queue_campaign_cells","wait_for_campaign_processing","revise_message_template_and_rerun","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","generate_messages"],"hardRules":["generated_message_count_excludes_stale_or_wrong_revision_messages","message_template_revision_uses_brief_update_then_generate_message_rerun","do_not_overwrite_generated_message_cells_directly","critique_failure_never_escalates","critique_sample_size_bounded_by_config","first_passing_generated_message_unblocks_review","critics_fixed_at_targeting_copy_voice","synthesis_enforces_phase_84_token_contract","opus_reserved_for_highest_value_subset","proposed_token_never_persisted_in_rewrite"],"waitFor":["generated_messages_approved","sample_revision_required"],"transitions":{"generated_messages_approved":"awaiting-user-greenlight","revise_filters":"filter-rubric","revise_messaging":"message-generation","escalation_triggered":"escalation"}},{"id":"awaiting-user-greenlight","label":"Settings, sender, sequence, and greenlight","currentStepValue":"awaiting-user-greenlight","reference":"references/final-handoff-contract.md","onEnter":[{"tool":"get_campaign","purpose":"verify senderIds and sequence state before final handoff"},{"tool":"list_senders","purpose":"surface available connected senders only at Settings"},{"tool":"update_campaign","requiredValues":{"currentStep":"settings","watchNarration.stage":"review-ready"},"purpose":"park the watched UI on Settings before sender selection","watchNarrationRule":"Say message review is complete and the app is on Settings for sender choice. Mention lead research/filtering already happened outside the user's LinkedIn account and nothing sends until Start."},{"action":"surface_sender_and_slack_handoff","requiredVisibleContent":["connect or select a LinkedIn sender","Lead research and filtering already happened outside your LinkedIn account.","This account is only used for approved sending after final launch.","Nothing sends until the final Start step.","Slack reply review","recommended sequence","final launch confirmation is still ahead"],"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]},{"action":"ask_sender_selection","uses":"request_user_input","singleChoice":true,"choices":["Use this connected sender","Connect a different sender in Settings","Pause here"],"autoSelectIf":"yolo_mode and exactly one safe connected sender is available","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]},{"action":"attach_selected_sender","tool":"update_campaign","when":"user selected an available connected sender","requiredValues":{"senderIds":["{selectedSenderId}"],"currentStep":"sequence","watchNarration.stage":"review-ready"},"watchNarrationRule":"Say the sender was attached and the app is moving to Sequence review; sequence remains editable and nothing sends until Start."},{"tool":"attach_recommended_sequence","when":"after senderIds are attached","requiredValues":{"campaignId":"{campaignId}","currentStep":"send","watchNarration.stage":"review-ready"},"watchNarrationRule":"The sequence tool owns this visible beat: say the recommended follow-up sequence is attached, the app is on final launch review, and it is still not sending until Start."},{"action":"ask_final_launch_greenlight","uses":"request_user_input","singleChoice":true,"choices":["Start campaign","Review campaign first","Pause here"],"copyMustInclude":["quality confidence means sample messages and prospect angle were checked","launch confidence means sender, sequence, and Start are ready","approved messages begin sending according to the sequence","replies and meetings follow connected settings","you can monitor and pause","no hidden extra approval disappears after clicking Start"],"onUserStart":"claude-greenlight","neverAutoSelectInYolo":true,"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"allowedTools":["get_campaign","get_campaign_navigation_state","list_senders","update_campaign","attach_recommended_sequence","AskUserQuestion","request_user_input"],"doNotAllow":["start_campaign","import_leads"],"autoStart":false,"watchRequired":true,"waitFor":["sender_connection_required","sender_attached","sequence_attached","ready_to_launch","user_greenlight","ui_start_detected"],"transitions":{"sender_connection_required":"awaiting-user-greenlight","sender_attached":"awaiting-user-greenlight","sequence_attached":"awaiting-user-greenlight","ready_to_launch":"awaiting-user-greenlight","user_greenlight":"claude-greenlight","ui_start_detected":"running"}},{"id":"claude-greenlight","label":"Explicit launch","reference":"references/final-handoff-contract.md","onEnter":[{"tool":"get_campaign","purpose":"detect_already_running"},{"action":"verify_sequence_and_current_step_before_start","requiredState":["workflowTableId","senderIds","sequenceTemplate","at least one approved generated message","currentStep in awaiting-user-greenlight|claude-greenlight|send"]},{"tool":"start_campaign","requiredFields":["campaignId"],"persistsCurrentStep":"running","watchNarrationRule":"After start_campaign succeeds, the running state must say the final greenlight was accepted, the campaign is now live/running, and the user can watch progress from the campaign."}],"allowedTools":["get_campaign","attach_recommended_sequence","start_campaign","AskUserQuestion","request_user_input"],"watchRequired":true,"waitFor":"campaign_started","transitions":{"campaign_started":"running"}},{"id":"running","label":"Campaign is live","currentStepValue":"running","onEnter":[{"action":"surface_campaign_live_confirmation_without_watch_link","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"allowedTools":["get_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["start_campaign"],"terminal":true},{"id":"escalation","label":"Escalation","reference":"references/escalation-ladder.md","allowedTools":["AskUserQuestion","request_user_input"],"doNotAllow":["start_campaign","import_leads"],"transitions":{"revise_brief":"brief-interview","revise_leads":"find-leads","revise_rubric":"filter-rubric","revise_messaging":"message-generation","abort":"abort"}},{"id":"abort","label":"Abort","allowedTools":["AskUserQuestion","request_user_input"],"terminal":true}]}
|
|
1
|
+
{"version":"v2.1-compact","workflow":"create-campaign-v2","principle":"CampaignOffer state and watch link are canonical. Create the watched shell, approve source, confirm the source list, process the first campaign-table slice, approve filters/message, then run the bounded cascade before Settings and explicit start.","normalCustomerPath":"Use campaign state, MCP responses, and concise watchNarration. Do not create, read, link, or surface local draft files. Print the exact tokenized create_campaign.watchUrl only once at the initial brief handoff.","legacyCompatibility":{"validationSubskill":"create-campaign-v2-validation","tailSubskill":"create-campaign-v2-tail","rule":"Legacy validation/rehearsal files are opt-in diagnostics only and are not active flow gates."},"commitGateChoices":["approve","revise-brief","revise-leads","revise-rubric","revise-messaging","abort"],"canonicalStateFields":["campaignId","watchUrl","campaignBrief","currentStep","watchNarration","interactionMode","providerSearchAssociation","selectedLeadListId","workflowTableId","filterChoice","leadScoringRubrics","messageDraftRecommendation","approvedMessageTemplate","senderIds","sequenceTemplate","runningState"],"watchNarrationTransitionContract":{"rule":"Every watched currentStep switch must include fresh watchNarration.","requiredFields":["stage","headline","visibleState","agentIntent","nextAction"],"copyMustInclude":["what just happened","current visible page/state","next user action"],"appliesToTools":["create_campaign","update_campaign","attach_recommended_sequence","start_campaign"],"oneTimeWatchLinkPolicy":"After the initial brief handoff, customer-facing turns must not print another watch link unless the user asks or link recovery is needed."},"lazyReferences":{"watch":["references/watch-link-handoff.md","references/watch-guide-narration.md"],"source":["references/lead-validation-preview.md","references/step-13-import-leads.md"],"filter":["references/filter-leads.md"],"message":[],"tail":["references/sample-validation-loop.md","references/step-15-re-cascade.md","references/final-handoff-contract.md"]},"safetyBoundaries":["Do not call list_senders before Settings after message approval.","Do not import leads until the source decision is approved.","Do not queue cells until confirmed source rows exist in the campaign and the message/filter gates are satisfied.","Do not call start_campaign until the user explicitly confirms launch.","Do not use local files as durable state in normal customer runs."],"yoloMode":{"triggerPhrases":["yolo","--yolo","mode=yolo","autopilot","use best guesses","use best estimates","answer for me","just run it","yolo autopilot"],"identityRequestOnlyWhenMissing":true,"operatorDirectionsRule":"Treat freeform directions supplied at invocation or later as durable operator directions; newest conflicting direction wins.","setInteractionModeAfterCampaignCreate":"autonomous","autoSelectsPreLaunchChoices":["campaign focus","brief approval","source plan","source review/import approval","filters vs skip filters","filter rubric approval","message template approval when recommendation is approve-message","generated message review when quality floor passes","sender selection when exactly one connected sender is safe"],"mustPauseFor":["missing LinkedIn profile URL or handle","missing credentials or required data","ambiguous choice with no reasonable estimate","source/message/filter quality floor failure","final live launch confirmation"],"neverAutoStart":true},"steps":[{"id":"bootstrap","label":"Bootstrap","onEnter":[{"tool":"bootstrap_create_campaign","requiredValues":{"flowVersion":"v2"}},{"tool":"get_subskill_prompt","requiredValues":{"subskillName":"create-campaign-v2"},"purpose":"load the entry prompt once"}],"allowedTools":["bootstrap_create_campaign","get_auth_status","get_active_workspace","get_subskill_prompt","AskUserQuestion","request_user_input"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","update_campaign","queue_cells","start_campaign"],"waitFor":"bootstrap_complete","transitions":{"bootstrap_complete":"brief-interview"}},{"id":"brief-interview","label":"Client, offer, research, and brief","onEnter":[{"action":"resolve_campaign_identity_before_strategy_packet","allowedTools":["fetch_company","fetch_linkedin_profile","WebFetch","WebSearch"],"mustNotInferFromNameOnly":true,"doNotPresentSenderPickerBeforeIdentityInference":true,"fallback":"Ask only: \"What is your LinkedIn profile URL or handle?\" Normalize bare handles or `/in/...` paths to `https://www.linkedin.com/in/{handle}/`. If the input is not a profile, ask again for the person profile URL before strategy questions.","requiresLinkedInProfileUrl":true,"doNotAcceptAsIdentityInput":["non-profile URL","company page","company/name-only input without a profile handle"],"acceptsLinkedInProfileHandle":true,"normalizeLinkedInProfileHandleToUrl":true},{"action":"run_campaign_identity_research_before_strategy_packet","target":"research-sender","requiredCompletion":"complete_sender_research","allowedTools":["get_subskill_prompt","fetch_linkedin_profile","fetch_company","fetch_linkedin_posts","fetch_company_posts","WebFetch","WebSearch","complete_sender_research"],"requiredInput":"LinkedIn profile URL or handle"},{"action":"confirm_target_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"Who should we target first?","options":["Use Sellable's researched recommendation","Choose a different buyer","I'm not sure yet"],"copyMustInclude":["research basis","recommendation is editable","who to target"],"requiredOption":"Other / custom","neverCollectOpenTextWithStructuredQuestion":true,"skipIf":"user already supplied a clear target or yolo_mode with any reasonable best-estimate target"},{"action":"confirm_current_offer_before_strategy_packet","uses":"request_user_input","question":"What should we pitch to prospects first?","options":["Use Sellable's researched recommendation","Use my current pitch","Shape the pitch together"],"copyMustInclude":["research basis","public LinkedIn research may be stale","recommendation is editable","pitch to prospects"],"skipIf":"user already supplied a clear current offer or yolo_mode with any reasonable researched recommendation"},{"action":"confirm_trust_proof_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"What is the strongest credibility signal we can lead with?","options":["Use Sellable's recommended proof","Use customer proof or a case study","Use founder or company credibility"],"copyMustInclude":["build trust with prospects","recommendation is editable","proof to use or avoid"],"requiredOption":"Other / custom","skipIf":"user already supplied clear proof to use or avoid, or yolo_mode with any reasonable researched proof"},{"action":"choose_prospect_source_path_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"How should we find prospects?","options":["Find prospects for me","Use a CSV of LinkedIn profiles","Use a CSV of company domains"],"copyMustInclude":["prospects","no one added yet","find prospects for me"],"skipIf":"user already supplied a source path or yolo_mode with any reasonable source path"},{"action":"infer_strategy_packet_in_yolo_mode","when":"yolo_mode","skipStructuredSetupQuestions":true,"inferFields":["buyer segment","offer/CTA","proof to use or avoid","lead source","filter choice","message direction"],"sourceOfTruth":"identity/company lookup plus operator directions","mustStateAssumptionsInBrief":true},{"action":"render_supplied_setup_receipt_before_brief_when_setup_questions_skip","when":"any setup question is skipped because the user already supplied or yolo mode inferred identity, target, offer, proof, or source","output":"normal chat receipt before the campaign brief or watch-link handoff","copyMustInclude":["Accepted LinkedIn input: normalized to the required LinkedIn profile URL.","Offer path: supplied current offer or Sellable's researched recommendation; you can replace it with your current offer."],"copyMustNotInclude":["Campaign Identity"]},{"action":"create_watchable_campaign_shell_with_v1_brief","tool":"create_campaign","requiredFields":["name","campaignBrief","clientProspectId or senderLinkedinUrl","currentStep","watchNarration"],"requiredValues":{"currentStep":"create-offer","watchNarration.stage":"brief"},"capture":["campaignId","watchUrl"],"canonicalStateWrites":["campaignId","watchUrl","campaignBrief","currentStep:create-offer"]},{"action":"surface_campaign_shell_watch_link","watchUrlSource":"create_campaign.watchUrl","requiredWatchUrlShape":"exact create_campaign.watchUrl; direct /campaign-builder/{campaignId}?mode={claude|codex} URL with workspaceId and token","copyMustInclude":["We'll keep building the campaign in this chat.","You can watch it being built in real time in the app here:","I'll ask for your approval whenever I need your expertise or taste before moving forward.","Send changes here."],"immediateNextMainChatLine":"Ask for brief approval now. After approval, say: \"Brief approved. Next, we'll choose where to find buyers. I won't add anyone yet.\"","codexBrowserHandoff":{"openWhenAvailable":false,"printWatchLinkOnly":true,"tellUserCommandEnterOrClick":false,"mustNotUseBrowserAutomation":true,"fallbackMustNotClaimInspection":true},"copyMustAvoid":["bare /campaign-builder/{campaignId} URL","derived watch URL","second Watch link:","Next, we'll choose where to find buyers"],"oneTimeOnly":true}],"allowedTools":["get_subskill_prompt","fetch_company","fetch_company_posts","fetch_linkedin_profile","fetch_linkedin_posts","complete_sender_research","create_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["campaign_shell_created","brief_ready","confirm_with_user"],"transitions":{"campaign_shell_created":"brief-review","brief_ready":"brief-review","confirm_with_user":"brief-review"}},{"id":"brief-review","label":"Brief review","onEnter":[{"action":"render_brief_inline","minimumVisibleDetail":"campaign direction, buyer, offer, proof, source plan, safety gates","copyMustInclude":["This brief is based on Sellable's research and your answers.","If your site or LinkedIn is stale","Do you agree with this researched campaign direction?"],"copyMustNotInclude":["quoted first-message copy","Message Angle as final proof","This approval covers","does not approve adding people","does not approve scraping posts","selecting a sender","attaching a sequence","Campaign Identity","what should this campaign sell first","sell first","[from you]","[from LinkedIn]","[from website]","[from case study]","[Sellable recommendation]"],"messageHintRule":"If any message-shape hint remains, keep it as non-final bullets and say real examples come after leads are found and filtered.","approvalBoundaryRule":"Keep this gate positive and short. Do not list later steps this approval does not cover; ask approve/revise and then move to choosing where to find buyers.","sectionLabelRule":"Use ## Sender and Company for the person/company context; never render ## Campaign Identity.","skipIf":"the full brief was already rendered in the current shell-creation handoff before create_campaign returned","renderOnceRule":"The brief must be visible exactly once before approval. Do not render it again immediately after shell creation; append the one watch-link handoff and ask the approval question.","copyMustAvoid":["duplicate brief render after shell creation","second Watch link:"]},{"action":"ask_brief_choice","uses":"request_user_input","choices":["Approve brief","Revise brief","Approve + YOLO autopilot setup"],"skipIf":"yolo_mode; auto_continue after rendering assumptions unless brief quality floor fails","question":"Do you agree with this researched campaign direction?","autonomousChoice":"Approve + YOLO autopilot setup","choiceDescriptions":{"Approve + YOLO autopilot setup":"Use best guesses to finish setup and stop before final launch."}}],"requiredCampaignState":["campaignId","watchUrl","campaignBrief","currentStep"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign"],"doNotAllow":["create_campaign","list_senders","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["user_brief_confirmed","revise_brief","auto_continue"],"transitions":{"user_brief_confirmed":"find-leads","revise_brief":"brief-interview","auto_continue":"find-leads"}},{"id":"find-leads","label":"Find leads","sourceSelectionFunnel":{"preScoutRecommendationGate":{"required":true,"label":"Find buyers plan approval","mustHappenBefore":["get_provider_prompt","search_signals","search_sales_nav","search_prospeo","fetch_post_engagers","source-scout dispatch"],"show":["buyer groups or places we could check","best place to start","why the right buyers are likely to be there","what signs the next search will check","where to look next if the first place is too thin","what approval authorizes"],"approvalAuthorizes":"looking for the best places to find buyers; no one added yet","explanationMustInclude":["where the right buyers are already talking","why that place fits this campaign","enough likely prospects","what counts as a good sign","where to look next if thin","I won't add anyone yet","choose where to find buyers"],"yoloMode":{"autoApproveRecommendedLane":true,"mustShowAssumedChoice":true,"pauseIfConfidenceLow":true},"customerLanguage":{"readability":"grade-5","requiredHeading":"Find Buyers Plan","prefer":["place to look","best place to start","people to check","prospects","I won't add anyone yet"],"avoid":["lane","source scouting","provider","precision/scale tradeoff","evidence quality","pilot volume","ICP","workflow pain","lead-source scouting","not importing leads"],"example":"Brief approved. Next, we'll choose where to find buyers. I won't add anyone yet. I recommend starting with LinkedIn posts about [topic]; fallback is [place].","termRule":{"buyers":"target market","peopleToCheck":"raw reactions/comments before fit is known","prospects":"likely usable people after fit","leads":"campaign rows"}},"questionOrder":"update_campaign to pick-provider, render ## Find Buyers Plan in normal chat without repeating the watch link, then open the structured approval question; never ask first or show the plan after approval","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"approvalFreshnessRule":"Only an approval question opened after the visible Find Buyers Plan, or an explicit source choice made after that plan, can set source_lane_approved. Do not reuse brief approval or pre-plan provider state."},"defaultWhenSourceUnspecified":["signal-discovery","sales-nav-recent-active","sales-nav-general","prospeo"],"userFacingFallbackOrder":["LinkedIn posts where the right buyers are already talking","people with the right titles who recently posted on LinkedIn","people with the right titles from a broader LinkedIn search","a broader company and contact search"],"parallelAllowedOnlyWhen":["user explicitly requested source comparison","resuming already-started parallel scouts","first viable source is borderline and one cheap fallback check is needed"]},"onEnter":[{"tool":"update_campaign","requiredValues":{"currentStep":"pick-provider","watchNarration.stage":"find-leads","watchNarration.headline":"Review where to find buyers","watchNarration.visibleState":"The app is showing source selection before lead finding starts.","watchNarration.agentIntent":"Codex is explaining where it recommends looking first and where it will look next if that is too thin.","watchNarration.nextAction":"Approve where to look first or choose another place","watchNarration.safety":"Only deciding where to look. No one is added yet."},"purpose":"show the visible source-plan approval checkpoint before provider lanes"},{"tool":"get_source_scout_registry","purpose":"load canonical source scout names before optional branch launch"},{"action":"render_find_buyers_plan_inline","oneShot":true,"requiredPrecondition":"currentStep=pick-provider has been saved with update_campaign","mustHappenAfter":["update_campaign currentStep=pick-provider"],"mustHappenBefore":["ask_find_buyers_plan_choice","get_provider_prompt","search_signals","search_sales_nav","search_prospeo","fetch_post_engagers","source-scout dispatch"],"output":"normal chat text before any request_user_input approval panel","requiredHeading":"Find Buyers Plan","requiredInlineFields":["plain-language buyer groups or places for this campaign","best place to start","why the right buyers are likely to be there","what signs the next search will check","where to look next if the first place is too thin","what approval authorizes"],"copyMustInclude":["Find Buyers Plan","best place to start","I won't add anyone","choose where to find buyers","I won't add anyone yet"],"copyMustAvoid":["lane","source scouting","provider","precision/scale tradeoff","evidence quality","pilot volume","ICP","workflow pain","lead-source scouting","not importing leads","Watch link:","campaign-builder URL","repeat the watch URL"],"approvalBoundary":"Explain what approval authorizes before asking: looking for the best places to find buyers; no one is added yet.","linkPolicy":"Do not include another watch link. The first brief handoff already gave the user the live app URL."},{"action":"ask_find_buyers_plan_choice","uses":"request_user_input","oneShot":true,"requiredPrecondition":"render_find_buyers_plan_inline completed in normal chat after pick-provider state was saved; no earlier approval can satisfy this","skipIf":"source_lane_approved was set by ask_find_buyers_plan_choice after the visible Find Buyers Plan, leadSourceProvider was explicitly chosen by the user after that plan, or yolo_mode auto-selected source_lane_approved after showing the assumed choice","choices":["Start with LinkedIn posts","Start with active LinkedIn profiles","Start with company/contact search","Choose a different place","Pause here"],"approvalChoiceLabelsByProvider":{"signal-discovery":"Start with LinkedIn posts","sales-nav":"Start with active LinkedIn profiles","prospeo":"Start with company/contact search"},"approvalState":"source_lane_approved","copyMustInclude":["Approve this plan for where to find buyers?","Start with LinkedIn posts"],"mustNotHappenBefore":["render_find_buyers_plan_inline"],"approvalStateRule":"Set source_lane_approved here after the visible plan; never from brief approval, generic approval, pre-plan provider state, or artifacts."},{"action":"persist_provider_search_step_before_search","tool":"update_campaign","requiredPrecondition":"source_lane_approved set after the visible Find Buyers Plan approval question","providerCurrentStepMap":{"signal-discovery":"signal-discovery","sales-nav":"sales-nav","prospeo":"prospeo"},"requiredValues":{"leadSourceType":"new","leadSourceProvider":"approved provider","currentStep":"provider-specific current step","watchNarration.stage":"find-leads","watchNarration.headline":"Searching the approved place","watchNarration.safety":"Looking only. No one is added yet."},"mustRunBefore":["search_signals","search_sales_nav","search_prospeo","fetch_post_engagers"]},{"action":"run_sequential_source_funnel","requiredPrecondition":"source_lane_approved set after the visible Find Buyers Plan approval question","defaultOrder":["source-scout-linkedin-engagement","source-scout-sales-nav","source-scout-prospeo-contact"],"campaignOfferIdRequired":true,"stopOnFirstViableUnlessComparisonRequested":true}],"requiredCampaignState":["campaignId","campaignBrief","currentStep"],"allowedTools":["get_source_scout_registry","get_provider_prompt","lookup_sales_nav_filter","search_sales_nav","search_prospeo","search_signals","select_promising_posts","fetch_post_engagers","fetch_company","fetch_linkedin_profile","load_csv_linkedin_leads","load_csv_domains","get_rows_minimal","update_campaign","AskUserQuestion","request_user_input","get_campaign_navigation_state"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["lead_review_ready","revise_brief","confirm_with_user"],"transitions":{"lead_review_ready":"lead-review","revise_brief":"brief-interview","confirm_with_user":"lead-review"}},{"id":"lead-review","label":"Source approval","onEnter":[{"action":"show_source_decision_card","requiredInlineFields":["primary source and exact filters/recipe","specific source action awaiting approval","for Signal Discovery: compact Source Recommendation in plain language with selected posts, people to check, likely prospects, first review, and fallback","for Signal Discovery: recommended scrape post count and target people-to-check volume","first campaign review size, clearly separate from source sampling","runner-up and why it lost","raw volume","sampled people","sampled fits as n/N plus percentage/range","estimated usable prospects","cleanup risk","what will happen after approval"],"copyMustAvoid":["lead-source scouting","source scouting","headline-fit","engagers needed","execution slice","ICP","good buyers","real buyers","This approval covers","It does not approve","does not approve filters","filters, messages, sender selection","sender selection, sequence, or sending","Watch link:","campaign-builder URL","repeat the watch URL"],"approvalBoundaryRule":"Use positive source-action copy only. Include: \"After approval, I will build the source list, add it to the campaign, and review the first 15 leads before we scale.\" Never list future non-approvals.","linkPolicy":"Do not include another watch link in Source Recommendation; describe the source decision only."},{"action":"ask_source_review_choice","uses":"request_user_input","choices":["Approve scraping N recommended LinkedIn posts","Run the approved source import","Revise source","Pause here"],"approvalChoiceLabelsByProvider":{"signal-discovery":"Approve scraping {scrapePostCount} recommended LinkedIn posts?","sales-nav":"Import the approved Sales Nav source list","prospeo":"Import the approved Prospeo source list"},"postApprovalContract":{"singleUseApproval":true,"doNotRepeatAfterApproval":["Source Recommendation","show_source_decision_card","ask_source_review_choice"],"requiredNextActionAfterApproval":"ack once; call import_leads immediately","signalDiscoveryNextTool":"import_leads({ provider: \"signal-discovery\", targetEngagerCount, maxPostsToScrape, confirmed: true })"},"autoSelectIf":"yolo_mode and projected usable pool clears the source quality floor","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"requiredCampaignState":["campaignId","campaignBrief","providerSearchAssociation"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["lead_review_confirmed","revise_leads","confirm_with_user","auto_continue"],"transitions":{"lead_review_confirmed":"auto-execute-leads","revise_leads":"find-leads","confirm_with_user":"auto-execute-leads","auto_continue":"auto-execute-leads"}},{"id":"auto-execute-leads","label":"Materialize confirmed source list","currentStepValue":"auto-execute-leads","reference":"references/step-13-import-leads.md","onEnter":[{"tool":"get_subskill_prompt","requiredValues":{"subskillName":"create-campaign-v2-tail"},"purpose":"load tail contract before execution tools"},{"tool":"import_leads","requiredFields":["campaignOfferId","selected source/list","sourceListTarget or targetEngagerCount"],"requiredValues":{"salesNavProspeoDefaultSourceListTarget":1000,"signalDiscoveryDefaultEngagerTarget":1500},"modeAddHandshake":{"firstCallReturns":"needsModeSelection when adding to an existing campaign-attached list","requiredFollowup":"retry with mode=add after explicit user/source-state compatibility is confirmed"},"surfaceDedupRatio":true},{"tool":"wait_for_lead_list_ready","purpose":"poll until the source lead-list import reaches a terminal complete/failed/cancelled state before copying rows into the campaign table","repeatUntil":"ready_true_or_cancelled_or_import_failed","requiredValues":{"requireComplete":true},"onImportStillRunning":"re-run wait_for_lead_list_ready; do not call confirm_lead_list just because rows exist","partialOverrideRule":"Only if the user explicitly asks to keep going early may the next confirm_lead_list call pass allowPartialSourceList: true."},{"tool":"confirm_lead_list","requiredFields":["campaignOfferId","selectedLeadListId","reviewBatchLimit"],"requiredValues":{"reviewBatchLimit":15},"capture":["workflowTableId","reviewBatchRowIds"],"requiredPrecondition":"wait_for_lead_list_ready returned ready:true, unless the user explicitly asked to keep going early with a partial list","partialOverrideField":"allowPartialSourceList:true only after explicit user early-continue instruction","mustNotRunWhen":["wait_for_lead_list_ready reason=import_still_running and no explicit early-continue instruction","wait_for_lead_list_ready reason=cancelled","wait_for_lead_list_ready reason=import_failed","missing import job metadata"]},{"tool":"wait_for_campaign_table_ready"},{"tool":"get_rows_minimal","requiredValues":{"tableId":"{workflowTableId}","limit":15},"capture":["reviewBatchRowHash"]},{"action":"summarize_review_batch_and_advance_to_filter_choice","purpose":"ask filter choice immediately; no post-lead registries, filter refs, or message prompts first","maxCustomerCopyLines":4,"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"linkPolicy":"Ask add filters vs skip filters without repeating the watch link."}],"requiredCampaignState":["campaignId","providerSearchAssociation","selectedLeadListId"],"allowedTools":["get_subskill_prompt","import_leads","wait_for_lead_list_ready","confirm_lead_list","wait_for_campaign_table_ready","get_rows_minimal","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["get_post_find_leads_scout_registry","Task","spawn_agent","list_senders","queue_cells","start_campaign","enrich_with_prospeo","bulk_enrich_with_prospeo","save_rubrics"],"waitFor":"source_list_confirmed_and_review_sample_ready","transitions":{"source_list_confirmed_and_review_sample_ready":"filter-choice","escalation_triggered":"escalation"},"hardRules":["import_leads_then_repeated_wait_for_lead_list_ready_then_confirm_lead_list_then_wait_for_campaign_table_ready","partial_source_rows_do_not_unblock_confirm_lead_list_without_explicit_user_early_continue","cancelled_or_failed_source_import_stops_before_campaign_table_copy"]},{"id":"filter-choice","label":"Filter choice","currentStepValue":"filter-choice","onEnter":[{"tool":"update_campaign","requiredValues":{"currentStep":"filter-choice","watchNarration.stage":"fit-message"}},{"action":"ask_filter_choice","uses":"request_user_input","choices":["Use filters","Skip filters","Revise source"],"autoSelectIf":"yolo_mode; choose filters unless source is tightly curated or user directed otherwise","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"copyMustInclude":["source rows are in the campaign","add fit filters before message generation or skip filters"]}],"hardRules":["ask_filter_choice_immediately_after_review_batch_import","do_not_call_get_subskill_prompt_before_filter_choice","do_not_call_get_subskill_asset_before_filter_choice","do_not_call_get_post_find_leads_scout_registry_before_filter_choice","do_not_spawn_post_lead_agents_before_filter_choice"],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign","get_campaign_navigation_state"],"doNotAllow":["get_subskill_prompt","get_subskill_asset","get_post_find_leads_scout_registry","Task","spawn_agent","create_campaign","list_senders","import_leads","confirm_lead_list","queue_cells","start_campaign","generate_messages"],"waitFor":["filters_enabled","filters_skipped","revise_leads"],"transitions":{"filters_enabled":"post-lead-workstreams","filters_skipped":"message-generation","revise_leads":"find-leads"}},{"id":"post-lead-workstreams","label":"Filter workstream","onEnter":[{"action":"persist_add_filters_approval","tool":"update_campaign","when":"filters_enabled","requiredFields":["campaignId","enableICPFilters","currentStep","watchNarration"],"requiredValues":{"enableICPFilters":true,"currentStep":"create-icp-rubric","watchNarration.stage":"fit-message","watchNarration.headline":"Create filter rules","watchNarration.visibleState":"Filters are enabled and the browser is showing Filter Rules while Codex defines the rubric.","watchNarration.nextAction":"Review saved filter rules"},"mustRunBefore":["get_post_find_leads_scout_registry","launch_lead_fit_builder_after_filter_choice","save_rubrics"]},{"tool":"get_post_find_leads_scout_registry","purpose":"load canonical post-lead worker names only after the user has chosen filters"},{"action":"launch_lead_fit_builder_after_filter_choice","mode":"parallel_when_host_supports_subagents","target":"post-find-leads-filter-scout","inputs":["campaignId","campaignBrief","selectedLeadListId","workflowTableId","reviewBatchRowIds/hash","filterChoice"],"stateSource":"live campaign/table","debugFilesOptionalOnly":true},{"action":"launch_message_draft_builder_after_filter_decision","mode":"parallel_when_host_supports_subagents","target":"post-find-leads-message-scout","when":"filters_enabled","doesNotQueueCells":true,"customerNarration":"Say message agent is drafting."},{"action":"save_filter_rubrics_to_campaign","tool":"save_rubrics","when":"filters_enabled and rubrics are production-shaped","requiredFields":["campaignOfferId","leadScoringRubrics"],"writesCampaignState":"leadScoringRubrics","requiredSideEffects":{"enableICPFilters":true,"currentStep":"create-icp-rubric","watchNarration.headline":"Filter rules saved for review"}},{"action":"ask_filter_rubric_review_choice","uses":"request_user_input","choices":["Approve filters","Revise filters","Pause"],"purpose":"let the user read saved rubrics before Filter Leads or enrichment","copyMustInclude":["These rules prevent wasted sends before I score/import the list.","Recommended because of the researched ICP, source sample, and repeated false-positive patterns.","one pass example and one block example when available","approval is for the filter rules","Approve these filter rules."],"autoSelectIf":"yolo_mode and rubrics are production-shaped","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["get_subskill_prompt","get_subskill_asset","get_post_find_leads_scout_registry","save_rubrics","get_campaign","get_rows_minimal","update_campaign","get_campaign_navigation_state","AskUserQuestion","request_user_input","Task","spawn_agent"],"doNotAllow":["create_campaign","list_senders","import_leads","confirm_lead_list","queue_cells","start_campaign","check_rubric","generate_messages"],"waitFor":["filter_rubrics_approved","revise_leads","revise_rubric","revise_messaging"],"hardRules":["after_save_rubrics_currentStep_must_stay_create-icp-rubric_until_filter_approval","filter_approval_required_before_apply-icp-rubric_or_queue_campaign_cells","do_not_move_browser_to_messages_until_filter_leads_step_is_current_or_filters_are_explicitly_skipped","no_post_lead_worker_or_deep_prompt_before_filter_choice","lead_fit_builder_starts_only_after_filters_enabled","msg_draft_after_filter_choice","msg_draft_no_cells"],"transitions":{"filter_rubrics_approved":"message-generation","revise_leads":"find-leads","revise_rubric":"filter-rubric","revise_messaging":"message-generation","confirm_with_user":"message-review"}},{"id":"filter-rubric","label":"Rubric revision","onEnter":[{"tool":"get_subskill_asset","requiredValues":{"subskillName":"create-campaign-v2","assetPath":"references/filter-leads.md"}},{"tool":"save_rubrics","requiredFields":["campaignOfferId","leadScoringRubrics"],"writesCampaignState":"leadScoringRubrics","requiredSideEffects":{"enableICPFilters":true,"currentStep":"create-icp-rubric","watchNarration.headline":"Filter rules saved for review"}},{"action":"ask_filter_rubric_review_choice","uses":"request_user_input","choices":["Approve filters","Revise filters","Pause"],"copyMustInclude":["These rules prevent wasted sends before I score/import the list.","approval object is filter rules only","approval is for the filter rules","Approve these filter rules."],"autoSelectIf":"yolo_mode and rubrics are production-shaped","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"requiredCampaignState":["campaignId","workflowTableId"],"allowedTools":["get_subskill_asset","save_rubrics","AskUserQuestion","request_user_input"],"doNotAllow":["create_campaign","list_senders","import_leads","confirm_lead_list","update_campaign","queue_cells","start_campaign","check_rubric"],"waitFor":["filter_rubrics_approved","revise_leads","confirm_with_user","auto_continue"],"transitions":{"filter_rubrics_approved":"message-generation","revise_leads":"find-leads","confirm_with_user":"message-generation","auto_continue":"message-generation"}},{"id":"message-generation","label":"Message generation","onEnter":[{"action":"set_message_review_visible_step_by_filter_choice","tool":"update_campaign","branchRules":["yes after filter approval: currentStep=apply-icp-rubric; wait on Filter Leads","no: currentStep=messages; message review"],"watchNarration.stage":"fit-message"},{"action":"run_or_reconcile_message_draft_builder","target":"post-find-leads-message-scout","toolCallRequiredBeforeDraft":["run background post-find-leads-message-scout when available","get_subskill_prompt({ subskillName: \"generate-messages\" }) for the required normal-path contract","do not use any alternate or examples-only message prompt"],"stateSource":"campaignBrief, source, selectedLeadListId, workflowTableId, execution-slice row ids/hash","outputState":"messageDraftRecommendation","revisionMode":{"when":"revise_messaging or latest user message requests template changes","requiredInputs":["latest user feedback","current messageDraftRecommendation","campaign/table execution-slice state"],"output":"revised messageDraftRecommendation rendered before request_user_input"}}],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["get_subskill_prompt","get_campaign","get_rows_minimal","update_campaign"],"toolRules":["Run post-find-leads-message-scout when available; otherwise load get_subskill_prompt({ subskillName: \"generate-messages\" }) from live state.","brief.md, lead-review.md, and lead-sample.json are optional debug context only.","messageDraftRecommendation returns templateRecommendation, tokenFillRules, renderedSample, concerns, status, basisToken, outputAt, outputHash, and error/retry detail.","If campaign/source/table/execution-slice basis does not match, classify the output stale or blocked."],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign","generate_messages","AskUserQuestion","request_user_input"],"waitFor":["message_validation_ready","revise_rubric","revise_messaging"],"transitions":{"message_validation_ready":"message-review","revise_rubric":"filter-rubric","revise_messaging":"message-generation"},"revisionLoop":{"trigger":"user gives copy feedback, asks for an update, or chooses revise-messaging","required":["apply latest user feedback to current messageDraftRecommendation and live campaign/table state","produce a new messageDraftRecommendation with revisionNotes","render revised ## Message Template, ## Rendered Example, and What changed before asking approval"],"blocked":["asking whether an unseen new version is better","reusing the old template after acknowledging feedback","update_campaign_brief before approve-message","request_user_input or AskUserQuestion during message-generation"],"outputState":"messageDraftRecommendation"},"hardRules":["message_revision_must_render_new_template_before_approval_question","message_revision_saves_only_after_approve_message","message_generation_must_not_call_request_user_input","message_generation_must_transition_to_message_review_only_after_renderable_draft_state"]},{"id":"message-review","label":"Message review","onEnter":[{"action":"render_message_review_from_state","requiredState":["campaignBrief","selectedLeadListId","workflowTableId","messageDraftRecommendation"],"requiredVisibleLabels":["## Message Template","## Rendered Example","Good token fill:","My take:","Question: approve-message or revise-messaging?","Recommendation:"],"doNotShowByDefault":["Token Notes","Good omit / fallback","Bad fill to avoid","Token Adherence Table"],"internalPersistenceOnly":["Token Fill Rules","Token Fill Examples","fallback guidance","bad-fill avoidance notes"],"copyMustInclude":["QA receipt before approval","tokens resolved","company/person match","proof claim","prospect angle","language/tone when known","obvious bad fits","Would I take this call?","weak personalization can burn the sender's reputation","full list remains paused"],"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"revisionRenderRule":"After revise_messaging, show revised template, rendered example, and What changed before ask_message_review_choice; never ask if an unseen version is better."},{"action":"ask_message_review_choice","uses":"request_user_input","choices":["approve-message","revise-messaging"],"copyMustInclude":["approve this message template and continue","nothing sends from this approval"],"autoSelectIf":"yolo_mode and Recommendation is approve-message; revise autonomously when Recommendation is revise-messaging","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"requiredPrecondition":"current or revised template and rendered example are visible in this turn","revisionPrecondition":"after revise_messaging, revised template is visible before this question"},{"action":"sync_approved_message_set_to_campaign_brief","tool":"update_campaign_brief","when":"after approve-message","requiredFields":["campaignId","approvedMessageTemplate","Token Fill Rules","Token Fill Examples"],"writesCampaignState":"approvedMessageTemplate","revisionApprovalRule":"If the user requested revisions, write only the latest approved revised template and token rules to campaignBrief."}],"requiredCampaignState":["campaignId","workflowTableId","messageDraftRecommendation"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign_brief","update_campaign","get_rows_minimal"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","start_campaign"],"waitFor":["message_approved","revise_messaging"],"transitions":{"message_approved":"validate-sample","revise_messaging":"message-generation"}},{"id":"validate-sample","label":"Validate campaign-table execution slice","currentStepValue":"apply-icp-rubric","reference":"references/sample-validation-loop.md","visibleStepRule":"Filter Leads is reached only after saved-filter approval; on approve-message, save the template and queue bounded Enrich Prospect cells through selector-based campaign processing.","onEnter":[{"tool":"update_campaign","requiredValues":{"currentStep":"apply-icp-rubric","watchNarration.stage":"fit-message"},"purpose":"keep the watched UI on Filter Leads while the approved template starts the bounded cascade","watchNarrationRule":"Say the template is saved and Filter Leads is now running the bounded enrichment and scoring pass."},{"tool":"queue_campaign_cells","purpose":"queue Enrich Prospect cells for the stored review batch without fetching bulky row payloads","requiredFields":["workflowTableId","columnRole","rowSelector"],"requiredValues":{"columnRole":"enrich","rowSelector":{"type":"reviewBatch"},"forceRerun":false},"targetCountSource":"WorkflowTable.config.mcp.reviewBatch.rowCount (default 15)"},{"tool":"wait_for_campaign_processing","purpose":"wait_for_first_passed_review_batch_row","requiredFields":["workflowTableId","minPassedCount"],"requiredValues":{"minPassedCount":1},"readVia":"stats_only_tool_result","timeoutGuidance":"If this times out, do not repoll identical args; use partial diagnostics to revise filters or surface blocked sample state.","customerSummaryPattern":["{checked} leads checked","{passed} passed fit scoring","{blocked} blocked before sending","full list remains paused until approval"]},{"action":"handle_partial_or_timeout_sample","customerStatus":"sample-needs-revision","rule":"Do not repeat waits indefinitely; surface partial status and route to revision.","copyMustInclude":"completed, passed, pending, blocked before sending, and full list remains paused"}],"requiredCampaignState":["campaignId","workflowTableId","approvedMessageTemplate","filterRubricsApproved when enableICPFilters=true"],"allowedTools":["get_subskill_asset","queue_campaign_cells","select_campaign_cells","get_campaign_table_schema","wait_for_campaign_processing","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","enrich_with_prospeo","bulk_enrich_with_prospeo","check_rubric"],"hardRules":["campaign_processing_waits_are_stats_only_by_default","queue_review_batch_by_selector_never_fetch_rows_for_cell_ids","timeout_never_repeats_without_customer_handoff","timeout_or_underfloor_sample_never_advances_to_settings"],"waitFor":["sample_validated","sample_revision_required"],"transitions":{"sample_validated":"auto-execute-messaging","sample_revision_required":"lead-review","revise_leads":"find-leads","revise_rubric":"filter-rubric","escalation_triggered":"escalation"}},{"id":"auto-execute-messaging","label":"Generate initial campaign-row messages","currentStepValue":"auto-execute-messaging","references":["references/parallel-critique-protocol.md","references/thomas-variant-selection.md","references/thomas-revision-filters.md","references/step-15-re-cascade.md"],"onEnter":[{"tool":"queue_campaign_cells","batchSize":100,"when":"any passing row has pending, empty, or stale Generate Message cell","requiredFields":["workflowTableId","columnRole","rowSelector"],"requiredValues":{"columnRole":"generateMessage","rowSelector":{"type":"needsGeneratedMessage"},"forceRerun":false},"cellSource":"selector-engine needsGeneratedMessage rows using approved campaign brief template"},{"tool":"wait_for_campaign_processing","purpose":"wait_for_first_current_revision_generated_message","requiredValues":{"minGeneratedMessages":1,"templateRevision":"current"},"readVia":"stats_only_tool_result","invariant":"A generated message implies the pass gate; stale or wrong-revision messages are excluded from readiness."},{"action":"observe_generate_message_results","reference":"references/step-15-re-cascade.md"},{"action":"enforce_token_contract","modeFromConfig":"messaging.tokenContract"},{"action":"optional_critique_pass","enabledFromConfig":"messaging.critique.enabled","plan":"85-03","protocol":"references/parallel-critique-protocol.md","sampleSizeFromConfig":"messaging.critique.sampleSize","budgetUsdCapFromConfig":"messaging.critique.budgetUsdCap","perCriticTimeoutFromConfig":"messaging.critique.perCriticTimeoutSeconds","totalTimeoutFromConfig":"messaging.critique.totalTimeoutSeconds","criticsFromConfig":"messaging.critique.critics","enforceFinalizerPassFromConfig":"messaging.critique.synthesis.enforceFinalizerPass","rejectOnFakeProofFromConfig":"messaging.critique.rejectOnFakeProof","rejectOnUnsupportedTokenFromConfig":"messaging.critique.rejectOnUnsupportedToken","onBudgetTrip":"halt_critique_continue_plain_tail","onPerCriticTimeout":"drop_critic_proceed_with_remaining","onTotalTimeout":"persist_plain_message_for_row","onTokenContractViolation":"persist_plain_message_for_row","onFakeProof":"persist_plain_message_for_row"},{"action":"optional_opus_subset","enabledFromConfig":"messaging.critique.opus.enabled","selection":"references/thomas-variant-selection.md","maxMessagesPerPassFromConfig":"messaging.critique.opus.maxMessagesPerPass","budgetUsdCapFromConfig":"messaging.critique.opus.budgetUsdCap","onOpusBudgetTrip":"halt_opus_continue_non_opus","onOpusTokenContractViolation":"fallback_to_non_opus_rewrite"},{"tool":"update_campaign","requiredValues":{"currentStep":"auto-execute-messaging","watchNarration.stage":"review-ready"},"watchNarrationRule":"Say the first passing generated message is ready in Messages; next is review before Settings."},{"action":"ask_generated_message_review_choice","uses":"request_user_input","choices":["Approve reviewed draft rows and continue to Settings","Revise filters","Revise message template","Pause here"],"copyMustInclude":["approve the reviewed draft rows and continue to Settings","tokens resolved","company/person match","proof claim","prospect angle","language/tone when known","obvious bad fits","Would I take this call?","weak personalization can burn the sender's reputation","full list remains paused","nothing sends from this approval"],"autoSelectIf":"yolo_mode and the first passing generated message clears the quality floor","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"allowedTools":["get_subskill_asset","get_campaign_table_schema","select_campaign_cells","queue_campaign_cells","wait_for_campaign_processing","revise_message_template_and_rerun","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","generate_messages"],"hardRules":["generated_message_count_excludes_stale_or_wrong_revision_messages","message_template_revision_uses_brief_update_then_generate_message_rerun","do_not_overwrite_generated_message_cells_directly","critique_failure_never_escalates","critique_sample_size_bounded_by_config","first_passing_generated_message_unblocks_review","critics_fixed_at_targeting_copy_voice","synthesis_enforces_phase_84_token_contract","opus_reserved_for_highest_value_subset","proposed_token_never_persisted_in_rewrite"],"waitFor":["generated_messages_approved","sample_revision_required"],"transitions":{"generated_messages_approved":"awaiting-user-greenlight","revise_filters":"filter-rubric","revise_messaging":"message-generation","escalation_triggered":"escalation"}},{"id":"awaiting-user-greenlight","label":"Settings, sender, sequence, and greenlight","currentStepValue":"awaiting-user-greenlight","reference":"references/final-handoff-contract.md","onEnter":[{"tool":"get_campaign","purpose":"verify senderIds and sequence state before final handoff"},{"tool":"list_senders","purpose":"surface available connected senders only at Settings"},{"tool":"update_campaign","requiredValues":{"currentStep":"settings","watchNarration.stage":"review-ready"},"purpose":"park the watched UI on Settings before sender selection","watchNarrationRule":"Say message review is complete and the app is on Settings for sender choice. Mention lead research/filtering already happened outside the user's LinkedIn account and nothing sends until Start."},{"action":"surface_sender_and_slack_handoff","requiredVisibleContent":["connect or select a LinkedIn sender","Lead research and filtering already happened outside your LinkedIn account.","This account is only used for approved sending after final launch.","Nothing sends until the final Start step.","Slack reply review","recommended sequence","final launch confirmation is still ahead"],"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]},{"action":"ask_sender_selection","uses":"request_user_input","singleChoice":true,"choices":["Use this connected sender","Connect a different sender in Settings","Pause here"],"autoSelectIf":"yolo_mode and exactly one safe connected sender is available","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]},{"action":"attach_selected_sender","tool":"update_campaign","when":"user selected an available connected sender","requiredValues":{"senderIds":["{selectedSenderId}"],"currentStep":"sequence","watchNarration.stage":"review-ready"},"watchNarrationRule":"Say the sender was attached and the app is moving to Sequence review; sequence remains editable and nothing sends until Start."},{"tool":"attach_recommended_sequence","when":"after senderIds are attached","requiredValues":{"campaignId":"{campaignId}","currentStep":"send","watchNarration.stage":"review-ready"},"watchNarrationRule":"The sequence tool owns this visible beat: say the recommended follow-up sequence is attached, the app is on final launch review, and it is still not sending until Start."},{"action":"ask_final_launch_greenlight","uses":"request_user_input","singleChoice":true,"choices":["Start campaign","Review campaign first","Pause here"],"copyMustInclude":["quality confidence means sample messages and prospect angle were checked","launch confidence means sender, sequence, and Start are ready","approved messages begin sending according to the sequence","replies and meetings follow connected settings","you can monitor and pause","no hidden extra approval disappears after clicking Start"],"onUserStart":"claude-greenlight","neverAutoSelectInYolo":true,"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"allowedTools":["get_campaign","get_campaign_navigation_state","list_senders","update_campaign","attach_recommended_sequence","AskUserQuestion","request_user_input"],"doNotAllow":["start_campaign","import_leads"],"autoStart":false,"watchRequired":true,"waitFor":["sender_connection_required","sender_attached","sequence_attached","ready_to_launch","user_greenlight","ui_start_detected"],"transitions":{"sender_connection_required":"awaiting-user-greenlight","sender_attached":"awaiting-user-greenlight","sequence_attached":"awaiting-user-greenlight","ready_to_launch":"awaiting-user-greenlight","user_greenlight":"claude-greenlight","ui_start_detected":"running"}},{"id":"claude-greenlight","label":"Explicit launch","reference":"references/final-handoff-contract.md","onEnter":[{"tool":"get_campaign","purpose":"detect_already_running"},{"action":"verify_sequence_and_current_step_before_start","requiredState":["workflowTableId","senderIds","sequenceTemplate","at least one approved generated message","currentStep in awaiting-user-greenlight|claude-greenlight|send"]},{"tool":"start_campaign","requiredFields":["campaignId"],"persistsCurrentStep":"running","watchNarrationRule":"After start_campaign succeeds, the running state must say the final greenlight was accepted, the campaign is now live/running, and the user can watch progress from the campaign."}],"allowedTools":["get_campaign","attach_recommended_sequence","start_campaign","AskUserQuestion","request_user_input"],"watchRequired":true,"waitFor":"campaign_started","transitions":{"campaign_started":"running"}},{"id":"running","label":"Campaign is live","currentStepValue":"running","onEnter":[{"action":"surface_campaign_live_confirmation_without_watch_link","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"allowedTools":["get_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["start_campaign"],"terminal":true},{"id":"escalation","label":"Escalation","reference":"references/escalation-ladder.md","allowedTools":["AskUserQuestion","request_user_input"],"doNotAllow":["start_campaign","import_leads"],"transitions":{"revise_brief":"brief-interview","revise_leads":"find-leads","revise_rubric":"filter-rubric","revise_messaging":"message-generation","abort":"abort"}},{"id":"abort","label":"Abort","allowedTools":["AskUserQuestion","request_user_input"],"terminal":true}]}
|
|
@@ -222,7 +222,8 @@ reviewBatchLimit: 15 })`. Do not call `import_leads` for this
|
|
|
222
222
|
- Signal Discovery: `get_provider_prompt({ provider: "signal-discovery",
|
|
223
223
|
campaignOfferId, confirmed: true })` -> `search_signals({ campaignOfferId,
|
|
224
224
|
...approved recipe })` -> `select_promising_posts({ campaignOfferId,
|
|
225
|
-
selectionMode: "replace", selections,
|
|
225
|
+
selectionMode: "replace", scrapePlanMode: "capacity-target", selections,
|
|
226
|
+
headlineICPCriteria })` ->
|
|
226
227
|
`import_leads({ campaignOfferId, provider: "signal-discovery",
|
|
227
228
|
targetEngagerCount: 1500 })`.
|
|
228
229
|
For LinkedIn engagement (`signal-discovery` internally), the promotion/select step is load-bearing. Use
|
|
@@ -305,8 +305,16 @@ When selecting promising posts, focus on QUALITATIVE factors:
|
|
|
305
305
|
|
|
306
306
|
When calling `select_promising_posts`:
|
|
307
307
|
|
|
308
|
-
-
|
|
309
|
-
- Use `selectionMode: "replace"`
|
|
308
|
+
- Always set `selectionMode`.
|
|
309
|
+
- Use `selectionMode: "replace"` for a fresh/absolute promoted set. This clears
|
|
310
|
+
previously promoted posts that are not in the new call.
|
|
311
|
+
- Use `selectionMode: "add"` only when the user explicitly wants to expand the
|
|
312
|
+
current promoted set.
|
|
313
|
+
- Promotion mode is separate from scrape plan mode. If the approval is to scrape
|
|
314
|
+
all promoted posts, set `scrapePlanMode: "all-selected"` and call
|
|
315
|
+
`import_leads` without `targetEngagerCount` / `maxPostsToScrape`. If the
|
|
316
|
+
approval is capacity-based, set `scrapePlanMode: "capacity-target"` and pass
|
|
317
|
+
`targetEngagerCount`.
|
|
310
318
|
|
|
311
319
|
</selection_mode>
|
|
312
320
|
|
|
@@ -410,7 +418,9 @@ Use `selectionTarget` and `recommendedPostIds` returned by `search_signals`
|
|
|
410
418
|
For campaign-attached viability sampling, this is also the promote step: call it
|
|
411
419
|
before `fetch_post_engagers` so the user sees the exact posts being sampled in
|
|
412
420
|
the watched Signal Discovery table. Use `selectionMode: "replace"` for the first
|
|
413
|
-
sample set unless the user explicitly wants to add to existing promoted posts
|
|
421
|
+
sample set unless the user explicitly wants to add to existing promoted posts;
|
|
422
|
+
set `scrapePlanMode` based on whether approval is all selected posts or a
|
|
423
|
+
capacity-target subset.
|
|
414
424
|
|
|
415
425
|
For `create-campaign-v2` source approval, do not treat the default
|
|
416
426
|
`selectionTarget` of 3 posts as enough by itself. Before the final source
|
|
@@ -442,6 +452,8 @@ sampled/projected headline-fit rate is below 10%, do not call
|
|
|
442
452
|
```json
|
|
443
453
|
select_promising_posts({
|
|
444
454
|
"campaignOfferId": "cmp_xxx",
|
|
455
|
+
"selectionMode": "replace",
|
|
456
|
+
"scrapePlanMode": "capacity-target",
|
|
445
457
|
"selections": [
|
|
446
458
|
{"postId": "post_abc", "reason": "High engagement from founders discussing PMF challenges"},
|
|
447
459
|
{"postId": "post_def", "reason": "Author is respected in startup community, recent post"}
|
|
@@ -487,7 +499,7 @@ decision, that approval is the explicit confirmation to materialize the approved
|
|
|
487
499
|
source list and then copy the confirmed source rows into the campaign. In that
|
|
488
500
|
tail flow, follow `create-campaign-v2-tail`: call
|
|
489
501
|
`select_promising_posts({ campaignOfferId, selectionMode: "replace",
|
|
490
|
-
selections, headlineICPCriteria })`, then call
|
|
502
|
+
scrapePlanMode: "capacity-target", selections, headlineICPCriteria })`, then call
|
|
491
503
|
`import_leads({ campaignOfferId, provider: "signal-discovery",
|
|
492
504
|
targetEngagerCount, maxPostsToScrape })` for the approved
|
|
493
505
|
source-capacity plan without asking for another yes/no gate. Then
|
|
@@ -614,6 +626,8 @@ Selection:
|
|
|
614
626
|
```json
|
|
615
627
|
select_promising_posts({
|
|
616
628
|
"campaignOfferId": "cmp_xxx",
|
|
629
|
+
"selectionMode": "replace",
|
|
630
|
+
"scrapePlanMode": "all-selected",
|
|
617
631
|
"selections": [
|
|
618
632
|
{"postId": "post1", "reason": "High engagement from content marketing leaders"},
|
|
619
633
|
{"postId": "post2", "reason": "Author is Head of Content at SaaS company"},
|