@sellable/mcp 0.1.183 → 0.1.184
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/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/tools/bootstrap.js +1 -1
- package/dist/tools/campaigns.d.ts +1 -1
- package/dist/tools/campaigns.js +6 -1
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +10 -6
- package/skills/create-campaign-v2/SKILL.md +25 -25
- package/skills/create-campaign-v2/SOUL.md +8 -7
- package/skills/create-campaign-v2/core/flow.v2.json +1 -1
- package/skills/create-campaign-v2/references/approval-gate-framing.md +1 -1
- package/skills/create-campaign-v2/references/escalation-ladder.md +5 -3
- package/skills/create-campaign-v2/references/final-handoff-contract.md +13 -12
- package/skills/create-campaign-v2/references/watch-link-handoff.md +18 -12
- package/skills/create-campaign-v2-tail/SKILL.md +12 -13
package/dist/index-dev.js
CHANGED
|
File without changes
|
package/dist/index.js
CHANGED
|
File without changes
|
package/dist/tools/bootstrap.js
CHANGED
|
@@ -270,7 +270,7 @@ export async function bootstrapCreateCampaign(input = {}) {
|
|
|
270
270
|
? resumeDetected
|
|
271
271
|
? `Bootstrap complete.${workspaceNotice} Resume from campaign state and navigation diagnostics first; treat local draft artifacts as debug-only evidence. Then load ${createCampaignSubskill?.name ?? "create-campaign"} instructions with get_subskill_prompt({ subskillName: "${createCampaignSubskill?.name ?? "create-campaign"}" }); if the response has hasMore=true, continue with nextOffset until hasMore=false.`
|
|
272
272
|
: flowVersion === "v2"
|
|
273
|
-
? `Bootstrap complete.${workspaceNotice} Load the compact create-campaign-v2 entry prompt once with get_subskill_prompt({ subskillName: "create-campaign-v2" }); load flow/reference assets lazily only when that stage needs them. Preserve the pre-intake sequence: confirm auth/workspace status, ask only for the LinkedIn profile URL or handle, normalize handles to a full profile URL, require that profile identity before continuing, run lightweight profile/company lookup, then ask the target, offer, credibility, and prospect-source setup questions. Do not call list_senders or sender discovery during setup; sender availability belongs only to Settings after message approval. Then write the campaign brief, call create_campaign once to mint the watchable shell, surface the returned watch link, and hand off to lead finding.`
|
|
273
|
+
? `Bootstrap complete.${workspaceNotice} Load the compact create-campaign-v2 entry prompt once with get_subskill_prompt({ subskillName: "create-campaign-v2" }); load flow/reference assets lazily only when that stage needs them. Preserve the pre-intake sequence: confirm auth/workspace status, ask only for the LinkedIn profile URL or handle, normalize handles to a full profile URL, require that profile identity before continuing, run lightweight profile/company lookup, then ask the target, offer, credibility, and prospect-source setup questions. Do not call list_senders or sender discovery during setup; sender availability belongs only to Settings after message approval. Then write the campaign brief, call create_campaign once to mint the watchable shell, surface the returned watch link once before brief approval, and hand off to lead finding without repeating the link.`
|
|
274
274
|
: `Bootstrap complete.${workspaceNotice} Load ${createCampaignSubskill?.name ?? "create-campaign"} instructions with get_subskill_prompt({ subskillName: "${createCampaignSubskill?.name ?? "create-campaign"}" }); if the response has hasMore=true, continue with nextOffset until hasMore=false. Follow that flow before calling create_campaign.`
|
|
275
275
|
: "Bootstrap incomplete. Resolve blockingErrors and rerun bootstrap_create_campaign before provider/search/import tools.";
|
|
276
276
|
// Strip prompt body from createCampaignSubskill — it's loaded via the host
|
|
@@ -568,7 +568,7 @@ export interface CreateCampaignResult {
|
|
|
568
568
|
watchUrl: string;
|
|
569
569
|
resumed?: boolean;
|
|
570
570
|
warning?: string;
|
|
571
|
-
approvalGate
|
|
571
|
+
approvalGate?: {
|
|
572
572
|
kind: "brief-review";
|
|
573
573
|
mustShowBeforeQuestion: string[];
|
|
574
574
|
questionToolBlockedUntilShown: true;
|
package/dist/tools/campaigns.js
CHANGED
|
@@ -666,6 +666,11 @@ function buildBriefApprovalGate(watchUrl) {
|
|
|
666
666
|
"Do not call AskUserQuestion or request_user_input until the user has seen both the brief content and this watch-link handoff.",
|
|
667
667
|
};
|
|
668
668
|
}
|
|
669
|
+
function buildResumeNextStep() {
|
|
670
|
+
return {
|
|
671
|
+
nextStep: "Resume state returned. Use campaign currentStep/watchNarration to orient the user. Do not print the watchUrl again during normal campaign progress or approval turns unless the user explicitly asked for the link or diagnostics require replacing a missing/broken watch URL.",
|
|
672
|
+
};
|
|
673
|
+
}
|
|
669
674
|
export async function createCampaign(input) {
|
|
670
675
|
const api = getApi();
|
|
671
676
|
const config = getConfig();
|
|
@@ -683,7 +688,7 @@ export async function createCampaign(input) {
|
|
|
683
688
|
createdAt: existing.createdAt,
|
|
684
689
|
currentStep: existing.currentStep ?? null,
|
|
685
690
|
watchUrl,
|
|
686
|
-
...
|
|
691
|
+
...buildResumeNextStep(),
|
|
687
692
|
resumed: true,
|
|
688
693
|
warning: hasCreateFields
|
|
689
694
|
? "campaignId provided; resume mode ignores create fields."
|
package/package.json
CHANGED
|
@@ -287,12 +287,16 @@ Use rendered Markdown for user review surfaces, not fenced code blocks. Keep
|
|
|
287
287
|
lines short, use indexed section labels and bullets, and translate internal
|
|
288
288
|
sourcing terms into plain language.
|
|
289
289
|
|
|
290
|
-
|
|
291
|
-
content. Show a short `Watch link:` line once
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
290
|
+
Only the first brief approval handoff should include live campaign access after
|
|
291
|
+
the readable inline content. Show a short `Watch link:` line once, immediately
|
|
292
|
+
after shell creation and before brief approval. Later approval gates should
|
|
293
|
+
describe what the already-open campaign app is showing and rely on
|
|
294
|
+
currentStep/watchNarration; do not print the URL again unless the user asks or a
|
|
295
|
+
recovery path must replace a missing/broken link. In normal customer runs, do
|
|
296
|
+
not show `Open artifact:` lines, raw filesystem paths, or local draft filenames.
|
|
297
|
+
Local artifacts are debug/UAT-only unless the user asks for them. The link is
|
|
298
|
+
for deeper inspection; never use it as a substitute for showing the content in
|
|
299
|
+
chat.
|
|
296
300
|
|
|
297
301
|
Never mention MCP namespaces, prompt chunking, plugin cache paths, missing
|
|
298
302
|
linked skill versions, runbooks, npm/package details, repo-local files, VPS or
|
|
@@ -8,7 +8,7 @@ visibility: internal
|
|
|
8
8
|
|
|
9
9
|
You are the create-campaign-v2 orchestrator. Run the active state machine in
|
|
10
10
|
`core/flow.v2.json`; load deeper references only at the stage that needs them.
|
|
11
|
-
This
|
|
11
|
+
This is the fast entry point, not the full manual.
|
|
12
12
|
|
|
13
13
|
Load the compact flow when you need exact gates:
|
|
14
14
|
|
|
@@ -23,14 +23,12 @@ validate-sample tool:
|
|
|
23
23
|
get_subskill_prompt({ subskillName: "create-campaign-v2-tail" })
|
|
24
24
|
```
|
|
25
25
|
|
|
26
|
-
CampaignOffer state
|
|
27
|
-
|
|
28
|
-
`campaignId`, `watchUrl`, `campaignBrief`, `currentStep`, source/search
|
|
26
|
+
CampaignOffer state, including `watchUrl`, is canonical for resume, gating, and
|
|
27
|
+
handoff: `campaignId`, `campaignBrief`, `currentStep`, source/search
|
|
29
28
|
association, `selectedLeadListId`, `workflowTableId`, `leadScoringRubrics`,
|
|
30
29
|
`approvedMessageTemplate`, `senderIds`, `sequenceTemplate`, and running state.
|
|
31
|
-
Local draft files are debug; do not create, link, or surface them in
|
|
32
|
-
normal runs.
|
|
33
|
-
state first.
|
|
30
|
+
Local draft files are debug outputs; do not create, link, or surface them in
|
|
31
|
+
normal runs.
|
|
34
32
|
|
|
35
33
|
## Normal Flow
|
|
36
34
|
|
|
@@ -41,7 +39,7 @@ Legacy commit-gate/atomic-mint is only in create-campaign-v2-validation.
|
|
|
41
39
|
2. Resolve the client/company before strategy questions.
|
|
42
40
|
3. Research the client/company and current offer enough to draft a concrete brief.
|
|
43
41
|
4. Create the watchable campaign shell with `create_campaign` and the v1 brief.
|
|
44
|
-
5. Surface the direct watch link.
|
|
42
|
+
5. Surface the direct watch link once before brief approval.
|
|
45
43
|
6. Choose and approve the lead source.
|
|
46
44
|
7. Materialize the approved source list and confirm 15 campaign-table rows for
|
|
47
45
|
filter/message setup; do not present that as source sampling.
|
|
@@ -152,10 +150,13 @@ when a bounded choice needs an escape hatch.
|
|
|
152
150
|
|
|
153
151
|
## Watch Link And Narration
|
|
154
152
|
|
|
155
|
-
The first
|
|
156
|
-
the
|
|
157
|
-
|
|
158
|
-
|
|
153
|
+
The first shell is created before lead import so the user can watch the work.
|
|
154
|
+
Print the returned `watchUrl` once, before brief approval. Later normal updates
|
|
155
|
+
and approval turns must not print `Watch link:`; update `currentStep` and
|
|
156
|
+
`watchNarration` so the already-open app changes live. Reprint only on user
|
|
157
|
+
request or missing/broken URL recovery. Never call browser-opening tools,
|
|
158
|
+
Computer Use, shell `open`, or in-app browser automation just because a watch
|
|
159
|
+
link exists.
|
|
159
160
|
|
|
160
161
|
Every watched step switch must call:
|
|
161
162
|
|
|
@@ -179,9 +180,9 @@ Prospeo because `search_prospeo` supports
|
|
|
179
180
|
is only an activity/referral fallback because it does not provide hiring-by-role filters.
|
|
180
181
|
|
|
181
182
|
Before any provider prompt/search/scout call, move the watched campaign to
|
|
182
|
-
provider/source selection, then show one `## Find Buyers Plan` gate
|
|
183
|
-
|
|
184
|
-
|
|
183
|
+
provider/source selection, then show one `## Find Buyers Plan` gate before
|
|
184
|
+
`request_user_input`, without repeating the watch URL. This approval only
|
|
185
|
+
authorizes finding where to look. It does not add anyone yet. Write:
|
|
185
186
|
|
|
186
187
|
- the buyer groups or places we could check
|
|
187
188
|
- the best place to start
|
|
@@ -199,8 +200,7 @@ after fit; leads=rows.
|
|
|
199
200
|
|
|
200
201
|
Do not surface blanket source heuristics. Make the recommendation campaign
|
|
201
202
|
specific. If LinkedIn engagement is recommended, name the exact post themes in
|
|
202
|
-
plain language
|
|
203
|
-
they don't trust" or "teams trying to agree on the right KPIs." Avoid using
|
|
203
|
+
plain language. Avoid using
|
|
204
204
|
"Signal Discovery", "lead-source scouting", "source scouting", "lane", "provider",
|
|
205
205
|
"precision/scale tradeoff", "evidence quality", "pilot volume", "workflow
|
|
206
206
|
pain", or "ICP" in customer-facing chat; those are internal labels. If the
|
|
@@ -310,14 +310,14 @@ filter-choice question immediately. Do not call
|
|
|
310
310
|
`get_post_find_leads_scout_registry`, load filter/message subskill prompts, or
|
|
311
311
|
spawn post-lead workers before that question. The only customer-facing work
|
|
312
312
|
before the question should be a short campaign setup summary and the choice:
|
|
313
|
-
add filters, skip filters, or revise source.
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
filter-choice
|
|
319
|
-
Messages and run
|
|
320
|
-
are optional only.
|
|
313
|
+
add filters, skip filters, or revise source. Do not include another watch link
|
|
314
|
+
in this summary; the existing app view updates through campaign state.
|
|
315
|
+
|
|
316
|
+
If the user chooses filters, load the post-lead registry/reference needed for
|
|
317
|
+
filtering and start the Lead Fit Builder. Also start the Message Draft Builder
|
|
318
|
+
from the same campaign/table basis once filter-choice is known. If filters are
|
|
319
|
+
skipped, move the watched app to Messages and run Message Draft Builder there.
|
|
320
|
+
Debug markdown/json artifacts are optional only.
|
|
321
321
|
|
|
322
322
|
When the user chooses filters, immediately call
|
|
323
323
|
`update_campaign({ campaignId, enableICPFilters: true, currentStep: "create-icp-rubric", watchNarration })`
|
|
@@ -68,11 +68,12 @@ Approvals only feel safe when the user can see what they are approving. Before
|
|
|
68
68
|
any approve/revise question, show the relevant decision in plain language. For a
|
|
69
69
|
brief approval, render the brief itself, not just a direction summary.
|
|
70
70
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
71
|
+
Only the first brief approval handoff should print the campaign watch URL. After
|
|
72
|
+
that, every approval should say what the already-open app is showing, but must
|
|
73
|
+
not include another `Watch link:` line unless the user explicitly asks for the
|
|
74
|
+
link again or a recovery path needs to replace a broken/missing URL. Local
|
|
75
|
+
artifacts are optional debug/UAT diagnostics only; do not surface `Open
|
|
76
|
+
artifact:` links, file paths, or local draft filenames in normal customer runs.
|
|
76
77
|
|
|
77
78
|
This applies especially to message approvals. Never ask someone to approve a
|
|
78
79
|
message they cannot see. Show the subject, tokenized template, a filled sample
|
|
@@ -241,8 +242,8 @@ Lead sourcing has two approvals. First, before any provider prompt/search/scout
|
|
|
241
242
|
call, move the watched campaign to the provider/source page, then give the user
|
|
242
243
|
a compact source plan in grade-5 language: "here are the places we could look, I
|
|
243
244
|
recommend starting here, here is why, and here is where I'll look next if this
|
|
244
|
-
is too thin." The order matters: show the plan
|
|
245
|
-
|
|
245
|
+
is too thin." The order matters: show the plan in normal chat first, then open
|
|
246
|
+
the approval question. Ask them to approve that plan or choose
|
|
246
247
|
a different source. Use plain customer language for the boundary: "This only
|
|
247
248
|
approves me to look for the best places to find buyers. I won't add anyone
|
|
248
249
|
yet." After brief approval, say "Brief approved. Next, we'll choose where to
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":"v2.1-compact","workflow":"create-campaign-v2","principle":"CampaignOffer state and watch link are canonical. Create the watched shell, approve source, materialize and confirm the source list, internally process the first campaign-table execution slice, save rubrics for approval, approve the message template, then run the bounded filter/message cascade before Settings, sequence, and explicit start.","normalCustomerPath":"Use campaign state, MCP responses, and concise watchNarration. Do not create, read, link, or surface local draft files.","legacyCompatibility":{"validationSubskill":"create-campaign-v2-validation","tailSubskill":"create-campaign-v2-tail","rule":"Legacy validation/rehearsal files are opt-in diagnostics only and are not active flow gates."},"commitGateChoices":["approve","revise-brief","revise-leads","revise-rubric","revise-messaging","abort"],"canonicalStateFields":["campaignId","watchUrl","campaignBrief","currentStep","watchNarration","interactionMode","providerSearchAssociation","selectedLeadListId","workflowTableId","filterChoice","leadScoringRubrics","messageDraftRecommendation","approvedMessageTemplate","senderIds","sequenceTemplate","runningState"],"watchNarrationTransitionContract":{"rule":"Every watched currentStep switch must include fresh watchNarration.","requiredFields":["stage","headline","visibleState","agentIntent","nextAction"],"copyMustInclude":["what just happened","current visible page/state","next user action"],"appliesToTools":["create_campaign","update_campaign","attach_recommended_sequence","start_campaign"]},"lazyReferences":{"watch":["references/watch-link-handoff.md","references/watch-guide-narration.md"],"source":["references/lead-validation-preview.md","references/step-13-import-leads.md"],"filter":["references/filter-leads.md"],"message":[],"tail":["references/sample-validation-loop.md","references/step-15-re-cascade.md","references/final-handoff-contract.md"]},"safetyBoundaries":["Do not call list_senders before Settings after message approval.","Do not import leads until the source decision is approved.","Do not queue cells until confirmed source rows exist in the campaign and the message/filter gates are satisfied.","Do not call start_campaign until the user explicitly confirms launch.","Do not use local files as durable state in normal customer runs."],"yoloMode":{"triggerPhrases":["yolo","--yolo","mode=yolo","autopilot","use best guesses","use best estimates","answer for me","just run it"],"identityRequestOnlyWhenMissing":true,"operatorDirectionsRule":"Treat freeform directions supplied at invocation or later as durable operator directions; newest conflicting direction wins.","setInteractionModeAfterCampaignCreate":"autonomous","autoSelectsPreLaunchChoices":["campaign focus","brief approval","source plan","source review/import approval","filters vs skip filters","filter rubric approval","message template approval when recommendation is approve-message","generated message review when quality floor passes","sender selection when exactly one connected sender is safe"],"mustPauseFor":["missing LinkedIn profile URL or handle","missing credentials or required data","ambiguous choice with no reasonable estimate","source/message/filter quality floor failure","final live launch confirmation"],"neverAutoStart":true},"steps":[{"id":"bootstrap","label":"Bootstrap","onEnter":[{"tool":"bootstrap_create_campaign","requiredValues":{"flowVersion":"v2"}},{"tool":"get_subskill_prompt","requiredValues":{"subskillName":"create-campaign-v2"},"purpose":"load the compact entry prompt once"}],"allowedTools":["bootstrap_create_campaign","get_auth_status","get_active_workspace","get_subskill_prompt","AskUserQuestion","request_user_input"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","update_campaign","queue_cells","start_campaign"],"waitFor":"bootstrap_complete","transitions":{"bootstrap_complete":"brief-interview"}},{"id":"brief-interview","label":"Client, offer, research, and brief","onEnter":[{"action":"resolve_campaign_identity_before_strategy_packet","allowedTools":["fetch_company","fetch_linkedin_profile","WebFetch","WebSearch"],"mustNotInferFromNameOnly":true,"doNotPresentSenderPickerBeforeIdentityInference":true,"fallback":"Ask only: \"What is your LinkedIn profile URL or handle?\" If the user provides a bare handle like `csreyes92` or a `/in/...` path, normalize it to `https://www.linkedin.com/in/{handle}/`. If the user provides a non-profile URL, company page, or anything else, ask again for the person's LinkedIn profile URL or handle before strategy questions.","requiresLinkedInProfileUrl":true,"doNotAcceptAsIdentityInput":["non-profile URL","company page","company/name-only input without a profile handle"],"acceptsLinkedInProfileHandle":true,"normalizeLinkedInProfileHandleToUrl":true},{"action":"run_campaign_identity_research_before_strategy_packet","target":"research-sender","requiredCompletion":"complete_sender_research","allowedTools":["get_subskill_prompt","fetch_linkedin_profile","fetch_company","fetch_linkedin_posts","fetch_company_posts","WebFetch","WebSearch","complete_sender_research"],"requiredInput":"LinkedIn profile URL or handle"},{"action":"confirm_target_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"Who should we target first?","options":["Use Sellable's researched recommendation","Choose a different buyer","I'm not sure yet"],"copyMustInclude":["research basis","recommendation is editable","who to target"],"requiredOption":"Other / custom","neverCollectOpenTextWithStructuredQuestion":true,"skipIf":"user already supplied a clear target or yolo_mode with any reasonable best-estimate target"},{"action":"confirm_current_offer_before_strategy_packet","uses":"request_user_input","question":"What should we pitch to prospects first?","options":["Use Sellable's researched recommendation","Use my current pitch","Shape the pitch together"],"copyMustInclude":["research basis","public LinkedIn research may be stale","recommendation is editable","pitch to prospects"],"skipIf":"user already supplied a clear current offer or yolo_mode with any reasonable researched recommendation"},{"action":"confirm_trust_proof_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"What is the strongest credibility signal we can lead with?","options":["Use Sellable's recommended proof","Use customer proof or a case study","Use founder or company credibility"],"copyMustInclude":["build trust with prospects","recommendation is editable","proof to use or avoid"],"requiredOption":"Other / custom","skipIf":"user already supplied clear proof to use or avoid, or yolo_mode with any reasonable researched proof"},{"action":"choose_prospect_source_path_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"How should we find prospects?","options":["Find prospects for me","Use a CSV of LinkedIn profiles","Use a CSV of company domains"],"copyMustInclude":["prospects","no one added yet","find prospects for me"],"skipIf":"user already supplied a source path or yolo_mode with any reasonable source path"},{"action":"infer_strategy_packet_in_yolo_mode","when":"yolo_mode","skipStructuredSetupQuestions":true,"inferFields":["buyer segment","offer/CTA","proof to use or avoid","lead source","filter choice","message direction"],"sourceOfTruth":"identity/company lookup plus operator directions","mustStateAssumptionsInBrief":true},{"action":"render_supplied_setup_receipt_before_brief_when_setup_questions_skip","when":"any setup question is skipped because the user already supplied or yolo mode inferred identity, target, offer, proof, or source","output":"normal chat receipt before the campaign brief or watch-link handoff","copyMustInclude":["Accepted LinkedIn input: normalized to the required LinkedIn profile URL.","Offer path: supplied current offer or Sellable's researched recommendation; you can replace it with your current offer."],"copyMustNotInclude":["Campaign Identity"]},{"action":"create_watchable_campaign_shell_with_v1_brief","tool":"create_campaign","requiredFields":["name","campaignBrief","clientProspectId or senderLinkedinUrl","currentStep","watchNarration"],"requiredValues":{"currentStep":"create-offer","watchNarration.stage":"brief"},"capture":["campaignId","watchUrl"],"canonicalStateWrites":["campaignId","watchUrl","campaignBrief","currentStep:create-offer"]},{"action":"surface_campaign_shell_watch_link","watchUrlSource":"create_campaign.watchUrl","requiredWatchUrlShape":"direct /campaign-builder/{campaignId}?mode={claude|codex} watch URL with token auto-login and workspace routing","copyMustInclude":["We'll keep building the campaign in this chat.","You can watch it being built in real time in the app here:","I'll ask for your approval whenever I need your expertise or taste before moving forward.","Send changes here."],"immediateNextMainChatLine":"Next, we'll choose where to find buyers. I won't add anyone yet.","codexBrowserHandoff":{"openWhenAvailable":false,"printWatchLinkOnly":true,"tellUserCommandEnterOrClick":false,"mustNotUseBrowserAutomation":true,"fallbackMustNotClaimInspection":true}}],"allowedTools":["get_subskill_prompt","fetch_company","fetch_company_posts","fetch_linkedin_profile","fetch_linkedin_posts","complete_sender_research","create_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["campaign_shell_created","brief_ready","confirm_with_user"],"transitions":{"campaign_shell_created":"brief-review","brief_ready":"brief-review","confirm_with_user":"brief-review"}},{"id":"brief-review","label":"Brief review","onEnter":[{"action":"render_brief_inline","minimumVisibleDetail":"campaign direction, buyer, offer, proof, source plan, safety gates","copyMustInclude":["This brief is based on Sellable's research and your answers.","If your site or LinkedIn is stale","Do you agree with this researched campaign direction?"],"copyMustNotInclude":["quoted first-message copy","Message Angle as final proof","This approval covers","does not approve adding people","does not approve scraping posts","selecting a sender","attaching a sequence","Campaign Identity","what should this campaign sell first","sell first","[from you]","[from LinkedIn]","[from website]","[from case study]","[Sellable recommendation]"],"messageHintRule":"If any message-shape hint remains, keep it as non-final bullets and say real examples come after leads are found and filtered.","approvalBoundaryRule":"Keep this gate positive and short. Do not list later steps this approval does not cover; ask approve/revise and then move to choosing where to find buyers.","sectionLabelRule":"Use ## Sender and Company for the person/company context; never render ## Campaign Identity."},{"action":"ask_brief_choice","uses":"request_user_input","choices":["Approve brief","Revise brief","Pause here"],"skipIf":"yolo_mode; auto_continue after rendering assumptions unless brief quality floor fails"}],"requiredCampaignState":["campaignId","watchUrl","campaignBrief","currentStep"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign"],"doNotAllow":["create_campaign","list_senders","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["user_brief_confirmed","revise_brief","auto_continue"],"transitions":{"user_brief_confirmed":"find-leads","revise_brief":"brief-interview","auto_continue":"find-leads"}},{"id":"find-leads","label":"Find leads","sourceSelectionFunnel":{"preScoutRecommendationGate":{"required":true,"label":"Find buyers plan approval","mustHappenBefore":["get_provider_prompt","search_signals","search_sales_nav","search_prospeo","fetch_post_engagers","source-scout dispatch"],"show":["buyer groups or places we could check","best place to start","why the right buyers are likely to be there","what signs the next search will check","where to look next if the first place is too thin","what approval authorizes"],"approvalAuthorizes":"looking for the best places to find buyers; no one added yet","explanationMustInclude":["where the right buyers are already talking","why that place fits this campaign","enough likely prospects","what counts as a good sign","where to look next if thin","I won't add anyone yet","choose where to find buyers"],"yoloMode":{"autoApproveRecommendedLane":true,"mustShowAssumedChoice":true,"pauseIfConfidenceLow":true},"customerLanguage":{"readability":"grade-5","requiredHeading":"Find Buyers Plan","prefer":["place to look","best place to start","people to check","prospects","I won't add anyone yet"],"avoid":["lane","source scouting","provider","precision/scale tradeoff","evidence quality","pilot volume","ICP","workflow pain","lead-source scouting","not importing leads"],"example":"Brief approved. Next, we'll choose where to find buyers. I won't add anyone yet. I recommend starting with people already talking on LinkedIn about [plain topic]. That should help us find people who already care about [plain problem]. If this is too thin, I'll try [plain fallback] next.","termRule":{"buyers":"target market","peopleToCheck":"raw reactions/comments before fit is known","prospects":"likely usable people after fit","leads":"campaign rows"}},"questionOrder":"update_campaign to pick-provider, render ## Find Buyers Plan in normal chat with watch link, then open the structured approval question"},"defaultWhenSourceUnspecified":["signal-discovery","sales-nav-recent-active","sales-nav-general","prospeo"],"userFacingFallbackOrder":["LinkedIn posts where the right buyers are already talking","people with the right titles who recently posted on LinkedIn","people with the right titles from a broader LinkedIn search","a broader company and contact search"],"parallelAllowedOnlyWhen":["user explicitly requested source comparison","resuming already-started parallel scouts","first viable source is borderline and one cheap fallback check is needed"]},"onEnter":[{"tool":"update_campaign","requiredValues":{"currentStep":"pick-provider","watchNarration.stage":"find-leads","watchNarration.headline":"Review where to find buyers","watchNarration.visibleState":"The app is showing source selection before lead finding starts.","watchNarration.agentIntent":"Codex is explaining where it recommends looking first and where it will look next if that is too thin.","watchNarration.nextAction":"Approve where to look first or choose another place","watchNarration.safety":"Only deciding where to look. No one is added yet."},"purpose":"show the visible source-plan approval checkpoint before provider lanes"},{"tool":"get_source_scout_registry","purpose":"load canonical source scout names before optional branch launch"},{"action":"render_find_buyers_plan_inline","oneShot":true,"requiredPrecondition":"currentStep=pick-provider has been saved with update_campaign","mustHappenAfter":["update_campaign currentStep=pick-provider"],"mustHappenBefore":["ask_find_buyers_plan_choice","get_provider_prompt","search_signals","search_sales_nav","search_prospeo","fetch_post_engagers","source-scout dispatch"],"output":"normal chat text before any request_user_input approval panel","requiredHeading":"Find Buyers Plan","requiredInlineFields":["plain-language buyer groups or places for this campaign","best place to start","why the right buyers are likely to be there","what signs the next search will check","where to look next if the first place is too thin","what approval authorizes"],"copyMustInclude":["Find Buyers Plan","best place to start","I won't add anyone","choose where to find buyers","Watch link:","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"],"approvalBoundary":"Explain what approval authorizes before asking: looking for the best places to find buyers; no one is added yet."},{"action":"ask_find_buyers_plan_choice","uses":"request_user_input","oneShot":true,"requiredPrecondition":"render_find_buyers_plan_inline completed in normal chat after pick-provider state was saved","skipIf":"approved or leadSourceProvider or yolo_mode auto-selected source_lane_approved","choices":["Start with LinkedIn posts","Start with active LinkedIn profiles","Start with company/contact search","Choose a different place","Pause here"],"approvalChoiceLabelsByProvider":{"signal-discovery":"Start with LinkedIn posts","sales-nav":"Start with active LinkedIn profiles","prospeo":"Start with company/contact search"},"approvalState":"source_lane_approved","copyMustInclude":["Approve this plan for where to find buyers?","Start with LinkedIn posts"],"mustNotHappenBefore":["render_find_buyers_plan_inline"]},{"action":"persist_provider_search_step_before_search","tool":"update_campaign","requiredPrecondition":"source_lane_approved","providerCurrentStepMap":{"signal-discovery":"signal-discovery","sales-nav":"sales-nav","prospeo":"prospeo"},"requiredValues":{"leadSourceType":"new","leadSourceProvider":"approved provider","currentStep":"provider-specific current step","watchNarration.stage":"find-leads","watchNarration.headline":"Searching the approved place","watchNarration.safety":"Looking only. No one is added yet."},"mustRunBefore":["search_signals","search_sales_nav","search_prospeo","fetch_post_engagers"]},{"action":"run_sequential_source_funnel","requiredPrecondition":"source_lane_approved","defaultOrder":["source-scout-linkedin-engagement","source-scout-sales-nav","source-scout-prospeo-contact"],"campaignOfferIdRequired":true,"stopOnFirstViableUnlessComparisonRequested":true}],"requiredCampaignState":["campaignId","campaignBrief","currentStep"],"allowedTools":["get_source_scout_registry","get_provider_prompt","lookup_sales_nav_filter","search_sales_nav","search_prospeo","search_signals","select_promising_posts","fetch_post_engagers","fetch_company","fetch_linkedin_profile","load_csv_linkedin_leads","load_csv_domains","get_rows_minimal","update_campaign","AskUserQuestion","request_user_input","get_campaign_navigation_state"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["lead_review_ready","revise_brief","confirm_with_user"],"transitions":{"lead_review_ready":"lead-review","revise_brief":"brief-interview","confirm_with_user":"lead-review"}},{"id":"lead-review","label":"Source approval","onEnter":[{"action":"show_source_decision_card","requiredInlineFields":["primary source and exact filters/recipe","specific source action awaiting approval","for Signal Discovery: compact Source Recommendation in plain language with selected posts, people to check, likely prospects, first review, and fallback","for Signal Discovery: recommended scrape post count and target people-to-check volume","first campaign review size, clearly separate from source sampling","runner-up and why it lost","raw volume","sampled people","sampled fits as n/N plus percentage/range","estimated usable prospects","cleanup risk","what will happen after approval"],"copyMustAvoid":["lead-source scouting","source scouting","headline-fit","engagers needed","execution slice","ICP","good buyers","real buyers","This approval covers","It does not approve","does not approve filters","filters, messages, sender selection","sender selection, sequence, or sending"],"approvalBoundaryRule":"Use positive source-action copy only. Include: \"After approval, I will build the source list, add it to the campaign, and review the first 15 leads before we scale.\" Never write \"This approval covers\" or \"It does not approve\" sentences."},{"action":"ask_source_review_choice","uses":"request_user_input","choices":["Approve scraping N recommended LinkedIn posts","Run the approved source import","Revise source","Pause here"],"approvalChoiceLabelsByProvider":{"signal-discovery":"Approve scraping {scrapePostCount} recommended LinkedIn posts?","sales-nav":"Import the approved Sales Nav source list","prospeo":"Import the approved Prospeo source list"},"postApprovalContract":{"singleUseApproval":true,"doNotRepeatAfterApproval":["Source Recommendation","show_source_decision_card","ask_source_review_choice"],"requiredNextActionAfterApproval":"ack once; call import_leads immediately","signalDiscoveryNextTool":"import_leads({ provider: \"signal-discovery\", targetEngagerCount, maxPostsToScrape, confirmed: true })"},"autoSelectIf":"yolo_mode and projected usable pool clears the source quality floor"}],"requiredCampaignState":["campaignId","campaignBrief","providerSearchAssociation"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["lead_review_confirmed","revise_leads","confirm_with_user","auto_continue"],"transitions":{"lead_review_confirmed":"auto-execute-leads","revise_leads":"find-leads","confirm_with_user":"auto-execute-leads","auto_continue":"auto-execute-leads"}},{"id":"auto-execute-leads","label":"Materialize confirmed source list","currentStepValue":"auto-execute-leads","reference":"references/step-13-import-leads.md","onEnter":[{"tool":"get_subskill_prompt","requiredValues":{"subskillName":"create-campaign-v2-tail"},"purpose":"load tail contract before execution tools"},{"tool":"import_leads","requiredFields":["campaignOfferId","selected source/list","sourceListTarget or targetEngagerCount"],"requiredValues":{"salesNavProspeoDefaultSourceListTarget":1000,"signalDiscoveryDefaultEngagerTarget":1500},"modeAddHandshake":{"firstCallReturns":"needsModeSelection when adding to an existing campaign-attached list","requiredFollowup":"retry with mode=add after explicit user/source-state compatibility is confirmed"},"surfaceDedupRatio":true},{"tool":"wait_for_lead_list_ready"},{"tool":"confirm_lead_list","requiredFields":["campaignOfferId","selectedLeadListId","reviewBatchLimit"],"requiredValues":{"reviewBatchLimit":15},"capture":["workflowTableId","reviewBatchRowIds"]},{"tool":"wait_for_campaign_table_ready"},{"tool":"get_rows_minimal","requiredValues":{"tableId":"{workflowTableId}","limit":15},"capture":["reviewBatchRowHash"]},{"action":"summarize_review_batch_and_advance_to_filter_choice","purpose":"ask filter choice immediately; no post-lead registries, filter refs, or message prompts first","maxCustomerCopyLines":4}],"requiredCampaignState":["campaignId","providerSearchAssociation","selectedLeadListId"],"allowedTools":["get_subskill_prompt","import_leads","wait_for_lead_list_ready","confirm_lead_list","wait_for_campaign_table_ready","get_rows_minimal","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["get_post_find_leads_scout_registry","Task","spawn_agent","list_senders","queue_cells","start_campaign","enrich_with_prospeo","bulk_enrich_with_prospeo","save_rubrics"],"waitFor":"source_list_confirmed_and_review_sample_ready","transitions":{"source_list_confirmed_and_review_sample_ready":"filter-choice","escalation_triggered":"escalation"}},{"id":"filter-choice","label":"Filter choice","currentStepValue":"filter-choice","onEnter":[{"tool":"update_campaign","requiredValues":{"currentStep":"filter-choice","watchNarration.stage":"fit-message"}},{"action":"ask_filter_choice","uses":"request_user_input","choices":["Use filters","Skip filters","Revise source"],"autoSelectIf":"yolo_mode; choose filters unless source is tightly curated or user directed otherwise"}],"hardRules":["ask_filter_choice_immediately_after_review_batch_import","do_not_call_get_subskill_prompt_before_filter_choice","do_not_call_get_subskill_asset_before_filter_choice","do_not_call_get_post_find_leads_scout_registry_before_filter_choice","do_not_spawn_post_lead_agents_before_filter_choice"],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign","get_campaign_navigation_state"],"doNotAllow":["get_subskill_prompt","get_subskill_asset","get_post_find_leads_scout_registry","Task","spawn_agent","create_campaign","list_senders","import_leads","confirm_lead_list","queue_cells","start_campaign","generate_messages"],"waitFor":["filters_enabled","filters_skipped","revise_leads"],"transitions":{"filters_enabled":"post-lead-workstreams","filters_skipped":"message-generation","revise_leads":"find-leads"}},{"id":"post-lead-workstreams","label":"Filter workstream","onEnter":[{"action":"persist_add_filters_approval","tool":"update_campaign","when":"filters_enabled","requiredFields":["campaignId","enableICPFilters","currentStep","watchNarration"],"requiredValues":{"enableICPFilters":true,"currentStep":"create-icp-rubric","watchNarration.stage":"fit-message","watchNarration.headline":"Create filter rules","watchNarration.visibleState":"Filters are enabled and the browser is showing Filter Rules while Codex defines the rubric.","watchNarration.nextAction":"Review saved filter rules"},"mustRunBefore":["get_post_find_leads_scout_registry","launch_lead_fit_builder_after_filter_choice","save_rubrics"]},{"tool":"get_post_find_leads_scout_registry","purpose":"load canonical post-lead worker names only after the user has chosen filters"},{"action":"launch_lead_fit_builder_after_filter_choice","mode":"parallel_when_host_supports_subagents","target":"post-find-leads-filter-scout","inputs":["campaignId","campaignBrief","selectedLeadListId","workflowTableId","reviewBatchRowIds/hash","filterChoice"],"stateSource":"live campaign/table","debugFilesOptionalOnly":true},{"action":"launch_message_draft_builder_after_filter_decision","mode":"parallel_when_host_supports_subagents","target":"post-find-leads-message-scout","when":"filters_enabled","doesNotQueueCells":true,"customerNarration":"Say message agent is drafting."},{"action":"save_filter_rubrics_to_campaign","tool":"save_rubrics","when":"filters_enabled and rubrics are production-shaped","requiredFields":["campaignOfferId","leadScoringRubrics"],"writesCampaignState":"leadScoringRubrics","requiredSideEffects":{"enableICPFilters":true,"currentStep":"create-icp-rubric","watchNarration.headline":"Filter rules saved for review"}},{"action":"ask_filter_rubric_review_choice","uses":"request_user_input","choices":["Approve filters","Revise filters","Pause"],"purpose":"let the user read saved rubrics before Filter Leads or enrichment","copyMustInclude":["These rules prevent wasted sends before I score/import the list.","Recommended because of the researched ICP, source sample, and repeated false-positive patterns.","one pass example and one block example when available","approval is for the filter rules","Approve these filter rules."],"autoSelectIf":"yolo_mode and rubrics are production-shaped"}],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["get_subskill_prompt","get_subskill_asset","get_post_find_leads_scout_registry","save_rubrics","get_campaign","get_rows_minimal","update_campaign","get_campaign_navigation_state","AskUserQuestion","request_user_input","Task","spawn_agent"],"doNotAllow":["create_campaign","list_senders","import_leads","confirm_lead_list","queue_cells","start_campaign","check_rubric","generate_messages"],"waitFor":["filter_rubrics_approved","revise_leads","revise_rubric","revise_messaging"],"hardRules":["after_save_rubrics_currentStep_must_stay_create-icp-rubric_until_filter_approval","filter_approval_required_before_apply-icp-rubric_or_queue_cells","do_not_move_browser_to_messages_until_filter_leads_step_is_current_or_filters_are_explicitly_skipped","no_post_lead_worker_or_deep_prompt_before_filter_choice","lead_fit_builder_starts_only_after_filters_enabled","msg_draft_after_filter_choice","msg_draft_no_cells"],"transitions":{"filter_rubrics_approved":"message-generation","revise_leads":"find-leads","revise_rubric":"filter-rubric","revise_messaging":"message-generation","confirm_with_user":"message-review"}},{"id":"filter-rubric","label":"Rubric revision","onEnter":[{"tool":"get_subskill_asset","requiredValues":{"subskillName":"create-campaign-v2","assetPath":"references/filter-leads.md"}},{"tool":"save_rubrics","requiredFields":["campaignOfferId","leadScoringRubrics"],"writesCampaignState":"leadScoringRubrics","requiredSideEffects":{"enableICPFilters":true,"currentStep":"create-icp-rubric","watchNarration.headline":"Filter rules saved for review"}},{"action":"ask_filter_rubric_review_choice","uses":"request_user_input","choices":["Approve filters","Revise filters","Pause"],"copyMustInclude":["These rules prevent wasted sends before I score/import the list.","approval object is filter rules only","approval is for the filter rules","Approve these filter rules."],"autoSelectIf":"yolo_mode and rubrics are production-shaped"}],"requiredCampaignState":["campaignId","workflowTableId"],"allowedTools":["get_subskill_asset","save_rubrics","AskUserQuestion","request_user_input"],"doNotAllow":["create_campaign","list_senders","import_leads","confirm_lead_list","update_campaign","queue_cells","start_campaign","check_rubric"],"waitFor":["filter_rubrics_approved","revise_leads","confirm_with_user","auto_continue"],"transitions":{"filter_rubrics_approved":"message-generation","revise_leads":"find-leads","confirm_with_user":"message-generation","auto_continue":"message-generation"}},{"id":"message-generation","label":"Message generation","onEnter":[{"action":"set_message_review_visible_step_by_filter_choice","tool":"update_campaign","branchRules":["yes after filter approval: currentStep=apply-icp-rubric; wait on Filter Leads","no: currentStep=messages; message review"],"watchNarration.stage":"fit-message"},{"action":"run_or_reconcile_message_draft_builder","target":"post-find-leads-message-scout","toolCallRequiredBeforeDraft":["run background post-find-leads-message-scout when available","get_subskill_prompt({ subskillName: \"generate-messages\", offset, limit }) until hasMore=false"],"stateSource":"campaignBrief, source, selectedLeadListId, workflowTableId, execution-slice row ids/hash","outputState":"messageDraftRecommendation"}],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["get_subskill_prompt","get_campaign","get_rows_minimal","update_campaign","AskUserQuestion","request_user_input"],"toolRules":["Run post-find-leads-message-scout when available; otherwise parent fallback must load get_subskill_prompt({ subskillName: \"generate-messages\" }) from live state.","brief.md, lead-review.md, and lead-sample.json are optional debug context only.","messageDraftRecommendation returns templateRecommendation, tokenFillRules, renderedSample, concerns, status, basisToken, outputAt, outputHash, and error/retry detail.","If campaign/source/table/execution-slice basis does not match, classify the output stale or blocked."],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign","generate_messages"],"waitFor":["message_validation_ready","revise_rubric","revise_messaging","confirm_with_user","auto_continue"],"transitions":{"message_validation_ready":"message-review","revise_rubric":"filter-rubric","revise_messaging":"message-generation","confirm_with_user":"message-review","auto_continue":"message-review"}},{"id":"message-review","label":"Message review","onEnter":[{"action":"render_message_review_from_state","requiredState":["campaignBrief","selectedLeadListId","workflowTableId","messageDraftRecommendation"],"requiredVisibleLabels":["## Message Template","## Rendered Example","Good token fill:","My take:","Question: approve-message or revise-messaging?","Recommendation:"],"doNotShowByDefault":["Token Notes","Good omit / fallback","Bad fill to avoid","Token Adherence Table"],"internalPersistenceOnly":["Token Fill Rules","Token Fill Examples","fallback guidance","bad-fill avoidance notes"],"copyMustInclude":["QA receipt before approval","tokens resolved","company/person match","proof claim","prospect angle","language/tone when known","obvious bad fits","Would I take this call?","weak personalization can burn the sender's reputation","full list remains paused"]},{"action":"ask_message_review_choice","uses":"request_user_input","choices":["approve-message","revise-messaging"],"copyMustInclude":["approve this message template and continue","nothing sends from this approval"],"autoSelectIf":"yolo_mode and Recommendation is approve-message; revise autonomously when Recommendation is revise-messaging"},{"action":"sync_approved_message_set_to_campaign_brief","tool":"update_campaign_brief","when":"after approve-message","requiredFields":["campaignId","approvedMessageTemplate","Token Fill Rules","Token Fill Examples"],"writesCampaignState":"approvedMessageTemplate"}],"requiredCampaignState":["campaignId","workflowTableId","messageDraftRecommendation"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign_brief","update_campaign","get_rows_minimal"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","start_campaign"],"waitFor":["message_approved","revise_messaging"],"transitions":{"message_approved":"validate-sample","revise_messaging":"message-generation"}},{"id":"validate-sample","label":"Validate campaign-table execution slice","currentStepValue":"apply-icp-rubric","reference":"references/sample-validation-loop.md","visibleStepRule":"Filter Leads is reached only after saved-filter approval. On approve-message, save the template, refresh apply-icp-rubric narration, then queue bounded Enrich Prospect cells.","onEnter":[{"tool":"update_campaign","requiredValues":{"currentStep":"apply-icp-rubric","watchNarration.stage":"fit-message"},"purpose":"keep the watched UI on Filter Leads while the approved template starts the bounded cascade","watchNarrationRule":"Say the template is saved and Filter Leads is now running the bounded enrichment and scoring pass."},{"tool":"queue_cells","purpose":"queue ICP/enrichment cells for the internal campaign-table execution slice only","requiredFields":["workflowTableId","reviewBatchRowIds","cellTypes"],"targetCountSource":"reviewBatchRowIds.length (default 15)"},{"tool":"wait_for_campaign_table_ready","purpose":"wait_for_review_batch_scoring_cells"},{"tool":"get_rows_minimal","requiredValues":{"includeRows":false},"purpose":"read compact row status only"},{"tool":"wait_for_rubric_results","requiredFields":["workflowTableId","targetCount","minPassedCount"],"requiredValues":{"includeRows":false,"minPassedCount":1},"minPassedCountSource":"firstPassingRowForMessageStart","readVia":"stats_only_tool_result","customerSummaryPattern":["{checked} leads checked","{passed} passed and have draft messages","{blocked} blocked before sending","full list remains paused until approval"]},{"action":"handle_partial_or_timeout_sample","customerStatus":"sample-needs-revision","rule":"Do not repeat waits indefinitely; surface partial status and route to revision.","copyMustInclude":"completed, passed, pending, blocked before sending, and full list remains paused"}],"requiredCampaignState":["campaignId","workflowTableId","approvedMessageTemplate","filterRubricsApproved when enableICPFilters=true"],"allowedTools":["get_subskill_asset","queue_cells","wait_for_campaign_table_ready","get_rows_minimal","wait_for_rubric_results","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","enrich_with_prospeo","bulk_enrich_with_prospeo","check_rubric"],"hardRules":["wait_for_rubric_results_never_retain_rows_payload_in_tail_context","wait_for_rubric_results_targetCount_always_explicit","timeout_never_repeats_without_customer_handoff","timeout_or_underfloor_sample_never_advances_to_settings"],"waitFor":["sample_validated","sample_revision_required"],"transitions":{"sample_validated":"auto-execute-messaging","sample_revision_required":"lead-review","revise_leads":"find-leads","revise_rubric":"filter-rubric","escalation_triggered":"escalation"}},{"id":"auto-execute-messaging","label":"Generate initial campaign-row messages","currentStepValue":"auto-execute-messaging","references":["references/parallel-critique-protocol.md","references/thomas-variant-selection.md","references/thomas-revision-filters.md","references/step-15-re-cascade.md"],"onEnter":[{"tool":"queue_cells","batchSize":100,"when":"any passing row has pending or empty Generate Message cell","cellSource":"pending_generate_message_cells_for_passing_rows using approved_campaign_brief_template"},{"tool":"wait_for_rubric_results","purpose":"wait_for_first_passing_generated_message","requiredValues":{"includeRows":false,"minPassedCount":1,"minMessagesCount":1},"readVia":"stats_only_tool_result"},{"action":"observe_generate_message_results","reference":"references/step-15-re-cascade.md"},{"action":"enforce_token_contract","modeFromConfig":"messaging.tokenContract"},{"action":"optional_critique_pass","enabledFromConfig":"messaging.critique.enabled","plan":"85-03","protocol":"references/parallel-critique-protocol.md","sampleSizeFromConfig":"messaging.critique.sampleSize","budgetUsdCapFromConfig":"messaging.critique.budgetUsdCap","perCriticTimeoutFromConfig":"messaging.critique.perCriticTimeoutSeconds","totalTimeoutFromConfig":"messaging.critique.totalTimeoutSeconds","criticsFromConfig":"messaging.critique.critics","enforceFinalizerPassFromConfig":"messaging.critique.synthesis.enforceFinalizerPass","rejectOnFakeProofFromConfig":"messaging.critique.rejectOnFakeProof","rejectOnUnsupportedTokenFromConfig":"messaging.critique.rejectOnUnsupportedToken","onBudgetTrip":"halt_critique_continue_plain_tail","onPerCriticTimeout":"drop_critic_proceed_with_remaining","onTotalTimeout":"persist_plain_message_for_row","onTokenContractViolation":"persist_plain_message_for_row","onFakeProof":"persist_plain_message_for_row"},{"action":"optional_opus_subset","enabledFromConfig":"messaging.critique.opus.enabled","selection":"references/thomas-variant-selection.md","maxMessagesPerPassFromConfig":"messaging.critique.opus.maxMessagesPerPass","budgetUsdCapFromConfig":"messaging.critique.opus.budgetUsdCap","onOpusBudgetTrip":"halt_opus_continue_non_opus","onOpusTokenContractViolation":"fallback_to_non_opus_rewrite"},{"tool":"update_campaign","requiredValues":{"currentStep":"auto-execute-messaging","watchNarration.stage":"review-ready"},"watchNarrationRule":"Say the first passing generated message is ready in Messages; next is review before Settings."},{"action":"ask_generated_message_review_choice","uses":"request_user_input","choices":["Approve reviewed draft rows and continue to Settings","Revise filters","Revise message template","Pause here"],"copyMustInclude":["approve the reviewed draft rows and continue to Settings","tokens resolved","company/person match","proof claim","prospect angle","language/tone when known","obvious bad fits","Would I take this call?","weak personalization can burn the sender's reputation","full list remains paused","nothing sends from this approval"],"autoSelectIf":"yolo_mode and the first passing generated message clears the quality floor"}],"allowedTools":["get_subskill_asset","get_rows_minimal","queue_cells","wait_for_rubric_results","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","generate_messages"],"hardRules":["critique_failure_never_escalates","critique_sample_size_bounded_by_config","first_passing_generated_message_unblocks_review","critics_fixed_at_targeting_copy_voice","synthesis_enforces_phase_84_token_contract","opus_reserved_for_highest_value_subset","proposed_token_never_persisted_in_rewrite"],"waitFor":["generated_messages_approved","sample_revision_required"],"transitions":{"generated_messages_approved":"awaiting-user-greenlight","revise_filters":"filter-rubric","revise_messaging":"message-generation","escalation_triggered":"escalation"}},{"id":"awaiting-user-greenlight","label":"Settings, sender, sequence, and greenlight","currentStepValue":"awaiting-user-greenlight","reference":"references/final-handoff-contract.md","onEnter":[{"tool":"get_campaign","purpose":"verify senderIds and sequence state before final handoff"},{"tool":"list_senders","purpose":"surface available connected senders only at Settings"},{"tool":"update_campaign","requiredValues":{"currentStep":"settings","watchNarration.stage":"review-ready"},"purpose":"park the watched UI on Settings before sender selection","watchNarrationRule":"Say message review is complete and the browser is now on Settings so the user can connect or choose a sender. Include that lead research and filtering already happened outside the user's LinkedIn account, this sender is only used for approved sending after final launch, and no launch happens from Settings."},{"action":"surface_sender_and_slack_handoff","requiredVisibleContent":["connect or select a LinkedIn sender","Lead research and filtering already happened outside your LinkedIn account.","This account is only used for approved sending after final launch.","Nothing sends until the final Start step.","Slack reply review","recommended sequence","final launch confirmation is still ahead"]},{"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"},{"action":"attach_selected_sender","tool":"update_campaign","when":"user selected an available connected sender","requiredValues":{"senderIds":["{selectedSenderId}"],"currentStep":"sequence","watchNarration.stage":"review-ready"},"watchNarrationRule":"Say the sender was attached and the browser is moving to Sequence review. Include that the sequence is editable before launch and still not sending until Start."},{"tool":"attach_recommended_sequence","when":"after senderIds are attached","requiredValues":{"campaignId":"{campaignId}","currentStep":"send","watchNarration.stage":"review-ready"},"watchNarrationRule":"The sequence tool owns the visible beat: say the recommended follow-up sequence is attached, the browser is now on final launch review, and the next action is final launch greenlight. Include that it is still not sending until Start. Do not follow with a separate step-only update_campaign fixup."},{"action":"ask_final_launch_greenlight","uses":"request_user_input","singleChoice":true,"choices":["Start campaign","Review campaign first","Pause here"],"copyMustInclude":["quality confidence means sample messages and prospect angle were checked","launch confidence means sender, sequence, and Start are ready","approved messages begin sending according to the sequence","replies and meetings follow connected settings","you can monitor and pause","no hidden extra approval disappears after clicking Start"],"onUserStart":"claude-greenlight","neverAutoSelectInYolo":true}],"allowedTools":["get_campaign","get_campaign_navigation_state","list_senders","update_campaign","attach_recommended_sequence","AskUserQuestion","request_user_input"],"doNotAllow":["start_campaign","import_leads"],"autoStart":false,"watchRequired":true,"waitFor":["sender_connection_required","sender_attached","sequence_attached","ready_to_launch","user_greenlight","ui_start_detected"],"transitions":{"sender_connection_required":"awaiting-user-greenlight","sender_attached":"awaiting-user-greenlight","sequence_attached":"awaiting-user-greenlight","ready_to_launch":"awaiting-user-greenlight","user_greenlight":"claude-greenlight","ui_start_detected":"running"}},{"id":"claude-greenlight","label":"Explicit launch","reference":"references/final-handoff-contract.md","onEnter":[{"tool":"get_campaign","purpose":"detect_already_running"},{"action":"verify_sequence_and_current_step_before_start","requiredState":["workflowTableId","senderIds","sequenceTemplate","at least one approved generated message","currentStep in awaiting-user-greenlight|claude-greenlight|send"]},{"tool":"start_campaign","requiredFields":["campaignId"],"persistsCurrentStep":"running","watchNarrationRule":"After start_campaign succeeds, the running state must say the final greenlight was accepted, the campaign is now live/running, and the user can watch progress from the campaign."}],"allowedTools":["get_campaign","attach_recommended_sequence","start_campaign","AskUserQuestion","request_user_input"],"watchRequired":true,"waitFor":"campaign_started","transitions":{"campaign_started":"running"}},{"id":"running","label":"Campaign is live","currentStepValue":"running","onEnter":[{"action":"surface_watch_link"},{"action":"surface_campaign_live_confirmation"}],"allowedTools":["get_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["start_campaign"],"terminal":true},{"id":"escalation","label":"Escalation","reference":"references/escalation-ladder.md","allowedTools":["AskUserQuestion","request_user_input"],"doNotAllow":["start_campaign","import_leads"],"transitions":{"revise_brief":"brief-interview","revise_leads":"find-leads","revise_rubric":"filter-rubric","revise_messaging":"message-generation","abort":"abort"}},{"id":"abort","label":"Abort","allowedTools":["AskUserQuestion","request_user_input"],"terminal":true}]}
|
|
1
|
+
{"version":"v2.1-compact","workflow":"create-campaign-v2","principle":"CampaignOffer state and watch link are canonical. Create the watched shell, approve source, materialize and confirm the source list, internally process the first campaign-table execution slice, save rubrics for approval, approve the message template, then run the bounded filter/message cascade before Settings, sequence, and explicit start.","normalCustomerPath":"Use campaign state, MCP responses, and concise watchNarration. Do not create, read, link, or surface local draft files. Print the watch URL only once at the initial brief handoff; after that, update currentStep/watchNarration and describe the visible app state without repeating the link.","legacyCompatibility":{"validationSubskill":"create-campaign-v2-validation","tailSubskill":"create-campaign-v2-tail","rule":"Legacy validation/rehearsal files are opt-in diagnostics only and are not active flow gates."},"commitGateChoices":["approve","revise-brief","revise-leads","revise-rubric","revise-messaging","abort"],"canonicalStateFields":["campaignId","watchUrl","campaignBrief","currentStep","watchNarration","interactionMode","providerSearchAssociation","selectedLeadListId","workflowTableId","filterChoice","leadScoringRubrics","messageDraftRecommendation","approvedMessageTemplate","senderIds","sequenceTemplate","runningState"],"watchNarrationTransitionContract":{"rule":"Every watched currentStep switch must include fresh watchNarration.","requiredFields":["stage","headline","visibleState","agentIntent","nextAction"],"copyMustInclude":["what just happened","current visible page/state","next user action"],"appliesToTools":["create_campaign","update_campaign","attach_recommended_sequence","start_campaign"],"oneTimeWatchLinkPolicy":"After the initial brief handoff, customer-facing progress and approval turns must not print another watch link unless the user explicitly asks for it or link recovery is required."},"lazyReferences":{"watch":["references/watch-link-handoff.md","references/watch-guide-narration.md"],"source":["references/lead-validation-preview.md","references/step-13-import-leads.md"],"filter":["references/filter-leads.md"],"message":[],"tail":["references/sample-validation-loop.md","references/step-15-re-cascade.md","references/final-handoff-contract.md"]},"safetyBoundaries":["Do not call list_senders before Settings after message approval.","Do not import leads until the source decision is approved.","Do not queue cells until confirmed source rows exist in the campaign and the message/filter gates are satisfied.","Do not call start_campaign until the user explicitly confirms launch.","Do not use local files as durable state in normal customer runs."],"yoloMode":{"triggerPhrases":["yolo","--yolo","mode=yolo","autopilot","use best guesses","use best estimates","answer for me","just run it"],"identityRequestOnlyWhenMissing":true,"operatorDirectionsRule":"Treat freeform directions supplied at invocation or later as durable operator directions; newest conflicting direction wins.","setInteractionModeAfterCampaignCreate":"autonomous","autoSelectsPreLaunchChoices":["campaign focus","brief approval","source plan","source review/import approval","filters vs skip filters","filter rubric approval","message template approval when recommendation is approve-message","generated message review when quality floor passes","sender selection when exactly one connected sender is safe"],"mustPauseFor":["missing LinkedIn profile URL or handle","missing credentials or required data","ambiguous choice with no reasonable estimate","source/message/filter quality floor failure","final live launch confirmation"],"neverAutoStart":true},"steps":[{"id":"bootstrap","label":"Bootstrap","onEnter":[{"tool":"bootstrap_create_campaign","requiredValues":{"flowVersion":"v2"}},{"tool":"get_subskill_prompt","requiredValues":{"subskillName":"create-campaign-v2"},"purpose":"load the compact entry prompt once"}],"allowedTools":["bootstrap_create_campaign","get_auth_status","get_active_workspace","get_subskill_prompt","AskUserQuestion","request_user_input"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","update_campaign","queue_cells","start_campaign"],"waitFor":"bootstrap_complete","transitions":{"bootstrap_complete":"brief-interview"}},{"id":"brief-interview","label":"Client, offer, research, and brief","onEnter":[{"action":"resolve_campaign_identity_before_strategy_packet","allowedTools":["fetch_company","fetch_linkedin_profile","WebFetch","WebSearch"],"mustNotInferFromNameOnly":true,"doNotPresentSenderPickerBeforeIdentityInference":true,"fallback":"Ask only: \"What is your LinkedIn profile URL or handle?\" If the user provides a bare handle like `csreyes92` or a `/in/...` path, normalize it to `https://www.linkedin.com/in/{handle}/`. If the user provides a non-profile URL, company page, or anything else, ask again for the person's LinkedIn profile URL or handle before strategy questions.","requiresLinkedInProfileUrl":true,"doNotAcceptAsIdentityInput":["non-profile URL","company page","company/name-only input without a profile handle"],"acceptsLinkedInProfileHandle":true,"normalizeLinkedInProfileHandleToUrl":true},{"action":"run_campaign_identity_research_before_strategy_packet","target":"research-sender","requiredCompletion":"complete_sender_research","allowedTools":["get_subskill_prompt","fetch_linkedin_profile","fetch_company","fetch_linkedin_posts","fetch_company_posts","WebFetch","WebSearch","complete_sender_research"],"requiredInput":"LinkedIn profile URL or handle"},{"action":"confirm_target_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"Who should we target first?","options":["Use Sellable's researched recommendation","Choose a different buyer","I'm not sure yet"],"copyMustInclude":["research basis","recommendation is editable","who to target"],"requiredOption":"Other / custom","neverCollectOpenTextWithStructuredQuestion":true,"skipIf":"user already supplied a clear target or yolo_mode with any reasonable best-estimate target"},{"action":"confirm_current_offer_before_strategy_packet","uses":"request_user_input","question":"What should we pitch to prospects first?","options":["Use Sellable's researched recommendation","Use my current pitch","Shape the pitch together"],"copyMustInclude":["research basis","public LinkedIn research may be stale","recommendation is editable","pitch to prospects"],"skipIf":"user already supplied a clear current offer or yolo_mode with any reasonable researched recommendation"},{"action":"confirm_trust_proof_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"What is the strongest credibility signal we can lead with?","options":["Use Sellable's recommended proof","Use customer proof or a case study","Use founder or company credibility"],"copyMustInclude":["build trust with prospects","recommendation is editable","proof to use or avoid"],"requiredOption":"Other / custom","skipIf":"user already supplied clear proof to use or avoid, or yolo_mode with any reasonable researched proof"},{"action":"choose_prospect_source_path_before_strategy_packet","uses":"request_user_input","singleChoice":true,"question":"How should we find prospects?","options":["Find prospects for me","Use a CSV of LinkedIn profiles","Use a CSV of company domains"],"copyMustInclude":["prospects","no one added yet","find prospects for me"],"skipIf":"user already supplied a source path or yolo_mode with any reasonable source path"},{"action":"infer_strategy_packet_in_yolo_mode","when":"yolo_mode","skipStructuredSetupQuestions":true,"inferFields":["buyer segment","offer/CTA","proof to use or avoid","lead source","filter choice","message direction"],"sourceOfTruth":"identity/company lookup plus operator directions","mustStateAssumptionsInBrief":true},{"action":"render_supplied_setup_receipt_before_brief_when_setup_questions_skip","when":"any setup question is skipped because the user already supplied or yolo mode inferred identity, target, offer, proof, or source","output":"normal chat receipt before the campaign brief or watch-link handoff","copyMustInclude":["Accepted LinkedIn input: normalized to the required LinkedIn profile URL.","Offer path: supplied current offer or Sellable's researched recommendation; you can replace it with your current offer."],"copyMustNotInclude":["Campaign Identity"]},{"action":"create_watchable_campaign_shell_with_v1_brief","tool":"create_campaign","requiredFields":["name","campaignBrief","clientProspectId or senderLinkedinUrl","currentStep","watchNarration"],"requiredValues":{"currentStep":"create-offer","watchNarration.stage":"brief"},"capture":["campaignId","watchUrl"],"canonicalStateWrites":["campaignId","watchUrl","campaignBrief","currentStep:create-offer"]},{"action":"surface_campaign_shell_watch_link","watchUrlSource":"create_campaign.watchUrl","requiredWatchUrlShape":"direct /campaign-builder/{campaignId}?mode={claude|codex} watch URL with token auto-login and workspace routing","copyMustInclude":["We'll keep building the campaign in this chat.","You can watch it being built in real time in the app here:","I'll ask for your approval whenever I need your expertise or taste before moving forward.","Send changes here."],"immediateNextMainChatLine":"Next, we'll choose where to find buyers. I won't add anyone yet.","codexBrowserHandoff":{"openWhenAvailable":false,"printWatchLinkOnly":true,"tellUserCommandEnterOrClick":false,"mustNotUseBrowserAutomation":true,"fallbackMustNotClaimInspection":true}}],"allowedTools":["get_subskill_prompt","fetch_company","fetch_company_posts","fetch_linkedin_profile","fetch_linkedin_posts","complete_sender_research","create_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["campaign_shell_created","brief_ready","confirm_with_user"],"transitions":{"campaign_shell_created":"brief-review","brief_ready":"brief-review","confirm_with_user":"brief-review"}},{"id":"brief-review","label":"Brief review","onEnter":[{"action":"render_brief_inline","minimumVisibleDetail":"campaign direction, buyer, offer, proof, source plan, safety gates","copyMustInclude":["This brief is based on Sellable's research and your answers.","If your site or LinkedIn is stale","Do you agree with this researched campaign direction?"],"copyMustNotInclude":["quoted first-message copy","Message Angle as final proof","This approval covers","does not approve adding people","does not approve scraping posts","selecting a sender","attaching a sequence","Campaign Identity","what should this campaign sell first","sell first","[from you]","[from LinkedIn]","[from website]","[from case study]","[Sellable recommendation]"],"messageHintRule":"If any message-shape hint remains, keep it as non-final bullets and say real examples come after leads are found and filtered.","approvalBoundaryRule":"Keep this gate positive and short. Do not list later steps this approval does not cover; ask approve/revise and then move to choosing where to find buyers.","sectionLabelRule":"Use ## Sender and Company for the person/company context; never render ## Campaign Identity."},{"action":"ask_brief_choice","uses":"request_user_input","choices":["Approve brief","Revise brief","Pause here"],"skipIf":"yolo_mode; auto_continue after rendering assumptions unless brief quality floor fails"}],"requiredCampaignState":["campaignId","watchUrl","campaignBrief","currentStep"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign"],"doNotAllow":["create_campaign","list_senders","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["user_brief_confirmed","revise_brief","auto_continue"],"transitions":{"user_brief_confirmed":"find-leads","revise_brief":"brief-interview","auto_continue":"find-leads"}},{"id":"find-leads","label":"Find leads","sourceSelectionFunnel":{"preScoutRecommendationGate":{"required":true,"label":"Find buyers plan approval","mustHappenBefore":["get_provider_prompt","search_signals","search_sales_nav","search_prospeo","fetch_post_engagers","source-scout dispatch"],"show":["buyer groups or places we could check","best place to start","why the right buyers are likely to be there","what signs the next search will check","where to look next if the first place is too thin","what approval authorizes"],"approvalAuthorizes":"looking for the best places to find buyers; no one added yet","explanationMustInclude":["where the right buyers are already talking","why that place fits this campaign","enough likely prospects","what counts as a good sign","where to look next if thin","I won't add anyone yet","choose where to find buyers"],"yoloMode":{"autoApproveRecommendedLane":true,"mustShowAssumedChoice":true,"pauseIfConfidenceLow":true},"customerLanguage":{"readability":"grade-5","requiredHeading":"Find Buyers Plan","prefer":["place to look","best place to start","people to check","prospects","I won't add anyone yet"],"avoid":["lane","source scouting","provider","precision/scale tradeoff","evidence quality","pilot volume","ICP","workflow pain","lead-source scouting","not importing leads"],"example":"Brief approved. Next, we'll choose where to find buyers. I won't add anyone yet. I recommend starting with people already talking on LinkedIn about [plain topic]. That should help us find people who already care about [plain problem]. If this is too thin, I'll try [plain fallback] next.","termRule":{"buyers":"target market","peopleToCheck":"raw reactions/comments before fit is known","prospects":"likely usable people after fit","leads":"campaign rows"}},"questionOrder":"update_campaign to pick-provider, render ## Find Buyers Plan in normal chat without repeating the watch link, then open the structured approval question"},"defaultWhenSourceUnspecified":["signal-discovery","sales-nav-recent-active","sales-nav-general","prospeo"],"userFacingFallbackOrder":["LinkedIn posts where the right buyers are already talking","people with the right titles who recently posted on LinkedIn","people with the right titles from a broader LinkedIn search","a broader company and contact search"],"parallelAllowedOnlyWhen":["user explicitly requested source comparison","resuming already-started parallel scouts","first viable source is borderline and one cheap fallback check is needed"]},"onEnter":[{"tool":"update_campaign","requiredValues":{"currentStep":"pick-provider","watchNarration.stage":"find-leads","watchNarration.headline":"Review where to find buyers","watchNarration.visibleState":"The app is showing source selection before lead finding starts.","watchNarration.agentIntent":"Codex is explaining where it recommends looking first and where it will look next if that is too thin.","watchNarration.nextAction":"Approve where to look first or choose another place","watchNarration.safety":"Only deciding where to look. No one is added yet."},"purpose":"show the visible source-plan approval checkpoint before provider lanes"},{"tool":"get_source_scout_registry","purpose":"load canonical source scout names before optional branch launch"},{"action":"render_find_buyers_plan_inline","oneShot":true,"requiredPrecondition":"currentStep=pick-provider has been saved with update_campaign","mustHappenAfter":["update_campaign currentStep=pick-provider"],"mustHappenBefore":["ask_find_buyers_plan_choice","get_provider_prompt","search_signals","search_sales_nav","search_prospeo","fetch_post_engagers","source-scout dispatch"],"output":"normal chat text before any request_user_input approval panel","requiredHeading":"Find Buyers Plan","requiredInlineFields":["plain-language buyer groups or places for this campaign","best place to start","why the right buyers are likely to be there","what signs the next search will check","where to look next if the first place is too thin","what approval authorizes"],"copyMustInclude":["Find Buyers Plan","best place to start","I won't add anyone","choose where to find buyers","I won't add anyone yet"],"copyMustAvoid":["lane","source scouting","provider","precision/scale tradeoff","evidence quality","pilot volume","ICP","workflow pain","lead-source scouting","not importing leads","Watch link:","campaign-builder URL","repeat the watch URL"],"approvalBoundary":"Explain what approval authorizes before asking: looking for the best places to find buyers; no one is added yet.","linkPolicy":"Do not include another watch link. The first brief handoff already gave the user the live app URL."},{"action":"ask_find_buyers_plan_choice","uses":"request_user_input","oneShot":true,"requiredPrecondition":"render_find_buyers_plan_inline completed in normal chat after pick-provider state was saved","skipIf":"approved or leadSourceProvider or yolo_mode auto-selected source_lane_approved","choices":["Start with LinkedIn posts","Start with active LinkedIn profiles","Start with company/contact search","Choose a different place","Pause here"],"approvalChoiceLabelsByProvider":{"signal-discovery":"Start with LinkedIn posts","sales-nav":"Start with active LinkedIn profiles","prospeo":"Start with company/contact search"},"approvalState":"source_lane_approved","copyMustInclude":["Approve this plan for where to find buyers?","Start with LinkedIn posts"],"mustNotHappenBefore":["render_find_buyers_plan_inline"]},{"action":"persist_provider_search_step_before_search","tool":"update_campaign","requiredPrecondition":"source_lane_approved","providerCurrentStepMap":{"signal-discovery":"signal-discovery","sales-nav":"sales-nav","prospeo":"prospeo"},"requiredValues":{"leadSourceType":"new","leadSourceProvider":"approved provider","currentStep":"provider-specific current step","watchNarration.stage":"find-leads","watchNarration.headline":"Searching the approved place","watchNarration.safety":"Looking only. No one is added yet."},"mustRunBefore":["search_signals","search_sales_nav","search_prospeo","fetch_post_engagers"]},{"action":"run_sequential_source_funnel","requiredPrecondition":"source_lane_approved","defaultOrder":["source-scout-linkedin-engagement","source-scout-sales-nav","source-scout-prospeo-contact"],"campaignOfferIdRequired":true,"stopOnFirstViableUnlessComparisonRequested":true}],"requiredCampaignState":["campaignId","campaignBrief","currentStep"],"allowedTools":["get_source_scout_registry","get_provider_prompt","lookup_sales_nav_filter","search_sales_nav","search_prospeo","search_signals","select_promising_posts","fetch_post_engagers","fetch_company","fetch_linkedin_profile","load_csv_linkedin_leads","load_csv_domains","get_rows_minimal","update_campaign","AskUserQuestion","request_user_input","get_campaign_navigation_state"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["lead_review_ready","revise_brief","confirm_with_user"],"transitions":{"lead_review_ready":"lead-review","revise_brief":"brief-interview","confirm_with_user":"lead-review"}},{"id":"lead-review","label":"Source approval","onEnter":[{"action":"show_source_decision_card","requiredInlineFields":["primary source and exact filters/recipe","specific source action awaiting approval","for Signal Discovery: compact Source Recommendation in plain language with selected posts, people to check, likely prospects, first review, and fallback","for Signal Discovery: recommended scrape post count and target people-to-check volume","first campaign review size, clearly separate from source sampling","runner-up and why it lost","raw volume","sampled people","sampled fits as n/N plus percentage/range","estimated usable prospects","cleanup risk","what will happen after approval"],"copyMustAvoid":["lead-source scouting","source scouting","headline-fit","engagers needed","execution slice","ICP","good buyers","real buyers","This approval covers","It does not approve","does not approve filters","filters, messages, sender selection","sender selection, sequence, or sending","Watch link:","campaign-builder URL","repeat the watch URL"],"approvalBoundaryRule":"Use positive source-action copy only. Include: \"After approval, I will build the source list, add it to the campaign, and review the first 15 leads before we scale.\" Never write \"This approval covers\" or \"It does not approve\" sentences.","linkPolicy":"Do not include another watch link in Source Recommendation; describe the source decision only."},{"action":"ask_source_review_choice","uses":"request_user_input","choices":["Approve scraping N recommended LinkedIn posts","Run the approved source import","Revise source","Pause here"],"approvalChoiceLabelsByProvider":{"signal-discovery":"Approve scraping {scrapePostCount} recommended LinkedIn posts?","sales-nav":"Import the approved Sales Nav source list","prospeo":"Import the approved Prospeo source list"},"postApprovalContract":{"singleUseApproval":true,"doNotRepeatAfterApproval":["Source Recommendation","show_source_decision_card","ask_source_review_choice"],"requiredNextActionAfterApproval":"ack once; call import_leads immediately","signalDiscoveryNextTool":"import_leads({ provider: \"signal-discovery\", targetEngagerCount, maxPostsToScrape, confirmed: true })"},"autoSelectIf":"yolo_mode and projected usable pool clears the source quality floor","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"requiredCampaignState":["campaignId","campaignBrief","providerSearchAssociation"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign"],"waitFor":["lead_review_confirmed","revise_leads","confirm_with_user","auto_continue"],"transitions":{"lead_review_confirmed":"auto-execute-leads","revise_leads":"find-leads","confirm_with_user":"auto-execute-leads","auto_continue":"auto-execute-leads"}},{"id":"auto-execute-leads","label":"Materialize confirmed source list","currentStepValue":"auto-execute-leads","reference":"references/step-13-import-leads.md","onEnter":[{"tool":"get_subskill_prompt","requiredValues":{"subskillName":"create-campaign-v2-tail"},"purpose":"load tail contract before execution tools"},{"tool":"import_leads","requiredFields":["campaignOfferId","selected source/list","sourceListTarget or targetEngagerCount"],"requiredValues":{"salesNavProspeoDefaultSourceListTarget":1000,"signalDiscoveryDefaultEngagerTarget":1500},"modeAddHandshake":{"firstCallReturns":"needsModeSelection when adding to an existing campaign-attached list","requiredFollowup":"retry with mode=add after explicit user/source-state compatibility is confirmed"},"surfaceDedupRatio":true},{"tool":"wait_for_lead_list_ready"},{"tool":"confirm_lead_list","requiredFields":["campaignOfferId","selectedLeadListId","reviewBatchLimit"],"requiredValues":{"reviewBatchLimit":15},"capture":["workflowTableId","reviewBatchRowIds"]},{"tool":"wait_for_campaign_table_ready"},{"tool":"get_rows_minimal","requiredValues":{"tableId":"{workflowTableId}","limit":15},"capture":["reviewBatchRowHash"]},{"action":"summarize_review_batch_and_advance_to_filter_choice","purpose":"ask filter choice immediately; no post-lead registries, filter refs, or message prompts first","maxCustomerCopyLines":4,"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"linkPolicy":"Ask add filters vs skip filters without repeating the watch link."}],"requiredCampaignState":["campaignId","providerSearchAssociation","selectedLeadListId"],"allowedTools":["get_subskill_prompt","import_leads","wait_for_lead_list_ready","confirm_lead_list","wait_for_campaign_table_ready","get_rows_minimal","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["get_post_find_leads_scout_registry","Task","spawn_agent","list_senders","queue_cells","start_campaign","enrich_with_prospeo","bulk_enrich_with_prospeo","save_rubrics"],"waitFor":"source_list_confirmed_and_review_sample_ready","transitions":{"source_list_confirmed_and_review_sample_ready":"filter-choice","escalation_triggered":"escalation"}},{"id":"filter-choice","label":"Filter choice","currentStepValue":"filter-choice","onEnter":[{"tool":"update_campaign","requiredValues":{"currentStep":"filter-choice","watchNarration.stage":"fit-message"}},{"action":"ask_filter_choice","uses":"request_user_input","choices":["Use filters","Skip filters","Revise source"],"autoSelectIf":"yolo_mode; choose filters unless source is tightly curated or user directed otherwise","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"],"copyMustInclude":["source rows are in the campaign","add fit filters before message generation or skip filters"]}],"hardRules":["ask_filter_choice_immediately_after_review_batch_import","do_not_call_get_subskill_prompt_before_filter_choice","do_not_call_get_subskill_asset_before_filter_choice","do_not_call_get_post_find_leads_scout_registry_before_filter_choice","do_not_spawn_post_lead_agents_before_filter_choice"],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign","get_campaign_navigation_state"],"doNotAllow":["get_subskill_prompt","get_subskill_asset","get_post_find_leads_scout_registry","Task","spawn_agent","create_campaign","list_senders","import_leads","confirm_lead_list","queue_cells","start_campaign","generate_messages"],"waitFor":["filters_enabled","filters_skipped","revise_leads"],"transitions":{"filters_enabled":"post-lead-workstreams","filters_skipped":"message-generation","revise_leads":"find-leads"}},{"id":"post-lead-workstreams","label":"Filter workstream","onEnter":[{"action":"persist_add_filters_approval","tool":"update_campaign","when":"filters_enabled","requiredFields":["campaignId","enableICPFilters","currentStep","watchNarration"],"requiredValues":{"enableICPFilters":true,"currentStep":"create-icp-rubric","watchNarration.stage":"fit-message","watchNarration.headline":"Create filter rules","watchNarration.visibleState":"Filters are enabled and the browser is showing Filter Rules while Codex defines the rubric.","watchNarration.nextAction":"Review saved filter rules"},"mustRunBefore":["get_post_find_leads_scout_registry","launch_lead_fit_builder_after_filter_choice","save_rubrics"]},{"tool":"get_post_find_leads_scout_registry","purpose":"load canonical post-lead worker names only after the user has chosen filters"},{"action":"launch_lead_fit_builder_after_filter_choice","mode":"parallel_when_host_supports_subagents","target":"post-find-leads-filter-scout","inputs":["campaignId","campaignBrief","selectedLeadListId","workflowTableId","reviewBatchRowIds/hash","filterChoice"],"stateSource":"live campaign/table","debugFilesOptionalOnly":true},{"action":"launch_message_draft_builder_after_filter_decision","mode":"parallel_when_host_supports_subagents","target":"post-find-leads-message-scout","when":"filters_enabled","doesNotQueueCells":true,"customerNarration":"Say message agent is drafting."},{"action":"save_filter_rubrics_to_campaign","tool":"save_rubrics","when":"filters_enabled and rubrics are production-shaped","requiredFields":["campaignOfferId","leadScoringRubrics"],"writesCampaignState":"leadScoringRubrics","requiredSideEffects":{"enableICPFilters":true,"currentStep":"create-icp-rubric","watchNarration.headline":"Filter rules saved for review"}},{"action":"ask_filter_rubric_review_choice","uses":"request_user_input","choices":["Approve filters","Revise filters","Pause"],"purpose":"let the user read saved rubrics before Filter Leads or enrichment","copyMustInclude":["These rules prevent wasted sends before I score/import the list.","Recommended because of the researched ICP, source sample, and repeated false-positive patterns.","one pass example and one block example when available","approval is for the filter rules","Approve these filter rules."],"autoSelectIf":"yolo_mode and rubrics are production-shaped","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["get_subskill_prompt","get_subskill_asset","get_post_find_leads_scout_registry","save_rubrics","get_campaign","get_rows_minimal","update_campaign","get_campaign_navigation_state","AskUserQuestion","request_user_input","Task","spawn_agent"],"doNotAllow":["create_campaign","list_senders","import_leads","confirm_lead_list","queue_cells","start_campaign","check_rubric","generate_messages"],"waitFor":["filter_rubrics_approved","revise_leads","revise_rubric","revise_messaging"],"hardRules":["after_save_rubrics_currentStep_must_stay_create-icp-rubric_until_filter_approval","filter_approval_required_before_apply-icp-rubric_or_queue_cells","do_not_move_browser_to_messages_until_filter_leads_step_is_current_or_filters_are_explicitly_skipped","no_post_lead_worker_or_deep_prompt_before_filter_choice","lead_fit_builder_starts_only_after_filters_enabled","msg_draft_after_filter_choice","msg_draft_no_cells"],"transitions":{"filter_rubrics_approved":"message-generation","revise_leads":"find-leads","revise_rubric":"filter-rubric","revise_messaging":"message-generation","confirm_with_user":"message-review"}},{"id":"filter-rubric","label":"Rubric revision","onEnter":[{"tool":"get_subskill_asset","requiredValues":{"subskillName":"create-campaign-v2","assetPath":"references/filter-leads.md"}},{"tool":"save_rubrics","requiredFields":["campaignOfferId","leadScoringRubrics"],"writesCampaignState":"leadScoringRubrics","requiredSideEffects":{"enableICPFilters":true,"currentStep":"create-icp-rubric","watchNarration.headline":"Filter rules saved for review"}},{"action":"ask_filter_rubric_review_choice","uses":"request_user_input","choices":["Approve filters","Revise filters","Pause"],"copyMustInclude":["These rules prevent wasted sends before I score/import the list.","approval object is filter rules only","approval is for the filter rules","Approve these filter rules."],"autoSelectIf":"yolo_mode and rubrics are production-shaped","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"requiredCampaignState":["campaignId","workflowTableId"],"allowedTools":["get_subskill_asset","save_rubrics","AskUserQuestion","request_user_input"],"doNotAllow":["create_campaign","list_senders","import_leads","confirm_lead_list","update_campaign","queue_cells","start_campaign","check_rubric"],"waitFor":["filter_rubrics_approved","revise_leads","confirm_with_user","auto_continue"],"transitions":{"filter_rubrics_approved":"message-generation","revise_leads":"find-leads","confirm_with_user":"message-generation","auto_continue":"message-generation"}},{"id":"message-generation","label":"Message generation","onEnter":[{"action":"set_message_review_visible_step_by_filter_choice","tool":"update_campaign","branchRules":["yes after filter approval: currentStep=apply-icp-rubric; wait on Filter Leads","no: currentStep=messages; message review"],"watchNarration.stage":"fit-message"},{"action":"run_or_reconcile_message_draft_builder","target":"post-find-leads-message-scout","toolCallRequiredBeforeDraft":["run background post-find-leads-message-scout when available","get_subskill_prompt({ subskillName: \"generate-messages\", offset, limit }) until hasMore=false"],"stateSource":"campaignBrief, source, selectedLeadListId, workflowTableId, execution-slice row ids/hash","outputState":"messageDraftRecommendation"}],"requiredCampaignState":["campaignId","campaignBrief","selectedLeadListId","workflowTableId"],"allowedTools":["get_subskill_prompt","get_campaign","get_rows_minimal","update_campaign","AskUserQuestion","request_user_input"],"toolRules":["Run post-find-leads-message-scout when available; otherwise parent fallback must load get_subskill_prompt({ subskillName: \"generate-messages\" }) from live state.","brief.md, lead-review.md, and lead-sample.json are optional debug context only.","messageDraftRecommendation returns templateRecommendation, tokenFillRules, renderedSample, concerns, status, basisToken, outputAt, outputHash, and error/retry detail.","If campaign/source/table/execution-slice basis does not match, classify the output stale or blocked."],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","queue_cells","start_campaign","generate_messages"],"waitFor":["message_validation_ready","revise_rubric","revise_messaging","confirm_with_user","auto_continue"],"transitions":{"message_validation_ready":"message-review","revise_rubric":"filter-rubric","revise_messaging":"message-generation","confirm_with_user":"message-review","auto_continue":"message-review"}},{"id":"message-review","label":"Message review","onEnter":[{"action":"render_message_review_from_state","requiredState":["campaignBrief","selectedLeadListId","workflowTableId","messageDraftRecommendation"],"requiredVisibleLabels":["## Message Template","## Rendered Example","Good token fill:","My take:","Question: approve-message or revise-messaging?","Recommendation:"],"doNotShowByDefault":["Token Notes","Good omit / fallback","Bad fill to avoid","Token Adherence Table"],"internalPersistenceOnly":["Token Fill Rules","Token Fill Examples","fallback guidance","bad-fill avoidance notes"],"copyMustInclude":["QA receipt before approval","tokens resolved","company/person match","proof claim","prospect angle","language/tone when known","obvious bad fits","Would I take this call?","weak personalization can burn the sender's reputation","full list remains paused"],"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]},{"action":"ask_message_review_choice","uses":"request_user_input","choices":["approve-message","revise-messaging"],"copyMustInclude":["approve this message template and continue","nothing sends from this approval"],"autoSelectIf":"yolo_mode and Recommendation is approve-message; revise autonomously when Recommendation is revise-messaging","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]},{"action":"sync_approved_message_set_to_campaign_brief","tool":"update_campaign_brief","when":"after approve-message","requiredFields":["campaignId","approvedMessageTemplate","Token Fill Rules","Token Fill Examples"],"writesCampaignState":"approvedMessageTemplate"}],"requiredCampaignState":["campaignId","workflowTableId","messageDraftRecommendation"],"allowedTools":["AskUserQuestion","request_user_input","update_campaign_brief","update_campaign","get_rows_minimal"],"doNotAllow":["create_campaign","list_senders","save_rubrics","import_leads","confirm_lead_list","start_campaign"],"waitFor":["message_approved","revise_messaging"],"transitions":{"message_approved":"validate-sample","revise_messaging":"message-generation"}},{"id":"validate-sample","label":"Validate campaign-table execution slice","currentStepValue":"apply-icp-rubric","reference":"references/sample-validation-loop.md","visibleStepRule":"Filter Leads is reached only after saved-filter approval. On approve-message, save the template, refresh apply-icp-rubric narration, then queue bounded Enrich Prospect cells.","onEnter":[{"tool":"update_campaign","requiredValues":{"currentStep":"apply-icp-rubric","watchNarration.stage":"fit-message"},"purpose":"keep the watched UI on Filter Leads while the approved template starts the bounded cascade","watchNarrationRule":"Say the template is saved and Filter Leads is now running the bounded enrichment and scoring pass."},{"tool":"queue_cells","purpose":"queue ICP/enrichment cells for the internal campaign-table execution slice only","requiredFields":["workflowTableId","reviewBatchRowIds","cellTypes"],"targetCountSource":"reviewBatchRowIds.length (default 15)"},{"tool":"wait_for_campaign_table_ready","purpose":"wait_for_review_batch_scoring_cells"},{"tool":"get_rows_minimal","requiredValues":{"includeRows":false},"purpose":"read compact row status only"},{"tool":"wait_for_rubric_results","requiredFields":["workflowTableId","targetCount","minPassedCount"],"requiredValues":{"includeRows":false,"minPassedCount":1},"minPassedCountSource":"firstPassingRowForMessageStart","readVia":"stats_only_tool_result","customerSummaryPattern":["{checked} leads checked","{passed} passed and have draft messages","{blocked} blocked before sending","full list remains paused until approval"]},{"action":"handle_partial_or_timeout_sample","customerStatus":"sample-needs-revision","rule":"Do not repeat waits indefinitely; surface partial status and route to revision.","copyMustInclude":"completed, passed, pending, blocked before sending, and full list remains paused"}],"requiredCampaignState":["campaignId","workflowTableId","approvedMessageTemplate","filterRubricsApproved when enableICPFilters=true"],"allowedTools":["get_subskill_asset","queue_cells","wait_for_campaign_table_ready","get_rows_minimal","wait_for_rubric_results","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","enrich_with_prospeo","bulk_enrich_with_prospeo","check_rubric"],"hardRules":["wait_for_rubric_results_never_retain_rows_payload_in_tail_context","wait_for_rubric_results_targetCount_always_explicit","timeout_never_repeats_without_customer_handoff","timeout_or_underfloor_sample_never_advances_to_settings"],"waitFor":["sample_validated","sample_revision_required"],"transitions":{"sample_validated":"auto-execute-messaging","sample_revision_required":"lead-review","revise_leads":"find-leads","revise_rubric":"filter-rubric","escalation_triggered":"escalation"}},{"id":"auto-execute-messaging","label":"Generate initial campaign-row messages","currentStepValue":"auto-execute-messaging","references":["references/parallel-critique-protocol.md","references/thomas-variant-selection.md","references/thomas-revision-filters.md","references/step-15-re-cascade.md"],"onEnter":[{"tool":"queue_cells","batchSize":100,"when":"any passing row has pending or empty Generate Message cell","cellSource":"pending_generate_message_cells_for_passing_rows using approved_campaign_brief_template"},{"tool":"wait_for_rubric_results","purpose":"wait_for_first_passing_generated_message","requiredValues":{"includeRows":false,"minPassedCount":1,"minMessagesCount":1},"readVia":"stats_only_tool_result"},{"action":"observe_generate_message_results","reference":"references/step-15-re-cascade.md"},{"action":"enforce_token_contract","modeFromConfig":"messaging.tokenContract"},{"action":"optional_critique_pass","enabledFromConfig":"messaging.critique.enabled","plan":"85-03","protocol":"references/parallel-critique-protocol.md","sampleSizeFromConfig":"messaging.critique.sampleSize","budgetUsdCapFromConfig":"messaging.critique.budgetUsdCap","perCriticTimeoutFromConfig":"messaging.critique.perCriticTimeoutSeconds","totalTimeoutFromConfig":"messaging.critique.totalTimeoutSeconds","criticsFromConfig":"messaging.critique.critics","enforceFinalizerPassFromConfig":"messaging.critique.synthesis.enforceFinalizerPass","rejectOnFakeProofFromConfig":"messaging.critique.rejectOnFakeProof","rejectOnUnsupportedTokenFromConfig":"messaging.critique.rejectOnUnsupportedToken","onBudgetTrip":"halt_critique_continue_plain_tail","onPerCriticTimeout":"drop_critic_proceed_with_remaining","onTotalTimeout":"persist_plain_message_for_row","onTokenContractViolation":"persist_plain_message_for_row","onFakeProof":"persist_plain_message_for_row"},{"action":"optional_opus_subset","enabledFromConfig":"messaging.critique.opus.enabled","selection":"references/thomas-variant-selection.md","maxMessagesPerPassFromConfig":"messaging.critique.opus.maxMessagesPerPass","budgetUsdCapFromConfig":"messaging.critique.opus.budgetUsdCap","onOpusBudgetTrip":"halt_opus_continue_non_opus","onOpusTokenContractViolation":"fallback_to_non_opus_rewrite"},{"tool":"update_campaign","requiredValues":{"currentStep":"auto-execute-messaging","watchNarration.stage":"review-ready"},"watchNarrationRule":"Say the first passing generated message is ready in Messages; next is review before Settings."},{"action":"ask_generated_message_review_choice","uses":"request_user_input","choices":["Approve reviewed draft rows and continue to Settings","Revise filters","Revise message template","Pause here"],"copyMustInclude":["approve the reviewed draft rows and continue to Settings","tokens resolved","company/person match","proof claim","prospect angle","language/tone when known","obvious bad fits","Would I take this call?","weak personalization can burn the sender's reputation","full list remains paused","nothing sends from this approval"],"autoSelectIf":"yolo_mode and the first passing generated message clears the quality floor","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"allowedTools":["get_subskill_asset","get_rows_minimal","queue_cells","wait_for_rubric_results","update_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["import_leads","list_senders","start_campaign","generate_messages"],"hardRules":["critique_failure_never_escalates","critique_sample_size_bounded_by_config","first_passing_generated_message_unblocks_review","critics_fixed_at_targeting_copy_voice","synthesis_enforces_phase_84_token_contract","opus_reserved_for_highest_value_subset","proposed_token_never_persisted_in_rewrite"],"waitFor":["generated_messages_approved","sample_revision_required"],"transitions":{"generated_messages_approved":"awaiting-user-greenlight","revise_filters":"filter-rubric","revise_messaging":"message-generation","escalation_triggered":"escalation"}},{"id":"awaiting-user-greenlight","label":"Settings, sender, sequence, and greenlight","currentStepValue":"awaiting-user-greenlight","reference":"references/final-handoff-contract.md","onEnter":[{"tool":"get_campaign","purpose":"verify senderIds and sequence state before final handoff"},{"tool":"list_senders","purpose":"surface available connected senders only at Settings"},{"tool":"update_campaign","requiredValues":{"currentStep":"settings","watchNarration.stage":"review-ready"},"purpose":"park the watched UI on Settings before sender selection","watchNarrationRule":"Say message review is complete and the browser is now on Settings so the user can connect or choose a sender. Include that lead research and filtering already happened outside the user's LinkedIn account, this sender is only used for approved sending after final launch, and no launch happens from Settings."},{"action":"surface_sender_and_slack_handoff","requiredVisibleContent":["connect or select a LinkedIn sender","Lead research and filtering already happened outside your LinkedIn account.","This account is only used for approved sending after final launch.","Nothing sends until the final Start step.","Slack reply review","recommended sequence","final launch confirmation is still ahead"],"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]},{"action":"ask_sender_selection","uses":"request_user_input","singleChoice":true,"choices":["Use this connected sender","Connect a different sender in Settings","Pause here"],"autoSelectIf":"yolo_mode and exactly one safe connected sender is available","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]},{"action":"attach_selected_sender","tool":"update_campaign","when":"user selected an available connected sender","requiredValues":{"senderIds":["{selectedSenderId}"],"currentStep":"sequence","watchNarration.stage":"review-ready"},"watchNarrationRule":"Say the sender was attached and the browser is moving to Sequence review. Include that the sequence is editable before launch and still not sending until Start."},{"tool":"attach_recommended_sequence","when":"after senderIds are attached","requiredValues":{"campaignId":"{campaignId}","currentStep":"send","watchNarration.stage":"review-ready"},"watchNarrationRule":"The sequence tool owns the visible beat: say the recommended follow-up sequence is attached, the browser is now on final launch review, and the next action is final launch greenlight. Include that it is still not sending until Start. Do not follow with a separate step-only update_campaign fixup."},{"action":"ask_final_launch_greenlight","uses":"request_user_input","singleChoice":true,"choices":["Start campaign","Review campaign first","Pause here"],"copyMustInclude":["quality confidence means sample messages and prospect angle were checked","launch confidence means sender, sequence, and Start are ready","approved messages begin sending according to the sequence","replies and meetings follow connected settings","you can monitor and pause","no hidden extra approval disappears after clicking Start"],"onUserStart":"claude-greenlight","neverAutoSelectInYolo":true,"copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"allowedTools":["get_campaign","get_campaign_navigation_state","list_senders","update_campaign","attach_recommended_sequence","AskUserQuestion","request_user_input"],"doNotAllow":["start_campaign","import_leads"],"autoStart":false,"watchRequired":true,"waitFor":["sender_connection_required","sender_attached","sequence_attached","ready_to_launch","user_greenlight","ui_start_detected"],"transitions":{"sender_connection_required":"awaiting-user-greenlight","sender_attached":"awaiting-user-greenlight","sequence_attached":"awaiting-user-greenlight","ready_to_launch":"awaiting-user-greenlight","user_greenlight":"claude-greenlight","ui_start_detected":"running"}},{"id":"claude-greenlight","label":"Explicit launch","reference":"references/final-handoff-contract.md","onEnter":[{"tool":"get_campaign","purpose":"detect_already_running"},{"action":"verify_sequence_and_current_step_before_start","requiredState":["workflowTableId","senderIds","sequenceTemplate","at least one approved generated message","currentStep in awaiting-user-greenlight|claude-greenlight|send"]},{"tool":"start_campaign","requiredFields":["campaignId"],"persistsCurrentStep":"running","watchNarrationRule":"After start_campaign succeeds, the running state must say the final greenlight was accepted, the campaign is now live/running, and the user can watch progress from the campaign."}],"allowedTools":["get_campaign","attach_recommended_sequence","start_campaign","AskUserQuestion","request_user_input"],"watchRequired":true,"waitFor":"campaign_started","transitions":{"campaign_started":"running"}},{"id":"running","label":"Campaign is live","currentStepValue":"running","onEnter":[{"action":"surface_campaign_live_confirmation_without_watch_link","copyMustAvoid":["Watch link:","campaign-builder URL","repeat the watch URL"]}],"allowedTools":["get_campaign","AskUserQuestion","request_user_input"],"doNotAllow":["start_campaign"],"terminal":true},{"id":"escalation","label":"Escalation","reference":"references/escalation-ladder.md","allowedTools":["AskUserQuestion","request_user_input"],"doNotAllow":["start_campaign","import_leads"],"transitions":{"revise_brief":"brief-interview","revise_leads":"find-leads","revise_rubric":"filter-rubric","revise_messaging":"message-generation","abort":"abort"}},{"id":"abort","label":"Abort","allowedTools":["AskUserQuestion","request_user_input"],"terminal":true}]}
|
|
@@ -309,7 +309,7 @@ leadSourceType, leadSourceProvider })` and capture `{ campaignId, watchUrl }`
|
|
|
309
309
|
4. `save_rubrics({ campaignOfferId, rubric })` using `rubric.json` (or derived
|
|
310
310
|
payload from `lead-filter.md`)
|
|
311
311
|
5. flip brief status to `committed`; move draft to `.committed/`
|
|
312
|
-
6. surface the watch
|
|
312
|
+
6. surface the first watch-link handoff using `watch-link-handoff.md`
|
|
313
313
|
7. hand off to the autonomous tail (Plan 85-02)
|
|
314
314
|
|
|
315
315
|
See `watch-link-handoff.md` for the exact link-surfacing protocol and the
|
|
@@ -96,7 +96,9 @@ When escalating, the skill:
|
|
|
96
96
|
2. Leaves `CampaignOffer.currentStep` at the failing step (so resuming
|
|
97
97
|
the skill picks up exactly where it stopped — no silent rollback, no
|
|
98
98
|
silent advance).
|
|
99
|
-
3.
|
|
99
|
+
3. Describes the failing visible campaign state. Only recover and print the
|
|
100
|
+
watch link if the user asks for it or diagnostics show the link is missing or
|
|
101
|
+
broken.
|
|
100
102
|
4. Presents an actionable options menu via `AskUserQuestion`:
|
|
101
103
|
- `revise-brief` — go back to Phase 83/84 brief editing
|
|
102
104
|
- `revise-leads` — go back to Phase 84 find-leads
|
|
@@ -113,7 +115,7 @@ When escalating, the skill:
|
|
|
113
115
|
- The revision-round counter NEVER resets on resume.
|
|
114
116
|
- Escalation NEVER silently rolls back campaign state. The campaign row
|
|
115
117
|
exists; only `currentStep` tells us where to resume.
|
|
116
|
-
-
|
|
117
|
-
|
|
118
|
+
- Escalation does not repeat the watch link by default; use campaign state and
|
|
119
|
+
narration for orientation, and recover the URL only on request or link failure.
|
|
118
120
|
- Escalation is not an error in the sense that the skill crashed — it's
|
|
119
121
|
a planned exit. Surface it calmly.
|
|
@@ -30,7 +30,7 @@ order (all before waiting for the user):
|
|
|
30
30
|
2. Call `list_senders()` so the handoff can name available connected senders if
|
|
31
31
|
the campaign has none selected.
|
|
32
32
|
3. Call `update_campaign({ campaignId, currentStep: "settings" })` and use
|
|
33
|
-
`get_campaign_navigation_state` when available so the
|
|
33
|
+
`get_campaign_navigation_state` when available so the watched app visibly
|
|
34
34
|
lands on Settings before sender setup.
|
|
35
35
|
4. Explain sender setup and Slack in normal customer language: Sellable needs a
|
|
36
36
|
connected LinkedIn sender for launch, and Slack reply review matters because
|
|
@@ -48,7 +48,7 @@ order (all before waiting for the user):
|
|
|
48
48
|
safe mode, auto-select only the safe mock sender; never select a real sender.
|
|
49
49
|
7. Call `update_campaign({ campaignId, senderIds: [selectedSenderId],
|
|
50
50
|
currentStep: "sequence" })` to attach the sender via the v3 senders route and
|
|
51
|
-
move the
|
|
51
|
+
move the watched app to Sequence.
|
|
52
52
|
8. Call `attach_recommended_sequence({ campaignId, currentStep: "send" })` to
|
|
53
53
|
bind the tier-aware recommended sequence. Explain the sequence choice in the
|
|
54
54
|
handoff: Sales Nav/Recruiter senders get the Sales Nav Open Profile
|
|
@@ -56,11 +56,9 @@ currentStep: "sequence" })` to attach the sender via the v3 senders route and
|
|
|
56
56
|
and Paid InMail Campaign is only an explicit paid-InMail opt-in because it
|
|
57
57
|
can spend InMail credits. If that response does not persist `currentStep:
|
|
58
58
|
"send"`, call `update_campaign({ campaignId, currentStep: "send" })`.
|
|
59
|
-
9.
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
alongside the watch link so the user sees both the link and the
|
|
63
|
-
"what to do next" sentence.
|
|
59
|
+
9. Surface the `handoff.orientation` string from `auto-execute.yaml` and
|
|
60
|
+
summarize the visible Settings/Sequence/Send state without repeating the
|
|
61
|
+
watch URL.
|
|
64
62
|
11. Ask the final launch greenlight with the structured question function:
|
|
65
63
|
Start campaign, Review campaign first, or Pause here.
|
|
66
64
|
Before the Start choice, summarize launch confidence separately from quality
|
|
@@ -74,7 +72,7 @@ currentStep: "sequence" })` to attach the sender via the v3 senders route and
|
|
|
74
72
|
|
|
75
73
|
The final handoff must answer five customer questions in plain language:
|
|
76
74
|
|
|
77
|
-
-
|
|
75
|
+
- what the already-open campaign view is showing
|
|
78
76
|
- what is ready now (brief, filters/rubrics, campaign rows, messages, and
|
|
79
77
|
sequence after a sender is attached)
|
|
80
78
|
- whether the next UI step is Settings, Sequence, or Send
|
|
@@ -157,8 +155,8 @@ order, atomically:
|
|
|
157
155
|
the backend start route. If this call fails, surface the error; do NOT leave
|
|
158
156
|
the approvals in an "approved but not started" half-state silently
|
|
159
157
|
— either retry or escalate with the explicit state.
|
|
160
|
-
6.
|
|
161
|
-
|
|
158
|
+
6. Return a short "campaign is live" confirmation sentence without repeating the
|
|
159
|
+
watch URL unless the user explicitly asks for it.
|
|
162
160
|
|
|
163
161
|
The mutating start steps (sequence repair when needed, bulk-approve, and
|
|
164
162
|
start_campaign) must succeed together. If approve-batch
|
|
@@ -177,8 +175,8 @@ greenlight path:
|
|
|
177
175
|
fetch is available).
|
|
178
176
|
2. If the campaign is already running (status === "running" /
|
|
179
177
|
`currentStep === "running"`), skip all three mutating steps and
|
|
180
|
-
return a confirmation message
|
|
181
|
-
|
|
178
|
+
return a confirmation message without repeating the watch URL unless the user
|
|
179
|
+
asks for it.
|
|
182
180
|
3. Do NOT call `start_campaign` again. Do NOT call approve-batch
|
|
183
181
|
again. Do NOT update `currentStep`.
|
|
184
182
|
|
|
@@ -247,3 +245,6 @@ does NOT silently proceed.
|
|
|
247
245
|
skill stays in `awaiting-user-greenlight`.
|
|
248
246
|
- Never print the watch link in a half-started state (approved but not
|
|
249
247
|
started, or a start response that did not return `currentStep: "running"`).
|
|
248
|
+
- After the initial brief handoff, do not print the watch URL during final
|
|
249
|
+
handoff, greenlight, or running confirmation unless the user explicitly asks
|
|
250
|
+
for it or recovery requires a replacement link.
|
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
# Watch Link Handoff
|
|
2
2
|
|
|
3
3
|
This reference governs how create-campaign-v2 surfaces the watch link in the
|
|
4
|
-
shell-first flow. Load it before
|
|
5
|
-
link
|
|
4
|
+
shell-first flow. Load it before the first link handoff and only on resumes
|
|
5
|
+
where the user explicitly asks for the link or the flow must repair a
|
|
6
|
+
missing/broken link.
|
|
6
7
|
|
|
7
8
|
CampaignOffer state and the watch link are canonical. Disk artifacts are
|
|
8
9
|
optional debug/UAT diagnostics; normal customer runs should not create, link, or
|
|
@@ -17,7 +18,8 @@ The URL includes the auth token and workspace routing needed for browser
|
|
|
17
18
|
auto-login, then the app exchanges the token through `/auth/continue` and
|
|
18
19
|
cleans it from the browser URL. V2 does NOT mint a new token, does NOT call a
|
|
19
20
|
different route, and does NOT construct the URL locally. Capture whatever
|
|
20
|
-
`watchUrl` the existing tool returns and surface it verbatim
|
|
21
|
+
`watchUrl` the existing tool returns and surface it verbatim only for the first
|
|
22
|
+
brief handoff or explicit link recovery.
|
|
21
23
|
|
|
22
24
|
## Shell-First Link
|
|
23
25
|
|
|
@@ -84,8 +86,8 @@ browser-control tools just because a watch link exists.
|
|
|
84
86
|
browser. If the user's Claude Code setup opens links in an in-app/preview
|
|
85
87
|
browser, that is fine, but do not attempt to open Chrome for them.
|
|
86
88
|
|
|
87
|
-
Surface the link as a normal Markdown link and continue with explicit
|
|
88
|
-
updates in chat.
|
|
89
|
+
Surface the first link as a normal Markdown link and continue with explicit
|
|
90
|
+
progress updates in chat.
|
|
89
91
|
|
|
90
92
|
If shell creation fails or the response is missing `watchUrl`, stop and surface
|
|
91
93
|
the error. Do not continue into campaignless source scouting in the active
|
|
@@ -99,8 +101,9 @@ watch link must be the real `watchUrl` returned by `create_campaign`.
|
|
|
99
101
|
## Step Orientation
|
|
100
102
|
|
|
101
103
|
After the shell exists, the main thread must update `currentStep` before each
|
|
102
|
-
meaningful visible stage, then
|
|
103
|
-
|
|
104
|
+
meaningful visible stage, then describe what the already-open app will show
|
|
105
|
+
next. Do not print another `Watch link:` line during normal progress or approval
|
|
106
|
+
turns:
|
|
104
107
|
|
|
105
108
|
- source selected: the campaign should point at the primary provider step
|
|
106
109
|
- rubrics saved: the campaign should show filter/rubric state
|
|
@@ -109,11 +112,12 @@ orient the user after a meaningful step change:
|
|
|
109
112
|
- settings handoff: the campaign should open to Settings for sender attachment
|
|
110
113
|
- running: the campaign is live
|
|
111
114
|
|
|
112
|
-
Do not
|
|
115
|
+
Do not repeat the link between internal tool calls or approval questions.
|
|
113
116
|
|
|
114
|
-
## Resume
|
|
117
|
+
## Resume And Recovery
|
|
115
118
|
|
|
116
|
-
On resume
|
|
119
|
+
On resume, do not print the link by default. If the user asks for the link again
|
|
120
|
+
or diagnostics say the prior watch URL is missing/broken:
|
|
117
121
|
|
|
118
122
|
1. Load the `CampaignOffer` for the campaign being resumed.
|
|
119
123
|
2. Recover the direct watch link through the existing resume path
|
|
@@ -122,8 +126,8 @@ On resume:
|
|
|
122
126
|
land now.
|
|
123
127
|
4. Print the recovered `watchUrl`.
|
|
124
128
|
|
|
125
|
-
|
|
126
|
-
|
|
129
|
+
Recovery must use the `watchUrl` from the tool response, not a cached or
|
|
130
|
+
reconstructed URL.
|
|
127
131
|
|
|
128
132
|
## Hard Rules
|
|
129
133
|
|
|
@@ -133,6 +137,8 @@ cached or reconstructed URL.
|
|
|
133
137
|
browser automation just because a watch link exists.
|
|
134
138
|
- Missing `watchUrl` is an error, not a silent skip.
|
|
135
139
|
- The first watch link must be shown only after the v1 brief is on the campaign.
|
|
140
|
+
- After the first brief handoff, normal approvals must not print `Watch link:`
|
|
141
|
+
again; update campaign state and narration instead.
|
|
136
142
|
- A watch link is not approval to import, attach sequence, or start.
|
|
137
143
|
- Import/enrichment remains blocked until rubrics are saved and the approved
|
|
138
144
|
message set is synced into the campaign brief.
|
|
@@ -69,13 +69,13 @@ Step 15 — observe messaging
|
|
|
69
69
|
|
|
70
70
|
Step 16 — awaiting-user-greenlight
|
|
71
71
|
get_campaign + list_senders
|
|
72
|
-
update_campaign(currentStep=settings) +
|
|
72
|
+
update_campaign(currentStep=settings) + describe the visible Settings state
|
|
73
73
|
if no connected sender exists: explain sender setup + Slack reply review, surface campaign settings link, and STOP
|
|
74
74
|
ask the user which connected sender to attach; in explicit UAT safe mode only, use the safe mock sender
|
|
75
75
|
update_campaign(senderIds=[selectedSenderId], currentStep=sequence)
|
|
76
76
|
attach_recommended_sequence({ campaignId, currentStep: "send" }) # tier-aware: premium/SN -> If Open Profile->INMAIL_OPEN, else INVITE->accepted->DM
|
|
77
77
|
if the attach response did not move the UI: update_campaign(currentStep=send)
|
|
78
|
-
|
|
78
|
+
surface campaign setup orientation + final launch choices without repeating the watch URL
|
|
79
79
|
STOP. DO NOT call start_campaign. DO NOT move to running without explicit launch greenlight.
|
|
80
80
|
```
|
|
81
81
|
|
|
@@ -146,7 +146,7 @@ as one load. If a tool result already told you to load a prompt, load all
|
|
|
146
146
|
chunks once and remember; do not restart the same prompt from offset 0 later.
|
|
147
147
|
|
|
148
148
|
After every `update_campaign({ currentStep: ... })` in the tail, narrate
|
|
149
|
-
what changed and orient the user to what the
|
|
149
|
+
what changed and orient the user to what the already-open app will show next —
|
|
150
150
|
reuse the v1 `create-campaign` watch-mode pattern verbatim.
|
|
151
151
|
|
|
152
152
|
Resume currentStep names covered by this tail: `"auto-execute-leads"`,
|
|
@@ -270,8 +270,8 @@ searchId, targetLeadCount: 1000 })`.
|
|
|
270
270
|
6. If the import returns zero usable leads, ESCALATE per
|
|
271
271
|
`references/escalation-ladder.md` (hard fail).
|
|
272
272
|
7. `update_campaign({ campaignId, currentStep: "filter-choice" })`.
|
|
273
|
-
8.
|
|
274
|
-
|
|
273
|
+
8. Describe the filter-choice screen now visible in the already-open app. Do not
|
|
274
|
+
repeat the watch URL.
|
|
275
275
|
|
|
276
276
|
**Do NOT call `check_rubric`, `wait_for_rubric_results`,
|
|
277
277
|
`queue_cells`, `enrich_with_prospeo`, or `bulk_enrich_with_prospeo` in Step 13.**
|
|
@@ -468,7 +468,7 @@ Shape:
|
|
|
468
468
|
2. Call `list_senders()` so you can surface connected sender options if the
|
|
469
469
|
campaign has no attached sender.
|
|
470
470
|
3. Call `update_campaign({ campaignId, currentStep: "settings" })`, then use
|
|
471
|
-
`get_campaign_navigation_state` when available to confirm the
|
|
471
|
+
`get_campaign_navigation_state` when available to confirm the watched app is
|
|
472
472
|
visibly on Settings.
|
|
473
473
|
4. Explain why the sender matters: Sellable needs a connected LinkedIn sender
|
|
474
474
|
for launch. Explain Slack reply review before launch: replies
|
|
@@ -482,7 +482,7 @@ Shape:
|
|
|
482
482
|
mode, use only the safe mock sender; never pick a real sender.
|
|
483
483
|
7. Call `update_campaign({ campaignId, senderIds: [selectedSenderId],
|
|
484
484
|
currentStep: "sequence" })` to attach the sender through the v3 senders
|
|
485
|
-
route and
|
|
485
|
+
route and describe the Sequence view.
|
|
486
486
|
8. Call `attach_recommended_sequence({ campaignId, currentStep: "send" })`
|
|
487
487
|
(bind the tier-recommended sequence to the campaign). Explain the sequence
|
|
488
488
|
choice plainly: Sales Nav/Recruiter senders get the Sales Nav Open Profile
|
|
@@ -491,9 +491,8 @@ currentStep: "sequence" })` to attach the sender through the v3 senders
|
|
|
491
491
|
opt-in because it can spend InMail credits. If the tool response
|
|
492
492
|
does not persist `currentStep: "send"`, call
|
|
493
493
|
`update_campaign({ campaignId, currentStep: "send" })`.
|
|
494
|
-
9.
|
|
495
|
-
|
|
496
|
-
alongside the link.
|
|
494
|
+
9. Surface the `handoff.orientation` string from `auto-execute.yaml` without
|
|
495
|
+
repeating the watch URL.
|
|
497
496
|
11. Ask the final launch greenlight with the structured question function:
|
|
498
497
|
Start campaign, Review campaign first, or Pause here.
|
|
499
498
|
12. STOP unless the user explicitly chooses Start campaign. Do NOT call
|
|
@@ -516,8 +515,8 @@ campaign:
|
|
|
516
515
|
bulk-approve queued messages via the EXISTING endpoint
|
|
517
516
|
`POST /api/v3/workflow-tables/cells/approve-batch` (do NOT invent a
|
|
518
517
|
new tool/endpoint), (d) `start_campaign({ campaignId })`, which persists
|
|
519
|
-
`currentStep: "running"`, (e)
|
|
520
|
-
|
|
518
|
+
`currentStep: "running"`, (e) surface a "campaign is live" confirmation
|
|
519
|
+
without repeating the watch URL unless the user asks for it.
|
|
521
520
|
|
|
522
521
|
A Claude greenlight on an already-running campaign is a no-op
|
|
523
522
|
confirmation, NOT a duplicate start. Detect via `get_campaign` before
|
|
@@ -560,7 +559,7 @@ runs.
|
|
|
560
559
|
| File | Load when |
|
|
561
560
|
| ------------------------------------------------ | ------------------------------------------------------------------------- |
|
|
562
561
|
| `references/approval-gate-framing.md` | Phase 85 commit gate, before showing the approval packet |
|
|
563
|
-
| `references/watch-link-handoff.md` |
|
|
562
|
+
| `references/watch-link-handoff.md` | First brief handoff + explicit link recovery only |
|
|
564
563
|
| `references/post-mint-source-materialization.md` | Step 13, before importing normal-discovery or legacy campaignless sources |
|
|
565
564
|
| `references/sample-validation-loop.md` | Step 14, before enriching + scoring the sample |
|
|
566
565
|
| `references/escalation-ladder.md` | Any tail step that needs to decide retry / revise / escalate |
|