@sellable/mcp 0.1.80 → 0.1.82

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.
Files changed (36) hide show
  1. package/agents/post-find-leads-filter-scout.md +19 -8
  2. package/agents/post-find-leads-message-scout.md +21 -6
  3. package/agents/registry.json +33 -18
  4. package/agents/source-scout-linkedin-engagement.md +10 -2
  5. package/agents/source-scout-prospeo-contact.md +9 -2
  6. package/agents/source-scout-sales-nav.md +10 -2
  7. package/dist/server.js +7 -1
  8. package/dist/tools/bootstrap.js +2 -2
  9. package/dist/tools/campaigns.d.ts +12 -0
  10. package/dist/tools/campaigns.js +40 -5
  11. package/dist/tools/context.d.ts +10 -4
  12. package/dist/tools/context.js +8 -4
  13. package/dist/tools/leads.d.ts +6 -3
  14. package/dist/tools/leads.js +39 -43
  15. package/dist/tools/navigation.d.ts +28 -1
  16. package/dist/tools/navigation.js +220 -10
  17. package/dist/tools/prompts.js +1 -1
  18. package/dist/tools/provider-preflight.js +2 -3
  19. package/dist/tools/readiness.d.ts +31 -6
  20. package/dist/tools/readiness.js +6 -1
  21. package/dist/tools/rubrics.d.ts +2 -0
  22. package/dist/tools/rubrics.js +2 -0
  23. package/package.json +1 -1
  24. package/skills/create-campaign/SKILL.md +24 -12
  25. package/skills/create-campaign-v2/SKILL.md +166 -114
  26. package/skills/create-campaign-v2/SOUL.md +19 -11
  27. package/skills/create-campaign-v2/core/auto-execute.yaml +14 -13
  28. package/skills/create-campaign-v2/core/flow.v2.json +964 -310
  29. package/skills/create-campaign-v2/references/approval-gate-framing.md +64 -32
  30. package/skills/create-campaign-v2/references/filter-leads.md +8 -0
  31. package/skills/create-campaign-v2/references/final-handoff-contract.md +55 -20
  32. package/skills/create-campaign-v2/references/lead-validation-preview.md +30 -17
  33. package/skills/create-campaign-v2/references/step-13-import-leads.md +62 -17
  34. package/skills/create-campaign-v2/references/validation-criteria.md +10 -5
  35. package/skills/create-campaign-v2/references/watch-link-handoff.md +72 -72
  36. package/skills/create-campaign-v2-tail/SKILL.md +123 -92
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "version": "v2",
3
3
  "workflow": "create-campaign-v2",
4
- "principle": "Start from a user interview, validate artifacts with interrupt-only gates, mint atomically after explicit commit, then run the autonomous tail.",
4
+ "principle": "CampaignOffer state is canonical from the first brief onward. The flow still writes the 14 disk artifacts as a debug trail, but resume, gating, and handoff read campaign state first. Start from user intake, create a watchable campaign shell with a v1 brief, attach source/search state to that CampaignOffer, import the bounded review batch before post-import fit/message scouts, save rubrics and an approved message template, then queue the 10-row cascade and hand off to settings/sequence/start.",
5
5
  "commitGateChoices": [
6
6
  "approve",
7
7
  "revise-brief",
@@ -12,6 +12,7 @@
12
12
  ],
13
13
  "events": [
14
14
  "bootstrap_complete",
15
+ "draft_shell_ready",
15
16
  "brief_ready",
16
17
  "source_intake_ready",
17
18
  "user_brief_confirmed",
@@ -22,20 +23,17 @@
22
23
  "message_candidate_drafts_ready",
23
24
  "message_validation_ready",
24
25
  "message_approved",
25
- "approval_packet_ready",
26
26
  "confirm_with_user",
27
27
  "revise_brief",
28
28
  "revise_leads",
29
29
  "revise_rubric",
30
30
  "revise_messaging",
31
31
  "artifacts_validated",
32
- "user_approved",
33
32
  "user_revise_brief",
34
33
  "user_revise_leads",
35
34
  "user_revise_rubric",
36
35
  "user_revise_messaging",
37
36
  "user_abort",
38
- "campaign_minted",
39
37
  "rubrics_saved",
40
38
  "watch_link_surfaced",
41
39
  "autonomous_tail_started",
@@ -46,7 +44,15 @@
46
44
  "awaiting_user_greenlight",
47
45
  "user_greenlight",
48
46
  "campaign_started",
49
- "escalation_triggered"
47
+ "escalation_triggered",
48
+ "campaign_shell_created",
49
+ "campaign_shell_brief_synced",
50
+ "source_synced_to_campaign",
51
+ "rubric_saved_to_campaign",
52
+ "message_set_synced_to_brief",
53
+ "review_batch_imported",
54
+ "settings_link_surfaced",
55
+ "sequence_attached"
50
56
  ],
51
57
  "steps": [
52
58
  {
@@ -91,7 +97,9 @@
91
97
  "confirm_lead_list",
92
98
  "update_campaign",
93
99
  "queue_cells",
94
- "start_campaign"
100
+ "start_campaign",
101
+ "enrich_with_prospeo",
102
+ "bulk_enrich_with_prospeo"
95
103
  ],
96
104
  "watchRequired": false,
97
105
  "waitFor": "bootstrap_complete",
@@ -130,7 +138,10 @@
130
138
  {
131
139
  "action": "confirm_ambiguous_campaign_focus_with_structured_gate",
132
140
  "when": "after_identity_before_founder_strategy_source_packet",
133
- "allowedTools": ["AskUserQuestion", "request_user_input"],
141
+ "allowedTools": [
142
+ "AskUserQuestion",
143
+ "request_user_input"
144
+ ],
134
145
  "questionType": "single-choice only; plain chat only for free-text custom detail",
135
146
  "multiSelect": false,
136
147
  "maxOptions": 4,
@@ -156,13 +167,37 @@
156
167
  "fallback": "If WebSearch is unavailable, use available MCP profile/company/post tools, call complete_sender_research with observed counts, and carry explicit proof gaps into the setup packet.",
157
168
  "chatRenderRule": "Use researched company positioning and proof options in the buyer/offer/proof/source setup packet; do not ask generic strategy questions from company name alone."
158
169
  },
170
+ {
171
+ "action": "ask_source_intake_after_strategy_batch",
172
+ "artifact": "lead-source-intake.json",
173
+ "optional": true,
174
+ "question": "How should we get the people for this campaign?",
175
+ "options": [
176
+ "Find people for me (recommended if you don't already have your own list)",
177
+ "I have a CSV of profiles to reach out to",
178
+ "I have a CSV of companies/domains I want to target"
179
+ ],
180
+ "forbiddenOptionLabels": [
181
+ "engagers",
182
+ "titles",
183
+ "Signals",
184
+ "Sales Nav",
185
+ "Prospeo",
186
+ "search LinkedIn posts"
187
+ ],
188
+ "forbiddenOptionLabelReason": "Provider/source mechanics are internal to the 'Find people for me' path; first-time users should choose the job they need done, not the scouting provider.",
189
+ "customInput": true,
190
+ "multiSelect": false,
191
+ "lastQuestionInFirstStrategyBatch": true
192
+ },
159
193
  {
160
194
  "action": "render_post_intake_progress_preamble",
161
195
  "after": "founder_strategy_source_packet_answered",
162
196
  "requiredVisibleContent": [
163
197
  "turn this into a campaign brief",
164
- "approve it or tell me what to change",
165
- "before I source leads"
198
+ "create the campaign shell with the brief already in it",
199
+ "watch the draft campaign as it fills in",
200
+ "no leads import and nothing sends yet"
166
201
  ],
167
202
  "timeEstimates": {
168
203
  "campaignBrief": "~1-2 min"
@@ -189,9 +224,13 @@
189
224
  "Proof",
190
225
  "Lead plan",
191
226
  "Risks",
192
- "After approval"
227
+ "What happens next"
228
+ ],
229
+ "mustRunBefore": [
230
+ "start_background_artifact_writer",
231
+ "Bash",
232
+ "mkdir"
193
233
  ],
194
- "mustRunBefore": ["start_background_artifact_writer", "Bash", "mkdir"],
195
234
  "chatRenderRule": "After synthesizing the brief, render the approval-ready brief in chat before any visible local folder creation, file write, or artifact copy. The user should see useful brief content before local persistence chrome. Do not ask the user to approve a hidden artifact."
196
235
  },
197
236
  {
@@ -204,31 +243,74 @@
204
243
  "chatRenderRule": "Do not surface folder/file-write progress in chat unless the write fails or the user asks. The main thread should keep the brief review moving while the sidecar persists brief.md."
205
244
  },
206
245
  {
207
- "action": "ask_source_intake_after_strategy_batch",
208
- "artifact": "lead-source-intake.json",
209
- "optional": true,
210
- "question": "How should we get the people for this campaign?",
211
- "options": [
212
- "Find people for me (recommended if you don't already have your own list)",
213
- "I have a CSV of profiles to reach out to",
214
- "I have a CSV of companies/domains I want to target"
246
+ "action": "create_watchable_campaign_shell_with_v1_brief",
247
+ "tool": "create_campaign",
248
+ "artifact": "campaign-shell.json",
249
+ "when": "after_v1_brief_is_visible_and_before_brief_review",
250
+ "mode": "shell-first-campaign",
251
+ "requiredFields": [
252
+ "name",
253
+ "campaignBrief",
254
+ "clientProspectId or senderLinkedinUrl",
255
+ "currentStep"
215
256
  ],
216
- "forbiddenOptionLabels": [
217
- "engagers",
218
- "titles",
219
- "Signals",
220
- "Sales Nav",
221
- "Prospeo",
222
- "search LinkedIn posts"
257
+ "requiredValues": {
258
+ "currentStep": "create-offer"
259
+ },
260
+ "briefMode": "v1 campaign brief shown to the user; not an empty placeholder",
261
+ "capture": [
262
+ "campaignId",
263
+ "watchUrl"
223
264
  ],
224
- "forbiddenOptionLabelReason": "Provider/source mechanics are internal to the 'Find people for me' path; first-time users should choose the job they need done, not the scouting provider.",
225
- "customInput": true,
226
- "multiSelect": false,
227
- "lastQuestionInFirstStrategyBatch": true
265
+ "surfaceWatchLink": true,
266
+ "watchLinkRule": "Show the link only after the brief is on the campaign, so the user lands on a useful brief instead of an empty campaign.",
267
+ "onFailure": "stop_and_surface_campaign_shell_creation_error; no no-shell mint fallback in shell-first flow",
268
+ "forbiddenUntilRubricAndMessageReady": [
269
+ "import_leads",
270
+ "confirm_lead_list",
271
+ "queue_cells",
272
+ "enrich_with_prospeo",
273
+ "bulk_enrich_with_prospeo"
274
+ ],
275
+ "forbiddenUntilSenderAndSequenceReady": [
276
+ "start_campaign"
277
+ ],
278
+ "canonicalStateWrites": [
279
+ "campaignId",
280
+ "watchUrl",
281
+ "campaignBrief",
282
+ "currentStep:create-offer"
283
+ ]
284
+ },
285
+ {
286
+ "action": "surface_campaign_shell_watch_link",
287
+ "reference": "references/watch-link-handoff.md",
288
+ "requires": [
289
+ "campaignId",
290
+ "watchUrl",
291
+ "campaignBrief"
292
+ ],
293
+ "requiredVisibleContent": [
294
+ "draft campaign shell",
295
+ "brief is already there",
296
+ "watch it fill in",
297
+ "no leads import and nothing sends yet",
298
+ "Watch link:"
299
+ ],
300
+ "requiresCampaignState": [
301
+ "campaignId",
302
+ "watchUrl",
303
+ "campaignBrief"
304
+ ],
305
+ "watchUrlSource": "create_campaign.watchUrl",
306
+ "immediateNextMainChatLine": "Cool, let's find leads."
228
307
  }
229
308
  ],
230
309
  "requiredArtifacts": [],
231
- "producesArtifacts": ["brief.md"],
310
+ "producesArtifacts": [
311
+ "brief.md",
312
+ "campaign-shell.json"
313
+ ],
232
314
  "allowedTools": [
233
315
  "get_subskill_prompt",
234
316
  "get_subskill_asset",
@@ -241,27 +323,40 @@
241
323
  "fetch_company_posts",
242
324
  "fetch_linkedin_profile",
243
325
  "fetch_linkedin_posts",
326
+ "create_campaign",
327
+ "update_campaign",
244
328
  "WebFetch",
245
329
  "WebSearch",
246
330
  "AskUserQuestion",
247
331
  "request_user_input"
248
332
  ],
249
333
  "doNotAllow": [
250
- "create_campaign",
251
334
  "save_rubrics",
252
335
  "import_leads",
253
336
  "confirm_lead_list",
254
- "update_campaign",
255
337
  "queue_cells",
256
- "start_campaign"
338
+ "start_campaign",
339
+ "enrich_with_prospeo",
340
+ "bulk_enrich_with_prospeo"
257
341
  ],
258
342
  "watchRequired": false,
259
- "waitFor": ["brief_ready", "confirm_with_user"],
343
+ "waitFor": [
344
+ "brief_ready",
345
+ "campaign_shell_created",
346
+ "confirm_with_user"
347
+ ],
260
348
  "transitions": {
261
349
  "brief_ready": "brief-review",
262
- "confirm_with_user": "brief-review"
350
+ "confirm_with_user": "brief-review",
351
+ "campaign_shell_created": "brief-review"
263
352
  },
264
- "optionalProducesArtifacts": ["lead-source-intake.json"]
353
+ "optionalProducesArtifacts": [
354
+ "lead-source-intake.json"
355
+ ],
356
+ "debugProducesArtifacts": [
357
+ "brief.md",
358
+ "campaign-shell.json"
359
+ ]
265
360
  },
266
361
  {
267
362
  "id": "brief-review",
@@ -282,13 +377,15 @@
282
377
  "Lead source hypothesis",
283
378
  "Message angle",
284
379
  "Risks / assumptions",
285
- "What happens after approval",
380
+ "What happens next",
286
381
  "approve this brief",
287
382
  "revise the brief",
288
383
  "then I will find good-fit leads"
289
384
  ],
290
385
  "minimumVisibleBriefDetail": "full_readable_brief_before_question",
291
- "requiredArtifactLinks": ["brief.md"],
386
+ "requiredArtifactLinks": [
387
+ "brief.md"
388
+ ],
292
389
  "artifactLinkTiming": "when_ready_before_downstream",
293
390
  "avoidQuestionWhenOnlyUsefulAnswerIs": "looks good"
294
391
  },
@@ -296,43 +393,80 @@
296
393
  "action": "ask_continue_or_revise_only_if_needed",
297
394
  "autoContinueWhen": {
298
395
  "artifactStatus": "confirmed",
299
- "confidenceIn": ["high", "medium"],
396
+ "confidenceIn": [
397
+ "high",
398
+ "medium"
399
+ ],
300
400
  "noOpenStrategicTradeoff": true,
301
401
  "noMissingRequiredInputs": true
302
402
  }
303
403
  },
304
404
  {
305
405
  "action": "ensure_background_artifact_ready",
306
- "artifacts": ["brief.md"],
406
+ "artifacts": [
407
+ "brief.md"
408
+ ],
307
409
  "requiredBeforeTransition": "find-leads",
308
410
  "reconcileRule": "Before lead sourcing reads brief.md, verify the sidecar persisted the same brief shown in chat. If the sidecar is still running, wait quietly or finish the write synchronously; if it differs, reconcile to the visible approved brief.",
309
411
  "chatRenderRule": "Do not block the brief approval question on this step. Only mention artifact persistence if it fails or needs user action."
412
+ },
413
+ {
414
+ "action": "sync_approved_brief_to_campaign_shell",
415
+ "tool": "update_campaign",
416
+ "requires": [
417
+ "campaignId",
418
+ "campaignBrief"
419
+ ],
420
+ "when": "after_user_brief_confirmed_or_auto_continue",
421
+ "fields": [
422
+ "campaignBrief",
423
+ "currentStep:create-offer"
424
+ ],
425
+ "fallback": "If campaignId is missing from CampaignOffer state, stop; shell-first flow requires an existing campaign before sourcing leads.",
426
+ "readsCampaignStateFirst": true,
427
+ "debugArtifacts": [
428
+ "campaign-shell.json",
429
+ "brief.md"
430
+ ]
310
431
  }
311
432
  ],
312
- "requiredArtifacts": [],
433
+ "requiredArtifacts": [
434
+ "brief.md"
435
+ ],
313
436
  "producesArtifacts": [],
314
437
  "allowedTools": [
315
438
  "AskUserQuestion",
316
439
  "request_user_input",
317
- "get_subskill_prompt"
440
+ "get_subskill_prompt",
441
+ "update_campaign"
318
442
  ],
319
443
  "doNotAllow": [
320
444
  "create_campaign",
321
445
  "save_rubrics",
322
446
  "import_leads",
323
447
  "confirm_lead_list",
324
- "update_campaign",
325
448
  "queue_cells",
326
- "start_campaign"
449
+ "start_campaign",
450
+ "enrich_with_prospeo",
451
+ "bulk_enrich_with_prospeo"
327
452
  ],
328
453
  "watchRequired": false,
329
- "waitFor": ["user_brief_confirmed", "revise_brief", "auto_continue"],
454
+ "waitFor": [
455
+ "user_brief_confirmed",
456
+ "revise_brief",
457
+ "auto_continue"
458
+ ],
330
459
  "transitions": {
331
460
  "user_brief_confirmed": "find-leads",
332
461
  "revise_brief": "brief-interview",
333
462
  "auto_continue": "find-leads"
334
463
  },
335
- "interruptOnly": true
464
+ "interruptOnly": true,
465
+ "requiredCampaignState": [
466
+ "campaignId",
467
+ "campaignBrief",
468
+ "currentStep"
469
+ ]
336
470
  },
337
471
  {
338
472
  "id": "find-leads",
@@ -342,6 +476,8 @@
342
476
  "action": "render_find_leads_progress",
343
477
  "requiredVisibleContent": [
344
478
  "good fit and likely to reply on LinkedIn",
479
+ "campaign source step",
480
+ "primary source",
345
481
  "expected volume",
346
482
  "likely connection acceptance rate",
347
483
  "likely reply rate",
@@ -352,7 +488,7 @@
352
488
  "sample size",
353
489
  "estimated usable leads",
354
490
  "source decision + sample",
355
- "before anything goes live"
491
+ "no import or enrichment yet"
356
492
  ],
357
493
  "timeEstimate": "~3-5 min"
358
494
  },
@@ -370,16 +506,24 @@
370
506
  {
371
507
  "action": "run_subskill",
372
508
  "target": "find-leads",
373
- "mode": "campaignless-preview",
374
- "sourceScoutRule": "When source is not user-supplied and at least two viable source angles exist, call get_source_scout_registry and use the returned canonical agent names, then scout independent source angles with real parallelism when host/tooling permits. In Codex, explicitly spawn one named custom scout per credible lane in the same turn: source-scout-linkedin-engagement for LinkedIn Engagement / active post engagers (internal signal-discovery provider prompt), source-scout-sales-nav for Sales Nav title/company/activity filters, and source-scout-prospeo-contact for Prospeo account/domain/contact expansion. In Claude Code, explicitly invoke the generated source-scout-linkedin-engagement, source-scout-sales-nav, and/or source-scout-prospeo-contact Task agents / Agent subagents in one assistant message so the lanes can run concurrently/background; those agents are installed from the canonical Sellable agent registry and carry explicit Sellable MCP tool allowlists. Fallback to independent MCP/tool calls in the same model turn or dedicated Sellable MCP tools that perform server-side fan-out. Run LinkedIn Engagement + Sales Nav, LinkedIn Engagement + Prospeo Contact, or all three when credible. If real parallel execution is unavailable, run the same scouts sequentially and do not claim parallel execution. Compare outputs by raw volume, n/N sampled fit, estimated good-fit range, expected reply range, and tradeoff. Keep Signals/LinkedIn Engagement as viable when selected posts can produce ~150+ ICP-fit warm prospects before final filtering, even if Sales Nav is more scalable; when both are viable, present the choice and recommend the stronger default."
509
+ "mode": "campaign-attached-required",
510
+ "sourceScoutRule": "Shell-first flow requires the CampaignOffer campaignId from durable state. Pass campaignId as campaignOfferId into every provider prompt/search that can persist source state (`get_provider_prompt({ provider, campaignOfferId, confirmed: true })`, `search_signals`, `search_sales_nav`, `search_prospeo`) so the user can watch the selected source inside the campaign. The later import_leads call must use the same campaignOfferId. When several source lanes are credible, scout them independently, then write one primary source recommendation and any runner-up tradeoffs. Do not import, confirm, enrich, queue, or start leads during source discovery."
375
511
  },
376
512
  {
377
513
  "action": "write_artifacts",
378
- "artifacts": ["lead-review.md", "lead-sample.json"]
514
+ "artifacts": [
515
+ "lead-review.md",
516
+ "lead-sample.json"
517
+ ]
379
518
  }
380
519
  ],
381
- "requiredArtifacts": ["brief.md"],
382
- "producesArtifacts": ["lead-review.md", "lead-sample.json"],
520
+ "requiredArtifacts": [
521
+ "brief.md"
522
+ ],
523
+ "producesArtifacts": [
524
+ "lead-review.md",
525
+ "lead-sample.json"
526
+ ],
383
527
  "allowedTools": [
384
528
  "get_subskill_prompt",
385
529
  "get_subskill_asset",
@@ -395,39 +539,64 @@
395
539
  "request_user_input",
396
540
  "load_csv_linkedin_leads",
397
541
  "load_csv_domains",
398
- "get_rows_minimal"
542
+ "get_rows_minimal",
543
+ "update_campaign"
399
544
  ],
400
545
  "doNotAllow": [
401
546
  "create_campaign",
402
547
  "save_rubrics",
403
548
  "import_leads",
404
549
  "confirm_lead_list",
405
- "update_campaign",
406
550
  "queue_cells",
407
- "start_campaign"
551
+ "start_campaign",
552
+ "enrich_with_prospeo",
553
+ "bulk_enrich_with_prospeo"
408
554
  ],
409
555
  "watchRequired": false,
410
- "waitFor": ["lead_review_ready", "revise_brief", "confirm_with_user"],
556
+ "waitFor": [
557
+ "lead_review_ready",
558
+ "revise_brief",
559
+ "confirm_with_user"
560
+ ],
411
561
  "transitions": {
412
562
  "lead_review_ready": "lead-review",
413
563
  "revise_brief": "brief-interview",
414
564
  "confirm_with_user": "lead-review"
415
565
  },
416
- "optionalRequiredArtifacts": ["lead-source-intake.json"],
566
+ "optionalRequiredArtifacts": [
567
+ "lead-source-intake.json"
568
+ ],
417
569
  "toolRules": {
418
- "suppliedLinkedinProfiles": "Preview only before approval: call load_csv_linkedin_leads without confirmed, campaignOfferId, currentStep, leadListId, sourceLeadListId, or provider-import parameters; skip provider discovery. After approval, batch/materialize the supplied CSV into a Sellable lead list first, then use the returned leadListId as sourceLeadListId for confirm_lead_list.",
419
- "suppliedDomains": "May confirm load_csv_domains before approval only without campaignOfferId/currentStep to produce a standalone domainFilterId, then run a campaignless Prospeo sample constrained by domainFilterId.",
420
- "existingLeadList": "Skip provider discovery and sample existing rows before approval; do not clone/import until after approval.",
421
- "forbiddenPreApproval": [
422
- "create_campaign",
570
+ "suppliedLinkedinProfiles": "Preview only before the bounded import step. campaignOfferId may attach the preview/list choice to campaign state, but do not import into the campaign table until auto-execute-leads confirms the source list.",
571
+ "suppliedDomains": "May confirm load_csv_domains to produce a domainFilterId. Campaign-associated Prospeo preview searches must use campaignOfferId so the user can watch source state in the campaign, but no import/enrichment happens until rubrics and message set are ready.",
572
+ "existingLeadList": "Sample existing rows before import; preserve the existing list as selectedLeadListId source state and do not clone/import until auto-execute-leads.",
573
+ "forbiddenPreImport": [
423
574
  "import_leads",
424
575
  "confirm_lead_list",
425
- "update_campaign",
426
576
  "queue_cells",
577
+ "enrich_with_prospeo",
578
+ "bulk_enrich_with_prospeo",
427
579
  "start_campaign",
428
580
  "save_domain_filters"
581
+ ],
582
+ "providerCampaignOfferIdRequiredFor": [
583
+ "get_provider_prompt",
584
+ "search_signals",
585
+ "search_sales_nav",
586
+ "search_prospeo",
587
+ "import_leads",
588
+ "selected source state"
429
589
  ]
430
- }
590
+ },
591
+ "requiredCampaignState": [
592
+ "campaignId",
593
+ "campaignBrief",
594
+ "currentStep"
595
+ ],
596
+ "debugProducesArtifacts": [
597
+ "lead-review.md",
598
+ "lead-sample.json"
599
+ ]
431
600
  },
432
601
  {
433
602
  "id": "lead-review",
@@ -493,7 +662,9 @@
493
662
  "use or discard decision"
494
663
  ],
495
664
  "forbidPercentOnlyFitRates": true,
496
- "requiredArtifactLinks": ["lead-review.md"],
665
+ "requiredArtifactLinks": [
666
+ "lead-review.md"
667
+ ],
497
668
  "artifactLinkTiming": "before_next_step_or_revision_question",
498
669
  "doNotCompressToSummaryOnly": false,
499
670
  "doNotRenderArtifactLinksOnly": true,
@@ -503,42 +674,72 @@
503
674
  "action": "render_post_lead_parallel_progress",
504
675
  "requiredVisibleContent": [
505
676
  "Lead source is set",
677
+ "campaign is on the primary source step",
506
678
  "Tighten the fit filter",
507
679
  "Message generation",
508
680
  "approved brief and real sample leads",
509
- "parallel only if real parallel branches were launched"
681
+ "no import or enrichment until rubrics and message set are ready",
682
+ "parallel only if real parallel branches were launched",
683
+ "import the first 10-row review batch before fit/message work",
684
+ "selectedLeadListId stays the source list and workflowTableId is the campaign table"
510
685
  ],
511
686
  "timeEstimate": "~2-3 min",
512
- "chatRenderRule": "If real parallel MCP/tool branches or host subagents were actually launched, say: 'I’m kicking off two workstreams now' and list 'Tighten the fit filter' and 'Message generation from the find-leads sample'. If not, do not mention parallel/background work; say: 'I’ll tighten the filter first, then run message generation from the same find-leads sample.' Never claim parallelism unless parallel execution actually started. User-facing stage name is message generation; message-validation.md is only the internal artifact. Message generation must use lead-sample.json from find-leads; filter leads only gates which of those sampled rows remain valid."
687
+ "chatRenderRule": "Lead source is set. Next import and confirm only the first 10-row review batch into the campaign table. After workflowTableId exists, run the fit-filter and message-generation workstreams from the campaign table sample. If real parallel MCP/tool branches or host subagents were actually launched, say so; otherwise say the branches will run sequentially. Never claim parallelism unless parallel execution actually started."
513
688
  },
514
689
  {
515
690
  "action": "ask_continue_revise_or_confirm_only_if_needed",
516
691
  "approvalQuestionRule": "If asking the user, the question and options must name the concrete decision. Prefer: 'Approve Sales Nav with {filters}, or use warmer Signals instead?' over 'Approve this lead source?' Option labels must name the source choice, such as 'Approve Sales Nav filters', 'Use warmer Signals posts', 'Try Prospeo/account search', and 'Revise source'.",
517
692
  "autoContinueWhen": {
518
693
  "status": "confirmed",
519
- "confidenceIn": ["high", "medium"],
694
+ "confidenceIn": [
695
+ "high",
696
+ "medium"
697
+ ],
520
698
  "projectedGoodFitsAtLeast": 150,
521
699
  "samplePassRateAtLeast": 0.7,
522
700
  "noProviderMismatch": true,
523
701
  "noStrategicTradeoff": true
524
702
  }
703
+ },
704
+ {
705
+ "action": "sync_selected_source_to_campaign_shell",
706
+ "tool": "update_campaign",
707
+ "requires": [
708
+ "campaignId",
709
+ "lead-review.md"
710
+ ],
711
+ "when": "after_source_confirmed_or_auto_continue",
712
+ "fields": [
713
+ "leadSourceType",
714
+ "leadSourceProvider",
715
+ "providerSearchAssociation",
716
+ "currentStep: primary provider step (signal-discovery, sales-nav, prospeo/contact-search, saved-lists, or leads)",
717
+ "selectedLeadListId as source list id only for existing-list or supplied-list preview"
718
+ ],
719
+ "fallback": "Stop if campaignId is missing; the source must be attached to the existing CampaignOffer before import.",
720
+ "readsCampaignStateFirst": true
525
721
  }
526
722
  ],
527
- "requiredArtifacts": ["lead-review.md", "lead-sample.json"],
723
+ "requiredArtifacts": [
724
+ "lead-review.md",
725
+ "lead-sample.json"
726
+ ],
528
727
  "producesArtifacts": [],
529
728
  "allowedTools": [
530
729
  "AskUserQuestion",
531
730
  "request_user_input",
532
- "get_subskill_prompt"
731
+ "get_subskill_prompt",
732
+ "update_campaign"
533
733
  ],
534
734
  "doNotAllow": [
535
735
  "create_campaign",
536
736
  "save_rubrics",
537
737
  "import_leads",
538
738
  "confirm_lead_list",
539
- "update_campaign",
540
739
  "queue_cells",
541
- "start_campaign"
740
+ "start_campaign",
741
+ "enrich_with_prospeo",
742
+ "bulk_enrich_with_prospeo"
542
743
  ],
543
744
  "watchRequired": false,
544
745
  "waitFor": [
@@ -548,16 +749,161 @@
548
749
  "auto_continue"
549
750
  ],
550
751
  "transitions": {
551
- "lead_review_confirmed": "post-lead-workstreams",
752
+ "lead_review_confirmed": "auto-execute-leads",
552
753
  "revise_leads": "find-leads",
553
- "confirm_with_user": "post-lead-workstreams",
554
- "auto_continue": "post-lead-workstreams"
754
+ "confirm_with_user": "auto-execute-leads",
755
+ "auto_continue": "auto-execute-leads"
756
+ },
757
+ "interruptOnly": true,
758
+ "requiredCampaignState": [
759
+ "campaignId",
760
+ "campaignBrief",
761
+ "providerSearchAssociation"
762
+ ]
763
+ },
764
+ {
765
+ "id": "auto-execute-leads",
766
+ "label": "Step 13: Confirm source and import 10 lead review batch",
767
+ "currentStepValue": "confirm-lead-list",
768
+ "reference": "references/step-13-import-leads.md",
769
+ "onEnter": [
770
+ {
771
+ "action": "resolve_campaign_attached_source",
772
+ "manifest": "lead-source-intake.json",
773
+ "defaultWhenMissing": "escalate_to_re_run_find_leads_with_campaignOfferId",
774
+ "branches": {
775
+ "normal-discovery": "Reuse the campaign-attached source selections/searches created during find-leads. Do not replay searches in Step 13; if no selected source/search exists, route back to find-leads with campaignOfferId.",
776
+ "supplied-linkedin-profiles": "Use the campaign-attached preview/list choice. Materialize the CSV into a source lead list when needed, then confirm_lead_list({ sourceLeadListId: leadListId, targetLeadCount: import.importLimit }).",
777
+ "existing-lead-list": "Use the campaign-attached existingLeadListId as selectedLeadListId and call confirm_lead_list({ sourceLeadListId: existingLeadListId, targetLeadCount: import.importLimit }).",
778
+ "supplied-domains": "Reuse the campaign-attached domainFilterId and Prospeo search created during find-leads, then import_leads({ campaignOfferId, targetLeadCount: import.importLimit })."
779
+ },
780
+ "persistMaterializedIds": [
781
+ "leadListId",
782
+ "domainFilterId",
783
+ "searchId",
784
+ "selectedLeadListId",
785
+ "workflowTableId",
786
+ "importedRowIds"
787
+ ],
788
+ "onSourceFileChanged": "escalate_before_materialization",
789
+ "onLowDomainYield": "confirm-with-user: widen role filters, add more domains, or abort; never drop domainFilterId silently",
790
+ "preconditions": [
791
+ "campaignId exists",
792
+ "providerSearchAssociation or selected source state exists"
793
+ ],
794
+ "onMissingCampaignAttachedSource": "stop_before_import_and_route_to_find_leads; source scouts must attach searches/selections with campaignOfferId before Step 13"
795
+ },
796
+ {
797
+ "action": "watch_mode_orient"
798
+ },
799
+ {
800
+ "tool": "import_leads",
801
+ "requiredFields": [
802
+ "campaignOfferId",
803
+ "targetLeadCount"
804
+ ],
805
+ "targetLeadCountFromConfig": "import.importLimit (10)",
806
+ "onZeroLeads": "escalate_hard_fail",
807
+ "modeAddHandshake": {
808
+ "firstCallReturns": "needsModeSelection=true + existingLeadListId",
809
+ "secondCallPasses": "sourceLeadListId=<existingLeadListId>",
810
+ "note": "import_leads with mode=add is a two-step handshake; see references/step-13-import-leads.md"
811
+ },
812
+ "surfaceDedupRatio": true,
813
+ "defaultBranch": "normal-discovery",
814
+ "skipWhen": "resolved source branch returns sourceLeadListId for confirm_lead_list",
815
+ "requiredBeforeCall": [
816
+ "campaign_attached_source_state"
817
+ ]
818
+ },
819
+ {
820
+ "tool": "wait_for_lead_list_ready",
821
+ "sourceBranches": [
822
+ "normal-discovery",
823
+ "supplied-domains"
824
+ ],
825
+ "skipWhen": "resolved source branch is supplied-linkedin-profiles or existing-lead-list; direct lead-list branches have no provider import job to wait on and this probe can poison the campaign import status"
826
+ },
827
+ {
828
+ "tool": "confirm_lead_list",
829
+ "targetLeadCountFromConfig": "import.importLimit (10)",
830
+ "sourceBranches": [
831
+ "normal-discovery",
832
+ "supplied-domains",
833
+ "supplied-linkedin-profiles",
834
+ "existing-lead-list"
835
+ ],
836
+ "requiredBeforeCall": [
837
+ "campaign_attached_source_state"
838
+ ],
839
+ "persists": [
840
+ "selectedLeadListId",
841
+ "workflowTableId"
842
+ ],
843
+ "identityBoundary": "selectedLeadListId remains the source list; workflowTableId is the campaign table returned by confirm/import."
844
+ },
845
+ {
846
+ "tool": "wait_for_campaign_table_ready",
847
+ "purpose": "wait_for_campaign_table_rows_after_confirm"
848
+ },
849
+ {
850
+ "tool": "get_rows_minimal",
851
+ "purpose": "read_imported_review_batch_from_workflowTableId",
852
+ "stripCarryData": true,
853
+ "sampleSizeFromConfig": "sample.sampleSize (10)"
854
+ },
855
+ {
856
+ "tool": "update_campaign",
857
+ "requiredFields": [
858
+ "campaignId",
859
+ "currentStep"
860
+ ],
861
+ "requiredValues": {
862
+ "currentStep": "filter-choice"
863
+ }
864
+ }
865
+ ],
866
+ "allowedTools": [
867
+ "get_subskill_asset",
868
+ "import_leads",
869
+ "wait_for_lead_list_ready",
870
+ "confirm_lead_list",
871
+ "wait_for_campaign_table_ready",
872
+ "get_rows_minimal",
873
+ "update_campaign",
874
+ "AskUserQuestion",
875
+ "request_user_input",
876
+ "load_csv_linkedin_leads",
877
+ "load_csv_domains",
878
+ "get_provider_prompt",
879
+ "get_campaign"
880
+ ],
881
+ "doNotAllow": [
882
+ "queue_cells",
883
+ "start_campaign",
884
+ "enrich_with_prospeo",
885
+ "bulk_enrich_with_prospeo"
886
+ ],
887
+ "watchRequired": true,
888
+ "waitFor": "review_batch_imported",
889
+ "transitions": {
890
+ "review_batch_imported": "post-lead-workstreams",
891
+ "escalation_triggered": "escalation"
555
892
  },
556
- "interruptOnly": true
893
+ "requiredArtifacts": [
894
+ "brief.md",
895
+ "lead-review.md",
896
+ "lead-sample.json"
897
+ ],
898
+ "requiredCampaignState": [
899
+ "campaignId",
900
+ "providerSearchAssociation",
901
+ "selectedLeadListId"
902
+ ]
557
903
  },
558
904
  {
559
905
  "id": "post-lead-workstreams",
560
- "label": "Post-lead filter and message workstreams",
906
+ "label": "Post-import filter and message workstreams",
561
907
  "normalFlow": true,
562
908
  "onEnter": [
563
909
  {
@@ -573,17 +919,33 @@
573
919
  {
574
920
  "name": "filter-leads",
575
921
  "target": "filter-leads",
576
- "inputs": ["brief.md", "lead-review.md", "lead-sample.json"],
577
- "producesArtifacts": ["lead-filter.md"],
578
- "optionalProducesArtifacts": ["rubric.json"],
922
+ "inputs": [
923
+ "brief.md",
924
+ "lead-review.md",
925
+ "lead-sample.json",
926
+ "workflowTableId review-batch rows"
927
+ ],
928
+ "producesArtifacts": [
929
+ "lead-filter.md"
930
+ ],
931
+ "optionalProducesArtifacts": [
932
+ "rubric.json"
933
+ ],
579
934
  "ownership": "lead quality, false-positive patterns, keep/exclude rules, and production rubric translation only"
580
935
  },
581
936
  {
582
937
  "name": "message-generation",
583
938
  "target": "generate-messages",
584
939
  "mode": "DRY MODE",
585
- "inputs": ["brief.md", "lead-review.md", "lead-sample.json"],
586
- "producesArtifacts": ["message-validation.md"],
940
+ "inputs": [
941
+ "brief.md",
942
+ "lead-review.md",
943
+ "lead-sample.json",
944
+ "workflowTableId review-batch rows"
945
+ ],
946
+ "producesArtifacts": [
947
+ "message-validation.md"
948
+ ],
587
949
  "optionalProducesArtifacts": [
588
950
  "message-prep.md",
589
951
  "message-candidate-drafts.md"
@@ -591,25 +953,54 @@
591
953
  "ownership": "proof inventory, token strategy, angle drafting, skeptical-prospect review, and selected winner only"
592
954
  }
593
955
  ],
594
- "earlyMessageStartRule": "As soon as lead-sample.json exists with at least 5 probable good-fit rows and the lead source is confirmed or auto-confirmed, launch message-generation from brief.md, lead-review.md, and lead-sample.json. Do not wait for lead-filter.md to begin proof inventory, token planning, or candidate angles.",
595
- "finalMessageReconcileRule": "message-validation.md may start before lead-filter.md exists, but before message-review it must cite only lead-sample.json rows that still pass lead-filter.md. If the selected winner depends on a row later excluded by lead-filter.md, revise message-generation before message review.",
956
+ "earlyMessageStartRule": "After auto-execute-leads confirms the 10-row review batch and workflowTableId is ready, launch message-generation from brief.md, lead-review.md, lead-sample.json, and the imported campaign table sample. Legacy/resume preview prep still requires at least 5 probable good-fit rows. It may run beside filter-leads, but the cascade cannot queue until save_rubrics and update_campaign_brief both succeed.",
957
+ "finalMessageReconcileRule": "message-validation.md may start before lead-filter.md exists, but before message-review it must cite only imported review-batch rows that still pass lead-filter.md. If the selected winner depends on a row later excluded by lead-filter.md, revise message-generation before message review.",
596
958
  "claudeRule": "In Claude Code, launch both returned post-find-leads scouts with Task/Agent subagents in the same assistant message when Task is available. Do not run filter first and then message generation unless subagents/background work are unavailable.",
597
959
  "codexRule": "In Codex, launch both returned post-find-leads scout names as disjoint subagents in the same assistant turn when the host exposes subagents for this run. If the host cannot spawn them, run the same branches sequentially and say so.",
598
960
  "fallback": "If real parallel branches are unavailable, run filter-leads and then message-generation in the parent thread. Do not claim background or parallel work in that fallback."
599
961
  },
600
962
  {
601
963
  "action": "wait_for_post_lead_artifacts",
602
- "requiredArtifacts": ["lead-filter.md", "message-validation.md"],
964
+ "requiredArtifacts": [
965
+ "lead-filter.md",
966
+ "message-validation.md"
967
+ ],
603
968
  "optionalArtifacts": [
604
969
  "rubric.json",
605
970
  "message-prep.md",
606
971
  "message-candidate-drafts.md"
607
972
  ],
608
973
  "reconciliationRule": "Before entering message-review, verify lead-filter.md and message-validation.md both came from the same brief.md, lead-review.md, and lead-sample.json. lead-filter.md gates the sample rows; lead-sample.json remains the message sample source."
974
+ },
975
+ {
976
+ "action": "save_filter_rubrics_to_campaign",
977
+ "tool": "save_rubrics",
978
+ "requires": [
979
+ "campaignId",
980
+ "workflowTableId",
981
+ "lead-filter.md"
982
+ ],
983
+ "optionalInput": "rubric.json",
984
+ "when": "after_imported_campaign_table_sample_and_lead_filter_ready_before_message_review",
985
+ "fallback": "Stop if campaignId/workflowTableId is missing or save_rubrics fails; do not queue the review-batch cascade without saved rubrics.",
986
+ "requiredBeforeImport": false,
987
+ "requiredFields": [
988
+ "campaignOfferId",
989
+ "leadScoringRubrics"
990
+ ],
991
+ "writesCampaignState": "leadScoringRubrics",
992
+ "requiredBeforeCascade": true
609
993
  }
610
994
  ],
611
- "requiredArtifacts": ["brief.md", "lead-review.md", "lead-sample.json"],
612
- "producesArtifacts": ["lead-filter.md", "message-validation.md"],
995
+ "requiredArtifacts": [
996
+ "brief.md",
997
+ "lead-review.md",
998
+ "lead-sample.json"
999
+ ],
1000
+ "producesArtifacts": [
1001
+ "lead-filter.md",
1002
+ "message-validation.md"
1003
+ ],
613
1004
  "optionalProducesArtifacts": [
614
1005
  "rubric.json",
615
1006
  "message-prep.md",
@@ -619,6 +1010,7 @@
619
1010
  "get_subskill_prompt",
620
1011
  "get_subskill_asset",
621
1012
  "get_post_find_leads_scout_registry",
1013
+ "save_rubrics",
622
1014
  "Task",
623
1015
  "spawn_agent",
624
1016
  "AskUserQuestion",
@@ -628,18 +1020,20 @@
628
1020
  "The post-lead workstreams are disjoint: filter-leads owns lead-filter.md/rubric.json; message-generation owns message-validation.md/message-prep.md/message-candidate-drafts.md.",
629
1021
  "message-generation can start before lead-filter.md, but message-review cannot start until both lead-filter.md and message-validation.md exist and reconcile against the same lead-sample.json.",
630
1022
  "Do not let filter-leads create a new message sample. Do not let message-generation fetch new prospects.",
631
- "Before writing message-validation.md, message-generation must read 100% of the real generate-messages prompt via chunked get_subskill_prompt({ subskillName: \"generate-messages\", offset, limit }) calls."
1023
+ "Before writing message-validation.md, message-generation must read 100% of the real generate-messages prompt via chunked get_subskill_prompt({ subskillName: \"generate-messages\", offset, limit }) calls.",
1024
+ "Do not queue, enrich, attach sequence, or start until save_rubrics succeeds and the approved message set is synced into campaignBrief."
632
1025
  ],
633
1026
  "doNotAllow": [
634
1027
  "create_campaign",
635
- "save_rubrics",
636
1028
  "import_leads",
637
1029
  "confirm_lead_list",
638
1030
  "update_campaign",
639
1031
  "queue_cells",
640
1032
  "start_campaign",
641
1033
  "check_rubric",
642
- "generate_messages"
1034
+ "generate_messages",
1035
+ "enrich_with_prospeo",
1036
+ "bulk_enrich_with_prospeo"
643
1037
  ],
644
1038
  "watchRequired": false,
645
1039
  "waitFor": [
@@ -656,7 +1050,18 @@
656
1050
  "revise_messaging": "message-generation",
657
1051
  "confirm_with_user": "message-review"
658
1052
  },
659
- "interruptOnly": true
1053
+ "interruptOnly": true,
1054
+ "requiredCampaignState": [
1055
+ "campaignId",
1056
+ "workflowTableId"
1057
+ ],
1058
+ "debugProducesArtifacts": [
1059
+ "lead-filter.md",
1060
+ "message-validation.md",
1061
+ "rubric.json",
1062
+ "message-prep.md",
1063
+ "message-candidate-drafts.md"
1064
+ ]
660
1065
  },
661
1066
  {
662
1067
  "id": "filter-rubric",
@@ -665,12 +1070,20 @@
665
1070
  {
666
1071
  "action": "run_stage",
667
1072
  "target": "filter-leads",
668
- "inputs": ["brief.md", "lead-review.md", "lead-sample.json"]
1073
+ "inputs": [
1074
+ "brief.md",
1075
+ "lead-review.md",
1076
+ "lead-sample.json"
1077
+ ]
669
1078
  },
670
1079
  {
671
1080
  "action": "write_artifacts",
672
- "artifacts": ["lead-filter.md"],
673
- "optionalArtifacts": ["rubric.json"]
1081
+ "artifacts": [
1082
+ "lead-filter.md"
1083
+ ],
1084
+ "optionalArtifacts": [
1085
+ "rubric.json"
1086
+ ]
674
1087
  },
675
1088
  {
676
1089
  "action": "show_lead_filter_summary",
@@ -682,38 +1095,72 @@
682
1095
  "Why",
683
1096
  "Expected impact"
684
1097
  ],
685
- "requiredArtifactLinks": ["lead-filter.md"],
1098
+ "requiredArtifactLinks": [
1099
+ "lead-filter.md"
1100
+ ],
686
1101
  "chatRenderRule": "Show a slim rendered-Markdown filter summary before message review, never a fenced code block. Use ## Lead filter plus indexed sections and short bullets for who we keep, who we exclude, sample-backed reason, and expected impact. Keep production rubric fields and rubric.json details in lead-filter.md/rubric.json. Do not show plain filesystem paths unless links cannot be created."
687
1102
  },
1103
+ {
1104
+ "action": "save_filter_rubrics_to_campaign",
1105
+ "tool": "save_rubrics",
1106
+ "requires": [
1107
+ "campaignId",
1108
+ "workflowTableId",
1109
+ "lead-filter.md"
1110
+ ],
1111
+ "optionalInput": "rubric.json",
1112
+ "when": "after_filter_confirmed_or_auto_continue",
1113
+ "fallback": "Stop if campaignId/workflowTableId is missing or save_rubrics fails; do not queue the review-batch cascade without saved rubrics.",
1114
+ "requiredBeforeImport": false,
1115
+ "requiredFields": [
1116
+ "campaignOfferId",
1117
+ "leadScoringRubrics"
1118
+ ],
1119
+ "writesCampaignState": "leadScoringRubrics",
1120
+ "requiredBeforeCascade": true
1121
+ },
688
1122
  {
689
1123
  "action": "ask_revise_only_if_filter_is_not_confirmed",
690
1124
  "autoContinueWhen": {
691
1125
  "status": "confirmed",
692
- "decisionIn": ["continue", "confirmed"],
1126
+ "decisionIn": [
1127
+ "continue",
1128
+ "confirmed"
1129
+ ],
693
1130
  "expectedUsableLeadsAtLeast": 150,
694
1131
  "noHeavyFilteringWarning": true,
695
1132
  "noCampaignMissRisk": true
696
1133
  }
697
1134
  }
698
1135
  ],
699
- "requiredArtifacts": ["brief.md", "lead-review.md", "lead-sample.json"],
700
- "producesArtifacts": ["lead-filter.md"],
701
- "optionalProducesArtifacts": ["rubric.json"],
1136
+ "requiredArtifacts": [
1137
+ "brief.md",
1138
+ "lead-review.md",
1139
+ "lead-sample.json"
1140
+ ],
1141
+ "producesArtifacts": [
1142
+ "lead-filter.md"
1143
+ ],
1144
+ "optionalProducesArtifacts": [
1145
+ "rubric.json"
1146
+ ],
702
1147
  "allowedTools": [
703
1148
  "get_subskill_prompt",
704
1149
  "get_subskill_asset",
1150
+ "save_rubrics",
705
1151
  "AskUserQuestion",
706
1152
  "request_user_input"
707
1153
  ],
708
1154
  "doNotAllow": [
709
1155
  "create_campaign",
710
- "save_rubrics",
711
1156
  "import_leads",
712
1157
  "confirm_lead_list",
713
1158
  "update_campaign",
714
1159
  "queue_cells",
715
1160
  "start_campaign",
716
- "check_rubric"
1161
+ "check_rubric",
1162
+ "enrich_with_prospeo",
1163
+ "bulk_enrich_with_prospeo"
717
1164
  ],
718
1165
  "watchRequired": false,
719
1166
  "waitFor": [
@@ -728,7 +1175,11 @@
728
1175
  "confirm_with_user": "message-generation",
729
1176
  "auto_continue": "message-generation"
730
1177
  },
731
- "interruptOnly": true
1178
+ "interruptOnly": true,
1179
+ "requiredCampaignState": [
1180
+ "campaignId",
1181
+ "workflowTableId"
1182
+ ]
732
1183
  },
733
1184
  {
734
1185
  "id": "message-generation",
@@ -750,8 +1201,14 @@
750
1201
  "artifact": "message-validation.md"
751
1202
  }
752
1203
  ],
753
- "requiredArtifacts": ["brief.md", "lead-review.md", "lead-sample.json"],
754
- "producesArtifacts": ["message-validation.md"],
1204
+ "requiredArtifacts": [
1205
+ "brief.md",
1206
+ "lead-review.md",
1207
+ "lead-sample.json"
1208
+ ],
1209
+ "producesArtifacts": [
1210
+ "message-validation.md"
1211
+ ],
755
1212
  "allowedTools": [
756
1213
  "get_subskill_prompt",
757
1214
  "get_subskill_asset",
@@ -759,12 +1216,13 @@
759
1216
  "request_user_input"
760
1217
  ],
761
1218
  "toolRules": [
762
- "Before writing message-validation.md, message-review.md, approval-packet.md, or a commit-gate AskUserQuestion, the current run must read 100% of the real generate-messages prompt via chunked get_subskill_prompt({ subskillName: \"generate-messages\", offset, limit }) calls.",
1219
+ "Before writing message-validation.md, message-review.md, or a message-review AskUserQuestion, the current run must read 100% of the real generate-messages prompt via chunked get_subskill_prompt({ subskillName: \"generate-messages\", offset, limit }) calls.",
763
1220
  "Lead Sample Basis must cite rows from lead-sample.json produced by find-leads. lead-filter.md only gates those rows; it is not a source for a new sample.",
764
1221
  "Do not hand-write message-validation.md from message-prep.md, message-candidate-drafts.md, or general campaign knowledge.",
765
1222
  "message-validation.md must prove the full generate-messages workflow ran: Gold Standard Strategy Map, Proof Inventory, Token Fill Rules, Token Adherence Table, Angle Drafts, Kill / Combine Review, Finalists, Finalizer Pass, Gold-Standard Quality Gate, Skeptical Prospect Review, Winner Gate, and a raw sendable Selected Winner are required before message-review can recommend approve-message.",
766
1223
  "If the Codex-hosted output is plausible but weaker than the loaded gold-standard examples, stop at message-review with revise-messaging. Do not continue to approval or mint just because the mechanical flow worked.",
767
- "If the message review or approval packet is ready but the generate-messages prompt was not retrieved in this run, route back to message-generation instead of asking the commit gate."
1224
+ "If message review is ready but the generate-messages prompt was not retrieved in this run, route back to message-generation instead of asking the user to approve the message.",
1225
+ "The approved message set must be written back into campaignBrief before validate-sample queues the review-batch cascade."
768
1226
  ],
769
1227
  "doNotAllow": [
770
1228
  "create_campaign",
@@ -774,7 +1232,9 @@
774
1232
  "update_campaign",
775
1233
  "queue_cells",
776
1234
  "start_campaign",
777
- "generate_messages"
1235
+ "generate_messages",
1236
+ "enrich_with_prospeo",
1237
+ "bulk_enrich_with_prospeo"
778
1238
  ],
779
1239
  "watchRequired": false,
780
1240
  "waitFor": [
@@ -792,10 +1252,15 @@
792
1252
  "auto_continue": "message-review"
793
1253
  },
794
1254
  "optionalRequiredArtifacts": [
1255
+ "lead-filter.md",
795
1256
  "message-prep.md",
796
1257
  "message-candidate-drafts.md"
797
1258
  ],
798
- "interruptOnly": true
1259
+ "interruptOnly": true,
1260
+ "requiredCampaignState": [
1261
+ "campaignId",
1262
+ "workflowTableId"
1263
+ ]
799
1264
  },
800
1265
  {
801
1266
  "id": "message-review",
@@ -812,7 +1277,10 @@
812
1277
  "lead-filter.md",
813
1278
  "message-validation.md"
814
1279
  ],
815
- "optionalFiles": ["message-prep.md", "message-candidate-drafts.md"]
1280
+ "optionalFiles": [
1281
+ "message-prep.md",
1282
+ "message-candidate-drafts.md"
1283
+ ]
816
1284
  },
817
1285
  {
818
1286
  "action": "write_message_review",
@@ -828,7 +1296,9 @@
828
1296
  ],
829
1297
  "mustRenderInlineBeforeQuestion": true,
830
1298
  "minimumVisibleMessageDetail": "filled_sample_message_only",
831
- "requiredArtifactLinks": ["message-review.md"],
1299
+ "requiredArtifactLinks": [
1300
+ "message-review.md"
1301
+ ],
832
1302
  "artifactLinkTiming": "before_approval_question",
833
1303
  "requiredFileLabels": [
834
1304
  "Subject:",
@@ -857,7 +1327,10 @@
857
1327
  "Recommendation:"
858
1328
  ],
859
1329
  "chatRenderRule": "Show a slim rendered-Markdown message review, never a fenced code block. Use ## Message review plus indexed sections and short bullets. Show one fully filled sample message with no {{tokens}} before asking for approval. Include subject, sample message, why it should work, one concern or None, recommendation, and Open artifact: message-review.md. Keep tokenized template, token fill basis, rendered examples, good/bad token fill examples, validation notes, and message-validation.md details in artifacts. Do not show plain filesystem paths unless links cannot be created.",
860
- "allowedRecommendations": ["approve-message", "revise-messaging"],
1330
+ "allowedRecommendations": [
1331
+ "approve-message",
1332
+ "revise-messaging"
1333
+ ],
861
1334
  "judgeAgainst": [
862
1335
  "generate-messages rules",
863
1336
  "gold-standard-* references",
@@ -885,7 +1358,10 @@
885
1358
  },
886
1359
  {
887
1360
  "action": "ask_message_review_choice",
888
- "choices": ["approve-message", "revise-messaging"],
1361
+ "choices": [
1362
+ "approve-message",
1363
+ "revise-messaging"
1364
+ ],
889
1365
  "questionPrerequisiteVisibleLabels": [
890
1366
  "Status: message-review",
891
1367
  "Subject:",
@@ -903,13 +1379,36 @@
903
1379
  {
904
1380
  "action": "write_message_review_decision",
905
1381
  "artifact": "message-review-decision.md",
906
- "allowedChoices": ["approve-message", "revise-messaging"],
907
- "doesNotAuthorizeMint": true
1382
+ "allowedChoices": [
1383
+ "approve-message",
1384
+ "revise-messaging"
1385
+ ],
1386
+ "authorizesReviewBatchImportWhenApproved": true
908
1387
  },
909
1388
  {
910
1389
  "action": "require_message_review_decision",
911
1390
  "artifact": "message-review-decision.md",
912
1391
  "requiredValue": "approve-message"
1392
+ },
1393
+ {
1394
+ "action": "sync_approved_message_set_to_campaign_brief",
1395
+ "tool": "update_campaign_brief",
1396
+ "requires": [
1397
+ "campaignId",
1398
+ "message-review-decision.md",
1399
+ "message-validation.md",
1400
+ "lead-filter.md"
1401
+ ],
1402
+ "when": "after_message_approved_before_import",
1403
+ "fields": [
1404
+ "campaignBrief with ## Approved Message Template, Token Fill Rules, Token Fill Examples",
1405
+ "currentStep:validate-sample",
1406
+ "useMessagingTemplate:true"
1407
+ ],
1408
+ "requiredBeforeImport": false,
1409
+ "onFailure": "stop_before_import_or_enrichment",
1410
+ "requiredBeforeCascade": true,
1411
+ "writesCampaignState": "campaignBrief.Approved Message Template"
913
1412
  }
914
1413
  ],
915
1414
  "requiredArtifacts": [
@@ -919,34 +1418,47 @@
919
1418
  "lead-filter.md",
920
1419
  "message-validation.md"
921
1420
  ],
922
- "producesArtifacts": ["message-review.md", "message-review-decision.md"],
1421
+ "producesArtifacts": [
1422
+ "message-review.md",
1423
+ "message-review-decision.md"
1424
+ ],
923
1425
  "allowedTools": [
924
1426
  "AskUserQuestion",
925
1427
  "request_user_input",
926
- "get_subskill_prompt"
1428
+ "get_subskill_prompt",
1429
+ "update_campaign_brief"
927
1430
  ],
928
1431
  "doNotAllow": [
929
1432
  "create_campaign",
930
1433
  "save_rubrics",
931
1434
  "import_leads",
932
1435
  "confirm_lead_list",
933
- "update_campaign",
934
1436
  "queue_cells",
935
- "start_campaign"
1437
+ "start_campaign",
1438
+ "enrich_with_prospeo",
1439
+ "bulk_enrich_with_prospeo"
936
1440
  ],
937
1441
  "watchRequired": false,
938
- "waitFor": ["message_approved", "revise_messaging"],
1442
+ "waitFor": [
1443
+ "message_approved",
1444
+ "revise_messaging"
1445
+ ],
939
1446
  "transitions": {
940
- "message_approved": "approval-packet",
1447
+ "message_approved": "validate-sample",
941
1448
  "revise_messaging": "message-generation"
942
1449
  },
943
- "interruptOnly": true
1450
+ "interruptOnly": true,
1451
+ "requiredCampaignState": [
1452
+ "campaignId",
1453
+ "workflowTableId",
1454
+ "leadScoringRubrics"
1455
+ ]
944
1456
  },
945
1457
  {
946
1458
  "id": "approval-packet",
947
- "label": "Approval packet",
948
- "normalFlow": true,
949
- "resumeOnly": false,
1459
+ "label": "Approval packet",
1460
+ "normalFlow": false,
1461
+ "resumeOnly": true,
950
1462
  "onEnter": [
951
1463
  {
952
1464
  "action": "read_draft_artifacts",
@@ -959,7 +1471,9 @@
959
1471
  "message-review.md",
960
1472
  "message-review-decision.md"
961
1473
  ],
962
- "optionalFiles": ["rubric.json"]
1474
+ "optionalFiles": [
1475
+ "rubric.json"
1476
+ ]
963
1477
  },
964
1478
  {
965
1479
  "action": "require_message_review_decision",
@@ -980,11 +1494,17 @@
980
1494
  "what approval creates"
981
1495
  ],
982
1496
  "minimumVisibleDetail": "slim_final_approval_summary",
983
- "requiredArtifactLinks": ["approval-packet.md"],
1497
+ "requiredArtifactLinks": [
1498
+ "approval-packet.md"
1499
+ ],
984
1500
  "artifactLinkTiming": "before_commit_gate_question",
985
1501
  "chatRenderRule": "Show a slim rendered-Markdown final approval packet only, never a fenced code block. Use ## Ready to create campaign plus indexed sections and short bullets. Include target with concrete role/title names, lead source with one key metric, filter summary, one fully filled sample message with no {{tokens}}, what approval creates, and safety note that nothing sends live until the review/greenlight step. Keep tokenized templates, evidence tables, rubrics, and validation details in approval-packet.md. Do not show sequence/settings in final approval; that comes after campaign creation. Do not show plain filesystem paths unless links cannot be created.",
986
1502
  "userFacing": true,
987
- "doNotUseWords": ["anchor", "validation anchor", "lane"]
1503
+ "doNotUseWords": [
1504
+ "anchor",
1505
+ "validation anchor",
1506
+ "lane"
1507
+ ]
988
1508
  },
989
1509
  {
990
1510
  "action": "optional_customer_roleplay",
@@ -1002,8 +1522,12 @@
1002
1522
  "message-review.md",
1003
1523
  "message-review-decision.md"
1004
1524
  ],
1005
- "producesArtifacts": ["approval-packet.md"],
1006
- "optionalProducesArtifacts": ["customer-roleplay.md"],
1525
+ "producesArtifacts": [
1526
+ "approval-packet.md"
1527
+ ],
1528
+ "optionalProducesArtifacts": [
1529
+ "customer-roleplay.md"
1530
+ ],
1007
1531
  "allowedTools": [
1008
1532
  "AskUserQuestion",
1009
1533
  "request_user_input",
@@ -1016,14 +1540,18 @@
1016
1540
  "confirm_lead_list",
1017
1541
  "update_campaign",
1018
1542
  "queue_cells",
1019
- "start_campaign"
1543
+ "start_campaign",
1544
+ "enrich_with_prospeo",
1545
+ "bulk_enrich_with_prospeo"
1020
1546
  ],
1021
1547
  "watchRequired": false,
1022
1548
  "waitFor": "approval_packet_ready",
1023
- "transitions": {
1024
- "approval_packet_ready": "commit-gate"
1025
- },
1026
- "optionalRequiredArtifacts": ["lead-source-intake.json"]
1549
+ "transitions": {},
1550
+ "optionalRequiredArtifacts": [
1551
+ "lead-source-intake.json"
1552
+ ],
1553
+ "deprecated": true,
1554
+ "deprecatedReason": "Shell-first flow has no final approval gate or mint. Message approval syncs the final template into the existing campaign and transitions directly to Step 13."
1027
1555
  },
1028
1556
  {
1029
1557
  "id": "validate-artifacts",
@@ -1038,7 +1566,9 @@
1038
1566
  "lead-filter.md",
1039
1567
  "message-validation.md"
1040
1568
  ],
1041
- "optionalFiles": ["rubric.json"]
1569
+ "optionalFiles": [
1570
+ "rubric.json"
1571
+ ]
1042
1572
  },
1043
1573
  {
1044
1574
  "action": "validate_anchors",
@@ -1051,13 +1581,20 @@
1051
1581
  "doesNotAuthorizeMint": true
1052
1582
  }
1053
1583
  ],
1054
- "allowedTools": ["AskUserQuestion", "request_user_input"],
1584
+ "allowedTools": [
1585
+ "AskUserQuestion",
1586
+ "request_user_input"
1587
+ ],
1055
1588
  "watchRequired": false,
1056
1589
  "waitFor": "artifacts_validated",
1057
- "transitions": {
1058
- "artifacts_validated": "approval-packet"
1059
- },
1060
- "optionalRequiredArtifacts": ["lead-source-intake.json"]
1590
+ "transitions": {},
1591
+ "optionalRequiredArtifacts": [
1592
+ "lead-source-intake.json"
1593
+ ],
1594
+ "deprecated": true,
1595
+ "deprecatedReason": "Shell-first flow has no final approval gate or mint. Message approval syncs the final template into the existing campaign and transitions directly to Step 13.",
1596
+ "resumeOnly": true,
1597
+ "normalFlow": false
1061
1598
  },
1062
1599
  {
1063
1600
  "id": "commit-gate",
@@ -1080,7 +1617,9 @@
1080
1617
  "sample message",
1081
1618
  "what approval creates"
1082
1619
  ],
1083
- "requiredArtifactLinks": ["approval-packet.md"],
1620
+ "requiredArtifactLinks": [
1621
+ "approval-packet.md"
1622
+ ],
1084
1623
  "chatRenderRule": "Same slim rendered-Markdown approval packet as approval-packet, never a fenced code block. Use indexed sections and short bullets. Include target with concrete role/title names and one fully filled sample message without tokens. Do not include sequence/settings. Do not repeat tokenized template or long evidence tables in chat.",
1085
1624
  "artifactLinkTiming": "same_turn_before_commit_question"
1086
1625
  },
@@ -1109,7 +1648,10 @@
1109
1648
  "onlyApproveAuthorizesMint": true
1110
1649
  }
1111
1650
  ],
1112
- "allowedTools": ["AskUserQuestion", "request_user_input"],
1651
+ "allowedTools": [
1652
+ "AskUserQuestion",
1653
+ "request_user_input"
1654
+ ],
1113
1655
  "watchRequired": false,
1114
1656
  "waitFor": [
1115
1657
  "user_approved",
@@ -1119,15 +1661,14 @@
1119
1661
  "user_revise_messaging",
1120
1662
  "user_abort"
1121
1663
  ],
1122
- "transitions": {
1123
- "user_approved": "atomic-mint",
1124
- "user_revise_brief": "revise-brief",
1125
- "user_revise_leads": "revise-leads",
1126
- "user_revise_rubric": "revise-rubric",
1127
- "user_revise_messaging": "revise-messaging",
1128
- "user_abort": "abort"
1129
- },
1130
- "optionalRequiredArtifacts": ["lead-source-intake.json"]
1664
+ "transitions": {},
1665
+ "optionalRequiredArtifacts": [
1666
+ "lead-source-intake.json"
1667
+ ],
1668
+ "deprecated": true,
1669
+ "deprecatedReason": "Shell-first flow has no final approval gate or mint. Message approval syncs the final template into the existing campaign and transitions directly to Step 13.",
1670
+ "resumeOnly": true,
1671
+ "normalFlow": false
1131
1672
  },
1132
1673
  {
1133
1674
  "id": "atomic-mint",
@@ -1142,10 +1683,16 @@
1142
1683
  },
1143
1684
  {
1144
1685
  "tool": "create_campaign",
1145
- "requiredFields": ["campaignBrief", "currentStep"],
1686
+ "mode": "create_new_or_resume_draft_shell",
1687
+ "shellArtifact": "campaign-shell.json",
1688
+ "requiredFields": [
1689
+ "campaignBrief",
1690
+ "currentStep"
1691
+ ],
1146
1692
  "requiredValues": {
1147
1693
  "currentStep": "auto-execute-leads"
1148
1694
  },
1695
+ "draftShellBehavior": "If campaign-shell.json exists, call create_campaign with campaignId only to recover watchUrl/resume context, then update_campaign with the final approved brief, provider, and currentStep. If no shell exists, create_campaign mints the campaign with the final approved brief.",
1149
1696
  "requiredCampaignBriefSections": [
1150
1697
  "Approved Message Template",
1151
1698
  "Token Fill Rules",
@@ -1163,12 +1710,37 @@
1163
1710
  "Fallback if missing:",
1164
1711
  "Token fill basis:"
1165
1712
  ],
1166
- "capture": ["campaignId", "watchUrl"],
1713
+ "capture": [
1714
+ "campaignId",
1715
+ "watchUrl"
1716
+ ],
1167
1717
  "onMissingWatchUrl": "recoverable_failure_abort_before_rubric_save"
1168
1718
  },
1719
+ {
1720
+ "tool": "update_campaign",
1721
+ "when": "campaign-shell.json exists",
1722
+ "requiredFields": [
1723
+ "campaignId",
1724
+ "campaignBrief",
1725
+ "currentStep"
1726
+ ],
1727
+ "requiredValues": {
1728
+ "currentStep": "auto-execute-leads"
1729
+ },
1730
+ "fields": [
1731
+ "campaignBrief",
1732
+ "leadSourceType",
1733
+ "leadSourceProvider",
1734
+ "useMessagingTemplate"
1735
+ ],
1736
+ "skipWhen": "create_campaign minted a new campaign with the final approved brief and currentStep already set"
1737
+ },
1169
1738
  {
1170
1739
  "tool": "save_rubrics",
1171
- "requiredFields": ["rubric"],
1740
+ "requiredFields": [
1741
+ "rubric"
1742
+ ],
1743
+ "idempotentWhenAlreadySavedToDraftShell": true,
1172
1744
  "onFailure": "rollback_or_recoverable_stop"
1173
1745
  },
1174
1746
  {
@@ -1179,7 +1751,10 @@
1179
1751
  },
1180
1752
  {
1181
1753
  "action": "surface_watch_link",
1182
- "requires": ["create_campaign_succeeded", "save_rubrics_succeeded"],
1754
+ "requires": [
1755
+ "create_campaign_succeeded",
1756
+ "save_rubrics_succeeded"
1757
+ ],
1183
1758
  "reference": "references/watch-link-handoff.md",
1184
1759
  "requiredVisibleContent": [
1185
1760
  "Campaign created",
@@ -1196,16 +1771,21 @@
1196
1771
  "allowedTools": [
1197
1772
  "bootstrap_create_campaign",
1198
1773
  "create_campaign",
1774
+ "update_campaign",
1199
1775
  "save_rubrics",
1200
1776
  "AskUserQuestion",
1201
1777
  "request_user_input"
1202
1778
  ],
1203
- "doNotAllow": ["import_leads"],
1779
+ "doNotAllow": [
1780
+ "import_leads"
1781
+ ],
1204
1782
  "watchRequired": true,
1205
1783
  "waitFor": "autonomous_tail_started",
1206
- "transitions": {
1207
- "autonomous_tail_started": "autonomous-tail-handoff"
1208
- }
1784
+ "transitions": {},
1785
+ "deprecated": true,
1786
+ "deprecatedReason": "Shell-first flow has no final approval gate or mint. Message approval syncs the final template into the existing campaign and transitions directly to Step 13.",
1787
+ "resumeOnly": true,
1788
+ "normalFlow": false
1209
1789
  },
1210
1790
  {
1211
1791
  "id": "autonomous-tail-handoff",
@@ -1238,110 +1818,9 @@
1238
1818
  "autonomous_tail_started": "auto-execute-leads"
1239
1819
  }
1240
1820
  },
1241
- {
1242
- "id": "auto-execute-leads",
1243
- "label": "Step 13: Import review batch",
1244
- "currentStepValue": "auto-execute-leads",
1245
- "reference": "references/step-13-import-leads.md",
1246
- "onEnter": [
1247
- {
1248
- "action": "resolve_approved_source",
1249
- "manifest": "lead-source-intake.json",
1250
- "defaultWhenMissing": "discovered-provider-import",
1251
- "branches": {
1252
- "normal-discovery": "import_leads({ campaignOfferId, targetLeadCount: import.importLimit })",
1253
- "supplied-linkedin-profiles": "confirm load_csv_linkedin_leads after approval to batch/materialize the CSV into a lead list, persist returned leadListId, then confirm_lead_list({ sourceLeadListId: leadListId, targetLeadCount: import.importLimit }); do not call import_leads for this branch",
1254
- "existing-lead-list": "confirm_lead_list({ sourceLeadListId: existingLeadListId, targetLeadCount: import.importLimit })",
1255
- "supplied-domains": "reuse or confirm load_csv_domains, run campaign-associated search_prospeo({ campaignOfferId, domainFilterId }), then import_leads({ campaignOfferId, targetLeadCount: import.importLimit })"
1256
- },
1257
- "persistMaterializedIds": [
1258
- "leadListId",
1259
- "domainFilterId",
1260
- "searchId",
1261
- "campaignTableId",
1262
- "importedRowIds",
1263
- "queuedCellIds"
1264
- ],
1265
- "onSourceFileChanged": "escalate_before_materialization",
1266
- "onLowDomainYield": "confirm-with-user: widen role filters, add more domains, or abort; never drop domainFilterId silently"
1267
- },
1268
- {
1269
- "action": "watch_mode_orient"
1270
- },
1271
- {
1272
- "tool": "import_leads",
1273
- "requiredFields": ["campaignOfferId", "targetLeadCount"],
1274
- "targetLeadCountFromConfig": "import.importLimit",
1275
- "onZeroLeads": "escalate_hard_fail",
1276
- "modeAddHandshake": {
1277
- "firstCallReturns": "needsModeSelection=true + existingLeadListId",
1278
- "secondCallPasses": "sourceLeadListId=<existingLeadListId>",
1279
- "note": "import_leads with mode=add is a two-step handshake; see references/step-13-import-leads.md"
1280
- },
1281
- "surfaceDedupRatio": true,
1282
- "defaultBranch": "normal-discovery",
1283
- "skipWhen": "resolved source branch returns sourceLeadListId for confirm_lead_list"
1284
- },
1285
- {
1286
- "tool": "wait_for_lead_list_ready",
1287
- "sourceBranches": ["normal-discovery", "supplied-domains"],
1288
- "skipWhen": "resolved source branch is supplied-linkedin-profiles or existing-lead-list; direct lead-list branches have no provider import job to wait on and this probe can poison the campaign import status"
1289
- },
1290
- {
1291
- "tool": "confirm_lead_list",
1292
- "targetLeadCountFromConfig": "import.importLimit",
1293
- "sourceBranches": ["supplied-linkedin-profiles", "existing-lead-list"]
1294
- },
1295
- {
1296
- "tool": "get_rows_minimal",
1297
- "purpose": "capture_enrich_cell_ids",
1298
- "stripCarryData": true
1299
- },
1300
- {
1301
- "tool": "queue_cells",
1302
- "cellSource": "review_batch_enrich_prospect_cells_only",
1303
- "batchSize": 100,
1304
- "note": "one call starts the workflow-table cascade for the imported review batch only: DNC Check -> ICP Score -> Passes Rubric -> Generate Message. Do not queue cells outside the review batch before user expansion approval."
1305
- },
1306
- {
1307
- "tool": "wait_for_campaign_table_ready",
1308
- "purpose": "wait_for_cascade_to_drain"
1309
- },
1310
- {
1311
- "tool": "update_campaign",
1312
- "requiredFields": ["campaignId", "currentStep"],
1313
- "requiredValues": {
1314
- "currentStep": "validate-sample"
1315
- }
1316
- }
1317
- ],
1318
- "allowedTools": [
1319
- "get_subskill_asset",
1320
- "import_leads",
1321
- "wait_for_lead_list_ready",
1322
- "confirm_lead_list",
1323
- "get_rows_minimal",
1324
- "queue_cells",
1325
- "wait_for_campaign_table_ready",
1326
- "update_campaign",
1327
- "AskUserQuestion",
1328
- "request_user_input",
1329
- "load_csv_linkedin_leads",
1330
- "load_csv_domains",
1331
- "get_provider_prompt",
1332
- "search_prospeo"
1333
- ],
1334
- "doNotAllow": ["start_campaign"],
1335
- "watchRequired": true,
1336
- "waitFor": "leads_imported",
1337
- "transitions": {
1338
- "leads_imported": "validate-sample",
1339
- "escalation_triggered": "escalation"
1340
- }
1341
- },
1342
1821
  {
1343
1822
  "id": "validate-sample",
1344
- "label": "Step 14: Sample validation loop",
1823
+ "label": "Step 14: Validate 10-lead test batch",
1345
1824
  "currentStepValue": "validate-sample",
1346
1825
  "reference": "references/sample-validation-loop.md",
1347
1826
  "counter": {
@@ -1355,22 +1834,42 @@
1355
1834
  {
1356
1835
  "action": "watch_mode_orient"
1357
1836
  },
1837
+ {
1838
+ "tool": "queue_cells",
1839
+ "cellSource": "ten_lead_review_batch_after_rubrics_and_message_set",
1840
+ "batchSize": 10,
1841
+ "requiredBeforeCall": [
1842
+ "rubrics_saved_to_campaign",
1843
+ "message_set_synced_to_brief"
1844
+ ],
1845
+ "note": "Queue only the 10-lead review batch after rubrics are saved and the approved message set is in campaignBrief. This starts the workflow-table cascade for DNC Check -> ICP Score -> Passes Rubric -> Generate Message."
1846
+ },
1847
+ {
1848
+ "tool": "wait_for_campaign_table_ready",
1849
+ "purpose": "wait_for_bounded_review_batch_cascade_to_drain"
1850
+ },
1358
1851
  {
1359
1852
  "tool": "get_rows_minimal",
1360
1853
  "purpose": "read_passes_rubric_and_generate_message_status",
1361
1854
  "stripCarryData": true,
1362
- "sampleSizeFromConfig": "sample.sampleSize"
1855
+ "sampleSizeFromConfig": "sample.sampleSize (10)"
1363
1856
  },
1364
1857
  {
1365
1858
  "tool": "wait_for_rubric_results",
1366
- "requiredFields": ["targetCount"],
1859
+ "requiredFields": [
1860
+ "targetCount"
1861
+ ],
1367
1862
  "targetCountSource": "stats.totalRows_or_imported_batch_count",
1368
1863
  "requiredValues": {
1369
1864
  "includeRows": false
1370
1865
  },
1371
- "note": "default targetCount=25 returns ready=true after first 25 completions even when cohort is still pending; always pass cohortSize explicitly (see references/sample-validation-loop.md §Known Tool Behaviors #3)",
1866
+ "note": "The shell-first flow tests 10 leads first; always pass cohortSize explicitly instead of relying on default 25 behavior.",
1372
1867
  "readVia": "stats_only_tool_result",
1373
- "extractFields": ["ready", "passRate.completed", "stats"],
1868
+ "extractFields": [
1869
+ "ready",
1870
+ "passRate.completed",
1871
+ "stats"
1872
+ ],
1374
1873
  "doNotRetain": "rows_payload",
1375
1874
  "contextBloatNote": "Pass includeRows=false so the MCP tool returns stats only; 25 rows ≈ 67KB and 100 rows ≈ 268KB when row payloads are retained (see references/sample-validation-loop.md §Known Tool Behaviors #2)."
1376
1875
  },
@@ -1392,8 +1891,9 @@
1392
1891
  ],
1393
1892
  "allowedTools": [
1394
1893
  "get_subskill_asset",
1395
- "get_rows_minimal",
1894
+ "queue_cells",
1396
1895
  "wait_for_campaign_table_ready",
1896
+ "get_rows_minimal",
1397
1897
  "wait_for_rubric_results",
1398
1898
  "update_campaign_brief",
1399
1899
  "update_campaign",
@@ -1411,18 +1911,28 @@
1411
1911
  "list_problem_never_triggers_autonomous_update_campaign_brief",
1412
1912
  "revision_round_persists_across_resume",
1413
1913
  "wait_for_rubric_results_never_retain_rows_payload_in_tail_context",
1414
- "wait_for_rubric_results_targetCount_always_explicit"
1914
+ "wait_for_rubric_results_targetCount_always_explicit",
1915
+ "review_batch_cascade_waits_for_saved_rubrics_and_approved_template"
1415
1916
  ],
1416
1917
  "watchRequired": true,
1417
- "waitFor": ["sample_validated", "sample_revision_required"],
1918
+ "waitFor": [
1919
+ "sample_validated",
1920
+ "sample_revision_required"
1921
+ ],
1418
1922
  "transitions": {
1419
1923
  "sample_validated": "auto-execute-messaging",
1420
1924
  "escalation_triggered": "escalation"
1421
- }
1925
+ },
1926
+ "requiredCampaignState": [
1927
+ "campaignId",
1928
+ "workflowTableId",
1929
+ "leadScoringRubrics",
1930
+ "approvedMessageTemplate"
1931
+ ]
1422
1932
  },
1423
1933
  {
1424
1934
  "id": "auto-execute-messaging",
1425
- "label": "Step 15: Generate messages (sample then scale)",
1935
+ "label": "Step 15: Complete messages for 10-lead test batch",
1426
1936
  "currentStepValue": "auto-execute-messaging",
1427
1937
  "references": [
1428
1938
  "references/parallel-critique-protocol.md",
@@ -1471,7 +1981,7 @@
1471
1981
  "requiredSection": "Approved Message Template",
1472
1982
  "requiredPattern": "{{...}}",
1473
1983
  "onFailure": "runtime_failure_before_messaging",
1474
- "repairBoundary": "pre_mint_only",
1984
+ "repairBoundary": "before_import_only",
1475
1985
  "purpose": "ensure Generate Message uses the approved template instead of full-generation mode"
1476
1986
  },
1477
1987
  {
@@ -1496,7 +2006,10 @@
1496
2006
  },
1497
2007
  {
1498
2008
  "tool": "update_campaign",
1499
- "requiredFields": ["campaignId", "currentStep"],
2009
+ "requiredFields": [
2010
+ "campaignId",
2011
+ "currentStep"
2012
+ ],
1500
2013
  "requiredValues": {
1501
2014
  "currentStep": "awaiting-user-greenlight"
1502
2015
  }
@@ -1512,7 +2025,11 @@
1512
2025
  "AskUserQuestion",
1513
2026
  "request_user_input"
1514
2027
  ],
1515
- "doNotAllow": ["import_leads", "start_campaign", "generate_messages"],
2028
+ "doNotAllow": [
2029
+ "import_leads",
2030
+ "start_campaign",
2031
+ "generate_messages"
2032
+ ],
1516
2033
  "hardRules": [
1517
2034
  "critique_failure_never_escalates",
1518
2035
  "critique_sample_size_bounded_by_config",
@@ -1524,7 +2041,10 @@
1524
2041
  "campaign_brief_must_contain_approved_message_template_tokens_before_generate_message_cascade"
1525
2042
  ],
1526
2043
  "watchRequired": true,
1527
- "waitFor": ["review_batch_ready", "sample_revision_required"],
2044
+ "waitFor": [
2045
+ "review_batch_ready",
2046
+ "sample_revision_required"
2047
+ ],
1528
2048
  "transitions": {
1529
2049
  "review_batch_ready": "awaiting-user-greenlight",
1530
2050
  "escalation_triggered": "escalation"
@@ -1532,16 +2052,37 @@
1532
2052
  },
1533
2053
  {
1534
2054
  "id": "awaiting-user-greenlight",
1535
- "label": "Step 16: Awaiting user greenlight",
2055
+ "label": "Step 16: Settings, sender, sequence, and greenlight handoff",
1536
2056
  "currentStepValue": "awaiting-user-greenlight",
1537
2057
  "reference": "references/final-handoff-contract.md",
1538
2058
  "onEnter": [
1539
2059
  {
1540
- "tool": "attach_recommended_sequence"
2060
+ "tool": "get_campaign",
2061
+ "purpose": "verify senderIds and sequence state before final handoff"
2062
+ },
2063
+ {
2064
+ "tool": "list_senders",
2065
+ "purpose": "surface available connected senders if the campaign has none attached"
2066
+ },
2067
+ {
2068
+ "action": "verify_sender_attached_or_route_to_settings",
2069
+ "settingsUrlPattern": "/campaign-builder/{campaignId}/settings?mode=claude",
2070
+ "onMissingSender": "surface settings link and STOP before sequence attach/start",
2071
+ "onSenderAttached": "continue_to_sequence_selection"
2072
+ },
2073
+ {
2074
+ "action": "verify_sequence_attached_or_route_to_sequence",
2075
+ "sequenceUrlPattern": "/campaign-builder/{campaignId}/sequence?mode=claude",
2076
+ "onMissingSequence": "surface sequence link and STOP before start",
2077
+ "onSequenceAttached": "continue_to_greenlight_handoff"
1541
2078
  },
1542
2079
  {
1543
2080
  "action": "surface_watch_link",
1544
- "requires": ["create_campaign_succeeded", "save_rubrics_succeeded"]
2081
+ "requires": [
2082
+ "campaign_shell_created",
2083
+ "rubrics_saved_to_campaign",
2084
+ "message_set_synced_to_brief"
2085
+ ]
1545
2086
  },
1546
2087
  {
1547
2088
  "action": "surface_source_materialization_summary",
@@ -1563,19 +2104,32 @@
1563
2104
  ],
1564
2105
  "allowedTools": [
1565
2106
  "get_subskill_asset",
1566
- "attach_recommended_sequence",
2107
+ "get_campaign",
2108
+ "list_senders",
1567
2109
  "AskUserQuestion",
1568
2110
  "request_user_input"
1569
2111
  ],
1570
- "doNotAllow": ["start_campaign", "import_leads"],
2112
+ "doNotAllow": [
2113
+ "start_campaign",
2114
+ "import_leads"
2115
+ ],
1571
2116
  "autoStart": false,
1572
2117
  "watchRequired": true,
1573
- "waitFor": ["user_greenlight", "ui_start_detected"],
2118
+ "waitFor": [
2119
+ "sender_attached",
2120
+ "sequence_attached",
2121
+ "user_greenlight",
2122
+ "ui_start_detected"
2123
+ ],
1574
2124
  "transitions": {
2125
+ "sender_attached": "awaiting-user-greenlight",
2126
+ "sequence_attached": "awaiting-user-greenlight",
1575
2127
  "user_greenlight": "claude-greenlight",
1576
2128
  "ui_start_detected": "running"
1577
2129
  },
1578
- "optionalRequiredArtifacts": ["lead-source-intake.json"]
2130
+ "optionalRequiredArtifacts": [
2131
+ "lead-source-intake.json"
2132
+ ]
1579
2133
  },
1580
2134
  {
1581
2135
  "id": "claude-greenlight",
@@ -1593,6 +2147,11 @@
1593
2147
  {
1594
2148
  "action": "validate_workspace_sender_invariant"
1595
2149
  },
2150
+ {
2151
+ "tool": "attach_recommended_sequence",
2152
+ "when": "sequence missing and campaign has attached sender",
2153
+ "purpose": "greenlight repair before approving/sending"
2154
+ },
1596
2155
  {
1597
2156
  "action": "bulk_approve_messages",
1598
2157
  "endpoint": "POST /api/v3/workflow-tables/cells/approve-batch",
@@ -1600,29 +2159,35 @@
1600
2159
  "doNotInventNewEndpoint": true
1601
2160
  },
1602
2161
  {
1603
- "tool": "start_campaign",
1604
- "requiredFields": ["campaignId"]
2162
+ "action": "verify_sequence_and_current_step_before_start",
2163
+ "requiredState": [
2164
+ "workflowTableId",
2165
+ "senderIds",
2166
+ "sequenceTemplate",
2167
+ "currentStep in awaiting-user-greenlight|claude-greenlight|send"
2168
+ ],
2169
+ "onFailure": "surface recoverable pre-start refusal and remain in awaiting-user-greenlight"
1605
2170
  },
1606
2171
  {
1607
- "tool": "update_campaign",
1608
- "requiredFields": ["campaignId", "currentStep"],
1609
- "requiredValues": {
1610
- "currentStep": "running"
1611
- }
2172
+ "tool": "start_campaign",
2173
+ "requiredFields": [
2174
+ "campaignId"
2175
+ ],
2176
+ "persistsCurrentStep": "running"
1612
2177
  },
1613
2178
  {
1614
2179
  "action": "surface_watch_link",
1615
2180
  "requires": [
1616
- "create_campaign_succeeded",
1617
- "save_rubrics_succeeded",
2181
+ "campaign_shell_created",
2182
+ "sequence_attached",
1618
2183
  "start_campaign_succeeded"
1619
2184
  ]
1620
2185
  }
1621
2186
  ],
1622
2187
  "allowedTools": [
1623
2188
  "get_campaign",
2189
+ "attach_recommended_sequence",
1624
2190
  "start_campaign",
1625
- "update_campaign",
1626
2191
  "AskUserQuestion",
1627
2192
  "request_user_input"
1628
2193
  ],
@@ -1644,8 +2209,14 @@
1644
2209
  "action": "surface_campaign_live_confirmation"
1645
2210
  }
1646
2211
  ],
1647
- "allowedTools": ["get_campaign", "AskUserQuestion", "request_user_input"],
1648
- "doNotAllow": ["start_campaign"],
2212
+ "allowedTools": [
2213
+ "get_campaign",
2214
+ "AskUserQuestion",
2215
+ "request_user_input"
2216
+ ],
2217
+ "doNotAllow": [
2218
+ "start_campaign"
2219
+ ],
1649
2220
  "terminal": true
1650
2221
  },
1651
2222
  {
@@ -1673,8 +2244,14 @@
1673
2244
  ]
1674
2245
  }
1675
2246
  ],
1676
- "allowedTools": ["AskUserQuestion", "request_user_input"],
1677
- "doNotAllow": ["start_campaign", "import_leads"],
2247
+ "allowedTools": [
2248
+ "AskUserQuestion",
2249
+ "request_user_input"
2250
+ ],
2251
+ "doNotAllow": [
2252
+ "start_campaign",
2253
+ "import_leads"
2254
+ ],
1678
2255
  "terminal": true
1679
2256
  },
1680
2257
  {
@@ -1697,8 +2274,15 @@
1697
2274
  ]
1698
2275
  }
1699
2276
  ],
1700
- "allowedTools": ["AskUserQuestion", "request_user_input"],
1701
- "doNotAllow": ["create_campaign", "save_rubrics", "import_leads"],
2277
+ "allowedTools": [
2278
+ "AskUserQuestion",
2279
+ "request_user_input"
2280
+ ],
2281
+ "doNotAllow": [
2282
+ "create_campaign",
2283
+ "save_rubrics",
2284
+ "import_leads"
2285
+ ],
1702
2286
  "terminal": true
1703
2287
  },
1704
2288
  {
@@ -1711,7 +2295,9 @@
1711
2295
  },
1712
2296
  {
1713
2297
  "action": "reset_draft_artifacts",
1714
- "preserve": ["brief.md"],
2298
+ "preserve": [
2299
+ "brief.md"
2300
+ ],
1715
2301
  "delete": [
1716
2302
  "lead-review.md",
1717
2303
  "lead-sample.json",
@@ -1721,8 +2307,15 @@
1721
2307
  ]
1722
2308
  }
1723
2309
  ],
1724
- "allowedTools": ["AskUserQuestion", "request_user_input"],
1725
- "doNotAllow": ["create_campaign", "save_rubrics", "import_leads"],
2310
+ "allowedTools": [
2311
+ "AskUserQuestion",
2312
+ "request_user_input"
2313
+ ],
2314
+ "doNotAllow": [
2315
+ "create_campaign",
2316
+ "save_rubrics",
2317
+ "import_leads"
2318
+ ],
1726
2319
  "terminal": true
1727
2320
  },
1728
2321
  {
@@ -1735,12 +2328,27 @@
1735
2328
  },
1736
2329
  {
1737
2330
  "action": "reset_draft_artifacts",
1738
- "preserve": ["brief.md", "lead-review.md", "lead-sample.json"],
1739
- "delete": ["lead-filter.md", "message-validation.md", "rubric.json"]
2331
+ "preserve": [
2332
+ "brief.md",
2333
+ "lead-review.md",
2334
+ "lead-sample.json"
2335
+ ],
2336
+ "delete": [
2337
+ "lead-filter.md",
2338
+ "message-validation.md",
2339
+ "rubric.json"
2340
+ ]
1740
2341
  }
1741
2342
  ],
1742
- "allowedTools": ["AskUserQuestion", "request_user_input"],
1743
- "doNotAllow": ["create_campaign", "save_rubrics", "import_leads"],
2343
+ "allowedTools": [
2344
+ "AskUserQuestion",
2345
+ "request_user_input"
2346
+ ],
2347
+ "doNotAllow": [
2348
+ "create_campaign",
2349
+ "save_rubrics",
2350
+ "import_leads"
2351
+ ],
1744
2352
  "terminal": true
1745
2353
  },
1746
2354
  {
@@ -1759,11 +2367,20 @@
1759
2367
  "lead-sample.json",
1760
2368
  "lead-filter.md"
1761
2369
  ],
1762
- "delete": ["message-validation.md"]
2370
+ "delete": [
2371
+ "message-validation.md"
2372
+ ]
1763
2373
  }
1764
2374
  ],
1765
- "allowedTools": ["AskUserQuestion", "request_user_input"],
1766
- "doNotAllow": ["create_campaign", "save_rubrics", "import_leads"],
2375
+ "allowedTools": [
2376
+ "AskUserQuestion",
2377
+ "request_user_input"
2378
+ ],
2379
+ "doNotAllow": [
2380
+ "create_campaign",
2381
+ "save_rubrics",
2382
+ "import_leads"
2383
+ ],
1767
2384
  "terminal": true
1768
2385
  },
1769
2386
  {
@@ -1775,9 +2392,46 @@
1775
2392
  "target": ".aborted/"
1776
2393
  }
1777
2394
  ],
1778
- "allowedTools": ["AskUserQuestion", "request_user_input"],
1779
- "doNotAllow": ["create_campaign", "save_rubrics", "import_leads"],
2395
+ "allowedTools": [
2396
+ "AskUserQuestion",
2397
+ "request_user_input"
2398
+ ],
2399
+ "doNotAllow": [
2400
+ "create_campaign",
2401
+ "save_rubrics",
2402
+ "import_leads"
2403
+ ],
1780
2404
  "terminal": true
1781
2405
  }
2406
+ ],
2407
+ "canonicalStateFields": [
2408
+ "campaignId",
2409
+ "watchUrl",
2410
+ "campaignBrief",
2411
+ "currentStep",
2412
+ "providerSearchAssociation",
2413
+ "selectedLeadListId",
2414
+ "workflowTableId",
2415
+ "leadScoringRubrics",
2416
+ "approvedMessageTemplate",
2417
+ "senderIds",
2418
+ "sequenceTemplate",
2419
+ "runningState"
2420
+ ],
2421
+ "debugArtifacts": [
2422
+ "brief.md",
2423
+ "campaign-shell.json",
2424
+ "lead-source-intake.json",
2425
+ "lead-review.md",
2426
+ "lead-sample.json",
2427
+ "lead-filter.md",
2428
+ "rubric.json",
2429
+ "message-validation.md",
2430
+ "message-prep.md",
2431
+ "message-candidate-drafts.md",
2432
+ "message-review.md",
2433
+ "message-review-decision.md",
2434
+ "approval-packet.md",
2435
+ "commit-gate-decision.md"
1782
2436
  ]
1783
2437
  }