---
name: create-campaign-v2
description: compact JSON-gated shell-first Sellable campaign flow through source, reviews, Settings, sequence, and explicit start.
visibility: internal
---

# Create Campaign Workflow

Fast entry point. This is the fast entry point for `core/flow.v2.json`; load deeper references only at the stage that needs them.
Load the compact flow for exact gates:

```text
get_subskill_asset({ subskillName: "create-campaign-v2", assetPath: "core/flow.v2.json" })
```

If the tail begins, load the tail prompt before auto-execute or validate-sample:

```text
get_subskill_prompt({ subskillName: "create-campaign-v2-tail" })
```

CampaignOffer state and the watch link are canonical. Resume, gating, and
handoff read campaign state first: `campaignId`, `watchUrl`, `campaignBrief`,
`currentStep`, source/search IDs, `selectedLeadListId`, `workflowTableId`,
`leadScoringRubrics`, `approvedMessageTemplate`, `senderIds`,
`sequenceTemplate`, and running state. Local draft files are debug-only.

There is no normal approval-packet, commit-gate, or atomic-mint path in the
active flow. Legacy packet/mint artifacts stay in
`create-campaign-v2-validation` and rehearsal material only.

## Normal Flow

1. Bootstrap and show the active Sellable workspace.
2. Resolve the client/company, research it, and draft the brief.
3. Create the watchable shell with `create_campaign`.
4. Print the direct watch link once before brief approval.
5. Approve Find Buyers Plan, then Start Import.
6. Import the source list and confirm 15 setup rows.
7. Choose filters, save/approve criteria, then review the message template.
8. Sync the approved template, queue bounded filtering, review one generated pass, then Settings and launch.

## Inbox Reply Tool Boundary

Inbox reply tools are outside campaign creation. Stop the campaign lane for
reply search/review/edit/send asks and use existing product /api/v3/inbox routes.
Before any draft edit/send tool call, require exact approval for workspace,
sender, thread, body, tool, and expected side effect.

## Identity-First Campaign Setup

Do not treat the active workspace as the campaign subject. Resolve the
client/company, then confirm target, offer, proof, and source before the brief.

First visible request when no identity is known, after showing the active workspace:

```text
Excited to help you launch your LinkedIn outbound campaign. First I'll use your LinkedIn profile to understand the company, then I'll draft the brief, choose where to find buyers, review messages, and wait for final launch approval. What's your LinkedIn profile URL or handle?
```

Require a LinkedIn profile URL/handle before setup continues. Normalize
handles like `@csreyes92` to `https://www.linkedin.com/in/{handle}/`; otherwise
ask again. Retain it as `senderLinkedinUrl` or resolve `clientProspectId` for
`create_campaign`. Run one lightweight profile lookup before strategy questions.

Restore setup intake before the brief. Ask bounded choices for:

- target: "Who should we target first?" with the researched recommendation
- pitch: "What should we pitch to prospects first?" with the researched recommendation editable
- trust proof: "What is the strongest credibility signal we can lead with?"
- prospect source: "How should we find prospects?" with "Find prospects for me", "Use a CSV of LinkedIn profiles", and "Use a CSV of company domains"

If setup choices were supplied and questions skipped, state before the brief
that the LinkedIn input was normalized and the offer path came from supplied or
researched direction.

Do not call `list_senders`, infer from connected senders, or show a sender
picker during setup. Sender availability belongs only to Settings after message
approval and campaign setup validation.

Use `research-sender`, call `complete_sender_research` before shell creation,
and carry proof gaps into the brief.

## Brief Provenance

When rendering the first brief, do not add bracketed inline source tags. Start
with one short provenance line:

```text
This brief is based on Sellable's research and your answers.
```

Use `## Sender and Company`, not `## Campaign Identity`.

Before asking for approval, say:

```text
This is based on what I found. If your site or LinkedIn is stale, tell me what
to update before I build the lead list.
```

Ask: "Does this brief look right, and how should Sellable continue?" The early
brief must not include quoted first-message copy. If a message-shape hint is useful,
keep it as non-final bullets and say: "This is not the message yet; I will write
real examples after we find and filter leads."

Keep brief approval positive: no "does not approve..." list. The first brief
must be visible exactly once before approval. If rendered before
`create_campaign`, do not render it again after shell creation; append the one
watch-link handoff, then ask approve/revise.

## YOLO Mode

Enable YOLO when the user asks for yolo/autopilot, passes `--yolo` or
`mode=yolo`, selects `Approve brief + activate YOLO mode (Recommended)`, or asks
you to use best guesses. If identity is missing, ask only for the LinkedIn
profile URL; never
continue from a website/domain alone. Infer buyer, offer/CTA, proof, source,
filters, and message direction from evidence plus user directions; newest wins.
Set `interactionMode: "autonomous"` once `campaignId` exists. Auto-select
confident pre-launch choices, show the assumption, and pause for missing data,
quality-floor failures, or final launch. Never call `start_campaign` without
explicit launch confirmation. YOLO still advances one visible checkpoint at a
time: brief approval moves only to source plan, never source/action approval.

## Structured Questions

Use the host-native structured question gate (`request_user_input` or
AskUserQuestion) only for bounded choices:

- target, offer, credibility, and prospect-source setup choices after research
- Find Buyers Plan, Start Import, or source revision
- use filters vs skip filters
- approve message vs revise message
- sender/sequence/launch choices at Settings

Never use it to collect open text, pasted URLs, domains, CSV contents, or
custom instructions. Ask those in normal chat. Keep `Other / custom` available
when a bounded choice needs an escape hatch.

## Watch Link And Narration

Create the shell before lead import so the user can watch. When
`create_campaign` returns `watchHandoff.markdown`, print it once before brief
approval, preserving the exact `watchUrl`:
`/campaign-builder/{campaignId}?mode={claude|codex}` plus `workspaceId` and
`token`; never derive, shorten, reconstruct, or print a bare campaign URL.
Recover missing/broken links with `create_campaign({ campaignId })` or
`get_campaign` before approval. Initial shell state is only brief review:
`currentStep: "create-offer"` plus brief/identity. Do not pass source, selected
list, workflow-table, or provider/search state until later approval transitions.
Later turns must not print another watch-link handoff; update `currentStep` and
`watchNarration` so the open app changes live. Never call browser-opening tools,
shell `open`, Computer Use, or browser automation just because a watch link
exists.

Every watched step switch must call:

```text
update_campaign({ campaignId, currentStep, watchNarration })
```

`watchNarration` must say what just happened, the current visible state, the
agent intent, and the next user action. Do not mention MCP, prompt chunks, file
paths, local artifacts, or internal worker mechanics in customer-facing watch
copy.

## Lead Source

Default source order when unspecified: LinkedIn post engagement, Sales Nav
recent activity, broader Sales Nav title search, then Prospeo. Use that order only
when not hiring-led. For hiring-by-role signals, start with Prospeo because
`search_prospeo` supports `company_job_posting_hiring_for` and
`company_job_posting_quantity`; Sales Nav does not provide hiring-by-role filters.
For company/account lookalikes, best-customer lookalikes, "companies like X",
companies that use AI, or companies with API/SSO/Chrome extension, use Prospeo
account discovery before people search:
`search_prospeo_companies -> confirm_prospeo_company_accounts -> search_prospeo`.
Show the account sample first, ask approval, then pass the returned
`companySearchToken` and selected Prospeo company IDs into
`confirm_prospeo_company_accounts`. The confirmed `domainFilterId` constrains
the follow-on people search; account rows are not people leads yet.

For lookalike seed selection, route by campaign intent. In outbound/sales
prospecting campaigns, treat "best customer", "top customer", "target domains",
"approved accounts", "customer domains", and similar wording as target
account/customer seed asks. Use explicit user-provided target/account/customer
domains or company names first, then verified past-customer/account evidence from
research, CRM, or proof. Never substitute the sender's current company/domain or
employer history as a lookalike seed for outbound unless the user explicitly
confirms that domain is a target/customer seed or asks for sender-company peers.
In job-search/application campaigns, lookalike seeds may be existing companies
from the candidate's current or past employers because those companies define
the candidate-fit lane. If campaign intent or seed source is ambiguous, ask
whether the seed should be target/customer domains or current/past employers; if
YOLO requires moving without a seed, switch to non-lookalike company filters
instead of inventing a seed. If the user asks for a geography like Germany,
preserve it in account discovery where supported (`company_location_search` or
`company_icp.geographic_markets`) and in follow-on people search
(`person_location_search`); do not drop geography when moving from lookalike
accounts to people leads.

Before any provider prompt/search/scout call, move the watched campaign to source selection, show `## Find Buyers Plan`, then open `request_user_input` without repeating the URL. The plan appears before the question and only authorizes where to look; it does not add anyone. There is exactly one structured source-plan approval question. Never open a generic pre-plan question or recover by opening a duplicate; if one already captured the exact visible recommendation, show the plan and continue from that choice.
Write:

- the buyer groups or places we could check
- the best place to start
- why the right buyers are likely to be there
- what signs the next search will check
- where you'll look next if the first place is too thin
- one concise line: "This only approves me to look for the best places to find
  buyers. I won't add anyone yet."

After brief approval, say: "Brief approved. Next, we'll choose where to find
buyers. I won't add anyone yet." Do not say "lead-source scouting", "source
scouting", or "not importing leads" in customer-facing copy.
Terms: buyers=market; people to check=raw reactions/comments; prospects=likely
after fit; leads=rows.

Make the recommendation campaign-specific. If LinkedIn engagement is recommended, name exact post themes in plain language. Avoid chat terms "Signal Discovery", "lead-source scouting", "source scouting", "lane", "provider", "precision/scale tradeoff", "evidence quality", "pilot volume", "workflow pain", or "ICP". Only the approval question after that visible plan, an explicit post-plan source choice, or an exact pre-plan source choice shown again satisfies this gate. Do not ask a second source-plan question or call `search_signals`, `search_sales_nav`,
`search_prospeo`, `fetch_post_engagers`, `search_prospeo_companies`, or provider subagents until approved.
Only then persist `leadSourceType`, `leadSourceProvider`, and provider `currentStep`.

Source work stays inline in the parent thread in normal create-campaign runs.
Do not launch source-scout background agents. Run the approved provider probes
with MCP tools, loading only the provider prompt for the active sequential
source. After Start Import approval, call `get_provider_prompt` before
`import_leads`. The packaged normal path installs only Message Drafting as a
background agent.
Use a 10% planning floor after cleanup. If LinkedIn engagement falls below it,
move to Sales Nav, then Prospeo. If Prospeo also falls below 10%, tighten the
ICP/source direction instead of inventing another provider.

After scouting, show a second approval gate for Start Import.
For LinkedIn engagement (`signal-discovery` internally), use plain language:
selected posts, people we can check, likely prospects, what the first
review will inspect, cleanup risk, and fallback. Label the approval like
"Approve scraping N recommended LinkedIn posts?" where N is the smallest
right-content post set that clears the approved buyer-search target. Do not
default to 3 just because `selectionTarget` was 3 for sampling. For Sales Nav or
Prospeo, name the specific search/import path and source lead count. Do not call
`import_leads` or `confirm_lead_list` until this gate is approved.

For Sales Nav and Prospeo, do not ask to import only the internal 15-row
campaign-table execution slice at Start Import. First-page samples are source
math. Once the path clears, ask approval for a source list with
`targetLeadCount` around 1,000 contacts by default. Then
`confirm_lead_list({ reviewBatchLimit: 15 })` copies rows into the campaign
table and returns execution-slice row ids for filter/message setup.

After the user approves this Start Import gate, do not show the
Source Recommendation again or ask another Start Import question.
Acknowledge once, call `get_provider_prompt`, then call `import_leads`
immediately. For Sales Nav/Prospeo, pass the approved source-list
`targetLeadCount`, not the campaign execution-slice size. For Signal Discovery,
pass `provider: "signal-discovery"`, `targetEngagerCount`, `maxPostsToScrape`,
and `confirmed: true`; the tool owns moving the watch UI.
After `import_leads`, poll `wait_for_lead_list_ready` until ready, failed, or
cancelled. Rows appearing is not enough for `confirm_lead_list`; use
`allowPartialSourceList: true` only when the user explicitly asks to continue early.

For LinkedIn engagement, Start Import uses `## Source Recommendation`: selected
posts, people to check, likely prospects, cleanup risk, fallback, first 15-lead
review. Tables show public activity + est. likely prospects, not duplicate
people-to-check; rows = fit rate * checkable people.

A source recommendation must show source path, filters/recipe, raw volume,
sample size, sampled fits as n/N plus percentage/range, estimated usable
prospects, cleanup risk, runner-up, and post-approval action. Sales
Nav/Prospeo also show `rawResultCount`, `sampledFitRate`, conservative
projected fit rate, `targetLeadCount` for the source list, and projected
good-fit count from that export. If below target, refine/broaden filters, not
run the internal campaign-table execution slice as if it were source sampling.

Supplied profile CSVs, company/domain CSVs, pasted domains, and existing
Sellable lead lists are supported, but keep provider mechanics out of the first
customer-facing source-choice labels.

## Prospect Setup Workstreams

After `confirm_lead_list` copies source rows and records the review batch, ask the filter-choice question immediately. Do not call `get_post_find_leads_scout_registry`, load filter/message prompts, or spawn Message Drafting before that question. Before it: short setup summary plus add filters, skip filters, or revise source; no extra watch link.

After filter choice, the only normal background worker is Message Drafting (`post-find-leads-message-scout`). The registry lookup is not a launch. Both choices must run this kickoff: call the registry, then `Task`/`spawn_agent` before filter refs, `save_rubrics`, or skip review. YOLO follows the same no-extra-question kickoff rule. If no background tool is callable, run inline as `parent-thread-fallback`; otherwise return `blocked` / `retry-needed`. `update_campaign({ currentStep: "messages" })` is not proof. On spawn, store proof at `watchNarration.workerDetails.messageDraftBuilder`: branch status, runId, timestamps, IDs, filterChoice, and reviewBatchRowHash/Ids. `workerStatuses.messageDraftBuilder` is only `running`; never store proof there or use `messageDrafting`.

Parent thread writes filters. On the filters path, start Message Drafting, load `references/filter-leads.md`, save rubrics, and ask users to approve saved criteria. Keep Filter Rules until criteria approval. After approval, move to `currentStep: "apply-icp-rubric"` so the app shows Filter Leads while Message Drafting finishes; no cells until template approval. Say Message Drafting is preparing it.

Message Drafting must not wait for `save_rubrics` or require visible
`leadScoringRubrics` before returning a reusable template. The parent waits for
saved-filter approval plus the message recommendation, so missing saved rubrics
in the branch read means "filters still owned by parent," not `blocked`.

Keep the handoff lean: `campaignId`, `workflowTableId`, concise brief/source summary, source-use rule, and 3-5 sample rows (`rowId`, name, title, company, signal). Do not paste copied row counts, hashes, full row IDs, broad row data, or local debug artifacts.

Route user copy feedback before `approve-message` back to Message Drafting; parent does not rewrite. The branch loads the full `generate-messages` prompt, every required asset, then `get_subskill_prompt({ subskillName: "create-campaign-v2-validation" })`; do not render `renderedFallbackSample`, concerns, or `qaReceipt` in the happy path. Generic fallback is `gpt-5.5` / `xhigh` Message Drafting. Handoff is labeled Markdown, not raw JSON.

## Hard Gates

- Do not call `create_campaign` until identity research and the brief are ready.
- Do not call `import_leads` or `confirm_lead_list` until the lead source is
  approved and `get_provider_prompt` has loaded in the current MCP session.
- Do not queue enrichment, ICP scoring, filtering, or product Generate Message
  cells until the campaign-table execution slice exists and the required message/filter approvals
  are complete.
- Do not call `check_rubric`; persist production rubrics with `save_rubrics`.
- Do not call `start_campaign` until the user explicitly confirms launch after
  Settings, sender, sequence, and final review.
- Do not use local files as durable state in normal runs.

## References

Load references only when needed:

- `references/watch-link-handoff.md` for watch-link handoff.
- `references/watch-guide-narration.md` for watch narration.
- `references/lead-validation-preview.md` for legacy/debug preview shapes.
- `references/filter-leads.md` for rubric design and `save_rubrics` rules.
- `references/step-13-import-leads.md` before source copy.
- `references/sample-validation-loop.md` before campaign setup validation.
- `references/final-handoff-contract.md` for Settings, sender, sequence, and
  start.
