@sellable/mcp 0.1.186 → 0.1.187
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/tools/leads.d.ts +28 -13
- package/dist/tools/leads.js +61 -22
- package/dist/tools/readiness.js +16 -11
- package/dist/tools/registry.d.ts +17 -0
- package/package.json +1 -1
- package/skills/create-campaign-v2/SKILL.md +13 -14
- package/skills/create-campaign-v2/core/flow.v2.json +1 -1
- package/skills/create-campaign-v2/references/step-13-import-leads.md +25 -5
package/dist/tools/leads.d.ts
CHANGED
|
@@ -146,6 +146,7 @@ export type ConfirmLeadListInput = {
|
|
|
146
146
|
keepInSync?: boolean;
|
|
147
147
|
jobId?: string;
|
|
148
148
|
reviewBatchLimit?: number;
|
|
149
|
+
allowPartialSourceList?: boolean;
|
|
149
150
|
/**
|
|
150
151
|
* Deprecated alias for reviewBatchLimit. Confirming a lead list now copies the
|
|
151
152
|
* confirmed source into the campaign table; this value no longer caps clone
|
|
@@ -244,6 +245,7 @@ export declare const leadToolDefinitions: ({
|
|
|
244
245
|
keepInSync?: undefined;
|
|
245
246
|
jobId?: undefined;
|
|
246
247
|
reviewBatchLimit?: undefined;
|
|
248
|
+
allowPartialSourceList?: undefined;
|
|
247
249
|
selections?: undefined;
|
|
248
250
|
selectionMode?: undefined;
|
|
249
251
|
};
|
|
@@ -420,6 +422,7 @@ export declare const leadToolDefinitions: ({
|
|
|
420
422
|
keepInSync?: undefined;
|
|
421
423
|
jobId?: undefined;
|
|
422
424
|
reviewBatchLimit?: undefined;
|
|
425
|
+
allowPartialSourceList?: undefined;
|
|
423
426
|
selections?: undefined;
|
|
424
427
|
selectionMode?: undefined;
|
|
425
428
|
};
|
|
@@ -495,6 +498,7 @@ export declare const leadToolDefinitions: ({
|
|
|
495
498
|
keepInSync?: undefined;
|
|
496
499
|
jobId?: undefined;
|
|
497
500
|
reviewBatchLimit?: undefined;
|
|
501
|
+
allowPartialSourceList?: undefined;
|
|
498
502
|
selections?: undefined;
|
|
499
503
|
selectionMode?: undefined;
|
|
500
504
|
};
|
|
@@ -642,6 +646,7 @@ export declare const leadToolDefinitions: ({
|
|
|
642
646
|
keepInSync?: undefined;
|
|
643
647
|
jobId?: undefined;
|
|
644
648
|
reviewBatchLimit?: undefined;
|
|
649
|
+
allowPartialSourceList?: undefined;
|
|
645
650
|
selections?: undefined;
|
|
646
651
|
selectionMode?: undefined;
|
|
647
652
|
};
|
|
@@ -731,6 +736,7 @@ export declare const leadToolDefinitions: ({
|
|
|
731
736
|
keepInSync?: undefined;
|
|
732
737
|
jobId?: undefined;
|
|
733
738
|
reviewBatchLimit?: undefined;
|
|
739
|
+
allowPartialSourceList?: undefined;
|
|
734
740
|
selections?: undefined;
|
|
735
741
|
selectionMode?: undefined;
|
|
736
742
|
};
|
|
@@ -829,6 +835,7 @@ export declare const leadToolDefinitions: ({
|
|
|
829
835
|
keepInSync?: undefined;
|
|
830
836
|
jobId?: undefined;
|
|
831
837
|
reviewBatchLimit?: undefined;
|
|
838
|
+
allowPartialSourceList?: undefined;
|
|
832
839
|
selections?: undefined;
|
|
833
840
|
selectionMode?: undefined;
|
|
834
841
|
};
|
|
@@ -909,6 +916,7 @@ export declare const leadToolDefinitions: ({
|
|
|
909
916
|
keepInSync?: undefined;
|
|
910
917
|
jobId?: undefined;
|
|
911
918
|
reviewBatchLimit?: undefined;
|
|
919
|
+
allowPartialSourceList?: undefined;
|
|
912
920
|
selections?: undefined;
|
|
913
921
|
selectionMode?: undefined;
|
|
914
922
|
};
|
|
@@ -1528,6 +1536,7 @@ export declare const leadToolDefinitions: ({
|
|
|
1528
1536
|
keepInSync?: undefined;
|
|
1529
1537
|
jobId?: undefined;
|
|
1530
1538
|
reviewBatchLimit?: undefined;
|
|
1539
|
+
allowPartialSourceList?: undefined;
|
|
1531
1540
|
selections?: undefined;
|
|
1532
1541
|
selectionMode?: undefined;
|
|
1533
1542
|
};
|
|
@@ -1660,6 +1669,7 @@ export declare const leadToolDefinitions: ({
|
|
|
1660
1669
|
keepInSync?: undefined;
|
|
1661
1670
|
jobId?: undefined;
|
|
1662
1671
|
reviewBatchLimit?: undefined;
|
|
1672
|
+
allowPartialSourceList?: undefined;
|
|
1663
1673
|
selections?: undefined;
|
|
1664
1674
|
selectionMode?: undefined;
|
|
1665
1675
|
};
|
|
@@ -1780,6 +1790,7 @@ export declare const leadToolDefinitions: ({
|
|
|
1780
1790
|
keepInSync?: undefined;
|
|
1781
1791
|
jobId?: undefined;
|
|
1782
1792
|
reviewBatchLimit?: undefined;
|
|
1793
|
+
allowPartialSourceList?: undefined;
|
|
1783
1794
|
selections?: undefined;
|
|
1784
1795
|
selectionMode?: undefined;
|
|
1785
1796
|
};
|
|
@@ -1858,6 +1869,7 @@ export declare const leadToolDefinitions: ({
|
|
|
1858
1869
|
keepInSync?: undefined;
|
|
1859
1870
|
jobId?: undefined;
|
|
1860
1871
|
reviewBatchLimit?: undefined;
|
|
1872
|
+
allowPartialSourceList?: undefined;
|
|
1861
1873
|
selections?: undefined;
|
|
1862
1874
|
selectionMode?: undefined;
|
|
1863
1875
|
};
|
|
@@ -1901,6 +1913,10 @@ export declare const leadToolDefinitions: ({
|
|
|
1901
1913
|
type: string;
|
|
1902
1914
|
description: string;
|
|
1903
1915
|
};
|
|
1916
|
+
allowPartialSourceList: {
|
|
1917
|
+
type: string;
|
|
1918
|
+
description: string;
|
|
1919
|
+
};
|
|
1904
1920
|
confirmed: {
|
|
1905
1921
|
type: string;
|
|
1906
1922
|
description: string;
|
|
@@ -2066,6 +2082,7 @@ export declare const leadToolDefinitions: ({
|
|
|
2066
2082
|
keepInSync?: undefined;
|
|
2067
2083
|
jobId?: undefined;
|
|
2068
2084
|
reviewBatchLimit?: undefined;
|
|
2085
|
+
allowPartialSourceList?: undefined;
|
|
2069
2086
|
};
|
|
2070
2087
|
required: string[];
|
|
2071
2088
|
};
|
|
@@ -2146,6 +2163,7 @@ export declare const leadToolDefinitions: ({
|
|
|
2146
2163
|
keepInSync?: undefined;
|
|
2147
2164
|
jobId?: undefined;
|
|
2148
2165
|
reviewBatchLimit?: undefined;
|
|
2166
|
+
allowPartialSourceList?: undefined;
|
|
2149
2167
|
selections?: undefined;
|
|
2150
2168
|
selectionMode?: undefined;
|
|
2151
2169
|
};
|
|
@@ -2388,25 +2406,14 @@ export declare function importLeads(input: ImportLeadsInput): Promise<{
|
|
|
2388
2406
|
existingLeadListId: string;
|
|
2389
2407
|
reusedExistingSourceList: boolean;
|
|
2390
2408
|
message: string;
|
|
2391
|
-
suggestedToolCalls:
|
|
2409
|
+
suggestedToolCalls: {
|
|
2392
2410
|
tool: string;
|
|
2393
2411
|
args: {
|
|
2394
2412
|
campaignOfferId: string;
|
|
2395
2413
|
leadListId: string;
|
|
2396
2414
|
provider: string;
|
|
2397
|
-
sourceLeadListId?: undefined;
|
|
2398
|
-
confirmed?: undefined;
|
|
2399
2415
|
};
|
|
2400
|
-
}
|
|
2401
|
-
tool: string;
|
|
2402
|
-
args: {
|
|
2403
|
-
campaignOfferId: string;
|
|
2404
|
-
sourceLeadListId: string;
|
|
2405
|
-
confirmed: boolean;
|
|
2406
|
-
leadListId?: undefined;
|
|
2407
|
-
provider?: undefined;
|
|
2408
|
-
};
|
|
2409
|
-
})[];
|
|
2416
|
+
}[];
|
|
2410
2417
|
error?: undefined;
|
|
2411
2418
|
needsModeSelection?: undefined;
|
|
2412
2419
|
modeOptions?: undefined;
|
|
@@ -2648,6 +2655,14 @@ export declare function confirmLeadList(input: ConfirmLeadListInput): Promise<{
|
|
|
2648
2655
|
copyStillRunning: boolean;
|
|
2649
2656
|
trimmedOverflowRowCount: number;
|
|
2650
2657
|
sourceShortfall: boolean;
|
|
2658
|
+
partialSourceListAccepted: boolean;
|
|
2659
|
+
};
|
|
2660
|
+
sourceListSnapshot: {
|
|
2661
|
+
partial: boolean;
|
|
2662
|
+
sourceRowCountAtConfirmation: number;
|
|
2663
|
+
targetLeadCount: number | null;
|
|
2664
|
+
importStatus: string | null;
|
|
2665
|
+
warning: string | null;
|
|
2651
2666
|
};
|
|
2652
2667
|
message: string;
|
|
2653
2668
|
}>;
|
package/dist/tools/leads.js
CHANGED
|
@@ -1510,6 +1510,10 @@ export const leadToolDefinitions = [
|
|
|
1510
1510
|
type: "number",
|
|
1511
1511
|
description: "Number of campaign rows to use as the initial review/process sample. Defaults to 15.",
|
|
1512
1512
|
},
|
|
1513
|
+
allowPartialSourceList: {
|
|
1514
|
+
type: "boolean",
|
|
1515
|
+
description: "Explicit override for user-approved early continuation. Default false. When true, confirm_lead_list may copy the currently materialized rows from a still-running source import; use only after the user explicitly asks to keep going with the partial list.",
|
|
1516
|
+
},
|
|
1513
1517
|
confirmed: {
|
|
1514
1518
|
type: "boolean",
|
|
1515
1519
|
description: "Set true after user approval when interaction mode requires confirmation for confirm/import.",
|
|
@@ -2398,7 +2402,7 @@ export async function importLeads(input) {
|
|
|
2398
2402
|
leadListId: campaignSelectedLeadListId,
|
|
2399
2403
|
existingLeadListId: campaignSelectedLeadListId,
|
|
2400
2404
|
reusedExistingSourceList: true,
|
|
2401
|
-
message: "A Signal Discovery source list already exists for the approved selected-post scrape. Reuse it and continue with wait_for_lead_list_ready
|
|
2405
|
+
message: "A Signal Discovery source list already exists for the approved selected-post scrape. Reuse it and continue with wait_for_lead_list_ready before confirm_lead_list; do not ask add/replace and do not rescrape unless the user approves a different selected-post scrape.",
|
|
2402
2406
|
suggestedToolCalls: [
|
|
2403
2407
|
{
|
|
2404
2408
|
tool: "wait_for_lead_list_ready",
|
|
@@ -2408,14 +2412,6 @@ export async function importLeads(input) {
|
|
|
2408
2412
|
provider: "signal-discovery",
|
|
2409
2413
|
},
|
|
2410
2414
|
},
|
|
2411
|
-
{
|
|
2412
|
-
tool: "confirm_lead_list",
|
|
2413
|
-
args: {
|
|
2414
|
-
campaignOfferId,
|
|
2415
|
-
sourceLeadListId: campaignSelectedLeadListId,
|
|
2416
|
-
confirmed: true,
|
|
2417
|
-
},
|
|
2418
|
-
},
|
|
2419
2415
|
],
|
|
2420
2416
|
};
|
|
2421
2417
|
}
|
|
@@ -2585,7 +2581,7 @@ export async function importLeads(input) {
|
|
|
2585
2581
|
targetLeadCount: cappedTargetLeadCount ?? null,
|
|
2586
2582
|
message: `Started scraping ${postsToScrape.length} posts (~${result.estimatedEngagers} people to check). Leads will appear as scraping completes.${importSelection.limited
|
|
2587
2583
|
? ` Limited from ${uniqueSelectedPosts.length} selected posts by the approved source-capacity scrape plan.`
|
|
2588
|
-
: ""} The watched campaign has been moved to confirm-lead-list with import progress copy;
|
|
2584
|
+
: ""} The watched campaign has been moved to confirm-lead-list with import progress copy. Keep calling wait_for_lead_list_ready until it reports ready, failed, or cancelled before confirm_lead_list; only confirm a partial list with allowPartialSourceList after the user explicitly asks to keep going early. Do not call update_campaign to fix that step.`,
|
|
2589
2585
|
};
|
|
2590
2586
|
}
|
|
2591
2587
|
// === SALES NAV / PROSPEO FLOW ===
|
|
@@ -2698,7 +2694,7 @@ export async function importLeads(input) {
|
|
|
2698
2694
|
jobResult,
|
|
2699
2695
|
jobId,
|
|
2700
2696
|
targetLeadCount: cappedTargetLeadCount ?? null,
|
|
2701
|
-
message: "Import started and the watched campaign has been moved to confirm-lead-list with import progress copy.
|
|
2697
|
+
message: "Import started and the watched campaign has been moved to confirm-lead-list with import progress copy. Wait until wait_for_lead_list_ready reports the source import is complete before calling confirm_lead_list. If the user explicitly asks to keep going early, call confirm_lead_list with allowPartialSourceList: true; otherwise keep polling, stop on failure/cancellation, and do not call update_campaign to fix the import step.",
|
|
2702
2698
|
};
|
|
2703
2699
|
}
|
|
2704
2700
|
export async function cancelLeadImport(input) {
|
|
@@ -2745,7 +2741,7 @@ export async function cancelLeadImport(input) {
|
|
|
2745
2741
|
}
|
|
2746
2742
|
export async function confirmLeadList(input) {
|
|
2747
2743
|
const api = getApi();
|
|
2748
|
-
const { campaignOfferId, currentStep, confirmed, sourceLeadListId, campaignName, keepInSync, jobId, reviewBatchLimit, targetLeadCount, } = input;
|
|
2744
|
+
const { campaignOfferId, currentStep, confirmed, sourceLeadListId, campaignName, keepInSync, jobId, reviewBatchLimit, allowPartialSourceList, targetLeadCount, } = input;
|
|
2749
2745
|
assertInteractionApproval({
|
|
2750
2746
|
campaignId: campaignOfferId,
|
|
2751
2747
|
action: "confirm-lead-list",
|
|
@@ -2859,26 +2855,61 @@ export async function confirmLeadList(input) {
|
|
|
2859
2855
|
intervalMs: 1000,
|
|
2860
2856
|
});
|
|
2861
2857
|
}
|
|
2858
|
+
let partialSourceListAccepted = false;
|
|
2859
|
+
let partialSourceListWarning = null;
|
|
2862
2860
|
if (!readiness.ready) {
|
|
2863
|
-
if (
|
|
2861
|
+
if (allowPartialSourceList === true &&
|
|
2862
|
+
readiness.reason === "import_still_running" &&
|
|
2863
|
+
leadListRowCount > 0) {
|
|
2864
|
+
partialSourceListAccepted = true;
|
|
2865
|
+
partialSourceListWarning =
|
|
2866
|
+
"User explicitly asked to continue before the source import completed. This campaign table is based on the currently materialized source-list rows only.";
|
|
2867
|
+
readiness = {
|
|
2868
|
+
ready: true,
|
|
2869
|
+
leadListId: resolvedLeadListId,
|
|
2870
|
+
provider: resolvedProvider ?? null,
|
|
2871
|
+
attempts: typeof readiness.attempts === "number" ? readiness.attempts : 1,
|
|
2872
|
+
elapsedMs: typeof readiness.elapsedMs === "number" ? readiness.elapsedMs : 0,
|
|
2873
|
+
rowCount: leadListRowCount,
|
|
2874
|
+
status: typeof readiness.status === "string"
|
|
2875
|
+
? readiness.status
|
|
2876
|
+
: leadListConfig?.importStatus ?? null,
|
|
2877
|
+
targetLeadCount: typeof readiness.targetLeadCount === "number"
|
|
2878
|
+
? readiness.targetLeadCount
|
|
2879
|
+
: typeof leadListConfig?.targetLeadCount === "number"
|
|
2880
|
+
? leadListConfig.targetLeadCount
|
|
2881
|
+
: null,
|
|
2882
|
+
partialSourceListAccepted: true,
|
|
2883
|
+
};
|
|
2884
|
+
}
|
|
2885
|
+
else if (readiness.reason === "missing_job_id") {
|
|
2864
2886
|
const recoveryNarration = buildSourceImportRecoveryWatchNarration({
|
|
2865
2887
|
reason: "pending",
|
|
2866
2888
|
provider: resolvedProvider,
|
|
2867
2889
|
});
|
|
2868
2890
|
throw new Error(`${recoveryNarration.headline}: Import job ID is missing. Keep the campaign at confirm-lead-list; provide the jobId, retry readiness, cancel the import, or re-run-source before confirming. ${recoveryNarration.safety}`);
|
|
2869
2891
|
}
|
|
2870
|
-
if (readiness.reason === "
|
|
2892
|
+
else if (readiness.reason === "cancelled") {
|
|
2893
|
+
const recoveryNarration = buildSourceImportRecoveryWatchNarration({
|
|
2894
|
+
reason: "failed",
|
|
2895
|
+
provider: resolvedProvider,
|
|
2896
|
+
});
|
|
2897
|
+
throw new Error(`${recoveryNarration.headline}: Import was cancelled. Keep the campaign at confirm-lead-list; do not copy partial rows unless the user explicitly asks to continue from the partial list. ${recoveryNarration.safety}`);
|
|
2898
|
+
}
|
|
2899
|
+
else if (readiness.reason === "import_failed") {
|
|
2871
2900
|
const recoveryNarration = buildSourceImportRecoveryWatchNarration({
|
|
2872
2901
|
reason: "failed",
|
|
2873
2902
|
provider: resolvedProvider,
|
|
2874
2903
|
});
|
|
2875
2904
|
throw new Error(`${recoveryNarration.headline}: Import failed. Keep the campaign at confirm-lead-list; retry the provider import, cancel it, or re-run-source before confirming. ${recoveryNarration.safety}`);
|
|
2876
2905
|
}
|
|
2877
|
-
|
|
2878
|
-
|
|
2879
|
-
|
|
2880
|
-
|
|
2881
|
-
|
|
2906
|
+
else {
|
|
2907
|
+
const recoveryNarration = buildSourceImportRecoveryWatchNarration({
|
|
2908
|
+
reason: "timeout",
|
|
2909
|
+
provider: resolvedProvider,
|
|
2910
|
+
});
|
|
2911
|
+
throw new Error(`${recoveryNarration.headline}: Import still in progress. Keep the campaign at confirm-lead-list; retry readiness, cancel the import, or re-run-source before launching post-import scouts. ${recoveryNarration.safety}`);
|
|
2912
|
+
}
|
|
2882
2913
|
}
|
|
2883
2914
|
const isTerminalAccessError = (error) => error instanceof SellableApiError && [401, 403, 404].includes(error.status);
|
|
2884
2915
|
const formatTerminalAccessError = (error) => {
|
|
@@ -3022,12 +3053,20 @@ export async function confirmLeadList(input) {
|
|
|
3022
3053
|
sourceShortfall: signalSourceTargetLeadCount !== null &&
|
|
3023
3054
|
leadListRowCount > 0 &&
|
|
3024
3055
|
leadListRowCount < signalSourceTargetLeadCount,
|
|
3056
|
+
partialSourceListAccepted,
|
|
3057
|
+
},
|
|
3058
|
+
sourceListSnapshot: {
|
|
3059
|
+
partial: partialSourceListAccepted,
|
|
3060
|
+
sourceRowCountAtConfirmation: leadListRowCount,
|
|
3061
|
+
targetLeadCount: readiness.targetLeadCount ?? null,
|
|
3062
|
+
importStatus: readiness.status ?? null,
|
|
3063
|
+
warning: partialSourceListWarning,
|
|
3025
3064
|
},
|
|
3026
3065
|
message: copyStillRunning
|
|
3027
|
-
?
|
|
3066
|
+
? `${partialSourceListAccepted ? "Using the user-approved partial source snapshot. " : ""}First ${importedRowCount.toLocaleString("en-US")} source candidate${importedRowCount === 1 ? "" : "s"} are copied into the campaign table and the rest of the confirmed source list is still copying. Use the first ${keptReviewRowCount.toLocaleString("en-US")} as the review/process sample. The watched campaign is now on filter-choice; ask add filters vs skip filters before loading post-lead workers.`
|
|
3028
3067
|
: importedRowCount > keptReviewRowCount
|
|
3029
|
-
?
|
|
3030
|
-
:
|
|
3068
|
+
? `${partialSourceListAccepted ? "Using the user-approved partial source snapshot. " : ""}Copied ${importedRowCount.toLocaleString("en-US")} source candidate${importedRowCount === 1 ? "" : "s"} into the campaign table. Use the first ${keptReviewRowCount.toLocaleString("en-US")} as the review/process sample. The watched campaign is now on filter-choice; ask add filters vs skip filters before loading post-lead workers.`
|
|
3069
|
+
: `${partialSourceListAccepted ? "Using the user-approved partial source snapshot. " : ""}Copied ${importedRowCount.toLocaleString("en-US")} source candidate${importedRowCount === 1 ? "" : "s"} into the campaign table for the review/process sample. The watched campaign is now on filter-choice; ask add filters vs skip filters before loading post-lead workers.`,
|
|
3031
3070
|
};
|
|
3032
3071
|
}
|
|
3033
3072
|
export function getProviderPrompt(input) {
|
package/dist/tools/readiness.js
CHANGED
|
@@ -500,15 +500,18 @@ export async function waitForLeadListReady(input) {
|
|
|
500
500
|
await sleep(intervalMs);
|
|
501
501
|
}
|
|
502
502
|
const timedOutWithRows = (lastRowCount ?? 0) > 0;
|
|
503
|
+
const stillRunningWithRows = requireComplete && timedOutWithRows;
|
|
503
504
|
const timeoutReason = missingJobId
|
|
504
505
|
? "missing_job_id"
|
|
505
|
-
:
|
|
506
|
-
? "
|
|
507
|
-
:
|
|
508
|
-
? "
|
|
509
|
-
:
|
|
506
|
+
: stillRunningWithRows
|
|
507
|
+
? "import_still_running"
|
|
508
|
+
: timedOutWithRows
|
|
509
|
+
? "timeout_with_rows"
|
|
510
|
+
: guardApplied
|
|
511
|
+
? "tool_timeout_guard"
|
|
512
|
+
: "timeout";
|
|
510
513
|
return {
|
|
511
|
-
ready: !missingJobId && timedOutWithRows,
|
|
514
|
+
ready: !requireComplete && !missingJobId && timedOutWithRows,
|
|
512
515
|
reason: timeoutReason,
|
|
513
516
|
leadListId,
|
|
514
517
|
provider: provider ?? null,
|
|
@@ -517,11 +520,13 @@ export async function waitForLeadListReady(input) {
|
|
|
517
520
|
rowCount: lastRowCount,
|
|
518
521
|
status: lastStatus ?? undefined,
|
|
519
522
|
targetLeadCount: targetLeadCount ?? undefined,
|
|
520
|
-
warning:
|
|
521
|
-
? "
|
|
522
|
-
:
|
|
523
|
-
?
|
|
524
|
-
:
|
|
523
|
+
warning: stillRunningWithRows
|
|
524
|
+
? `Import still appears to be running with ${lastRowCount?.toLocaleString("en-US") ?? "some"} row(s) available. Re-run wait_for_lead_list_ready to keep polling, or only call confirm_lead_list with allowPartialSourceList after the user explicitly asks to continue early.`
|
|
525
|
+
: !missingJobId && timedOutWithRows
|
|
526
|
+
? "Timed out waiting for import status, but rows exist."
|
|
527
|
+
: guardApplied
|
|
528
|
+
? `Stopped after ${effectiveTimeoutMs}ms to avoid host tool timeout (requested ${requestedTimeoutMs}ms). Re-run wait_for_lead_list_ready to keep polling.`
|
|
529
|
+
: undefined,
|
|
525
530
|
error: lastError ?? undefined,
|
|
526
531
|
};
|
|
527
532
|
}
|
package/dist/tools/registry.d.ts
CHANGED
|
@@ -1392,6 +1392,7 @@ export declare const allTools: ({
|
|
|
1392
1392
|
keepInSync?: undefined;
|
|
1393
1393
|
jobId?: undefined;
|
|
1394
1394
|
reviewBatchLimit?: undefined;
|
|
1395
|
+
allowPartialSourceList?: undefined;
|
|
1395
1396
|
selections?: undefined;
|
|
1396
1397
|
selectionMode?: undefined;
|
|
1397
1398
|
};
|
|
@@ -1568,6 +1569,7 @@ export declare const allTools: ({
|
|
|
1568
1569
|
keepInSync?: undefined;
|
|
1569
1570
|
jobId?: undefined;
|
|
1570
1571
|
reviewBatchLimit?: undefined;
|
|
1572
|
+
allowPartialSourceList?: undefined;
|
|
1571
1573
|
selections?: undefined;
|
|
1572
1574
|
selectionMode?: undefined;
|
|
1573
1575
|
};
|
|
@@ -1643,6 +1645,7 @@ export declare const allTools: ({
|
|
|
1643
1645
|
keepInSync?: undefined;
|
|
1644
1646
|
jobId?: undefined;
|
|
1645
1647
|
reviewBatchLimit?: undefined;
|
|
1648
|
+
allowPartialSourceList?: undefined;
|
|
1646
1649
|
selections?: undefined;
|
|
1647
1650
|
selectionMode?: undefined;
|
|
1648
1651
|
};
|
|
@@ -1790,6 +1793,7 @@ export declare const allTools: ({
|
|
|
1790
1793
|
keepInSync?: undefined;
|
|
1791
1794
|
jobId?: undefined;
|
|
1792
1795
|
reviewBatchLimit?: undefined;
|
|
1796
|
+
allowPartialSourceList?: undefined;
|
|
1793
1797
|
selections?: undefined;
|
|
1794
1798
|
selectionMode?: undefined;
|
|
1795
1799
|
};
|
|
@@ -1879,6 +1883,7 @@ export declare const allTools: ({
|
|
|
1879
1883
|
keepInSync?: undefined;
|
|
1880
1884
|
jobId?: undefined;
|
|
1881
1885
|
reviewBatchLimit?: undefined;
|
|
1886
|
+
allowPartialSourceList?: undefined;
|
|
1882
1887
|
selections?: undefined;
|
|
1883
1888
|
selectionMode?: undefined;
|
|
1884
1889
|
};
|
|
@@ -1977,6 +1982,7 @@ export declare const allTools: ({
|
|
|
1977
1982
|
keepInSync?: undefined;
|
|
1978
1983
|
jobId?: undefined;
|
|
1979
1984
|
reviewBatchLimit?: undefined;
|
|
1985
|
+
allowPartialSourceList?: undefined;
|
|
1980
1986
|
selections?: undefined;
|
|
1981
1987
|
selectionMode?: undefined;
|
|
1982
1988
|
};
|
|
@@ -2057,6 +2063,7 @@ export declare const allTools: ({
|
|
|
2057
2063
|
keepInSync?: undefined;
|
|
2058
2064
|
jobId?: undefined;
|
|
2059
2065
|
reviewBatchLimit?: undefined;
|
|
2066
|
+
allowPartialSourceList?: undefined;
|
|
2060
2067
|
selections?: undefined;
|
|
2061
2068
|
selectionMode?: undefined;
|
|
2062
2069
|
};
|
|
@@ -2676,6 +2683,7 @@ export declare const allTools: ({
|
|
|
2676
2683
|
keepInSync?: undefined;
|
|
2677
2684
|
jobId?: undefined;
|
|
2678
2685
|
reviewBatchLimit?: undefined;
|
|
2686
|
+
allowPartialSourceList?: undefined;
|
|
2679
2687
|
selections?: undefined;
|
|
2680
2688
|
selectionMode?: undefined;
|
|
2681
2689
|
};
|
|
@@ -2808,6 +2816,7 @@ export declare const allTools: ({
|
|
|
2808
2816
|
keepInSync?: undefined;
|
|
2809
2817
|
jobId?: undefined;
|
|
2810
2818
|
reviewBatchLimit?: undefined;
|
|
2819
|
+
allowPartialSourceList?: undefined;
|
|
2811
2820
|
selections?: undefined;
|
|
2812
2821
|
selectionMode?: undefined;
|
|
2813
2822
|
};
|
|
@@ -2928,6 +2937,7 @@ export declare const allTools: ({
|
|
|
2928
2937
|
keepInSync?: undefined;
|
|
2929
2938
|
jobId?: undefined;
|
|
2930
2939
|
reviewBatchLimit?: undefined;
|
|
2940
|
+
allowPartialSourceList?: undefined;
|
|
2931
2941
|
selections?: undefined;
|
|
2932
2942
|
selectionMode?: undefined;
|
|
2933
2943
|
};
|
|
@@ -3006,6 +3016,7 @@ export declare const allTools: ({
|
|
|
3006
3016
|
keepInSync?: undefined;
|
|
3007
3017
|
jobId?: undefined;
|
|
3008
3018
|
reviewBatchLimit?: undefined;
|
|
3019
|
+
allowPartialSourceList?: undefined;
|
|
3009
3020
|
selections?: undefined;
|
|
3010
3021
|
selectionMode?: undefined;
|
|
3011
3022
|
};
|
|
@@ -3049,6 +3060,10 @@ export declare const allTools: ({
|
|
|
3049
3060
|
type: string;
|
|
3050
3061
|
description: string;
|
|
3051
3062
|
};
|
|
3063
|
+
allowPartialSourceList: {
|
|
3064
|
+
type: string;
|
|
3065
|
+
description: string;
|
|
3066
|
+
};
|
|
3052
3067
|
confirmed: {
|
|
3053
3068
|
type: string;
|
|
3054
3069
|
description: string;
|
|
@@ -3214,6 +3229,7 @@ export declare const allTools: ({
|
|
|
3214
3229
|
keepInSync?: undefined;
|
|
3215
3230
|
jobId?: undefined;
|
|
3216
3231
|
reviewBatchLimit?: undefined;
|
|
3232
|
+
allowPartialSourceList?: undefined;
|
|
3217
3233
|
};
|
|
3218
3234
|
required: string[];
|
|
3219
3235
|
};
|
|
@@ -3294,6 +3310,7 @@ export declare const allTools: ({
|
|
|
3294
3310
|
keepInSync?: undefined;
|
|
3295
3311
|
jobId?: undefined;
|
|
3296
3312
|
reviewBatchLimit?: undefined;
|
|
3313
|
+
allowPartialSourceList?: undefined;
|
|
3297
3314
|
selections?: undefined;
|
|
3298
3315
|
selectionMode?: undefined;
|
|
3299
3316
|
};
|
package/package.json
CHANGED
|
@@ -23,14 +23,12 @@ validate-sample tool:
|
|
|
23
23
|
get_subskill_prompt({ subskillName: "create-campaign-v2-tail" })
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
CampaignOffer state
|
|
27
|
-
|
|
28
|
-
`
|
|
29
|
-
|
|
30
|
-
`
|
|
31
|
-
|
|
32
|
-
normal runs; these files are debug outputs; resume and gating read campaign
|
|
33
|
-
state first.
|
|
26
|
+
CampaignOffer state and the watch link are canonical. Resume, gating, and
|
|
27
|
+
handoff read campaign state first: `campaignId`, `watchUrl`, `campaignBrief`,
|
|
28
|
+
`currentStep`, source/search association, `selectedLeadListId`,
|
|
29
|
+
`workflowTableId`, `leadScoringRubrics`, `approvedMessageTemplate`,
|
|
30
|
+
`senderIds`, `sequenceTemplate`, and running state. Local draft files are debug
|
|
31
|
+
outputs; do not create, link, or surface them in normal runs.
|
|
34
32
|
|
|
35
33
|
## Normal Flow
|
|
36
34
|
|
|
@@ -172,12 +170,10 @@ copy.
|
|
|
172
170
|
## Lead Source
|
|
173
171
|
|
|
174
172
|
Default source order when source is unspecified: LinkedIn post engagement,
|
|
175
|
-
Sales Nav recent activity, broader Sales Nav title search, then Prospeo. Use
|
|
176
|
-
when the campaign is not hiring-led
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
`company_job_posting_hiring_for` and `company_job_posting_quantity`; Sales Nav
|
|
180
|
-
is only an activity/referral fallback because it does not provide hiring-by-role filters.
|
|
173
|
+
Sales Nav recent activity, broader Sales Nav title search, then Prospeo. Use it
|
|
174
|
+
only when the campaign is not hiring-led. For hiring-by-role signals, start with
|
|
175
|
+
Prospeo because `search_prospeo` supports `company_job_posting_hiring_for` and
|
|
176
|
+
`company_job_posting_quantity`; Sales Nav is only an activity/referral fallback.
|
|
181
177
|
|
|
182
178
|
Before any provider prompt/search/scout call, move the watched campaign to
|
|
183
179
|
provider/source selection, then show one `## Find Buyers Plan` gate before
|
|
@@ -252,6 +248,9 @@ export count, not the campaign execution-slice size. For Signal Discovery, pass
|
|
|
252
248
|
`provider: "signal-discovery"`, `targetEngagerCount`, `maxPostsToScrape`, and
|
|
253
249
|
`confirmed: true`; the tool owns moving the watch UI to source-list progress
|
|
254
250
|
after a lead-list/job id exists.
|
|
251
|
+
After `import_leads`, poll `wait_for_lead_list_ready` until ready, failed, or
|
|
252
|
+
cancelled. Rows appearing is not enough for `confirm_lead_list`; use
|
|
253
|
+
`allowPartialSourceList: true` only when the user explicitly asks to continue early.
|
|
255
254
|
|
|
256
255
|
For LinkedIn engagement, use this compact source-action approval shape after
|
|
257
256
|
selected posts exist:
|
|
@@ -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, materialize and confirm the source list, internally process the first campaign-table execution slice, save rubrics for approval, approve the message template, then run the bounded filter/message cascade before Settings, sequence, and explicit start.","normalCustomerPath":"Use campaign state, MCP responses, and concise watchNarration. Do not create, read, link, or surface local draft files. Print the watch URL only once at the initial brief handoff; after that, update currentStep/watchNarration and describe the visible app state without repeating the link.","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 progress and approval turns must not print another watch link unless the user explicitly asks for it or link recovery is required."},"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"],"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 compact 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?\" If the user provides a bare handle like `csreyes92` or a `/in/...` path, normalize it to `https://www.linkedin.com/in/{handle}/`. If the user provides a non-profile URL, company page, or anything else, ask again for the person's LinkedIn profile URL or handle 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":"direct /campaign-builder/{campaignId}?mode={claude|codex} watch URL with token auto-login and workspace routing","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":"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}}],"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."},{"action":"ask_brief_choice","uses":"request_user_input","choices":["Approve brief","Revise brief","Pause here"],"skipIf":"yolo_mode; auto_continue after rendering assumptions unless brief quality floor fails"}],"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 people already talking on LinkedIn about [plain topic]. That should help us find people who already care about [plain problem]. If this is too thin, I'll try [plain fallback] next.","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"},"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","skipIf":"approved or leadSourceProvider or yolo_mode auto-selected source_lane_approved","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"]},{"action":"persist_provider_search_step_before_search","tool":"update_campaign","requiredPrecondition":"source_lane_approved","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","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 write \"This approval covers\" or \"It does not approve\" sentences.","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"},{"tool":"confirm_lead_list","requiredFields":["campaignOfferId","selectedLeadListId","reviewBatchLimit"],"requiredValues":{"reviewBatchLimit":15},"capture":["workflowTableId","reviewBatchRowIds"]},{"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"}},{"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_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\", offset, limit }) until hasMore=false"],"stateSource":"campaignBrief, source, selectedLeadListId, workflowTableId, execution-slice row ids/hash","outputState":"messageDraftRecommendation"}],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["get_subskill_prompt","get_campaign","get_rows_minimal","update_campaign","AskUserQuestion","request_user_input"],"toolRules":["Run post-find-leads-message-scout when available; otherwise parent fallback must 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"],"waitFor":["message_validation_ready","revise_rubric","revise_messaging","confirm_with_user","auto_continue"],"transitions":{"message_validation_ready":"message-review","revise_rubric":"filter-rubric","revise_messaging":"message-generation","confirm_with_user":"message-review","auto_continue":"message-review"}},{"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"]},{"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"]},{"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"}],"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, refresh apply-icp-rubric narration, then queue bounded Enrich Prospect cells.","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_cells","purpose":"queue ICP/enrichment cells for the internal campaign-table execution slice only","requiredFields":["workflowTableId","reviewBatchRowIds","cellTypes"],"targetCountSource":"reviewBatchRowIds.length (default 15)"},{"tool":"wait_for_campaign_table_ready","purpose":"wait_for_review_batch_scoring_cells"},{"tool":"get_rows_minimal","requiredValues":{"includeRows":false},"purpose":"read compact row status only"},{"tool":"wait_for_rubric_results","requiredFields":["workflowTableId","targetCount","minPassedCount"],"requiredValues":{"includeRows":false,"minPassedCount":1},"minPassedCountSource":"firstPassingRowForMessageStart","readVia":"stats_only_tool_result","customerSummaryPattern":["{checked} leads checked","{passed} passed and have draft messages","{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_cells","wait_for_campaign_table_ready","get_rows_minimal","wait_for_rubric_results","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","enrich_with_prospeo","bulk_enrich_with_prospeo","check_rubric"],"hardRules":["wait_for_rubric_results_never_retain_rows_payload_in_tail_context","wait_for_rubric_results_targetCount_always_explicit","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_cells","batchSize":100,"when":"any passing row has pending or empty Generate Message cell","cellSource":"pending_generate_message_cells_for_passing_rows using approved_campaign_brief_template"},{"tool":"wait_for_rubric_results","purpose":"wait_for_first_passing_generated_message","requiredValues":{"includeRows":false,"minPassedCount":1,"minMessagesCount":1},"readVia":"stats_only_tool_result"},{"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_rows_minimal","queue_cells","wait_for_rubric_results","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","generate_messages"],"hardRules":["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 browser is now on Settings so the user can connect or choose a sender. Include that lead research and filtering already happened outside the user's LinkedIn account, this sender is only used for approved sending after final launch, and no launch happens from Settings."},{"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 browser is moving to Sequence review. Include that the sequence is editable before launch and still not sending 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 the visible beat: say the recommended follow-up sequence is attached, the browser is now on final launch review, and the next action is final launch greenlight. Include that it is still not sending until Start. Do not follow with a separate step-only update_campaign fixup."},{"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, materialize and confirm the source list, internally process the first campaign-table execution slice, save rubrics for approval, approve the message template, then run the bounded filter/message cascade before Settings, sequence, and explicit start.","normalCustomerPath":"Use campaign state, MCP responses, and concise watchNarration. Do not create, read, link, or surface local draft files. Print the watch URL only once at the initial brief handoff; after that, update currentStep/watchNarration and describe the visible app state without repeating the link.","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 progress and approval turns must not print another watch link unless the user explicitly asks for it or link recovery is required."},"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"],"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 compact 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?\" If the user provides a bare handle like `csreyes92` or a `/in/...` path, normalize it to `https://www.linkedin.com/in/{handle}/`. If the user provides a non-profile URL, company page, or anything else, ask again for the person's LinkedIn profile URL or handle 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":"direct /campaign-builder/{campaignId}?mode={claude|codex} watch URL with token auto-login and workspace routing","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":"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}}],"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."},{"action":"ask_brief_choice","uses":"request_user_input","choices":["Approve brief","Revise brief","Pause here"],"skipIf":"yolo_mode; auto_continue after rendering assumptions unless brief quality floor fails"}],"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 people already talking on LinkedIn about [plain topic]. That should help us find people who already care about [plain problem]. If this is too thin, I'll try [plain fallback] next.","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"},"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","skipIf":"approved or leadSourceProvider or yolo_mode auto-selected source_lane_approved","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"]},{"action":"persist_provider_search_step_before_search","tool":"update_campaign","requiredPrecondition":"source_lane_approved","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","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 write \"This approval covers\" or \"It does not approve\" sentences.","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_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\", offset, limit }) until hasMore=false"],"stateSource":"campaignBrief, source, selectedLeadListId, workflowTableId, execution-slice row ids/hash","outputState":"messageDraftRecommendation"}],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["get_subskill_prompt","get_campaign","get_rows_minimal","update_campaign","AskUserQuestion","request_user_input"],"toolRules":["Run post-find-leads-message-scout when available; otherwise parent fallback must 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"],"waitFor":["message_validation_ready","revise_rubric","revise_messaging","confirm_with_user","auto_continue"],"transitions":{"message_validation_ready":"message-review","revise_rubric":"filter-rubric","revise_messaging":"message-generation","confirm_with_user":"message-review","auto_continue":"message-review"}},{"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"]},{"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"]},{"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"}],"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, refresh apply-icp-rubric narration, then queue bounded Enrich Prospect cells.","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_cells","purpose":"queue ICP/enrichment cells for the internal campaign-table execution slice only","requiredFields":["workflowTableId","reviewBatchRowIds","cellTypes"],"targetCountSource":"reviewBatchRowIds.length (default 15)"},{"tool":"wait_for_campaign_table_ready","purpose":"wait_for_review_batch_scoring_cells"},{"tool":"get_rows_minimal","requiredValues":{"includeRows":false},"purpose":"read compact row status only"},{"tool":"wait_for_rubric_results","requiredFields":["workflowTableId","targetCount","minPassedCount"],"requiredValues":{"includeRows":false,"minPassedCount":1},"minPassedCountSource":"firstPassingRowForMessageStart","readVia":"stats_only_tool_result","customerSummaryPattern":["{checked} leads checked","{passed} passed and have draft messages","{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_cells","wait_for_campaign_table_ready","get_rows_minimal","wait_for_rubric_results","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","enrich_with_prospeo","bulk_enrich_with_prospeo","check_rubric"],"hardRules":["wait_for_rubric_results_never_retain_rows_payload_in_tail_context","wait_for_rubric_results_targetCount_always_explicit","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_cells","batchSize":100,"when":"any passing row has pending or empty Generate Message cell","cellSource":"pending_generate_message_cells_for_passing_rows using approved_campaign_brief_template"},{"tool":"wait_for_rubric_results","purpose":"wait_for_first_passing_generated_message","requiredValues":{"includeRows":false,"minPassedCount":1,"minMessagesCount":1},"readVia":"stats_only_tool_result"},{"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_rows_minimal","queue_cells","wait_for_rubric_results","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","generate_messages"],"hardRules":["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 browser is now on Settings so the user can connect or choose a sender. Include that lead research and filtering already happened outside the user's LinkedIn account, this sender is only used for approved sending after final launch, and no launch happens from Settings."},{"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 browser is moving to Sequence review. Include that the sequence is editable before launch and still not sending 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 the visible beat: say the recommended follow-up sequence is attached, the browser is now on final launch review, and the next action is final launch greenlight. Include that it is still not sending until Start. Do not follow with a separate step-only update_campaign fixup."},{"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}]}
|
|
@@ -112,6 +112,20 @@ import_leads({
|
|
|
112
112
|
})
|
|
113
113
|
```
|
|
114
114
|
|
|
115
|
+
Required sequence after the provider import starts:
|
|
116
|
+
|
|
117
|
+
```text
|
|
118
|
+
import_leads(...) -> repeated wait_for_lead_list_ready(...) -> confirm_lead_list(...) -> wait_for_campaign_table_ready(...)
|
|
119
|
+
```
|
|
120
|
+
|
|
121
|
+
`wait_for_lead_list_ready` must report `ready: true` before
|
|
122
|
+
`confirm_lead_list` runs. Partial rows are not enough. If it returns
|
|
123
|
+
`reason: "import_still_running"`, keep waiting unless the user explicitly asks
|
|
124
|
+
to continue early; only then pass `allowPartialSourceList: true` to
|
|
125
|
+
`confirm_lead_list`. If it returns `reason: "cancelled"` or
|
|
126
|
+
`reason: "import_failed"`, stop at the import step and do not copy partial rows
|
|
127
|
+
into the campaign table.
|
|
128
|
+
|
|
115
129
|
Provider is inherited from the selected source decision (not re-selected here)
|
|
116
130
|
and should already be saved on the campaign before import. For Sales Nav and
|
|
117
131
|
Prospeo, `<sourceCandidateTarget>` is not the internal campaign-table execution slice. It is
|
|
@@ -240,11 +254,17 @@ cannot scale further without a new source lane.
|
|
|
240
254
|
- Zero-imported + high-skipped ⇒ lane-exhausted operator notice (NOT a
|
|
241
255
|
silent proceed).
|
|
242
256
|
- `import_leads` is allowed immediately after the concrete source-action
|
|
243
|
-
approval. It materializes the source list
|
|
244
|
-
|
|
245
|
-
|
|
246
|
-
|
|
247
|
-
|
|
257
|
+
approval. It materializes the source list. Then keep calling
|
|
258
|
+
`wait_for_lead_list_ready` until the source import is complete, failed, or
|
|
259
|
+
cancelled. Only after completion should `confirm_lead_list` copy the confirmed
|
|
260
|
+
source rows into the campaign table while returning only the first 15 rows for
|
|
261
|
+
the internal campaign-table execution slice. Enrichment and fit scoring wait
|
|
262
|
+
for saved rubrics/run conditions; message cells wait for the approved message
|
|
263
|
+
set.
|
|
264
|
+
- Do not call `confirm_lead_list` from a partial source list unless the user
|
|
265
|
+
explicitly asks to keep going early; in that case pass
|
|
266
|
+
`allowPartialSourceList: true` and say the campaign is using a partial source
|
|
267
|
+
snapshot.
|
|
248
268
|
- Step 13 MUST reuse an approved source already attached with `campaignOfferId`
|
|
249
269
|
before `import_leads`; it does not replay provider searches.
|
|
250
270
|
- `import_leads` is NOT called again in Step 14 (validate-sample). Full
|