@sellable/mcp 0.1.120 → 0.1.122

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.
@@ -27,14 +27,25 @@ Process:
27
27
 
28
28
  1. Read the campaign brief, kickoff doc, or lane prompt supplied by the parent.
29
29
  2. Search 3-5 keyword/topic lanes, favoring fresh posts from the last 7-14 days.
30
- 3. Select 3-5 promising posts when available. If a `campaignOfferId` was
31
- supplied, call `select_promising_posts({ campaignOfferId, selectionMode:
32
- "replace", selections, headlineICPCriteria, currentStep:
33
- "signal-discovery" })` before sampling so the watched Signal Discovery table
34
- shows the promoted posts.
35
- 4. Fetch or sample engagers for promoted posts and score rough ICP fit from visible headline/display-name cues only. Do not enrich people during viability estimation.
36
- 5. Estimate usable prospects per selected post from sampled pass rate. If the sample is good but volume is low, say how many more similar posts should be added or scraped.
37
- 6. Return false positives and dead ends explicitly.
30
+ 3. Inspect finalist posts by content type before final selection. Prefer posts
31
+ where the topic matches the campaign wedge, not generic high-engagement news.
32
+ 4. Promote the first narrow sample set when campaign-attached. If a
33
+ `campaignOfferId` was supplied, call `select_promising_posts({
34
+ campaignOfferId, selectionMode: "replace", selections, headlineICPCriteria,
35
+ currentStep: "signal-discovery" })` before sampling so the watched Signal
36
+ Discovery table shows the exact posts being tested.
37
+ 5. Fetch or sample engagers for promoted posts and score rough ICP fit from
38
+ visible headline/display-name cues only. Do not enrich people during
39
+ viability estimation.
40
+ 6. Compute capacity before recommending the source: target good-fit leads
41
+ (default 500 unless the parent supplies a target), reachable engagers,
42
+ sampled ICP-fit rate as `n/N` plus an easy percentage/range, expected usable
43
+ leads per right-content post after dedupe/cleanup, and posts needed to hit
44
+ the target.
45
+ 7. Select/promote enough right-content posts to plausibly hit the target. If the
46
+ warm Signals pool is useful but too small, return the expected warm range and
47
+ recommend Sales Nav/Prospeo for scale instead of padding with noisy posts.
48
+ 8. Return false positives and dead ends explicitly.
38
49
 
39
50
  Return a concise structured result with:
40
51
 
@@ -43,8 +54,11 @@ Return a concise structured result with:
43
54
  - `keyword_lanes` with timeframe, raw posts found, finalist posts reviewed
44
55
  - `selected_posts` with URL/title, author/topic, age, engager count, sampled engagers, good fits as n/N, estimated usable prospects per post, use/discard
45
56
  - `sample_leads`, if any
57
+ - `approval_math` with eligible posts, target good-fit leads, sampled engagers,
58
+ ICP-fit rate as `n/N` plus percentage/range, posts needed for target, selected
59
+ post count, expected usable lead range, and scale fallback
46
60
  - `estimated_good_fit_range`
47
- - `expected_reply_rate_range`, directional if inferred
61
+ - `message_context_strength`, directional and source-specific
48
62
  - `false_positive_patterns`
49
63
  - `recommendation`
50
64
  - `confidence`
@@ -53,5 +67,8 @@ Evidence standards:
53
67
 
54
68
  - Do not trust raw post volume without inspecting finalist post quality.
55
69
  - Prefer sample-based pass rates over intuition.
70
+ - Do not make the user infer capacity. Say, plainly, how many eligible posts
71
+ exist, what percent of sampled engagers looked in-ICP, how many posts are
72
+ needed for 500+ good-fit leads, and which posts you would use.
56
73
  - If `fetch_post_engagers` is unavailable or fails, report that explicitly and mark the estimate lower-confidence.
57
74
  - Keep LinkedIn Engagement viable when selected posts can produce roughly 150+ ICP-fit warm prospects before final filtering, even if Sales Nav is more scalable.
package/dist/index-dev.js CHANGED
File without changes
package/dist/index.js CHANGED
File without changes
@@ -21,6 +21,7 @@ function readEntrypointDir() {
21
21
  }
22
22
  }
23
23
  function readCurrentVersion() {
24
+ // @ts-ignore - this MCP package is NodeNext ESM, but the app build typechecks MCP tests through its CommonJS tsconfig.
24
25
  const moduleDir = path.dirname(fileURLToPath(import.meta.url));
25
26
  const entrypointDir = readEntrypointDir();
26
27
  const candidates = [
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/mcp",
3
- "version": "0.1.120",
3
+ "version": "0.1.122",
4
4
  "type": "module",
5
5
  "description": "Sellable MCP server for Claude Code and Codex campaign workflows",
6
6
  "main": "dist/index.js",
@@ -433,7 +433,10 @@ brief`, `Revise target`, `Revise offer/proof`, and `Other / custom`.
433
433
  campaign brief. Do not wait for file-write chrome before asking for approval.
434
434
  Before Find Leads, call `update_campaign({ campaignId, campaignBrief,
435
435
  currentStep: "pick-provider", watchNarration: { ... } })` after approval so
436
- the watch link moves out of Plan while the main thread compares source paths.
436
+ the watch link moves out of Plan while the main thread explains the source
437
+ path. This must be a visible Pick Provider checkpoint before the watched
438
+ browser moves into Signal Discovery, Sales Nav, Prospeo, or another provider
439
+ lane.
437
440
 
438
441
  - After the brief is approved, show the next progress line. When the user has
439
442
  not given a specific source direction, use the default sequential source
@@ -444,6 +447,11 @@ that gives the strongest message context if enough ICP-looking people are
444
447
  engaging. If that does not clear a quick viability check, I'll switch to Sales
445
448
  Nav with recent activity, then broader Sales Nav, and use Prospeo only as the
446
449
  fallback. No leads import until you approve the source.`
450
+ The watched campaign should still be on Pick Provider while this source logic
451
+ is being explained. If the default first lane is Signal Discovery, say clearly
452
+ that Codex is about to test Signal Discovery as a viability path, not import
453
+ leads: it will look for relevant posts, sample engagers, estimate ICP fit, and
454
+ only continue if the source math works.
447
455
  - If the user's request already points to a source, do not force the default
448
456
  funnel. Start with the matching lane and say why:
449
457
  - specific posts, creators, topics, comments, or engagement signals ->
@@ -453,8 +461,9 @@ fallback. No leads import until you approve the source.`
453
461
  search, or target accounts -> Prospeo
454
462
  - supplied CSV/profile list -> existing/supplied list preview
455
463
  - explicit compare request -> compare only the requested sources
456
- - In watch mode, do not leave the user sitting on only `pick-provider` while
457
- source viability is checked. Move the campaign to the first source lane that
464
+ - In watch mode, do not skip the Pick Provider checkpoint. First move the
465
+ campaign to `pick-provider` and narrate the source-selection reasoning there.
466
+ After that visible checkpoint, move the campaign to the first source lane that
458
467
  will actually be tested (`signal-discovery`, `sales-nav`, `prospeo`, or the
459
468
  user-directed lane), then run the campaign-attached provider prompt + provider
460
469
  search in the parent thread with `campaignOfferId`, `confirmed: true`, and
@@ -808,9 +817,11 @@ Required behavior:
808
817
  1. Start with LinkedIn Engagement / active LinkedIn posts (internal provider:
809
818
  Signals / `signal-discovery`) because recent engagement gives the strongest
810
819
  message context and expected reply-rate upside. Search relevant keyword
811
- lanes, review finalist posts, promote the sampled posts with
820
+ lanes, review finalist posts, promote a narrow sample set with
812
821
  `select_promising_posts` before fetching engagers when a campaign shell
813
- exists, fetch top-post engagers, and estimate warm-fit volume.
822
+ exists, fetch top-post engagers, estimate ICP-fit rate, compute posts
823
+ needed for the target good-fit lead count, and only then recommend the
824
+ selected post set or move to the next source.
814
825
  2. If Signals does not have enough recent, relevant, ICP-looking engagers,
815
826
  switch to Sales Nav with recent activity when the target can be expressed
816
827
  as title/persona/company filters. Run preview filters, inspect preview rows,
@@ -867,12 +878,12 @@ Required behavior:
867
878
  smaller, higher-reply-upside first batch versus Sales Nav/Prospeo for broader
868
879
  scale. Recommend the source you believe should win, often Sales Nav when scale
869
880
  matters, but keep the smaller Signals lane available as a real option.
870
- - user-facing source logic must use numbers, not vibes. Never write percentages
871
- in the source review. Use the numerator, denominator, and sample basis instead,
872
- e.g. `8 of 11 sampled engagers fit the ICP`. If the sample is small, say it is
873
- directional. If an estimate depends on assumptions, show the math without
874
- percentage notation: `5 selected posts x ~40-80 reachable engagers/post x
875
- roughly 1 in 4 to 2 in 5 expected fit = ~50-160 likely usable leads`.
881
+ - user-facing source logic must use sample-backed numbers, not vibes. Show the
882
+ numerator, denominator, sample basis, and an easy percentage or range, e.g.
883
+ `18 of 40 sampled engagers fit the ICP (~45%, directional)`. If the sample is
884
+ small, say it is directional. If an estimate depends on assumptions, show the
885
+ math plainly: `8 eligible posts x ~140 reachable engagers/post x ~35-45% ICP
886
+ fit x cleanup factor = ~300-500 likely usable warm leads`.
876
887
  - source progress updates should expose the confidence-building numbers as soon
877
888
  as they exist: keyword lanes searched, timeframe used, post results by lane,
878
889
  finalist posts reviewed, engagers fetched, sampled engagers, sampled fits,
@@ -882,10 +893,12 @@ roughly 1 in 4 to 2 in 5 expected fit = ~50-160 likely usable leads`.
882
893
  last 30 days, prefer the last 7-14 days when quality is comparable, and call
883
894
  out any older post as a deliberate tradeoff. Do not hide post age inside the
884
895
  raw search count
885
- - default source quality target is 300-500+ likely usable leads for scalable
886
- outbound; accept ~150+ likely ICP-fit warm prospects as viable for a focused
887
- Signals-first campaign or first review batch. Name the volume tradeoff in
888
- `lead-review.md` and the message review context rather than forcing a discard.
896
+ - default source quality target is 500+ likely good-fit leads for scalable
897
+ outbound unless the campaign or user supplies a different target. Accept
898
+ ~150+ likely ICP-fit warm prospects as viable for a focused Signals-first
899
+ campaign or first review batch only when the user is choosing warmth over
900
+ scale. Name the volume tradeoff in `lead-review.md` and the source approval
901
+ card rather than forcing a discard.
889
902
  - if `lead-source-intake.json` is present, read `sourceType`,
890
903
  `sourceInputMode`, file path or existing lead-list ID, selected columns,
891
904
  confirmation token, normalized counts, and any preview-created
@@ -919,7 +932,8 @@ roughly 1 in 4 to 2 in 5 expected fit = ~50-160 likely usable leads`.
919
932
  - row/domain counts, invalid counts, duplicate counts, and sample method for
920
933
  supplied sources
921
934
  - preview count
922
- - ICP match rate with numerator/denominator and sample basis; never percent-only
935
+ - ICP match rate with numerator/denominator, sample basis, and a simple
936
+ percentage/range; never percent-only
923
937
  - volume comparison
924
938
  - source viability: expected source volume, expected usable leads after
925
939
  filtering, source activity/warmth indicators, cleanup risk, and confidence
@@ -977,7 +991,7 @@ table with one row per selected or finalist post:
977
991
  - raw results found
978
992
  - finalist posts or preview rows reviewed
979
993
  - sampled people
980
- - sampled fits, shown as `n/N` only; do not include a percentage
994
+ - sampled fits, shown as `n/N` plus a simple percentage/range
981
995
  - estimated usable people
982
996
  - confidence note (`sample-backed`, `directional`, or `needs more sample`)
983
997
 
@@ -994,47 +1008,34 @@ workspace/sender.
994
1008
  When showing `lead-review.md` to the user, render a slim decision summary in
995
1009
  chat, not the full evidence table. Use rendered Markdown directly with short
996
1010
  indexed sections and bullet lines; do not use fenced code blocks for the
997
- user-facing lead review. The visible response must include:
1011
+ user-facing lead review. The visible response must be a compact math-first card,
1012
+ not a research memo. It must include only:
998
1013
 
999
1014
  - `Lead source decision`
1000
- - `Recommendation`
1001
- - `Primary source and filters` with the concrete source recipe the agent would
1002
- use if approved. For Sales Nav, name the actual role/title, company-size,
1003
- geography, industry/account, and activity filters. For Signals, name the
1004
- selected post set. For Prospeo, name the account/domain and title recipe.
1005
- - `Runner-up sources` with the second-best choice and why it lost. Make the
1006
- tradeoff explicit, e.g. Sales Nav = clearer scale and fit control, Signals =
1007
- warmer but smaller/noisier, Prospeo = broader account coverage but weaker
1008
- LinkedIn intent.
1009
- - `Why it won`
1010
- - `Quick numbers` as bullet points, with one provider/source angle per bullet.
1011
- Each bullet must include raw volume, sampled fit rate as `n/N`, estimated
1012
- good-fit range after cleanup, source activity/warmth basis, and confidence
1013
- note.
1014
- - If Signals was searched or considered, `Signal keyword lanes` as a compact
1015
- table with keyword lane, timeframe, posts found, and finalist posts reviewed.
1016
- - If Signals was searched or considered, `LinkedIn posts sampled` as a compact
1017
- table with one row per selected or finalist post: post URL/title,
1018
- author/topic, age, engagers, sampled engagers, good fits as `n/N`, estimated
1019
- usable prospects per post, and use/discard decision.
1020
- - `Sample leads` with 3-5 representative `Name Title, Company` rows
1021
- - `Tradeoff`
1022
- - `Watch link: {watchUrl}` when the campaign shell exists
1023
- - `Source math before approval` as the final content block immediately above
1024
- the lead-source approval question. This is the bottom-line arithmetic that
1025
- makes the recommendation feel obvious. It must use concrete counts and a
1026
- simple formula, for example:
1027
- `Signals: 5 relevant posts -> ~320 reachable engagers; 18 of 40 sampled
1028
- engagers fit the ICP, so the expected usable range is roughly 120-160 warm
1029
- prospects after cleanup. If we want more volume than that, use Sales Nav:
1030
- ~1,900 preview rows with 14 of 25 sampled rows looking usable, but the message
1031
- context is colder.`
1032
- For Signals, include selected/relevant posts, total or per-post engagers,
1033
- sampled engagers, sampled ICP fits as `n/N`, estimated usable prospects, and
1034
- the volume tradeoff. For Sales Nav/Prospeo alternatives, include preview/raw
1035
- volume, sampled usable rows as `n/N`, estimated usable range, and the warmth
1036
- or context tradeoff. Do not use percent-only fit rates or unsupported reply
1037
- rate claims.
1015
+ - `Source recommendation: {source}` with the concrete recipe. For Sales Nav,
1016
+ name the actual role/title, company-size, geography, industry/account, and
1017
+ activity filters. For Signals, name the selected post set. For Prospeo, name
1018
+ the account/domain and title recipe.
1019
+ - `Math:` with these labels and values:
1020
+ `Eligible posts`, `Sample`, `ICP-fit`, `Target`, `Posts needed`, `Selected`,
1021
+ `Expected good-fit leads`, and `Scale option`.
1022
+ - `Why this source:` with at most two bullets.
1023
+ - `Watch link: {watchUrl}` only when the campaign shell exists and the host
1024
+ needs a handoff link.
1025
+ - the concrete source approval question and options.
1026
+
1027
+ Do not include keyword-lane tables, LinkedIn-post-sampled tables, sample-lead
1028
+ lists, or long tradeoff prose in the default approval packet. Keep those details
1029
+ in `lead-review.md` or campaign/source decision state. The math block is the
1030
+ bottom-line arithmetic that makes the recommendation obvious. For example:
1031
+ `Eligible posts: 8 outbound + Claude posts. Sample: 120 engagers. ICP-fit: 52/120
1032
+ (~43%, directional). Target: 500 good-fit leads. Posts needed: about 8-10 at
1033
+ this fit rate. Selected: 8 posts. Expected good-fit leads: ~420-560 after
1034
+ dedupe/cleanup. Scale option: Sales Nav gives more rows but loses the engaged
1035
+ with this content message hook.` For Sales Nav/Prospeo alternatives, include
1036
+ preview/raw volume, sampled usable rows as `n/N` plus percentage/range,
1037
+ estimated usable range, and the warmth or context tradeoff. Do not use
1038
+ percent-only fit rates or unsupported reply-rate claims.
1038
1039
 
1039
1040
  The first sentence of the visible decision must make the actual choice clear:
1040
1041
  `I recommend {primary source} using {exact filter/source recipe}. The runner-up
@@ -1068,7 +1069,8 @@ posts`, `Try Prospeo/account search`, and `Revise source`.
1068
1069
  Immediately above that approval question, render the `Source math before
1069
1070
  approval` block. Do not ask the user to approve from the recommendation alone;
1070
1071
  show the math tying relevant posts, available engagers, sampled fit, expected
1071
- usable lead count, and the scale alternative together first.
1072
+ usable lead count, posts needed for the target, and the scale alternative
1073
+ together first.
1072
1074
 
1073
1075
  Do not skip or discard Signals based only on raw post count or vibes. If
1074
1076
  Signals was considered, show the post-level math in chat first so the user can
@@ -1077,13 +1079,16 @@ sampled engagers looked like good fits, and roughly how many usable prospects
1077
1079
  each post can produce. If no engagers could be fetched, say that explicitly and
1078
1080
  lower confidence instead of presenting a precise estimate.
1079
1081
 
1080
- For Signals, default to sampling a few promising posts first rather than trying
1081
- to prove the entire source can scale before the user sees evidence. Pick 3-5
1082
- fresh, high-density posts when available. In campaign-attached watch runs,
1083
- promote those posts with `select_promising_posts` before sampling so the user
1084
- sees which posts are being tested. Then sample engagers, show fit rate, and
1085
- state how many additional posts could be added/scraped if that first sample is
1086
- good but volume is low.
1082
+ For Signals, default to a quick capacity test before final recommendation. Pick
1083
+ a narrow first sample of fresh, high-density posts, and in campaign-attached
1084
+ watch runs promote those posts with `select_promising_posts` before sampling so
1085
+ the user sees which posts are being tested. Then sample engagers, show ICP-fit
1086
+ rate as `n/N` plus percentage/range, calculate how many right-content posts are
1087
+ needed to reach the target good-fit lead count, and select/promote enough
1088
+ right-content posts if the lane can hit the target. If the first sample is good
1089
+ but volume is low, say how many more posts would be needed and offer the Sales
1090
+ Nav/Prospeo scale alternative instead of recommending an under-sized source as
1091
+ if it can produce 500+ leads.
1087
1092
 
1088
1093
  Keep discarded paths and full sample rows in the campaign/source decision
1089
1094
  context. In explicit debug/UAT runs they may also be copied into
@@ -525,7 +525,7 @@
525
525
  "suppliedProfilesOrCsv": "supplied-list",
526
526
  "explicitCompare": "compare-requested-sources"
527
527
  },
528
- "quickViabilityRule": "Run only enough of the current lane to decide whether it can supply relevant, reachable ICP-looking leads. Stop on the first viable source unless the user asked for comparison.",
528
+ "quickViabilityRule": "Run only enough of the current lane to decide whether it can supply relevant, reachable ICP-looking leads. For Signals, viability requires sampled ICP-fit rate plus posts-needed math against the target good-fit lead count, not raw post count. Stop on the first viable source unless the user asked for comparison.",
529
529
  "parallelAllowedOnlyWhen": [
530
530
  "user explicitly requested source comparison",
531
531
  "resuming already-started parallel scouts",
@@ -546,7 +546,9 @@
546
546
  "watchNarration.stage": "find-leads"
547
547
  },
548
548
  "when": "before_sequential_source_funnel",
549
- "chatRenderRule": "Move the campaign watch view to Find Leads before the main thread starts source viability work. If the user did not specify a source, explain the default order: start with warm LinkedIn post engagement because it gives stronger message context and expected reply-rate upside when enough ICP-looking engagers exist; if it is not viable, switch to Sales Nav with recent activity, then broader Sales Nav, and use Prospeo only as the fallback. If the user did specify hiring signals, domains/accounts, supplied lists, posts/comments, or titles/personas, explain that the matching source overrides the default funnel. State that no leads import until a source is approved. The watchNarration headline/body must name the lane being tested, why this lane now, the quick viability gate, and the safety boundary. Do not mention MCP or local artifacts."
549
+ "requiresVisibleCheckpointBeforeProviderLane": true,
550
+ "forbidDirectPlanToProviderLane": true,
551
+ "chatRenderRule": "Move the campaign watch view to the Pick Provider step before the main thread starts source viability work. This must be a distinct visible checkpoint: do not jump directly from Plan to Signal Discovery, Sales Nav, Prospeo, or another provider lane in the same visible beat. While the watched browser is on Pick Provider, explain the source-selection logic. If the user did not specify a source, explain the default order: start with warm LinkedIn post engagement because it gives stronger message context and expected reply-rate upside when enough ICP-looking engagers exist; if it is not viable, switch to Sales Nav with recent activity, then broader Sales Nav, and use Prospeo only as the fallback. If starting with Signal Discovery, explicitly say this is a viability test: Codex will look for relevant posts, sample engagers, estimate ICP fit, and only continue if the source math works. If the user did specify hiring signals, domains/accounts, supplied lists, posts/comments, or titles/personas, explain that the matching source overrides the default funnel. State that no leads import until a source is approved. The watchNarration headline/body must name the first lane being considered, why this lane now, the quick viability gate, and the safety boundary. Do not mention MCP or local artifacts."
550
552
  },
551
553
  {
552
554
  "action": "advance_watch_to_initial_source_lane",
@@ -564,8 +566,9 @@
564
566
  "existingList": "saved-lists",
565
567
  "uploadedDomains": "prospeo"
566
568
  },
567
- "when": "before_first_source_attempt",
568
- "rule": "Choose the first visible source lane from explicit source direction when present, otherwise default to Signal Discovery. Send watchNarration with stage find-leads that says why this lane is being tried now, what quick sample or filter gate will pass/fail it, and why that helps this campaign. For Signal Discovery sampling, promote/select the posts with select_promising_posts before fetch_post_engagers so the watched table shows the exact posts being sampled; the guide copy should say Codex is pulling sample engagers from these posts to confirm the ICP is actually engaging and the source is viable. If the lane fails, update currentStep and watchNarration before moving to Sales Nav recent activity, normal Sales Nav, or Prospeo."
569
+ "when": "after_source_selection_checkpoint_before_first_source_attempt",
570
+ "requiresPriorVisibleStep": "pick-provider",
571
+ "rule": "Choose the first visible source lane from explicit source direction when present, otherwise default to Signal Discovery. Only run this after the Pick Provider source-selection checkpoint has been made visible and explained in chat/watchNarration. Send watchNarration with stage find-leads that says why this lane is being tried now, what quick sample or filter gate will pass/fail it, and why that helps this campaign. For Signal Discovery sampling, make it clear this is a viability path before lead import: promote/select the first narrow sample posts with select_promising_posts before fetch_post_engagers so the watched table shows the exact posts being sampled; the guide copy should say Codex is pulling sample engagers from these posts to confirm the ICP is actually engaging and estimate whether enough right-content posts exist for the target lead count. Before final source approval, compute eligible posts, sampled ICP-fit rate, target good-fit lead count, posts needed, selected posts, expected good-fit range, and scale fallback. If the lane fails, update currentStep and watchNarration before moving to Sales Nav recent activity, normal Sales Nav, or Prospeo."
569
572
  },
570
573
  {
571
574
  "action": "run_first_campaign_attached_source_search",
@@ -614,7 +617,7 @@
614
617
  "action": "run_subskill",
615
618
  "target": "find-leads",
616
619
  "mode": "campaign-attached-required",
617
- "sourceScoutRule": "Shell-first flow requires the CampaignOffer campaignId from durable state. Pass campaignId as campaignOfferId into every provider prompt/search that can persist source state (`get_provider_prompt({ provider, campaignOfferId, confirmed: true })`, `search_signals`, `search_sales_nav`, `search_prospeo`) and include currentStep for tools that accept it so the user can watch the selected source inside the campaign. Use the default sequential source viability funnel when the user did not specify a source: Signal Discovery first, then Sales Nav with recent activity, then general Sales Nav, then Prospeo only as fallback. Stop on the first viable source unless the user explicitly asked to compare. If the user names hiring signals, domains/accounts, supplied lists, posts/comments, or title/persona filters, start with the matching source instead. Parallel source scouts only when the user requested comparison, an existing parallel run is being resumed, or the first viable source is borderline and one cheap fallback check is needed. The later import_leads call must use the same campaignOfferId. Do not import, confirm, enrich, queue, or start leads during source discovery."
620
+ "sourceScoutRule": "Shell-first flow requires the CampaignOffer campaignId from durable state. Pass campaignId as campaignOfferId into every provider prompt/search that can persist source state (`get_provider_prompt({ provider, campaignOfferId, confirmed: true })`, `search_signals`, `search_sales_nav`, `search_prospeo`) and include currentStep for tools that accept it so the user can watch the selected source inside the campaign. Use the default sequential source viability funnel when the user did not specify a source: Signal Discovery first, then Sales Nav with recent activity, then general Sales Nav, then Prospeo only as fallback. For Signal Discovery, do not recommend from raw post count; sample engagers, calculate ICP-fit rate, target good-fit lead count, posts needed, selected posts, expected good-fit range, and scale fallback. Stop on the first viable source unless the user explicitly asked to compare. If the user names hiring signals, domains/accounts, supplied lists, posts/comments, or title/persona filters, start with the matching source instead. Parallel source scouts only when the user requested comparison, an existing parallel run is being resumed, or the first viable source is borderline and one cheap fallback check is needed. The later import_leads call must use the same campaignOfferId. Do not import, confirm, enrich, queue, or start leads during source discovery."
618
621
  },
619
622
  {
620
623
  "action": "optional_debug_artifacts",
@@ -712,16 +715,17 @@
712
715
  "artifact": "lead-review.md",
713
716
  "renderInlineSections": [
714
717
  "## Lead source decision",
715
- "Recommendation",
716
- "Primary source and filters",
717
- "Runner-up sources",
718
- "Why it won",
719
- "Quick numbers",
720
- "Signal keyword lanes",
721
- "LinkedIn posts sampled",
722
- "Sample leads",
723
- "Tradeoff",
724
- "Source math before approval"
718
+ "Source recommendation",
719
+ "Math",
720
+ "Eligible posts",
721
+ "Sample",
722
+ "ICP-fit",
723
+ "Target",
724
+ "Posts needed",
725
+ "Selected",
726
+ "Expected good-fit leads",
727
+ "Scale option",
728
+ "Why this source"
725
729
  ],
726
730
  "signalsFirstRequiredFields": [
727
731
  "post URL",
@@ -742,7 +746,7 @@
742
746
  "raw results found",
743
747
  "finalist posts or preview rows reviewed",
744
748
  "sampled people",
745
- "sampled fits as n/N only; no percentage",
749
+ "sampled fits as n/N plus percentage or range",
746
750
  "estimated usable people",
747
751
  "estimated good-fit range after cleanup",
748
752
  "confidence note"
@@ -756,13 +760,17 @@
756
760
  ],
757
761
  "approvalMathRequiredFields": [
758
762
  "selected or relevant posts count",
763
+ "eligible right-content posts count",
759
764
  "reachable engagers count",
760
765
  "sampled engagers count",
761
- "sampled ICP fits as n/N only; no percentage",
766
+ "sampled ICP fits as n/N plus percentage or range",
767
+ "target good-fit lead count",
768
+ "posts needed for target",
769
+ "selected posts count",
762
770
  "estimated usable prospects range",
763
771
  "scale alternative source",
764
772
  "scale alternative raw or preview volume",
765
- "scale alternative sampled usable rows as n/N only; no percentage",
773
+ "scale alternative sampled usable rows as n/N plus percentage or range",
766
774
  "warmth or message-context tradeoff",
767
775
  "what to choose if the user wants more volume"
768
776
  ],
@@ -786,7 +794,7 @@
786
794
  "doNotCompressToSummaryOnly": false,
787
795
  "doNotRenderArtifactLinksOnly": true,
788
796
  "sourceRecommendationReadyWatchRule": "When the source recommendation decision card is ready in chat with counts and sample quality, and before asking for source approval, call update_campaign with leadSourceType `new`, leadSourceProvider set to the recommended primary provider, currentStep set to that provider lane (`sales-nav`, `signal-discovery`, or `prospeo`), and find-leads watchNarration. If the recommendation changed from the lane last sampled, switch the watched provider page to the recommended lane first so the user can inspect what they are approving. Use a headline like `Review the source in Codex`, body copy that says the browser is showing the evaluated source/results, and nextAction like `Approve in Codex`. Do not keep future-tense copy like `I'll show a source recommendation` after the decision is visible. Include a safety note that no leads import until the user approves the source.",
789
- "chatRenderRule": "Show a slim rendered-Markdown decision summary only, never a fenced code block. The first sentence must make the decision explicit: 'I recommend {primary source} using {exact filter/source recipe}. The runner-up is {source} because {reason}.' Use indexed sections and short bullets: recommendation, Primary source and filters, Runner-up sources, why it won, Quick numbers with one provider/source angle per bullet, raw volume, sampled fit count as n/N only (no percentages), estimated good-fit range after cleanup, activity/warmth basis, confidence note, 3-5 representative sample leads, one tradeoff, and a final Source math before approval block immediately above the source approval question. Do not forecast connection acceptance rates, reply rates, meetings, pipeline, revenue, or ROI unless the user supplied verified benchmark data for this exact workspace/sender. If Signals was searched or considered, include two compact inline Markdown tables before the recommendation is treated as final: Signal keyword lanes with keyword lane, timeframe, posts found, and finalist posts reviewed; and LinkedIn posts sampled with post URL/title, author/topic, age, engagers, sampled engagers, good fits as n/N only, estimated usable prospects per post, and use/discard decision. Default to selecting a few promising Signals posts for the first sample instead of trying to prove full Signals scale up front; if the sample is good but volume is low, say how many more posts to add/scrape next. The Source math before approval block must tie the approval choice to concrete arithmetic: relevant/selected posts, available engagers, sampled engagers, sampled ICP fits as n/N only, estimated usable prospects, and the scale alternative math. If the user wants more volume than the warm post-engagement estimate, explicitly say which Sales Nav or Prospeo alternative provides that volume and what message-context warmth is lost. Do not skip or discard Signals based only on raw post count or vibes; show the post-level math first, or explicitly say no engagers could be fetched and lower confidence. Keep discarded paths, full sample rows, and lead-sample.json details in lead-review.md. Do not show plain filesystem paths unless links cannot be created."
797
+ "chatRenderRule": "Show a compact rendered-Markdown decision card only, never a fenced code block. The first sentence must make the decision explicit: 'I recommend {primary source} using {exact filter/source recipe}. The runner-up is {source} because {reason}.' Use only these sections: Lead source decision, Source recommendation, Math, Why this source, and the concrete approval question. The Math section must include labeled lines for Eligible posts, Sample, ICP-fit, Target, Posts needed, Selected, Expected good-fit leads, and Scale option. Show fit as n/N plus an easy percentage or range, never percent-only. Do not include keyword-lane tables, LinkedIn-post-sampled tables, sample-lead lists, or long tradeoff prose in the default approval packet; keep those details in lead-review.md or campaign/source decision state. Do not forecast connection acceptance rates, reply rates, meetings, pipeline, revenue, or ROI unless the user supplied verified benchmark data for this exact workspace/sender. For Signals, do not recommend from raw post count. Compute eligible right-content posts, reachable engagers, sampled ICP-fit rate, target good-fit lead count, posts needed, selected posts, expected good-fit range, and scale fallback. If the user wants more volume than the warm post-engagement estimate, explicitly say which Sales Nav or Prospeo alternative provides that volume and what message-context warmth is lost. If no engagers could be fetched, say that explicitly and lower confidence. Do not show plain filesystem paths unless links cannot be created."
790
798
  },
791
799
  {
792
800
  "action": "render_post_lead_parallel_progress",
@@ -808,7 +816,7 @@
808
816
  {
809
817
  "action": "ask_continue_revise_or_confirm_only_if_needed",
810
818
  "approvalQuestionRule": "If asking the user, the question and options must name the concrete decision. Prefer: 'Approve Sales Nav with {filters}, or use warmer Signals instead?' over 'Approve this lead source?' Option labels must name the source choice, such as 'Approve Sales Nav filters', 'Use warmer Signals posts', 'Try Prospeo/account search', and 'Revise source'.",
811
- "approvalMathRule": "Immediately above the approval question, render the Source math before approval block: given this many relevant posts, this many reachable engagers, and this sampled ICP fit as n/N, we expect this usable lead range; if the user wants more volume, name the Sales Nav or Prospeo alternative, its preview/sample math, and the colder/weaker-context tradeoff. Do not ask the approval question until this math is visible.",
819
+ "approvalMathRule": "Immediately above the approval question, render the compact Math block: given this many eligible relevant posts, this many reachable engagers, this sampled ICP fit as n/N plus percentage/range, this target good-fit lead count, and this many posts needed, we expect this usable lead range from the selected posts; if the user wants more volume, name the Sales Nav or Prospeo alternative, its preview/sample math, and the colder/weaker-context tradeoff. Do not ask the approval question until this math is visible.",
812
820
  "autoContinueWhen": {
813
821
  "status": "confirmed",
814
822
  "confidenceIn": [
@@ -79,14 +79,27 @@ Default source funnel:
79
79
  ```json
80
80
  {
81
81
  "stage": "find-leads",
82
- "headline": "Testing warm LinkedIn activity",
83
- "visibleState": "Codex is starting with recent post engagement because it gives the strongest message context if enough ICP-looking people are active there.",
84
- "agentIntent": "If this lane does not clear the quick sample gate, it will switch to Sales Nav with recent activity, then broader Sales Nav, and use Prospeo only as the fallback.",
82
+ "headline": "Choosing the lead source",
83
+ "visibleState": "The browser is on Pick Provider while Codex explains the source path before opening a provider lane.",
84
+ "agentIntent": "Codex will start with Signal Discovery when warm LinkedIn engagement looks plausible, then switch to Sales Nav or Prospeo only if the quick viability math fails.",
85
85
  "nextAction": "Review source",
86
86
  "safety": "No leads import until you approve the source."
87
87
  }
88
88
  ```
89
89
 
90
+ Signal Discovery viability handoff:
91
+
92
+ ```json
93
+ {
94
+ "stage": "find-leads",
95
+ "headline": "Checking Signal Discovery viability",
96
+ "visibleState": "The browser is moving from Pick Provider into Signal Discovery so the exact posts and sample math are visible.",
97
+ "agentIntent": "Codex is testing whether relevant posts have enough ICP-looking engagers before recommending this as the source.",
98
+ "nextAction": "Review source math",
99
+ "safety": "No leads import until you approve the source."
100
+ }
101
+ ```
102
+
90
103
  Source direction override:
91
104
 
92
105
  ```json
@@ -332,6 +332,22 @@ before `fetch_post_engagers` so the user sees the exact posts being sampled in
332
332
  the watched Signal Discovery table. Use `selectionMode: "replace"` for the first
333
333
  sample set unless the user explicitly wants to add to existing promoted posts.
334
334
 
335
+ For `create-campaign-v2` source approval, do not treat the default
336
+ `selectionTarget` of 3 posts as enough by itself. Before the final source
337
+ recommendation, estimate source capacity from real sample math:
338
+
339
+ - target good-fit leads (default 500 unless the campaign says otherwise)
340
+ - eligible right-content posts by lane/content type
341
+ - reachable engagers from those posts
342
+ - sampled ICP-fit rate as `n/N` plus an easy percentage/range
343
+ - expected good-fit leads per selected post after dedupe/cleanup
344
+ - posts needed to reach the target
345
+
346
+ Then select enough right-content posts to plausibly hit the target. If the math
347
+ says the warm post lane only supports a smaller first batch, say that and name
348
+ the Sales Nav or Prospeo scale fallback rather than padding the selection with
349
+ noisy posts.
350
+
335
351
  ```json
336
352
  select_promising_posts({
337
353
  "campaignOfferId": "cmp_xxx",