@sellable/mcp 0.1.275 → 0.1.277

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/server.js CHANGED
@@ -6,6 +6,7 @@ import { getAuthStatus } from "./tools/auth.js";
6
6
  import { handleAddColumn, handleCommitBlueprint, } from "./tools/blueprint-commit.js";
7
7
  import { bootstrapCreateCampaign } from "./tools/bootstrap.js";
8
8
  import { getCampaignTableSchema, queueCampaignCells, reviseMessageTemplateAndRerun, selectCampaignCells, waitForCampaignProcessing, } from "./tools/campaign-processing.js";
9
+ import { cancelPrepareCampaignMessages, getPrepareCampaignMessagesStatus, startPrepareCampaignMessages, } from "./tools/campaign-message-preparation.js";
9
10
  import { createCampaign, duplicateCampaign, getCampaign, getCampaignMessagesPreview, getCampaigns, pauseCampaign, startCampaign, updateCampaign, updateCampaignBrief, } from "./tools/campaigns.js";
10
11
  import { prepareCampaignAbTest } from "./tools/campaign-ab-test.js";
11
12
  import { queueCells, updateCell } from "./tools/cells.js";
@@ -164,6 +165,25 @@ server.setRequestHandler(CallToolRequestSchema, async (request) => {
164
165
  case "get_campaign_messages_preview":
165
166
  result = await getCampaignMessagesPreview(args);
166
167
  break;
168
+ case "start_prepare_campaign_messages":
169
+ result = await startPrepareCampaignMessages(args);
170
+ if (args?.campaignId) {
171
+ markCampaignContextDirty(args.campaignId, "start_prepare_campaign_messages");
172
+ }
173
+ break;
174
+ case "get_prepare_campaign_messages_status":
175
+ result = await getPrepareCampaignMessagesStatus(args);
176
+ if (args?.campaignId &&
177
+ ["succeeded", "cancelled", "failed"].includes(String(result?.status))) {
178
+ markCampaignContextDirty(args.campaignId, "get_prepare_campaign_messages_status");
179
+ }
180
+ break;
181
+ case "cancel_prepare_campaign_messages":
182
+ result = await cancelPrepareCampaignMessages(args);
183
+ if (args?.campaignId) {
184
+ markCampaignContextDirty(args.campaignId, "cancel_prepare_campaign_messages");
185
+ }
186
+ break;
167
187
  case "get_campaign_table_schema":
168
188
  result = await getCampaignTableSchema(args);
169
189
  break;
@@ -1,5 +1,11 @@
1
1
  export interface PrepareCampaignAbTestInput {
2
- sourceCampaignId: string;
2
+ sourceCampaignId?: string;
3
+ sourceLeadListId?: string;
4
+ campaignName?: string;
5
+ campaignBrief?: string;
6
+ offerPositioning?: Record<string, unknown>;
7
+ clientProspectId?: string;
8
+ senderLinkedinUrl?: string;
3
9
  variantName: string;
4
10
  variantBriefDelta: string;
5
11
  variantALabel?: string;
@@ -22,6 +28,31 @@ export declare const campaignAbTestToolDefinitions: {
22
28
  type: string;
23
29
  description: string;
24
30
  };
31
+ sourceLeadListId: {
32
+ type: string;
33
+ description: string;
34
+ };
35
+ campaignName: {
36
+ type: string;
37
+ description: string;
38
+ };
39
+ campaignBrief: {
40
+ type: string;
41
+ description: string;
42
+ };
43
+ offerPositioning: {
44
+ type: string;
45
+ additionalProperties: boolean;
46
+ description: string;
47
+ };
48
+ clientProspectId: {
49
+ type: string;
50
+ description: string;
51
+ };
52
+ senderLinkedinUrl: {
53
+ type: string;
54
+ description: string;
55
+ };
25
56
  variantName: {
26
57
  type: string;
27
58
  description: string;
@@ -2,13 +2,38 @@ import { getApi } from "../api.js";
2
2
  export const campaignAbTestToolDefinitions = [
3
3
  {
4
4
  name: "prepare_campaign_ab_test",
5
- description: "Prepare a campaign A/B test from an existing campaign using its clean source lead list. Creates deterministic A/B split lead lists and review-copy campaigns, updates the variant brief, and never launches either campaign.",
5
+ description: "Prepare a campaign A/B test from an existing campaign, a clean source lead list, or just a campaign idea. If sourceCampaignId is omitted, pass campaignName and campaignBrief so the tool creates the A campaign first and duplicates it for B. sourceLeadListId is optional; when provided the tool also creates deterministic A/B split lead lists and imports those rows. The tool never launches either campaign.",
6
6
  inputSchema: {
7
7
  type: "object",
8
8
  properties: {
9
9
  sourceCampaignId: {
10
10
  type: "string",
11
- description: "Existing source campaign ID.",
11
+ description: "Optional existing source campaign ID. If omitted, campaignName and campaignBrief are required.",
12
+ },
13
+ sourceLeadListId: {
14
+ type: "string",
15
+ description: "Optional clean source lead-list table ID. When supplied, rows are split into A/B lead lists and imported into the draft campaigns.",
16
+ },
17
+ campaignName: {
18
+ type: "string",
19
+ description: "Base campaign name generated from the user's idea when sourceCampaignId is omitted.",
20
+ },
21
+ campaignBrief: {
22
+ type: "string",
23
+ description: "Base campaign brief generated from the user's idea when sourceCampaignId is omitted.",
24
+ },
25
+ offerPositioning: {
26
+ type: "object",
27
+ additionalProperties: true,
28
+ description: "Optional offer positioning JSON to store on the newly created A campaign.",
29
+ },
30
+ clientProspectId: {
31
+ type: "string",
32
+ description: "Optional client prospect ID to store on the newly created A campaign.",
33
+ },
34
+ senderLinkedinUrl: {
35
+ type: "string",
36
+ description: "Optional sender LinkedIn profile URL to preserve in A/B metadata.",
12
37
  },
13
38
  variantName: {
14
39
  type: "string",
@@ -49,7 +74,7 @@ export const campaignAbTestToolDefinitions = [
49
74
  description: "Optional stable key for retrying the same A/B preparation safely.",
50
75
  },
51
76
  },
52
- required: ["sourceCampaignId", "variantName", "variantBriefDelta"],
77
+ required: ["variantName", "variantBriefDelta"],
53
78
  additionalProperties: false,
54
79
  },
55
80
  },
@@ -0,0 +1,81 @@
1
+ type ApprovalMode = "mark_ready" | "approve";
2
+ type PrepareMessagesBaseInput = {
3
+ campaignId?: string;
4
+ tableId?: string;
5
+ jobId?: string;
6
+ };
7
+ type StartPrepareMessagesInput = PrepareMessagesBaseInput & {
8
+ campaignId: string;
9
+ targetPreparedMessages?: number;
10
+ maxRowsToCheck?: number;
11
+ batchSize?: number;
12
+ approvalMode?: ApprovalMode;
13
+ autoContinue?: boolean;
14
+ };
15
+ type StatusPrepareMessagesInput = PrepareMessagesBaseInput;
16
+ type CancelPrepareMessagesInput = PrepareMessagesBaseInput;
17
+ export declare const campaignMessagePreparationToolDefinitions: ({
18
+ name: string;
19
+ description: string;
20
+ inputSchema: {
21
+ type: string;
22
+ properties: {
23
+ campaignId: {
24
+ type: string;
25
+ description: string;
26
+ };
27
+ tableId: {
28
+ type: string;
29
+ };
30
+ targetPreparedMessages: {
31
+ type: string;
32
+ };
33
+ maxRowsToCheck: {
34
+ type: string;
35
+ };
36
+ batchSize: {
37
+ type: string;
38
+ };
39
+ approvalMode: {
40
+ type: string;
41
+ enum: string[];
42
+ description: string;
43
+ };
44
+ autoContinue: {
45
+ type: string;
46
+ };
47
+ jobId?: undefined;
48
+ };
49
+ required: string[];
50
+ additionalProperties: boolean;
51
+ };
52
+ } | {
53
+ name: string;
54
+ description: string;
55
+ inputSchema: {
56
+ type: string;
57
+ properties: {
58
+ jobId: {
59
+ type: string;
60
+ };
61
+ campaignId: {
62
+ type: string;
63
+ description?: undefined;
64
+ };
65
+ tableId: {
66
+ type: string;
67
+ };
68
+ targetPreparedMessages?: undefined;
69
+ maxRowsToCheck?: undefined;
70
+ batchSize?: undefined;
71
+ approvalMode?: undefined;
72
+ autoContinue?: undefined;
73
+ };
74
+ additionalProperties: boolean;
75
+ required?: undefined;
76
+ };
77
+ })[];
78
+ export declare function startPrepareCampaignMessages(input: StartPrepareMessagesInput): Promise<unknown>;
79
+ export declare function getPrepareCampaignMessagesStatus(input: StatusPrepareMessagesInput): Promise<unknown>;
80
+ export declare function cancelPrepareCampaignMessages(input: CancelPrepareMessagesInput): Promise<unknown>;
81
+ export {};
@@ -0,0 +1,86 @@
1
+ import { getApi } from "../api.js";
2
+ async function postPrepareMessages(body) {
3
+ const api = getApi();
4
+ return api.post("/api/v3/mcp/campaign-message-preparation", body);
5
+ }
6
+ export const campaignMessagePreparationToolDefinitions = [
7
+ {
8
+ name: "start_prepare_campaign_messages",
9
+ description: 'Start a bounded Prepare Messages job for a CampaignOffer campaignId. Use this after lead/message approval when the user asks for a target like "prepare 100 messages"; default approvalMode is mark_ready and it never starts the campaign.',
10
+ inputSchema: {
11
+ type: "object",
12
+ properties: {
13
+ campaignId: {
14
+ type: "string",
15
+ description: "CampaignOffer.id for the campaign whose workflow table should prepare messages.",
16
+ },
17
+ tableId: { type: "string" },
18
+ targetPreparedMessages: { type: "number" },
19
+ maxRowsToCheck: { type: "number" },
20
+ batchSize: { type: "number" },
21
+ approvalMode: {
22
+ type: "string",
23
+ enum: ["mark_ready", "approve"],
24
+ description: 'Defaults to mark_ready. Use approve only when the user explicitly asks to flip Approved cells.',
25
+ },
26
+ autoContinue: { type: "boolean" },
27
+ },
28
+ required: ["campaignId"],
29
+ additionalProperties: false,
30
+ },
31
+ },
32
+ {
33
+ name: "get_prepare_campaign_messages_status",
34
+ description: "Poll compact Prepare Messages progress: checked rows, prepared/approved count, target, remaining row budget, warnings, and stop reason. Does not return raw rows or message bodies.",
35
+ inputSchema: {
36
+ type: "object",
37
+ properties: {
38
+ jobId: { type: "string" },
39
+ campaignId: { type: "string" },
40
+ tableId: { type: "string" },
41
+ },
42
+ additionalProperties: false,
43
+ },
44
+ },
45
+ {
46
+ name: "cancel_prepare_campaign_messages",
47
+ description: "Cancel an active Prepare Messages job. Cancellation is cooperative and launch remains a separate explicit start_campaign step.",
48
+ inputSchema: {
49
+ type: "object",
50
+ properties: {
51
+ jobId: { type: "string" },
52
+ campaignId: { type: "string" },
53
+ tableId: { type: "string" },
54
+ },
55
+ additionalProperties: false,
56
+ },
57
+ },
58
+ ];
59
+ export function startPrepareCampaignMessages(input) {
60
+ return postPrepareMessages({
61
+ action: "start",
62
+ campaignId: input.campaignId,
63
+ tableId: input.tableId,
64
+ targetPreparedMessages: input.targetPreparedMessages,
65
+ maxRowsToCheck: input.maxRowsToCheck,
66
+ batchSize: input.batchSize,
67
+ approvalMode: input.approvalMode,
68
+ autoContinue: input.autoContinue,
69
+ });
70
+ }
71
+ export function getPrepareCampaignMessagesStatus(input) {
72
+ return postPrepareMessages({
73
+ action: "status",
74
+ jobId: input.jobId,
75
+ campaignId: input.campaignId,
76
+ tableId: input.tableId,
77
+ });
78
+ }
79
+ export function cancelPrepareCampaignMessages(input) {
80
+ return postPrepareMessages({
81
+ action: "cancel",
82
+ jobId: input.jobId,
83
+ campaignId: input.campaignId,
84
+ tableId: input.tableId,
85
+ });
86
+ }
@@ -82,6 +82,7 @@ export interface SourceScoutRegistryResponse {
82
82
  codex: string;
83
83
  claude: string;
84
84
  parentThreadRule: string;
85
+ prepareMessagesRule?: string;
85
86
  };
86
87
  }
87
88
  export interface PostFindLeadsScoutRegistryResponse {
@@ -131,6 +132,7 @@ export interface PostFindLeadsScoutRegistryResponse {
131
132
  codex: string;
132
133
  claude: string;
133
134
  parentThreadRule: string;
135
+ prepareMessagesRule?: string;
134
136
  };
135
137
  }
136
138
  export declare const DEFAULT_SUBSKILL_PROMPT_CHUNK_CHARS = 48000;
@@ -366,6 +366,7 @@ export function getPostFindLeadsScoutRegistry() {
366
366
  codex: "After confirm_lead_list copies source rows and the initial campaign-table execution slice exists, ask the filter-choice question immediately. Do not spawn anything before that question. After the answer, launch only Message Drafting whenever Codex agent-launch policy is satisfied. The registry lookup is not a launch: after get_post_find_leads_scout_registry, immediately invoke Task/spawn_agent or the host background-agent mechanism before loading filter-leads.md or saving rubrics. If filters are chosen, the parent stays on Filter Rules and drafts/saves rubrics with MCP tools while Message Drafting runs in the background. If filters are skipped, move to Messages/message review after Message Drafting is ready. Treat YOLO/autonomous mode as campaign-scoped permission for this single post-import worker; do not ask for another permission click in YOLO. If the user has not enabled YOLO and has not explicitly asked for background agents/subagents/parallel agents/delegation/message bg agent in this campaign, ask once before loading the long message prompt in the parent. If permission is granted and the named Message Drafting custom agent is unavailable, spawn a generic gpt-5.5 xhigh Message Drafting background agent with the same lean campaign/table basis. If no background-agent tool is callable, start the same full message branch inline before filter drafting, or return blocked/retry-needed; do not wait until filters are saved and then call the registry.",
367
367
  claude: "After confirm_lead_list copies source rows and the initial campaign-table execution slice exists, ask the filter-choice question immediately. Do not invoke any Task/Agent before that question. After the answer, invoke only Message Drafting. If filters are chosen, parent drafts/saves rubrics with MCP tools while Message Drafting runs, asks filter approval, then joins Message Drafting. If filters are skipped, invoke only Message Drafting and move to Messages/message review.",
368
368
  parentThreadRule: 'Named agents are optional acceleration, but message drafting is not optional. The only normal background worker is Message Drafting. YOLO/autonomous mode counts as campaign-scoped permission for this single post-import worker; do not ask for another permission click in YOLO. If a named agent is unavailable after permission, use a generic gpt-5.5 xhigh Message Drafting background agent. source work and filter work stay in the parent thread with MCP tools. If post-find-leads-message-scout is available, run it as the background Message Draft Builder after the filter-choice answer. The registry lookup is not a launch: get_post_find_leads_scout_registry only identifies the worker, and Message Drafting counts as started only after Task/spawn_agent or the host background-agent tool is invoked, or after the parent begins the same full message branch inline because no background-agent tool is callable. This launch must happen before loading filter-leads.md, save_rubrics, or filter approval. If post-find-leads-message-scout is absent, do not customer-surface install status. Do not silently fall back to parent-thread message drafting; the main thread must execute the same message branch from CampaignOffer state, selected source state, workflowTableId, and initial campaign-table execution slice rows. If no background-agent tool is callable, start that same full message branch inline before filter drafting, or return blocked/retry-needed; do not wait until filters are saved and then call the registry. The Message Drafting handoff must be lean. Do not paste copied row counts, brief hashes, review-batch hashes, full reviewBatchRowIds, broad row data, or local debug artifacts into the spawn prompt. Local markdown/json files are not normal-path inputs. The filter-choice question is the first post-import user gate; do not load post-lead registries or filter references before it. Message drafting starts after the filter-choice answer, must load get_subskill_prompt({ subskillName: "generate-messages" }), and must load every required message asset named by generate-messages Mode 0 through get_subskill_asset before drafting. Reference Asset Loading means loading the required pre-draft reference pack before drafting; return blocked/retry-needed if required assets cannot be loaded; load ai-tells.md because it is never optional. The branch loads the full generate-messages prompt and every referenced asset through get_subskill_asset. After generating/revising the candidate and before returning ready, must load get_subskill_prompt({ subskillName: "create-campaign-v2-validation" }) as the final internal validation gate, must read live campaign table state through scoped MCP/product tools, and must reject mismatched selectedLeadListId/workflowTableId/campaign/workspace input. Do not block when filters were chosen but leadScoringRubrics are not yet visible in the branch read; the parent owns save_rubrics and filter approval in parallel, so Message Drafting should return status ready with basisStatus usable_initial when the campaign/list/table and non-empty execution slice match. Do not use any alternate, local-artifact, or examples-only message prompt. User copy feedback, message QA, or rewrite requests before approve-message must be routed back to Message Drafting with the current recommendation, lean campaign/table basis, and latest user text; the parent must not rewrite or QA the template from memory and must not call update_campaign_brief before approve-message. The worker validates internally and returns only templateRecommendation, tokenFillRules, renderedGoodSample, status, approveOrReviseRecommendation, validationStatus, outputAt, outputHash, and blocked/retry detail. Do not render renderedFallbackSample, risk notes, or a qaReceipt on the normal happy path. On the filter path, save_rubrics keeps the browser on Filter Rules after save_rubrics so the user can approve the saved criteria; only then move to Filter Leads, show `Filters saved + waiting for message approval`, and wait there for message approval. Enrichment, filtering, Generate Message cells, sender setup, sequence attach, and launch wait for template approval on the Use Template path. On the skip path, move to Messages/message review after Message Drafting is ready and wait for message approval before enrichment or Settings. Do not render message review from checklist or shortcut instructions; message review requires a messageDraftRecommendation whose basis proves the generate-messages prompt, required message assets, and validation gate ran for the current campaign/table execution slice. Do not automatically rerun Message Drafting after filters/enrichment finish; show the initial draft by default and offer an enriched rewrite only with explicit user opt-in. Handoff and recommendation output are Markdown with labeled fields, not raw JSON.',
369
+ prepareMessagesRule: 'After message approval, prefer start_prepare_campaign_messages for target-ready preparation such as prepare 100 messages checking up to 300 rows. campaignId is CampaignOffer.id. approvalMode defaults to mark_ready; approvalMode approve requires explicit user intent to flip Approved cells. Poll get_prepare_campaign_messages_status and summarize checked rows, prepared/approved count, target, rows remaining, and stop reason. If the user asks to stop preparation, the target is wrong, or status shows the wrong campaign/table, call cancel_prepare_campaign_messages; otherwise do not cancel a healthy prepare run. Low-level selectors are diagnostics and recovery only for this lane. start_campaign remains forbidden until final launch greenlight.',
369
370
  },
370
371
  };
371
372
  }
@@ -659,6 +659,66 @@ export declare const allTools: ({
659
659
  required: string[];
660
660
  additionalProperties: boolean;
661
661
  };
662
+ } | {
663
+ name: string;
664
+ description: string;
665
+ inputSchema: {
666
+ type: string;
667
+ properties: {
668
+ campaignId: {
669
+ type: string;
670
+ description: string;
671
+ };
672
+ tableId: {
673
+ type: string;
674
+ };
675
+ targetPreparedMessages: {
676
+ type: string;
677
+ };
678
+ maxRowsToCheck: {
679
+ type: string;
680
+ };
681
+ batchSize: {
682
+ type: string;
683
+ };
684
+ approvalMode: {
685
+ type: string;
686
+ enum: string[];
687
+ description: string;
688
+ };
689
+ autoContinue: {
690
+ type: string;
691
+ };
692
+ jobId?: undefined;
693
+ };
694
+ required: string[];
695
+ additionalProperties: boolean;
696
+ };
697
+ } | {
698
+ name: string;
699
+ description: string;
700
+ inputSchema: {
701
+ type: string;
702
+ properties: {
703
+ jobId: {
704
+ type: string;
705
+ };
706
+ campaignId: {
707
+ type: string;
708
+ description?: undefined;
709
+ };
710
+ tableId: {
711
+ type: string;
712
+ };
713
+ targetPreparedMessages?: undefined;
714
+ maxRowsToCheck?: undefined;
715
+ batchSize?: undefined;
716
+ approvalMode?: undefined;
717
+ autoContinue?: undefined;
718
+ };
719
+ additionalProperties: boolean;
720
+ required?: undefined;
721
+ };
662
722
  } | {
663
723
  name: string;
664
724
  description: string;
@@ -1150,6 +1210,31 @@ export declare const allTools: ({
1150
1210
  type: string;
1151
1211
  description: string;
1152
1212
  };
1213
+ sourceLeadListId: {
1214
+ type: string;
1215
+ description: string;
1216
+ };
1217
+ campaignName: {
1218
+ type: string;
1219
+ description: string;
1220
+ };
1221
+ campaignBrief: {
1222
+ type: string;
1223
+ description: string;
1224
+ };
1225
+ offerPositioning: {
1226
+ type: string;
1227
+ additionalProperties: boolean;
1228
+ description: string;
1229
+ };
1230
+ clientProspectId: {
1231
+ type: string;
1232
+ description: string;
1233
+ };
1234
+ senderLinkedinUrl: {
1235
+ type: string;
1236
+ description: string;
1237
+ };
1153
1238
  variantName: {
1154
1239
  type: string;
1155
1240
  description: string;
@@ -3,6 +3,7 @@ import { blueprintCommitToolDefinitions } from "./blueprint-commit.js";
3
3
  import { bootstrapToolDefinitions } from "./bootstrap.js";
4
4
  import { campaignToolDefinitions } from "./campaigns.js";
5
5
  import { campaignAbTestToolDefinitions } from "./campaign-ab-test.js";
6
+ import { campaignMessagePreparationToolDefinitions } from "./campaign-message-preparation.js";
6
7
  import { campaignProcessingToolDefinitions } from "./campaign-processing.js";
7
8
  import { cellToolDefinitions } from "./cells.js";
8
9
  import { startCliLoginToolDef, waitForCliLoginToolDef } from "./cli-login.js";
@@ -33,6 +34,7 @@ import { workspaceToolDefinitions } from "./workspaces.js";
33
34
  export const allTools = [
34
35
  ...campaignToolDefinitions,
35
36
  ...campaignAbTestToolDefinitions,
37
+ ...campaignMessagePreparationToolDefinitions,
36
38
  ...campaignProcessingToolDefinitions,
37
39
  ...authToolDefinitions,
38
40
  startCliLoginToolDef,
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.275",
3
+ "version": "0.1.277",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -24,18 +24,27 @@ campaign-message approaches from the same source list.
24
24
 
25
25
  ## Opening Contract
26
26
 
27
- Start by identifying the source campaign, the one variable being tested, and
28
- the clean source lead origin. Then prepare the split and stop for review. The
29
- goal is two review-copy campaigns with clean split source lists, not a launched
27
+ Start by identifying the campaign idea and the one variable being tested. A
28
+ source campaign and clean source lead list are both optional: use an existing
29
+ campaign when the user already has one, use a clean source lead list when leads
30
+ are already available, or use only the idea by writing a campaign name and base
31
+ brief for A. The tool creates A first, duplicates A into B, applies the B delta,
32
+ and stops for review. The goal is two review-copy campaigns, not a launched
30
33
  campaign.
31
34
 
32
35
  ## Required Flow
33
36
 
34
- 1. Confirm the source campaign ID or resolve it with `get_campaign`.
37
+ 1. Decide the source mode:
38
+ - Existing campaign mode: confirm the source campaign ID or resolve it with
39
+ `get_campaign`.
40
+ - Idea-first mode: write a proper campaign name and base campaign brief from
41
+ the user's idea. If the user also has leads, confirm the clean source
42
+ lead-list ID or use `load_csv_linkedin_leads` first.
35
43
  2. Confirm the A/B variable in plain language. Keep variant B as the explicit
36
44
  change and leave variant A as the control unless the user names an A change.
37
- 3. Verify the campaign has a clean source lead list. A clean source list is a
38
- lead-list table, not the generated campaign workflow table.
45
+ 3. If a source lead list is supplied, verify it is clean. A clean source list is
46
+ a lead-list or signal-lead-list table, not the generated campaign workflow
47
+ table.
39
48
  4. Call `prepare_campaign_ab_test` with `dryRun: true` first.
40
49
  5. Review split counts, duplicate/skipped counts, campaign names, and variant
41
50
  brief deltas with the user.
@@ -44,6 +53,18 @@ campaign.
44
53
  7. Return the A and B campaign IDs, split lead-list IDs, counts, and the
45
54
  explicit note that both campaigns are `not_started`.
46
55
 
56
+ ## Tool Inputs
57
+
58
+ Use one of these shapes:
59
+
60
+ - Existing campaign mode: pass `sourceCampaignId`, `variantName`,
61
+ `variantBriefDelta`, and any optional split or A-variant fields.
62
+ - Idea-first mode: omit `sourceCampaignId`, pass `campaignName`,
63
+ `campaignBrief`, `variantName`, and `variantBriefDelta`. If a clean lead list
64
+ is available, also pass `sourceLeadListId`; otherwise leave it out. The tool
65
+ creates the A campaign, then duplicates A to create B and applies the B brief
66
+ delta.
67
+
47
68
  ## Anti-Patterns
48
69
 
49
70
  - Do not call `export_table_csv` from an enriched/generated campaign workflow
@@ -60,18 +81,19 @@ campaign.
60
81
  If the source campaign has no clean source lead list, or the source list is
61
82
  polluted with workflow/output columns, ask for the original CSV or clean source
62
83
  list. Use `load_csv_linkedin_leads`; it strips Sellable workflow columns if a
63
- contaminated CSV is supplied, then creates a clean lead list. After the clean
64
- source is confirmed, retry `prepare_campaign_ab_test`.
84
+ contaminated CSV is supplied, then creates a clean lead list. If no clean source
85
+ is available, continue in idea-first mode without `sourceLeadListId`.
65
86
 
66
87
  ## Output Contract
67
88
 
68
89
  Report:
69
90
 
70
- - source campaign ID
71
- - source lead-list ID
72
- - split strategy and counts
73
- - duplicate/skipped lead counts
74
- - A review campaign ID and split lead-list ID
75
- - B review campaign ID and split lead-list ID
91
+ - source campaign ID, or note that the A campaign was created as the source
92
+ campaign when no source campaign was provided
93
+ - source lead-list ID when supplied
94
+ - split strategy and counts when a source lead list was supplied
95
+ - duplicate/skipped lead counts when a source lead list was supplied
96
+ - A review campaign ID and split lead-list ID when supplied
97
+ - B review campaign ID and split lead-list ID when supplied
76
98
  - variant difference
77
99
  - `launchState: not_started` for both campaigns
@@ -46,6 +46,9 @@ allowed-tools:
46
46
  - mcp__sellable__select_campaign_cells
47
47
  - mcp__sellable__queue_campaign_cells
48
48
  - mcp__sellable__wait_for_campaign_processing
49
+ - mcp__sellable__start_prepare_campaign_messages
50
+ - mcp__sellable__get_prepare_campaign_messages_status
51
+ - mcp__sellable__cancel_prepare_campaign_messages
49
52
  - mcp__sellable__revise_message_template_and_rerun
50
53
  - mcp__sellable__update_campaign_brief
51
54
  - mcp__sellable__update_campaign
@@ -89,6 +92,18 @@ When filters are chosen, save rubrics, get filter approval, then wait for
89
92
  message-template approval before enrichment/filtering or Generate Message cells.
90
93
  After filter approval, Filter Leads should show `Filters saved + waiting for
91
94
  message approval` until the template is approved.
95
+ After message approval, use `start_prepare_campaign_messages` for target-ready
96
+ message preparation such as "prepare 100 messages, checking up to 300 rows".
97
+ Treat `campaignId` as `CampaignOffer.id`, keep the default
98
+ `approvalMode:"mark_ready"` unless the user explicitly asks to flip Approved
99
+ cells, and poll `get_prepare_campaign_messages_status` for checked rows,
100
+ prepared/approved count, target, row budget remaining, and stop reason. Low
101
+ level selector/queue tools are diagnostics and recovery only for this lane. If
102
+ the user asks to stop preparation, the target is wrong, or status shows the
103
+ wrong campaign/table, call `cancel_prepare_campaign_messages`; otherwise do not
104
+ cancel a healthy prepare run.
105
+ Never call `start_campaign` from Prepare Messages; final launch remains a
106
+ separate explicit user greenlight.
92
107
  Use Template is the default message path; AI Generated is only an explicit
93
108
  opt-out.
94
109
 
@@ -955,7 +970,8 @@ updates.
955
970
  observation state,
956
971
  `auto-execute-messaging` after at least one row passes and initial campaign-row
957
972
  messages are being generated or reviewed, `awaiting-user-greenlight` only
958
- after generated campaign-row messages are approved, `settings` for sender
973
+ after generated campaign-row messages are approved and the Prepare Messages
974
+ job has reported compact checked/prepared/stop status, `settings` for sender
959
975
  selection, `sequence` after sender attach, and `send` once the recommended
960
976
  sequence is attached. Do not advance the step backward.
961
977
  7. Keep `selectedLeadListId` as the source list and `workflowTableId` as the
@@ -1 +1 @@
1
- {"version":"v2.1-compact","workflow":"create-campaign-v2","principle":"CampaignOffer state/watch link are canonical; require brief/source/import, filters/message, Settings, explicit Start.","normalCustomerPath":"Use campaign state/MCP/watchNarration. Do not create, read, link, or surface local files. Print first watch link once.","legacyCompatibility":{"validationSubskill":"create-campaign-v2-validation","tailSubskill":"create-campaign-v2-tail","rule":"Legacy diagnostics are opt-in only, 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 initial brief handoff, must not print another watch-link handoff unless user asks or link recovery is needed."},"lazyReferences":{"watch":["references/watch-link-handoff.md","references/watch-guide-narration.md"],"source":["references/lead-validation-preview.md","references/step-13-import-leads.md"],"filter":["references/filter-leads.md"],"message":[],"tail":["references/sample-validation-loop.md","references/step-15-re-cascade.md","references/final-handoff-contract.md"]},"safetyBoundaries":["Do not call list_senders before Settings after message approval.","Do not import leads until Start Import is approved.","Do not queue cells until confirmed source rows exist in the campaign and the message/filter gates are satisfied.","Do not call start_campaign until the user explicitly confirms launch.","Do not use local files as durable state in normal customer runs."],"yoloMode":{"triggerPhrases":["yolo","--yolo","mode=yolo","autopilot","use best guesses","use best estimates","answer for me","just run it","yolo autopilot"],"identityRequestOnlyWhenMissing":true,"operatorDirectionsRule":"Treat freeform directions supplied at invocation or later as durable operator directions; newest conflicting direction wins.","setInteractionModeAfterCampaignCreate":"autonomous","autoSelectsPreLaunchChoices":["campaign focus","brief approval","source plan","Start 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"}}],"allowedTools":["bootstrap_create_campaign","get_auth_status","get_active_workspace","get_subskill_prompt","AskUserQuestion","request_user_input"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","update_campaign","queue_cells","start_campaign"],"waitFor":"bootstrap_complete","transitions":{"bootstrap_complete":"brief-interview"}},{"id":"brief-interview","label":"Client, offer, research, and brief","onEnter":[{"action":"resolve_campaign_identity_before_strategy_packet","allowedTools":["fetch_company","fetch_linkedin_profile","WebFetch","WebSearch"],"mustNotInferFromNameOnly":true,"doNotPresentSenderPickerBeforeIdentityInference":true,"fallback":"Ask only: \"What is your LinkedIn profile URL or handle?\" Normalize handles to profile URLs; if not a profile, ask again before strategy questions.","requiresLinkedInProfileUrl":true,"doNotAcceptAsIdentityInput":["non-profile URL","company page","company/name-only input without a profile handle"],"acceptsLinkedInProfileHandle":true,"normalizeLinkedInProfileHandleToUrl":true},{"action":"run_campaign_identity_research_before_strategy_packet","target":"research-sender","requiredCompletion":"complete_sender_research","allowedTools":["get_subskill_prompt","fetch_linkedin_profile","fetch_company","fetch_linkedin_posts","fetch_company_posts","WebFetch","WebSearch","complete_sender_research"],"requiredInput":"LinkedIn profile URL or handle"},{"action":"confirm_target_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"Who should we target first?","options":["Use Sellable's researched recommendation","Choose a different buyer","I'm not sure yet"],"copyMustInclude":["research basis","recommendation is editable","who to target"],"requiredOption":"Other / custom","neverCollectOpenTextWithStructuredQuestion":true,"skipIf":"user already supplied a clear target or yolo_mode with any reasonable best-estimate target"},{"action":"confirm_current_offer_before_strategy_packet","uses":"request_user_input","question":"What should we pitch to prospects first?","options":["Use Sellable's researched recommendation","Use my current pitch","Shape the pitch together"],"copyMustInclude":["research basis","public LinkedIn research may be stale","recommendation is editable","pitch to prospects"],"skipIf":"user already supplied a clear current offer or yolo_mode with any reasonable researched recommendation"},{"action":"confirm_trust_proof_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"What is the strongest credibility signal we can lead with?","options":["Use Sellable's recommended proof","Use customer proof or a case study","Use founder or company credibility"],"copyMustInclude":["build trust with prospects","recommendation is editable","proof to use or avoid"],"requiredOption":"Other / custom","skipIf":"user already supplied clear proof to use or avoid, or yolo_mode with any reasonable researched proof"},{"action":"choose_prospect_source_path_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"How should we find prospects?","options":["Find prospects for me","Use a CSV of LinkedIn profiles","Use a CSV of company domains"],"copyMustInclude":["prospects","no one added yet","find prospects for me"],"skipIf":"user already supplied a source path or yolo_mode with any reasonable source path"},{"action":"infer_strategy_packet_in_yolo_mode","when":"yolo_mode","skipStructuredSetupQuestions":true,"inferFields":["buyer segment","offer/CTA","proof to use or avoid","lead source","filter choice","message direction"],"sourceOfTruth":"identity/company lookup plus operator directions","mustStateAssumptionsInBrief":true},{"action":"render_supplied_setup_receipt_before_brief_when_setup_questions_skip","when":"any setup question is skipped because the user already supplied or yolo mode inferred identity, target, offer, proof, or source","output":"normal chat receipt before the campaign brief or watch-link handoff","copyMustInclude":["Accepted LinkedIn input: normalized to the required LinkedIn profile URL.","Offer path: supplied current offer or Sellable's researched recommendation; you can replace it with your current offer."],"copyMustNotInclude":["Campaign Identity"]},{"action":"create_watchable_campaign_shell_with_v1_brief","tool":"create_campaign","requiredFields":["name","campaignBrief","clientProspectId or senderLinkedinUrl","currentStep","watchNarration"],"requiredValues":{"currentStep":"create-offer","watchNarration.stage":"brief"},"capture":["campaignId","watchUrl"],"canonicalStateWrites":["campaignId","watchUrl","campaignBrief","currentStep:create-offer"]},{"action":"surface_campaign_shell_watch_link","watchUrlSource":"create_campaign.watchUrl","requiredWatchUrlShape":"exact create_campaign.watchUrl; direct /campaign-builder/{campaignId}?mode={claude|codex} URL with workspaceId and token","copyMustInclude":["WATCH CODEX BUILD","Open live campaign builder","Keep this chat open.","approval questions here"],"immediateNextMainChatLine":"Ask for brief approval now. After approval, say: \"Brief approved. Next, we'll choose where to find buyers. I won't add anyone yet.\"","codexBrowserHandoff":{"openWhenAvailable":false,"printWatchLinkOnly":true,"tellUserCommandEnterOrClick":false,"mustNotUseBrowserAutomation":true,"fallbackMustNotClaimInspection":true},"copyMustAvoid":["bare /campaign-builder/{campaignId} URL","derived watch URL","second watch-link handoff","shell open command","Next, we'll choose where to find buyers"],"oneTimeOnly":true}],"allowedTools":["get_subskill_prompt","fetch_company","fetch_company_posts","fetch_linkedin_profile","fetch_linkedin_posts","complete_sender_research","create_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["campaign_shell_created","brief_ready","confirm_with_user"],"transitions":{"campaign_shell_created":"brief-review","brief_ready":"brief-review","confirm_with_user":"brief-review"}},{"id":"brief-review","label":"Brief review","onEnter":[{"action":"render_brief_inline","minimumVisibleDetail":"campaign direction, buyer, offer, proof, source plan, safety gates","copyMustInclude":["This brief is based on Sellable's research and your answers.","If your site or LinkedIn is stale","Does this brief look right, and how should Sellable continue?"],"copyMustNotInclude":["quoted first-message copy","Message Angle as final proof","This approval covers","does not approve adding people","does not approve scraping posts","selecting a sender","attaching a sequence","Campaign Identity","what should this campaign sell first","sell first","[from you]","[from LinkedIn]","[from website]","[from case study]","[Sellable recommendation]"],"messageHintRule":"If any message-shape hint remains, keep it as non-final bullets and say real examples come after leads are found and filtered.","approvalBoundaryRule":"Keep this gate positive and short. Do not list later steps this approval does not cover; ask approve/revise and then move to choosing where to find buyers.","sectionLabelRule":"Use ## Sender and Company for the person/company context; never render ## Campaign Identity.","skipIf":"the full brief was already rendered in the current shell-creation handoff before create_campaign returned","renderOnceRule":"Brief visible once before approval. If shown before shell creation, do not repeat after; append one watch-link handoff, then ask.","copyMustAvoid":["duplicate brief render after shell creation","second Watch link:"]},{"action":"ask_brief_choice","uses":"request_user_input","choices":["Approve brief + activate YOLO mode (Recommended)","Approve brief + build campaign with AI, step by step","Revise brief"],"skipIf":"yolo_mode; auto_continue after rendering assumptions unless brief quality floor fails","question":"Does this brief look right, and how should Sellable continue?","autonomousChoice":"Approve brief + activate YOLO mode (Recommended)","choiceDescriptions":{"Approve brief + activate YOLO mode (Recommended)":"Sellable will auto-decide the remaining setup choices with best guesses, including sources, filters, and messaging. It still stops before final launch.","Approve brief + build campaign with AI, step by step":"Approve this direction, then work with the AI through each major setup decision before it continues.","Revise brief":"Change the target, offer, proof, or campaign direction before continuing."}}],"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_companies","confirm_prospeo_company_accounts","search_prospeo","fetch_post_engagers"],"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"],"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 without repeating the watch link; never ask first.","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL","watch-link handoff"],"approvalFreshnessRule":"Only source approval opened after the visible Find Buyers Plan, or explicit source choice after it, sets source_lane_approved. Do not reuse brief approval."},"defaultWhenSourceUnspecified":["signal-discovery","sales-nav-recent-active","sales-nav-general","prospeo"],"parallelAllowedOnlyWhen":["user explicitly requested source comparison"]},"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."}},{"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_companies","confirm_prospeo_company_accounts","search_prospeo","fetch_post_engagers"],"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","watch-link handoff"],"approvalBoundary":"Explain what approval authorizes before asking: looking for the best places to find buyers; no one is added yet.","linkPolicy":"Do not include another watch link. The first brief handoff already gave the user the live app URL."},{"action":"ask_find_buyers_plan_choice","uses":"request_user_input","oneShot":true,"requiredPrecondition":"render_find_buyers_plan_inline completed in normal chat after pick-provider state was saved; no earlier approval can satisfy this","skipIf":"source_lane_approved after visible Find Buyers Plan, explicit post-plan leadSourceProvider, or yolo assumed choice","choices":["Start with LinkedIn posts","Start with active LinkedIn profiles","Start with company/contact search","Choose a different place","Pause here"],"approvalChoiceLabelsByProvider":{"signal-discovery":"Start with LinkedIn posts","sales-nav":"Start with active LinkedIn profiles","prospeo":"Start with company/contact search"},"approvalState":"source_lane_approved","copyMustInclude":["Approve this plan for where to find buyers?","Start with LinkedIn posts"],"mustNotHappenBefore":["render_find_buyers_plan_inline"],"approvalStateRule":"Set source_lane_approved here after the visible plan; never from brief approval, generic approval, pre-plan provider state, or artifacts."},{"action":"persist_provider_search_step_before_search","tool":"update_campaign","requiredPrecondition":"source_lane_approved set after the visible Find Buyers Plan approval question","providerCurrentStepMap":{"signal-discovery":"signal-discovery","sales-nav":"sales-nav","prospeo":"prospeo"},"requiredValues":{"leadSourceType":"new","leadSourceProvider":"approved provider","currentStep":"provider-specific current step","watchNarration.stage":"find-leads","watchNarration.headline":"Searching the approved place","watchNarration.safety":"Looking only. No one is added yet."},"mustRunBefore":["search_signals","search_sales_nav","search_prospeo_companies","confirm_prospeo_company_accounts","search_prospeo","fetch_post_engagers"]},{"action":"run_parent_thread_source_funnel","requiredPrecondition":"source_lane_approved after visible Find Buyers Plan approval","mode":"parent_thread_inline_sequential","sourceAgents":"forbidden in normal create-campaign","loadsOnlyActiveProviderPrompt":true,"defaultOrder":["signal-discovery","sales-nav-recent-active","sales-nav-general","prospeo"],"stopOnFirstViableUnlessComparisonRequested":true}],"requiredCampaignState":["campaignId","campaignBrief","currentStep"],"allowedTools":["get_provider_prompt","lookup_sales_nav_filter","search_sales_nav","search_prospeo_companies","confirm_prospeo_company_accounts","search_prospeo","search_signals","select_promising_posts","fetch_post_engagers","fetch_company","fetch_linkedin_profile","load_csv_dnc_entries","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","Task","spawn_agent"],"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":"Start Import approval","onEnter":[{"action":"show_source_decision_card","requiredInlineFields":["primary source and exact filters/recipe","Start Import 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 happens after Start Import"],"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 Start Import copy only: after approval build source list, add to campaign, review first 15 leads; never list future non-approvals.","linkPolicy":"Do not include another watch link in Source Recommendation; describe the Start Import decision only."},{"action":"ask_source_review_choice","uses":"request_user_input","choices":["Approve scraping N recommended LinkedIn posts","Start Import for the approved source list","Revise source","Pause here"],"approvalChoiceLabelsByProvider":{"signal-discovery":"Approve scraping {scrapePostCount} recommended LinkedIn posts?","sales-nav":"Start Import for the approved Sales Nav source list","prospeo":"Start Import for 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"}},{"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","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","maxCustomerCopyLines":4,"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"linkPolicy":"Ask add filters vs skip filters without repeating the watch link.","purpose":"ask filter choice immediately"}],"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 drafting 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_prospect_setup_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":"prospect-setup","filters_skipped":"message-generation","revise_leads":"find-leads"}},{"id":"prospect-setup","label":"Filter setup + message drafting","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":"Filter Rules is visible while Codex drafts and saves filters in the parent thread.","watchNarration.nextAction":"Review saved filter rules"},"mustRunBefore":["get_post_find_leads_scout_registry","launch_message_drafting_after_filter_choice","load_filter_reference_in_parent","save_rubrics","ask_filter_rubric_review_choice"]},{"tool":"get_post_find_leads_scout_registry","returns":["post-find-leads-message-scout"],"rule":"normal post-import registry exposes only Message Drafting; filters are parent-thread MCP work","mustRunBefore":["launch_message_drafting_after_filter_choice","get_subskill_asset:references/filter-leads.md","save_rubrics","ask_filter_rubric_review_choice"],"registryOnlyIsNotLaunch":true},{"action":"launch_message_drafting_after_filter_choice","mode":"background_agent_when_host_supports_subagents","target":"post-find-leads-message-scout","when":"filters_enabled or filters_skipped after filter-choice answer","doesNotQueueCells":true,"customerNarration":"Message Drafting is preparing.","inputs":["campaignId","workflowTableId","brief/source summary","3-5 sample workflow-table rows","optional campaignName/selectedLeadListId/filterChoice"],"forbiddenSiblings":["source/filter workers"],"handoffCompression":"lean_message_drafting_handoff_no_hashes_no_counts_no_full_row_data","requiredConcreteToolCall":"Task or spawn_agent when host supports background agents","registryOnlyIsNotLaunch":true,"mustRunBefore":["get_subskill_asset:references/filter-leads.md","load_filter_reference_in_parent","save_rubrics","ask_filter_rubric_review_choice"],"yoloPermissionRule":"do not ask the user for worker permission in YOLO.","startProof":"Message Drafting counts as started only after Task/spawn_agent.","noBackgroundFallback":"Start the same full message branch inline before filter reference or block.","blockedIfSkipped":"Do not proceed to get_subskill_asset(filter-leads), save_rubrics."},{"tool":"get_subskill_asset","action":"load_filter_reference_in_parent","when":"filters_enabled","requiredValues":{"subskillName":"create-campaign-v2","assetPath":"references/filter-leads.md"},"rule":"Parent thread uses this reference to draft production rubrics; do not spawn a filter worker."},{"tool":"save_rubrics","when":"filters_enabled","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":["Filter rules saved for review","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 saved filter rules","Approve these filter rules."],"autoSelectIf":"yolo_mode and parent saved production-shaped rubrics","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"requiredPrecondition":"save_rubrics succeeded or active campaign rubrics verified"}],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["get_post_find_leads_scout_registry","get_subskill_asset","save_rubrics","get_campaign","get_rows_minimal","update_campaign","get_campaign_navigation_state","AskUserQuestion","request_user_input","Task","spawn_agent"],"doNotAllow":["create_campaign","list_senders","import_leads","confirm_lead_list","queue_cells","start_campaign","check_rubric","generate_messages"],"waitFor":["filter_rubrics_approved","revise_leads","revise_rubric","revise_messaging"],"hardRules":["after_save_rubrics_currentStep_must_stay_create-icp-rubric_until_filter_approval","filter_approval_required_before_apply-icp-rubric_or_queue_campaign_cells","do_not_move_browser_to_messages_until_filter_leads_step_is_current_or_filters_are_explicitly_skipped","no_prospect_setup_worker_or_deep_prompt_before_filter_choice","message_drafting_after_filter_choice","message_drafting_no_cells","only_message_drafting_may_spawn_background_agent","filters_are_parent_thread_mcp_work","no_post_find_leads_filter_scout_in_normal_path","registry_call_is_not_message_drafting_launch","yolo_must_not_ask_permission_for_message_drafting_worker","message_drafting_must_start_before_filter_reference_or_save_rubrics","if_background_agent_unavailable_start_full_message_branch_inline_before_filters_or_block"],"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 drafting","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":["join already-running Message Drafting when prospect-setup launched it","run Message Drafting via post-find-leads-message-scout only if missing; yolo counts","handoff lean basis only: campaignId, workflowTableId, brief summary, source summary/source-use rule, 3-5 sample rows","do not paste copied row counts, brief hashes, review-batch hashes, full reviewBatchRowIds, broad row data, or local debug artifacts","branch loads current brief/context, full generate-messages prompt, and every required message asset referenced by generate-messages Mode 0","return labeled Markdown only: templateRecommendation, tokenFillRules, renderedGoodSample, status, recommendation, validationStatus, outputHash, retry detail","do not return renderedFallbackSample, risk notes, or qaReceipt on the normal happy path","if no yolo and permission missing, ask once before parent inline fallback","use generic gpt-5.5 xhigh Message Drafting agent if named unavailable"],"stateSource":"message branch-loaded live campaign/table state plus parent-provided sample rows","outputState":"messageDraftRecommendation with internal validation passed","revisionMode":{"when":"revise_messaging, message QA request, or latest user message requests template changes before approve-message","route":"send feedback/QA/rewrite request to post-find-leads-message-scout or generic gpt-5.5 xhigh Message Drafting branch","requiredInputs":["latest user feedback","current messageDraftRecommendation and basisToken/outputHash","lean campaign/table basis and brief summary; branch reloads live state"],"forbiddenParentActions":["rewrite template in parent","QA template in parent from memory","update_campaign_brief before approve-message"],"output":"revised messageDraftRecommendation rendered before request_user_input"},"onlyMessageBackgroundAgent":true,"forbiddenSiblingAgents":["source-scout-*","any filter background worker"],"handoffCompression":"lean_message_drafting_handoff_no_hashes_no_counts_no_full_row_data","finalGateRequiredBeforeReady":"Post-generation final gate: After candidate generation/revision, load get_subskill_prompt({ subskillName: \"create-campaign-v2-validation\" })."}],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["get_subskill_prompt","get_campaign","get_rows_minimal","update_campaign","Task","spawn_agent"],"toolRules":["message_drafting_agent_first_no_silent_parent_fallback; yolo counts","on_filters_enabled_path_parent_must_not_enter_message_review_until_save_rubrics_success_and_filter_approval","brief.md, lead-review.md, and lead-sample.json are optional debug context only.","messageDraftRecommendation returns templateRecommendation, tokenFillRules, renderedGoodSample, status, recommendation, validationStatus, outputHash, and retry detail.","messageDraftRecommendation does not return renderedFallbackSample, risk notes, or qaReceipt on the normal happy path.","If campaign/table/sample basis does not match, classify the output stale or blocked.","message_revision_feedback_routes_to_message_drafting_agent_not_parent","message_qa_routes_to_message_drafting_agent_not_parent","message_drafting_loads_full_generate_messages_prompt_all_chunks","message_drafting_loads_all_reference_assets_via_get_subskill_asset","message_drafting_loads_create_campaign_v2_validation_prompt_before_return","message_drafting_handoff_uses_lean_basis_no_hashes_no_counts_no_full_row_id_list","message_drafting_loads_current_campaign_brief_before_drafting","parent_may_load_generate_messages_only_for_explicitly_approved_inline_fallback","before return branch loads create-campaign-v2-validation prompt and runs internal validation"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign","generate_messages","AskUserQuestion","request_user_input"],"waitFor":["message_validation_ready","revise_rubric","revise_messaging"],"transitions":{"message_validation_ready":"message-review","revise_rubric":"filter-rubric","revise_messaging":"message-generation"},"revisionLoop":{"trigger":"user gives copy feedback, asks for QA/update, or chooses revise-messaging","required":["route latest user feedback or QA request to Message Drafting with current recommendation and live campaign/table state","produce a new messageDraftRecommendation with revisionNotes or validationStatus","render revised ## Message Template, ## Rendered Example, and What changed before asking approval"],"blocked":["asking whether an unseen new version is better","reusing the old template after acknowledging feedback","rewriting the template in the parent thread","update_campaign_brief before approve-message","request_user_input or AskUserQuestion during message-generation","QAing the template in the parent thread"],"outputState":"messageDraftRecommendation"},"hardRules":["message_revision_must_render_new_template_before_approval_question","message_revision_saves_only_after_approve_message","message_generation_must_not_call_request_user_input","message_generation_must_transition_to_message_review_only_after_renderable_draft_state","message_drafting_agent_first_no_silent_parent_fallback","message_revision_routes_to_message_drafting_agent","message_qa_routes_to_message_drafting_agent","filter_path_requires_saved_filters_before_message_review","only_message_drafting_may_spawn_background_agent"]},{"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":["Rendered fallback sample","Concerns","QA receipt","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","validation notes","QA details"],"copyMustInclude":["one recommended template","one rendered sample","token rules and fallbacks","full list remains paused"],"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"revisionRenderRule":"After revise_messaging, QA, or copy feedback, route feedback to Message Drafting; show revised template/example/What changed before approval; never ask if an unseen version is better.","parentMustNot":["rewrite template from memory","invent validation summary without Message Drafting output","render qaReceipt, risk notes, or renderedFallbackSample on the normal happy path"]},{"action":"ask_message_review_choice","uses":"request_user_input","choices":["approve-message","revise-messaging"],"copyMustInclude":["approve this message template and continue","template approval only locks the draft direction; final Start still controls sending"],"autoSelectIf":"yolo_mode and Recommendation is approve-message; revise autonomously when Recommendation is revise-messaging","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"requiredPrecondition":"current or revised template and rendered example are visible in this turn","revisionPrecondition":"after revise_messaging, revised template is visible before this question"},{"action":"sync_approved_message_set_to_campaign_brief","tool":"update_campaign_brief","when":"after approve-message","requiredFields":["campaignId","approvedMessageTemplate","Token Fill Rules","Token Fill Examples"],"writesCampaignState":"approvedMessageTemplate","revisionApprovalRule":"If the user requested revisions, write only the latest Message Drafting-approved revised template and token rules after approve-message.","forbiddenBefore":"approve-message"}],"requiredCampaignState":["campaignId","workflowTableId","messageDraftRecommendation"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign_brief","update_campaign","get_rows_minimal"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","start_campaign"],"waitFor":["message_approved","revise_messaging"],"transitions":{"message_approved":"validate-sample","revise_messaging":"message-generation"}},{"id":"validate-sample","label":"Validate campaign-table execution slice","currentStepValue":"apply-icp-rubric","reference":"references/sample-validation-loop.md","visibleStepRule":"Filter Leads is reached only after saved-filter approval; on approve-message, save the template and queue bounded Enrich Prospect cells through selector-based campaign processing.","onEnter":[{"tool":"update_campaign","requiredValues":{"currentStep":"apply-icp-rubric","watchNarration.stage":"fit-message"},"watchNarrationRule":"Say the template is saved and Filter Leads is now running the bounded enrichment and scoring pass."},{"tool":"queue_campaign_cells","requiredFields":["workflowTableId","columnRole","rowSelector"],"requiredValues":{"columnRole":"enrich","rowSelector":{"type":"reviewBatch"},"forceRerun":false},"targetCountSource":"WorkflowTable.config.mcp.reviewBatch.rowCount (default 15)"},{"tool":"wait_for_campaign_processing","requiredFields":["workflowTableId","minPassedCount"],"requiredValues":{"minPassedCount":1},"readVia":"stats_only_tool_result","timeoutGuidance":"If this times out, do not repoll identical args; use partial diagnostics to revise filters or surface blocked sample state.","customerSummaryPattern":["{checked} leads checked","{passed} passed fit scoring","{blocked} blocked before sending","full list remains paused until approval"]},{"action":"handle_partial_or_timeout_sample","customerStatus":"sample-needs-revision","rule":"Do not repeat waits indefinitely; surface partial status and route to revision.","copyMustInclude":"completed, passed, pending, blocked before sending, and full list remains paused"}],"requiredCampaignState":["campaignId","workflowTableId","approvedMessageTemplate","filterRubricsApproved when enableICPFilters=true"],"allowedTools":["get_subskill_asset","queue_campaign_cells","select_campaign_cells","get_campaign_table_schema","wait_for_campaign_processing","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","enrich_with_prospeo","bulk_enrich_with_prospeo","check_rubric"],"hardRules":["campaign_processing_waits_are_stats_only_by_default","queue_review_batch_by_selector_never_fetch_rows_for_cell_ids","timeout_never_repeats_without_customer_handoff","timeout_or_underfloor_sample_never_advances_to_settings"],"waitFor":["sample_validated","sample_revision_required"],"transitions":{"sample_validated":"auto-execute-messaging","sample_revision_required":"lead-review","revise_leads":"find-leads","revise_rubric":"filter-rubric","escalation_triggered":"escalation"}},{"id":"auto-execute-messaging","label":"Generate initial campaign-row messages","currentStepValue":"auto-execute-messaging","references":["references/parallel-critique-protocol.md","references/thomas-variant-selection.md","references/sellable-cleanup-rules.md","references/step-15-re-cascade.md"],"onEnter":[{"tool":"queue_campaign_cells","batchSize":100,"when":"any passing row has pending, empty, or stale Generate Message cell","requiredFields":["workflowTableId","columnRole","rowSelector"],"requiredValues":{"columnRole":"generateMessage","rowSelector":{"type":"needsGeneratedMessage"},"forceRerun":false},"cellSource":"selector-engine needsGeneratedMessage rows using approved campaign brief template"},{"tool":"wait_for_campaign_processing","purpose":"wait_for_first_current_revision_generated_message","requiredValues":{"minGeneratedMessages":1,"templateRevision":"current"},"readVia":"stats_only_tool_result","invariant":"A generated message implies the pass gate; stale or wrong-revision messages are excluded from readiness."},{"action":"observe_generate_message_results","reference":"references/step-15-re-cascade.md"},{"action":"enforce_token_contract","modeFromConfig":"messaging.tokenContract"},{"action":"optional_critique_pass","enabledFromConfig":"messaging.critique.enabled","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","this approves reviewed draft rows for setup; final Start still controls sending"],"autoSelectIf":"yolo_mode and the first passing generated message clears the quality floor","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"allowedTools":["get_subskill_asset","get_campaign_table_schema","select_campaign_cells","queue_campaign_cells","wait_for_campaign_processing","revise_message_template_and_rerun","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","generate_messages"],"hardRules":["generated_message_count_excludes_stale_or_wrong_revision_messages","message_template_revision_uses_brief_update_then_generate_message_rerun","do_not_overwrite_generated_message_cells_directly","critique_failure_never_escalates","critique_sample_size_bounded_by_config","first_passing_generated_message_unblocks_review","critics_fixed_at_targeting_copy_voice","synthesis_enforces_message_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"},{"tool":"list_senders","purpose":"surface available connected senders only at Settings"},{"tool":"update_campaign","requiredValues":{"currentStep":"settings","watchNarration.stage":"review-ready"},"watchNarrationRule":"Say message review is complete, app is on Settings, lead research/filtering happened outside LinkedIn, and nothing sends until Start."},{"action":"surface_sender_and_slack_handoff","requiredVisibleContent":["connect or select a LinkedIn sender","Lead research and filtering already happened outside your LinkedIn account.","This account is only used for approved sending after final launch.","Nothing sends until the final Start step.","Slack reply review","recommended sequence","final launch confirmation is still ahead"],"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]},{"action":"ask_sender_selection","uses":"request_user_input","singleChoice":true,"choices":["Use this connected sender","Connect a different sender in Settings","Pause here"],"autoSelectIf":"yolo_mode and exactly one safe connected sender is available","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]},{"action":"attach_selected_sender","tool":"update_campaign","when":"user selected an available connected sender","requiredValues":{"senderIds":["{selectedSenderId}"],"currentStep":"sequence","watchNarration.stage":"review-ready"},"watchNarrationRule":"Say the sender was attached and the app is moving to Sequence review; sequence remains editable and nothing sends until Start."},{"tool":"attach_recommended_sequence","when":"after senderIds are attached","requiredValues":{"campaignId":"{campaignId}","currentStep":"send","watchNarration.stage":"review-ready"},"watchNarrationRule":"The sequence tool owns this visible beat: say the recommended follow-up sequence is attached, the app is on final launch review, and it is still not sending until Start."},{"action":"ask_final_launch_greenlight","uses":"request_user_input","singleChoice":true,"choices":["Start campaign","Review campaign first","Pause here"],"copyMustInclude":["quality confidence means sample messages and prospect angle were checked","launch confidence means sender, sequence, and Start are ready","approved messages begin sending according to the sequence","replies and meetings follow connected settings","you can monitor and pause","no hidden extra approval disappears after clicking Start"],"onUserStart":"claude-greenlight","neverAutoSelectInYolo":true,"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"allowedTools":["get_campaign","get_campaign_navigation_state","list_senders","update_campaign","attach_recommended_sequence","AskUserQuestion","request_user_input"],"doNotAllow":["start_campaign","import_leads"],"autoStart":false,"watchRequired":true,"waitFor":["sender_connection_required","sender_attached","sequence_attached","ready_to_launch","user_greenlight","ui_start_detected"],"transitions":{"sender_connection_required":"awaiting-user-greenlight","sender_attached":"awaiting-user-greenlight","sequence_attached":"awaiting-user-greenlight","ready_to_launch":"awaiting-user-greenlight","user_greenlight":"claude-greenlight","ui_start_detected":"running"}},{"id":"claude-greenlight","label":"Explicit launch","reference":"references/final-handoff-contract.md","onEnter":[{"tool":"get_campaign"},{"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/watch canonical; require brief/source/import, filters/message, Settings, explicit Start.","normalCustomerPath":"Use campaign state/MCP/watchNarration. Do not create, read, link, or surface local files. Print watch link once.","legacyCompatibility":{"validationSubskill":"create-campaign-v2-validation","tailSubskill":"create-campaign-v2-tail","rule":"Legacy diagnostics are opt-in, not active 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":"Watched currentStep switches need fresh watchNarration.","requiredFields":["stage","headline","visibleState","agentIntent","nextAction"],"copyMustInclude":["what just happened","visible page/state","next user action"],"appliesToTools":["create_campaign","update_campaign","attach_recommended_sequence","start_campaign"],"oneTimeWatchLinkPolicy":"must not print another watch-link handoff unless asked or recovering link."},"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":["No list_senders before Settings post-approval.","No import before Start Import.","No queueing until rows exist and gates pass.","No start_campaign without launch confirmation.","No local files as durable state."],"yoloMode":{"triggerPhrases":["yolo","--yolo","mode=yolo","autopilot","use best guesses","use best estimates","answer for me","just run it","yolo autopilot"],"identityRequestOnlyWhenMissing":true,"operatorDirectionsRule":"Operator directions persist; newest conflicting direction wins.","setInteractionModeAfterCampaignCreate":"autonomous","autoSelectsPreLaunchChoices":["campaign focus","brief approval","source plan","Start 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/data","ambiguous choice with no reasonable estimate","source/message/filter quality 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"}}],"allowedTools":["bootstrap_create_campaign","get_auth_status","get_active_workspace","get_subskill_prompt","AskUserQuestion","request_user_input"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","update_campaign","queue_cells","start_campaign"],"waitFor":"bootstrap_complete","transitions":{"bootstrap_complete":"brief-interview"}},{"id":"brief-interview","label":"Client, offer, research, and brief","onEnter":[{"action":"resolve_campaign_identity_before_strategy_packet","allowedTools":["fetch_company","fetch_linkedin_profile","WebFetch","WebSearch"],"mustNotInferFromNameOnly":true,"doNotPresentSenderPickerBeforeIdentityInference":true,"fallback":"Ask only: \"What is your LinkedIn profile URL or handle?\" Normalize handles to profile URLs; if not a profile, ask again before strategy questions.","requiresLinkedInProfileUrl":true,"doNotAcceptAsIdentityInput":["non-profile URL","company page","company/name-only input without a profile handle"],"acceptsLinkedInProfileHandle":true,"normalizeLinkedInProfileHandleToUrl":true},{"action":"run_campaign_identity_research_before_strategy_packet","target":"research-sender","requiredCompletion":"complete_sender_research","allowedTools":["get_subskill_prompt","fetch_linkedin_profile","fetch_company","fetch_linkedin_posts","fetch_company_posts","WebFetch","WebSearch","complete_sender_research"],"requiredInput":"LinkedIn profile URL or handle"},{"action":"confirm_target_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"Who should we target first?","options":["Use Sellable's researched recommendation","Choose a different buyer","I'm not sure yet"],"copyMustInclude":["research basis","recommendation is editable","who to target"],"requiredOption":"Other / custom","neverCollectOpenTextWithStructuredQuestion":true,"skipIf":"user already supplied a clear target or yolo_mode with any reasonable best-estimate target"},{"action":"confirm_current_offer_before_strategy_packet","uses":"request_user_input","question":"What should we pitch to prospects first?","options":["Use Sellable's researched recommendation","Use my current pitch","Shape the pitch together"],"copyMustInclude":["research basis","public LinkedIn research may be stale","recommendation is editable","pitch to prospects"],"skipIf":"user already supplied a clear current offer or yolo_mode with any reasonable researched recommendation"},{"action":"confirm_trust_proof_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"What is the strongest credibility signal we can lead with?","options":["Use Sellable's recommended proof","Use customer proof or a case study","Use founder or company credibility"],"copyMustInclude":["build trust with prospects","recommendation is editable","proof to use or avoid"],"requiredOption":"Other / custom","skipIf":"user already supplied clear proof to use or avoid, or yolo_mode with any reasonable researched proof"},{"action":"choose_prospect_source_path_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"How should we find prospects?","options":["Find prospects for me","Use a CSV of LinkedIn profiles","Use a CSV of company domains"],"copyMustInclude":["prospects","no one added yet","find prospects for me"],"skipIf":"user already supplied a source path or yolo_mode with any reasonable source path"},{"action":"infer_strategy_packet_in_yolo_mode","when":"yolo_mode","skipStructuredSetupQuestions":true,"inferFields":["buyer segment","offer/CTA","proof to use or avoid","lead source","filter choice","message direction"],"sourceOfTruth":"identity/company lookup plus operator directions","mustStateAssumptionsInBrief":true},{"action":"render_supplied_setup_receipt_before_brief_when_setup_questions_skip","when":"any setup question is skipped because the user already supplied or yolo mode inferred identity, target, offer, proof, or source","output":"normal chat receipt before the campaign brief or watch-link handoff","copyMustInclude":["Accepted LinkedIn input: normalized to the required LinkedIn profile URL.","Offer path: supplied current offer or Sellable's researched recommendation; you can replace it with your current offer."],"copyMustNotInclude":["Campaign Identity"]},{"action":"create_watchable_campaign_shell_with_v1_brief","tool":"create_campaign","requiredFields":["name","campaignBrief","clientProspectId or senderLinkedinUrl","currentStep","watchNarration"],"requiredValues":{"currentStep":"create-offer","watchNarration.stage":"brief"},"capture":["campaignId","watchUrl"],"canonicalStateWrites":["campaignId","watchUrl","campaignBrief","currentStep:create-offer"]},{"action":"surface_campaign_shell_watch_link","watchUrlSource":"create_campaign.watchUrl","requiredWatchUrlShape":"exact create_campaign.watchUrl; direct /campaign-builder/{campaignId}?mode={claude|codex} URL with workspaceId and token","copyMustInclude":["WATCH CODEX BUILD","Open live campaign builder","Keep this chat open.","approval questions here"],"immediateNextMainChatLine":"Ask for brief approval now. After approval, say: \"Brief approved. Next, we'll choose where to find buyers. I won't add anyone yet.\"","codexBrowserHandoff":{"openWhenAvailable":false,"printWatchLinkOnly":true,"tellUserCommandEnterOrClick":false,"mustNotUseBrowserAutomation":true,"fallbackMustNotClaimInspection":true},"copyMustAvoid":["bare /campaign-builder/{campaignId} URL","derived watch URL","second watch-link handoff","shell open command","Next, we'll choose where to find buyers"],"oneTimeOnly":true}],"allowedTools":["get_subskill_prompt","fetch_company","fetch_company_posts","fetch_linkedin_profile","fetch_linkedin_posts","complete_sender_research","create_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["campaign_shell_created","brief_ready","confirm_with_user"],"transitions":{"campaign_shell_created":"brief-review","brief_ready":"brief-review","confirm_with_user":"brief-review"}},{"id":"brief-review","label":"Brief review","onEnter":[{"action":"render_brief_inline","minimumVisibleDetail":"campaign direction, buyer, offer, proof, source plan, safety gates","copyMustInclude":["This brief is based on Sellable's research and your answers.","If your site or LinkedIn is stale","Does this brief look right, and how should Sellable continue?"],"copyMustNotInclude":["quoted first-message copy","Message Angle as final proof","This approval covers","does not approve adding people","does not approve scraping posts","selecting a sender","attaching a sequence","Campaign Identity","what should this campaign sell first","sell first","[from you]","[from LinkedIn]","[from website]","[from case study]","[Sellable recommendation]"],"messageHintRule":"If any message-shape hint remains, keep it as non-final bullets and say real examples come after leads are found and filtered.","approvalBoundaryRule":"Keep this gate positive and short. Do not list later steps this approval does not cover; ask approve/revise and then move to choosing where to find buyers.","sectionLabelRule":"Use ## Sender and Company for the person/company context; never render ## Campaign Identity.","skipIf":"the full brief was already rendered in the current shell-creation handoff before create_campaign returned","renderOnceRule":"Brief visible once before approval. If shown before shell creation, do not repeat after; append one watch-link handoff, then ask.","copyMustAvoid":["duplicate brief render after shell creation","second Watch link:"]},{"action":"ask_brief_choice","uses":"request_user_input","choices":["Approve brief + activate YOLO mode (Recommended)","Approve brief + build campaign with AI, step by step","Revise brief"],"skipIf":"yolo_mode; auto_continue after rendering assumptions unless brief quality floor fails","question":"Does this brief look right, and how should Sellable continue?","autonomousChoice":"Approve brief + activate YOLO mode (Recommended)","choiceDescriptions":{"Approve brief + activate YOLO mode (Recommended)":"Sellable will auto-decide the remaining setup choices with best guesses, including sources, filters, and messaging. It still stops before final launch.","Approve brief + build campaign with AI, step by step":"Approve this direction, then work with the AI through each major setup decision before it continues.","Revise brief":"Change the target, offer, proof, or campaign direction before continuing."}}],"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_companies","confirm_prospeo_company_accounts","search_prospeo","fetch_post_engagers"],"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"],"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 without repeating the watch link; never ask first.","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL","watch-link handoff"],"approvalFreshnessRule":"Only source approval opened after the visible Find Buyers Plan, or explicit source choice after it, sets source_lane_approved. Do not reuse brief approval."},"defaultWhenSourceUnspecified":["signal-discovery","sales-nav-recent-active","sales-nav-general","prospeo"],"parallelAllowedOnlyWhen":["user explicitly requested source comparison"]},"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."}},{"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_companies","confirm_prospeo_company_accounts","search_prospeo","fetch_post_engagers"],"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","watch-link handoff"],"approvalBoundary":"Explain what approval authorizes before asking: looking for the best places to find buyers; no one is added yet.","linkPolicy":"Do not include another watch link. The first brief handoff already gave the user the live app URL."},{"action":"ask_find_buyers_plan_choice","uses":"request_user_input","oneShot":true,"requiredPrecondition":"render_find_buyers_plan_inline completed in normal chat after pick-provider state was saved; no earlier approval can satisfy this","skipIf":"source_lane_approved after visible Find Buyers Plan, explicit post-plan leadSourceProvider, or yolo assumed choice","choices":["Start with LinkedIn posts","Start with active LinkedIn profiles","Start with company/contact search","Choose a different place","Pause here"],"approvalChoiceLabelsByProvider":{"signal-discovery":"Start with LinkedIn posts","sales-nav":"Start with active LinkedIn profiles","prospeo":"Start with company/contact search"},"approvalState":"source_lane_approved","copyMustInclude":["Approve this plan for where to find buyers?","Start with LinkedIn posts"],"mustNotHappenBefore":["render_find_buyers_plan_inline"],"approvalStateRule":"Set source_lane_approved here after the visible plan; never from brief approval, generic approval, pre-plan provider state, or artifacts."},{"action":"persist_provider_search_step_before_search","tool":"update_campaign","requiredPrecondition":"source_lane_approved set after the visible Find Buyers Plan approval question","providerCurrentStepMap":{"signal-discovery":"signal-discovery","sales-nav":"sales-nav","prospeo":"prospeo"},"requiredValues":{"leadSourceType":"new","leadSourceProvider":"approved provider","currentStep":"provider-specific current step","watchNarration.stage":"find-leads","watchNarration.headline":"Searching the approved place","watchNarration.safety":"Looking only. No one is added yet."},"mustRunBefore":["search_signals","search_sales_nav","search_prospeo_companies","confirm_prospeo_company_accounts","search_prospeo","fetch_post_engagers"]},{"action":"run_parent_thread_source_funnel","requiredPrecondition":"source_lane_approved after visible Find Buyers Plan approval","mode":"parent_thread_inline_sequential","sourceAgents":"forbidden in normal create-campaign","loadsOnlyActiveProviderPrompt":true,"defaultOrder":["signal-discovery","sales-nav-recent-active","sales-nav-general","prospeo"],"stopOnFirstViableUnlessComparisonRequested":true}],"requiredCampaignState":["campaignId","campaignBrief","currentStep"],"allowedTools":["get_provider_prompt","lookup_sales_nav_filter","search_sales_nav","search_prospeo_companies","confirm_prospeo_company_accounts","search_prospeo","search_signals","select_promising_posts","fetch_post_engagers","fetch_company","fetch_linkedin_profile","load_csv_dnc_entries","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","Task","spawn_agent"],"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":"Start Import approval","onEnter":[{"action":"show_source_decision_card","requiredInlineFields":["primary source and exact filters/recipe","Start Import 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 happens after Start Import"],"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 Start Import copy only: after approval build source list, add to campaign, review first 15 leads; never list future non-approvals.","linkPolicy":"Do not include another watch link in Source Recommendation; describe the Start Import decision only."},{"action":"ask_source_review_choice","uses":"request_user_input","choices":["Approve scraping N recommended LinkedIn posts","Start Import for the approved source list","Revise source","Pause here"],"approvalChoiceLabelsByProvider":{"signal-discovery":"Approve scraping {scrapePostCount} recommended LinkedIn posts?","sales-nav":"Start Import for the approved Sales Nav source list","prospeo":"Start Import for 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"}},{"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","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","maxCustomerCopyLines":4,"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"linkPolicy":"Ask add filters vs skip filters without repeating the watch link.","purpose":"ask filter choice immediately"}],"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 drafting 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_prospect_setup_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":"prospect-setup","filters_skipped":"message-generation","revise_leads":"find-leads"}},{"id":"prospect-setup","label":"Filter setup + message drafting","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":"Filter Rules is visible while Codex drafts and saves filters in the parent thread.","watchNarration.nextAction":"Review saved filter rules"},"mustRunBefore":["get_post_find_leads_scout_registry","launch_message_drafting_after_filter_choice","load_filter_reference_in_parent","save_rubrics","ask_filter_rubric_review_choice"]},{"tool":"get_post_find_leads_scout_registry","returns":["post-find-leads-message-scout"],"rule":"normal post-import registry exposes only Message Drafting; filters are parent-thread MCP work","mustRunBefore":["launch_message_drafting_after_filter_choice","get_subskill_asset:references/filter-leads.md","save_rubrics","ask_filter_rubric_review_choice"],"registryOnlyIsNotLaunch":true},{"action":"launch_message_drafting_after_filter_choice","mode":"background_agent_when_host_supports_subagents","target":"post-find-leads-message-scout","when":"filters_enabled or filters_skipped after filter-choice answer","doesNotQueueCells":true,"customerNarration":"Message Drafting is preparing.","inputs":["campaignId","workflowTableId","brief/source summary","3-5 sample workflow-table rows","optional campaignName/selectedLeadListId/filterChoice"],"forbiddenSiblings":["source/filter workers"],"handoffCompression":"lean_message_drafting_handoff_no_hashes_no_counts_no_full_row_data","requiredConcreteToolCall":"Task or spawn_agent when host supports background agents","registryOnlyIsNotLaunch":true,"mustRunBefore":["get_subskill_asset:references/filter-leads.md","load_filter_reference_in_parent","save_rubrics","ask_filter_rubric_review_choice"],"yoloPermissionRule":"do not ask the user for worker permission in YOLO.","startProof":"Message Drafting counts as started only after Task/spawn_agent.","noBackgroundFallback":"Start the same full message branch inline before filter reference or block.","blockedIfSkipped":"Do not proceed to get_subskill_asset(filter-leads), save_rubrics."},{"tool":"get_subskill_asset","action":"load_filter_reference_in_parent","when":"filters_enabled","requiredValues":{"subskillName":"create-campaign-v2","assetPath":"references/filter-leads.md"},"rule":"Parent thread uses this reference to draft production rubrics; do not spawn a filter worker."},{"tool":"save_rubrics","when":"filters_enabled","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":["Filter rules saved for review","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 saved filter rules","Approve these filter rules."],"autoSelectIf":"yolo_mode and parent saved production-shaped rubrics","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"requiredPrecondition":"save_rubrics succeeded or active campaign rubrics verified"}],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["get_post_find_leads_scout_registry","get_subskill_asset","save_rubrics","get_campaign","get_rows_minimal","update_campaign","get_campaign_navigation_state","AskUserQuestion","request_user_input","Task","spawn_agent"],"doNotAllow":["create_campaign","list_senders","import_leads","confirm_lead_list","queue_cells","start_campaign","check_rubric","generate_messages"],"waitFor":["filter_rubrics_approved","revise_leads","revise_rubric","revise_messaging"],"hardRules":["after_save_rubrics_currentStep_must_stay_create-icp-rubric_until_filter_approval","filter_approval_required_before_apply-icp-rubric_or_queue_campaign_cells","do_not_move_browser_to_messages_until_filter_leads_step_is_current_or_filters_are_explicitly_skipped","no_prospect_setup_worker_or_deep_prompt_before_filter_choice","message_drafting_after_filter_choice","message_drafting_no_cells","only_message_drafting_may_spawn_background_agent","filters_are_parent_thread_mcp_work","no_post_find_leads_filter_scout_in_normal_path","registry_call_is_not_message_drafting_launch","yolo_must_not_ask_permission_for_message_drafting_worker","message_drafting_must_start_before_filter_reference_or_save_rubrics","if_background_agent_unavailable_start_full_message_branch_inline_before_filters_or_block"],"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 drafting","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":["join already-running Message Drafting when prospect-setup launched it","run Message Drafting via post-find-leads-message-scout only if missing; yolo counts","handoff lean basis only: campaignId, workflowTableId, brief summary, source summary/source-use rule, 3-5 sample rows","do not paste copied row counts, brief hashes, review-batch hashes, full reviewBatchRowIds, broad row data, or local debug artifacts","branch loads current brief/context, full generate-messages prompt, and every required message asset referenced by generate-messages Mode 0","return labeled Markdown only: templateRecommendation, tokenFillRules, renderedGoodSample, status, recommendation, validationStatus, outputHash, retry detail","do not return renderedFallbackSample, risk notes, or qaReceipt on the normal happy path","if no yolo and permission missing, ask once before parent inline fallback","use generic gpt-5.5 xhigh Message Drafting agent if named unavailable"],"stateSource":"message branch-loaded live campaign/table state plus parent-provided sample rows","outputState":"messageDraftRecommendation with internal validation passed","revisionMode":{"when":"revise_messaging, message QA request, or latest user message requests template changes before approve-message","route":"send feedback/QA/rewrite request to post-find-leads-message-scout or generic gpt-5.5 xhigh Message Drafting branch","requiredInputs":["latest user feedback","current messageDraftRecommendation and basisToken/outputHash","lean campaign/table basis and brief summary; branch reloads live state"],"forbiddenParentActions":["rewrite template in parent","QA template in parent from memory","update_campaign_brief before approve-message"],"output":"revised messageDraftRecommendation rendered before request_user_input"},"onlyMessageBackgroundAgent":true,"forbiddenSiblingAgents":["source-scout-*","any filter background worker"],"handoffCompression":"lean_message_drafting_handoff_no_hashes_no_counts_no_full_row_data","finalGateRequiredBeforeReady":"Post-generation final gate: After candidate generation/revision, load get_subskill_prompt({ subskillName: \"create-campaign-v2-validation\" })."}],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["get_subskill_prompt","get_campaign","get_rows_minimal","update_campaign","Task","spawn_agent"],"toolRules":["message_drafting_agent_first_no_silent_parent_fallback; yolo counts","on_filters_enabled_path_parent_must_not_enter_message_review_until_save_rubrics_success_and_filter_approval","brief.md, lead-review.md, and lead-sample.json are optional debug context only.","messageDraftRecommendation returns templateRecommendation, tokenFillRules, renderedGoodSample, status, recommendation, validationStatus, outputHash, and retry detail.","messageDraftRecommendation does not return renderedFallbackSample, risk notes, or qaReceipt on the normal happy path.","If campaign/table/sample basis does not match, classify the output stale or blocked.","message_revision_feedback_routes_to_message_drafting_agent_not_parent","message_qa_routes_to_message_drafting_agent_not_parent","message_drafting_loads_full_generate_messages_prompt_all_chunks","message_drafting_loads_all_reference_assets_via_get_subskill_asset","message_drafting_loads_create_campaign_v2_validation_prompt_before_return","message_drafting_handoff_uses_lean_basis_no_hashes_no_counts_no_full_row_id_list","message_drafting_loads_current_campaign_brief_before_drafting","parent_may_load_generate_messages_only_for_explicitly_approved_inline_fallback","before return branch loads create-campaign-v2-validation prompt and runs internal validation"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign","generate_messages","AskUserQuestion","request_user_input"],"waitFor":["message_validation_ready","revise_rubric","revise_messaging"],"transitions":{"message_validation_ready":"message-review","revise_rubric":"filter-rubric","revise_messaging":"message-generation"},"revisionLoop":{"trigger":"user gives copy feedback, asks for QA/update, or chooses revise-messaging","required":["route latest user feedback or QA request to Message Drafting with current recommendation and live campaign/table state","produce a new messageDraftRecommendation with revisionNotes or validationStatus","render revised ## Message Template, ## Rendered Example, and What changed before asking approval"],"blocked":["asking whether an unseen new version is better","reusing the old template after acknowledging feedback","rewriting the template in the parent thread","update_campaign_brief before approve-message","request_user_input or AskUserQuestion during message-generation","QAing the template in the parent thread"],"outputState":"messageDraftRecommendation"},"hardRules":["message_revision_must_render_new_template_before_approval_question","message_revision_saves_only_after_approve_message","message_generation_must_not_call_request_user_input","message_generation_must_transition_to_message_review_only_after_renderable_draft_state","message_drafting_agent_first_no_silent_parent_fallback","message_revision_routes_to_message_drafting_agent","message_qa_routes_to_message_drafting_agent","filter_path_requires_saved_filters_before_message_review","only_message_drafting_may_spawn_background_agent"]},{"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":["Rendered fallback sample","Concerns","QA receipt","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","validation notes","QA details"],"copyMustInclude":["one recommended template","one rendered sample","token rules and fallbacks","full list remains paused"],"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"revisionRenderRule":"After revise_messaging, QA, or copy feedback, route feedback to Message Drafting; show revised template/example/What changed before approval; never ask if an unseen version is better.","parentMustNot":["rewrite template from memory","invent validation summary without Message Drafting output","render qaReceipt, risk notes, or renderedFallbackSample on the normal happy path"]},{"action":"ask_message_review_choice","uses":"request_user_input","choices":["approve-message","revise-messaging"],"copyMustInclude":["approve this message template and continue","template approval only locks the draft direction; final Start still controls sending"],"autoSelectIf":"yolo_mode and Recommendation is approve-message; revise autonomously when Recommendation is revise-messaging","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"requiredPrecondition":"current or revised template and rendered example are visible in this turn","revisionPrecondition":"after revise_messaging, revised template is visible before this question"},{"action":"sync_approved_message_set_to_campaign_brief","tool":"update_campaign_brief","when":"after approve-message","requiredFields":["campaignId","approvedMessageTemplate","Token Fill Rules","Token Fill Examples"],"writesCampaignState":"approvedMessageTemplate","revisionApprovalRule":"If the user requested revisions, write only the latest Message Drafting-approved revised template and token rules after approve-message.","forbiddenBefore":"approve-message"}],"requiredCampaignState":["campaignId","workflowTableId","messageDraftRecommendation"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign_brief","update_campaign","get_rows_minimal"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","start_campaign"],"waitFor":["message_approved","revise_messaging"],"transitions":{"message_approved":"validate-sample","revise_messaging":"message-generation"}},{"id":"validate-sample","label":"Validate campaign-table execution slice","currentStepValue":"apply-icp-rubric","reference":"references/sample-validation-loop.md","visibleStepRule":"Filter Leads is reached only after saved-filter approval; on approve-message, save the template and queue bounded Enrich Prospect cells through selector-based campaign processing.","onEnter":[{"tool":"update_campaign","requiredValues":{"currentStep":"apply-icp-rubric","watchNarration.stage":"fit-message"},"watchNarrationRule":"Say the template is saved and Filter Leads is now running the bounded enrichment and scoring pass."},{"tool":"queue_campaign_cells","requiredFields":["workflowTableId","columnRole","rowSelector"],"requiredValues":{"columnRole":"enrich","rowSelector":{"type":"reviewBatch"},"forceRerun":false},"targetCountSource":"WorkflowTable.config.mcp.reviewBatch.rowCount (default 15)"},{"tool":"wait_for_campaign_processing","requiredFields":["workflowTableId","minPassedCount"],"requiredValues":{"minPassedCount":1},"readVia":"stats_only_tool_result","timeoutGuidance":"If this times out, do not repoll identical args; use partial diagnostics to revise filters or surface blocked sample state.","customerSummaryPattern":["{checked} leads checked","{passed} passed fit scoring","{blocked} blocked before sending","full list remains paused until approval"]},{"action":"handle_partial_or_timeout_sample","customerStatus":"sample-needs-revision","rule":"Do not repeat waits indefinitely; surface partial status and route to revision.","copyMustInclude":"completed, passed, pending, blocked before sending, and full list remains paused"}],"requiredCampaignState":["campaignId","workflowTableId","approvedMessageTemplate","filterRubricsApproved when enableICPFilters=true"],"allowedTools":["get_subskill_asset","queue_campaign_cells","select_campaign_cells","get_campaign_table_schema","wait_for_campaign_processing","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","enrich_with_prospeo","bulk_enrich_with_prospeo","check_rubric"],"hardRules":["campaign_processing_waits_are_stats_only_by_default","queue_review_batch_by_selector_never_fetch_rows_for_cell_ids","timeout_never_repeats_without_customer_handoff","timeout_or_underfloor_sample_never_advances_to_settings"],"waitFor":["sample_validated","sample_revision_required"],"transitions":{"sample_validated":"auto-execute-messaging","sample_revision_required":"lead-review","revise_leads":"find-leads","revise_rubric":"filter-rubric","escalation_triggered":"escalation"}},{"id":"auto-execute-messaging","label":"Generate initial campaign-row messages","currentStepValue":"auto-execute-messaging","references":["references/parallel-critique-protocol.md","references/thomas-variant-selection.md","references/sellable-cleanup-rules.md","references/step-15-re-cascade.md"],"onEnter":[{"tool":"queue_campaign_cells","batchSize":100,"when":"any passing row has pending, empty, or stale Generate Message cell","requiredFields":["workflowTableId","columnRole","rowSelector"],"requiredValues":{"columnRole":"generateMessage","rowSelector":{"type":"needsGeneratedMessage"},"forceRerun":false},"cellSource":"selector-engine needsGeneratedMessage rows using approved campaign brief template"},{"tool":"wait_for_campaign_processing","purpose":"wait_for_first_current_revision_generated_message","requiredValues":{"minGeneratedMessages":1,"templateRevision":"current"},"readVia":"stats_only_tool_result","invariant":"A generated message implies the pass gate; stale or wrong-revision messages are excluded from readiness."},{"action":"observe_generate_message_results","reference":"references/step-15-re-cascade.md"},{"action":"enforce_token_contract","modeFromConfig":"messaging.tokenContract"},{"action":"optional_critique_pass","enabledFromConfig":"messaging.critique.enabled","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","this approves reviewed draft rows for setup; final Start still controls sending"],"autoSelectIf":"yolo_mode and the first passing generated message clears the quality floor","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"allowedTools":["get_subskill_asset","get_campaign_table_schema","select_campaign_cells","queue_campaign_cells","wait_for_campaign_processing","revise_message_template_and_rerun","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","generate_messages"],"hardRules":["generated_message_count_excludes_stale_or_wrong_revision_messages","message_template_revision_uses_brief_update_then_generate_message_rerun","do_not_overwrite_generated_message_cells_directly","critique_failure_never_escalates","critique_sample_size_bounded_by_config","first_passing_generated_message_unblocks_review","critics_fixed_at_targeting_copy_voice","synthesis_enforces_message_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":"start_prepare_campaign_messages","requiredValues":{"targetPreparedMessages":100,"maxRowsToCheck":300,"batchSize":50,"approvalMode":"mark_ready"}},{"tool":"get_prepare_campaign_messages_status","copyMustInclude":["checked rows","target","stop reason"]},{"tool":"get_campaign"},{"tool":"list_senders","purpose":"surface available connected senders only at Settings"},{"tool":"update_campaign","requiredValues":{"currentStep":"settings","watchNarration.stage":"review-ready"},"watchNarrationRule":"Say message review is complete, app is on Settings, lead research/filtering happened outside LinkedIn, and nothing sends until Start."},{"action":"surface_sender_and_slack_handoff","requiredVisibleContent":["connect or select a LinkedIn sender","Lead research and filtering already happened outside your LinkedIn account.","This account is only used for approved sending after final launch.","Nothing sends until the final Start step.","Slack reply review","recommended sequence","final launch confirmation is still ahead"],"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]},{"action":"ask_sender_selection","uses":"request_user_input","singleChoice":true,"choices":["Use this connected sender","Connect a different sender in Settings","Pause here"],"autoSelectIf":"yolo_mode and exactly one safe connected sender is available","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]},{"action":"attach_selected_sender","tool":"update_campaign","when":"user selected an available connected sender","requiredValues":{"senderIds":["{selectedSenderId}"],"currentStep":"sequence","watchNarration.stage":"review-ready"},"watchNarrationRule":"Say the sender was attached and the app is moving to Sequence review; sequence remains editable and nothing sends until Start."},{"tool":"attach_recommended_sequence","when":"after senderIds are attached","requiredValues":{"campaignId":"{campaignId}","currentStep":"send","watchNarration.stage":"review-ready"},"watchNarrationRule":"The sequence tool owns this visible beat: say the recommended follow-up sequence is attached, the app is on final launch review, and it is still not sending until Start."},{"action":"ask_final_launch_greenlight","uses":"request_user_input","singleChoice":true,"choices":["Start campaign","Review campaign first","Pause here"],"copyMustInclude":["quality confidence means sample messages and prospect angle were checked","launch confidence means sender, sequence, and Start are ready","approved messages begin sending according to the sequence","replies and meetings follow connected settings","you can monitor and pause","no hidden extra approval disappears after clicking Start"],"onUserStart":"claude-greenlight","neverAutoSelectInYolo":true,"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"allowedTools":["cancel_prepare_campaign_messages","get_prepare_campaign_messages_status","start_prepare_campaign_messages","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"},{"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}]}