@sellable/mcp 0.1.149 → 0.1.151

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (32) hide show
  1. package/README.md +8 -4
  2. package/agents/post-find-leads-message-scout.md +2 -2
  3. package/agents/source-scout-prospeo-contact.md +12 -5
  4. package/agents/source-scout-sales-nav.md +13 -3
  5. package/dist/tools/leads.d.ts +2 -1
  6. package/dist/tools/leads.js +162 -15
  7. package/dist/tools/prompts.d.ts +1 -0
  8. package/dist/tools/prompts.js +4 -3
  9. package/dist/tools/registry.d.ts +9 -1
  10. package/dist/tools/rows.d.ts +1 -0
  11. package/dist/tools/rows.js +2 -0
  12. package/dist/tools/rubrics.d.ts +55 -23
  13. package/dist/tools/rubrics.js +95 -10
  14. package/package.json +1 -1
  15. package/skills/create-campaign/SKILL.md +57 -29
  16. package/skills/create-campaign/core/providers/apollo.json +4 -2
  17. package/skills/create-campaign/core/providers/prospeo.json +3 -2
  18. package/skills/create-campaign/references/provider-selection-strategy.md +66 -25
  19. package/skills/create-campaign-v2/SKILL.md +67 -37
  20. package/skills/create-campaign-v2/SOUL.md +11 -7
  21. package/skills/create-campaign-v2/core/flow.v2.json +70 -47
  22. package/skills/create-campaign-v2/references/approval-gate-framing.md +7 -8
  23. package/skills/create-campaign-v2/references/sample-validation-loop.md +9 -5
  24. package/skills/create-campaign-v2/references/step-13-import-leads.md +10 -4
  25. package/skills/create-campaign-v2/references/step-15-re-cascade.md +12 -7
  26. package/skills/create-campaign-v2/references/watch-guide-narration.md +16 -13
  27. package/skills/create-campaign-v2-tail/SKILL.md +24 -16
  28. package/skills/providers/apollo.md +8 -1
  29. package/skills/providers/prospeo.md +11 -1
  30. package/skills/providers/sales-nav.md +1 -1
  31. package/skills/research/config.json +9 -0
  32. package/skills/create-campaign-v2/references/message-review-safety-gate.md +0 -162
@@ -33,14 +33,13 @@ Before showing the commit gate, the draft directory must contain all of:
33
33
  - `message-validation.md`
34
34
  - `approval-packet.md`
35
35
 
36
- The current run must also have retrieved message-generation rules before
37
- `message-validation.md`, `approval-packet.md`, or the commit gate is written. In
38
- `create-campaign-v2`, use the compact
39
- `references/message-review-safety-gate.md` asset or the embedded
40
- `post-find-leads-message-scout` prompt. Do not load the full long-form
41
- `generate-messages` subskill for the hosted campaign launch path. If the message
42
- template was written directly from memory or planning artifacts, do not show the
43
- commit gate; route back to message generation.
36
+ The current run must also have retrieved the full message-generation rules
37
+ before `message-validation.md`, `approval-packet.md`, or the commit gate is
38
+ written. In `create-campaign-v2`, use the embedded
39
+ `post-find-leads-message-scout` prompt or load the full long-form
40
+ `generate-messages` subskill. If the message template was written directly from
41
+ memory, checklist instructions, or planning artifacts, do not show the commit
42
+ gate; route back to message generation.
44
43
 
45
44
  `rubric.json` is optional but strongly preferred — when missing, derive it from
46
45
  `lead-filter.md` in the atomic mint step.
@@ -60,9 +60,10 @@ auto-revise leads.
60
60
  explicit batch count anyway so future larger expansion batches do not
61
61
  accidentally stop early
62
62
  (see §Known Tool Behaviors #3)
63
- - minPassedCount=1 means one passing filtered row unblocks Step 15
64
- Generate Message observation. Do not wait for all sample rows to finish
65
- before messages start.
63
+ - minPassedCount=1 means one passing filtered row unblocks Step 15 Generate
64
+ Message observation. Step 15 then waits only for one generated message
65
+ (`minMessagesCount=1`) before review. Do not wait for all sample rows to
66
+ finish before messages start.
66
67
 
67
68
  7. call `wait_for_rubric_results` with `includeRows=false`; extract ONLY:
68
69
  - ready: boolean
@@ -101,7 +102,9 @@ auto-revise leads.
101
102
  10. branch:
102
103
  if passInSample >= 1:
103
104
  proceed to Step 15 (auto-execute-messaging) with currently passing rows
104
- so Generate Message can start without waiting for the full sample
105
+ so Generate Message can start without waiting for the full sample. Once
106
+ one passing generated message is ready, stop for user review instead of
107
+ waiting for a stronger sample.
105
108
  else:
106
109
  diagnose (see Brief-vs-List Diagnosis below)
107
110
  revisionRound += 1
@@ -179,7 +182,8 @@ processing makes the experience feel frozen.
179
182
 
180
183
  Workaround: treat timeout stats as a partial sample. If at least one row has
181
184
  passed, move to Step 15 and observe or queue Generate Message for the passing
182
- rows. If zero rows have passed and no active processing is visible, stop at
185
+ rows; Step 15 stops when one generated message is ready. If zero rows have
186
+ passed and no active processing is visible, stop at
183
187
  `Status: sample-needs-revision` before Settings. Show the completed / passed /
184
188
  pending counts and ask whether to revise source, revise filter/rubric, or wait
185
189
  once only if active processing is still visible.
@@ -106,7 +106,7 @@ fills the source lead list; it does **not** clone rows into the campaign table:
106
106
  ```text
107
107
  import_leads({
108
108
  campaignOfferId,
109
- targetLeadCount: <sourceCandidateTarget; provider default>,
109
+ targetLeadCount: <sourceCandidateTarget from approved good-fit math>,
110
110
  // Signal Discovery only:
111
111
  targetEngagerCount: <ceil(targetGoodFitLeads / sampledFitRateAfterCleanup)>,
112
112
  maxPostsToScrape: <postsNeeded from approved math>
@@ -114,9 +114,15 @@ import_leads({
114
114
  ```
115
115
 
116
116
  Provider is inherited from the selected source decision (not re-selected here)
117
- and should already be saved on the campaign before import. Response returns
118
- `{ imported, skipped, duplicates }` surface all three to the escalation logic
119
- so dedup ratios are visible.
117
+ and should already be saved on the campaign before import. For Sales Nav and
118
+ Prospeo, `<sourceCandidateTarget>` is not the 25-row review batch. It is the
119
+ source-list export/materialization count from the approved source math:
120
+ `min(rawResultCount, providerMax, ceil(targetGoodFitLeads /
121
+ projectedFitRateAfterCleanup))`. If projected good fits remain below the target
122
+ (often about 150+ usable prospects unless the campaign/source defaults specify
123
+ otherwise), return to find-leads and refine filters before import. Response
124
+ returns `{ imported, skipped, duplicates }` — surface all three to the
125
+ escalation logic so dedup ratios are visible.
120
126
 
121
127
  For Signal Discovery, do not scrape every currently selected/promoted sample
122
128
  post by default. Before `import_leads`, reconcile selected posts with the
@@ -4,15 +4,16 @@ This reference governs Step 15 (`auto-execute-messaging`) re-cascade
4
4
  behavior when Step 14's validate-sample loop graduates additional rows
5
5
  from pending → passed after the first Generate Message cells have already run.
6
6
 
7
- Load whenever Step 15 is about to transition to
8
- `awaiting-user-greenlight`, and on every resume into Step 15.
7
+ Load whenever Step 15 is about to ask for generated-message review or transition
8
+ to `awaiting-user-greenlight`, and on every resume into Step 15.
9
9
 
10
10
  ## Principle
11
11
 
12
- Generate Message cells are cascade-scoped. If Step 14's rubric flips rows
13
- from pending → passed AFTER Step 15 first observes messages for the review
14
- batch, the new rows can sit pending with no message. Step 15 must explicitly
15
- queue the newly-pending Generate Message cells for the review-batch subset.
12
+ Generate Message cells are cascade-scoped. If Step 14's rubric flips rows from
13
+ pending → passed AFTER Step 15 first observes messages for the review batch, the
14
+ new rows can sit pending with no message. That must not block the first review
15
+ handoff. Step 15 opens review as soon as one passing generated message exists;
16
+ late-passed rows can be re-cascaded after explicit user continuation or resume.
16
17
 
17
18
  Observed on manual Phase-85 signal-discovery run (2026-04-20): 15 new
18
19
  rows graduated pending → passed mid-tail while `messagesCount` stayed
@@ -22,13 +23,17 @@ flat at 257. The new rows never got messages.
22
23
 
23
24
  Re-cascade runs whenever ALL of the following are true:
24
25
 
25
- 1. Step 15 has already observed the initial review-batch message cascade.
26
+ 1. The user has already approved the first generated message or explicitly asked
27
+ to continue processing more review-batch rows.
26
28
  2. A subsequent check of rubric state shows rows that were pending at
27
29
  first message-generation pass are now passed.
28
30
  3. Those newly-passed rows do NOT yet have generated messages
29
31
  (`messagesCount` flat relative to pre-cascade).
30
32
  4. Step 15 has NOT yet transitioned to `awaiting-user-greenlight`.
31
33
 
34
+ Before that first generated-message approval, do not run this loop just to wait
35
+ for a bigger sample. One passing generated message is enough for review.
36
+
32
37
  If condition 4 already fired (i.e. we're in Step 16), the re-cascade
33
38
  runs on resume into Step 15 if validate-sample was re-entered and new
34
39
  rows graduated.
@@ -194,10 +194,10 @@ After review batch import:
194
194
  ```json
195
195
  {
196
196
  "stage": "fit-message",
197
- "headline": "I recommend adding filters",
198
- "visibleState": "The first review batch is in the campaign. The visible sample looks mixed enough that filters should be added before message review.",
199
- "agentIntent": "Codex is asking whether you want to further filter these leads before message review. Skip filters only if the visible rows already look clean.",
200
- "nextAction": "Choose filters or skip",
197
+ "headline": "Filtering may not be needed",
198
+ "visibleState": "25 review leads are in the campaign table from 181 source candidates. Sample read: 23/25 look like senior target-role fits; 2 need cleanup or manual review. The browser is showing the filter-choice screen.",
199
+ "agentIntent": "Codex is not recommending filters because the sample already looks clean. Fit examples: Dave Kranowitz (Chief Revenue Officer); Marie Didominica (Head of Growth Marketing). Add filters only for a narrow exclusion.",
200
+ "nextAction": "Skip filters or add a narrow cleanup rule",
201
201
  "workerStatuses": {
202
202
  "leadFitBuilder": "idle",
203
203
  "messageDraftBuilder": "running"
@@ -206,11 +206,13 @@ After review batch import:
206
206
  ```
207
207
 
208
208
  Use this after the selected review rows are present and before filters are
209
- saved. This is add-filters intent, not active filtering. Recommend adding
210
- filters when the sample is mixed/noisy, tell the user to Choose filters or skip,
211
- and do not say filtering the batch before rubrics and message approval are
212
- saved. The Message Draft Builder should start immediately after the bounded
213
- review batch exists; mark it running only when that branch actually started.
209
+ saved. The filter recommendation must be row-specific: say why filters are
210
+ recommended from the sample counts and examples, or say filtering may not be
211
+ needed when the visible review rows already look clean. This is a user decision
212
+ gate, not active filtering; do not say filtering the batch before rubrics and
213
+ message approval are saved. The Message Draft Builder should start immediately
214
+ after the bounded review batch exists; mark it running only when that branch
215
+ actually started.
214
216
  When the user chooses filters, immediately persist `enableICPFilters: true` and
215
217
  move to `create-icp-rubric` so the watched app shows Filter Rules while Codex
216
218
  defines the rules in chat. After `save_rubrics`, move to `apply-icp-rubric`
@@ -252,16 +254,17 @@ Template approved, bounded filter test running:
252
254
  "stage": "fit-message",
253
255
  "headline": "Template saved",
254
256
  "visibleState": "The browser stays on Filter Leads while the bounded enrichment and filter test runs.",
255
- "agentIntent": "Codex saved the approved message template, queued the review-batch Enrich Prospect cells, and is waiting for at least one row to pass before moving to Messages.",
256
- "nextAction": "Move to Messages after a passing row"
257
+ "agentIntent": "Codex saved the approved message template, queued the review-batch Enrich Prospect cells, and is waiting for one passing row with one generated message before moving to review.",
258
+ "nextAction": "Review the first passing generated message"
257
259
  }
258
260
  ```
259
261
 
260
262
  Do not move to Messages immediately after `approve-message`. The visible route
261
263
  is already Filter Leads after `save_rubrics`; approving the message only unlocks
262
264
  the bounded cascade from that screen. Move to Messages only once at least one
263
- review-batch row passes and Generate Message cells are ready/running for the
264
- passing rows.
265
+ review-batch row passes and one generated message is ready for review. Do not
266
+ wait for the rest of the review batch or a stronger sample before asking the
267
+ user to approve the generated message.
265
268
 
266
269
  Messages waiting for template:
267
270
 
@@ -58,11 +58,12 @@ Step 14 — kick bounded cascade + observe sample
58
58
  passing filtered row exists.)
59
59
 
60
60
  Step 15 — observe messaging
61
- get_rows_minimal # confirm passing rows have completed Generate Message cells
61
+ wait_for_rubric_results({ minPassedCount: 1, minMessagesCount: 1, includeRows: false })
62
+ get_rows_minimal # confirm the first passing generated message exists
62
63
  (rare) queue_cells on any pending Generate Message cells
63
64
  token-contract spot check via get_rows
64
65
  update_campaign(currentStep=auto-execute-messaging) with review-ready narration
65
- ask the user to approve generated review-batch messages before Settings
66
+ ask the user to approve the generated message before Settings
66
67
  only after approval: update_campaign(currentStep=awaiting-user-greenlight)
67
68
  (generate_messages is NOT an MCP tool; messages come from the cascade)
68
69
 
@@ -291,8 +292,10 @@ Do not route to a visible `validate-sample` step. Full decision tree lives in
291
292
  review batch only. After `save_rubrics` and the approved message template are
292
293
  persisted, Step 14 queues the review-batch Enrich Prospect cells, waits until
293
294
  filter results start landing, then moves to message observation as soon as one
294
- row passes. It does NOT call `check_rubric`, `bulk_enrich_with_prospeo`, or any
295
- other direct enrichment/scoring tool.
295
+ row passes. Step 15 opens review as soon as one passing generated message
296
+ exists. Do not wait for a larger or stronger sample once that first passing
297
+ message is ready. It does NOT call `check_rubric`,
298
+ `bulk_enrich_with_prospeo`, or any other direct enrichment/scoring tool.
296
299
 
297
300
  Shape:
298
301
 
@@ -307,6 +310,7 @@ projectedPass = round(passInSample / sampleSize * importLimit)
307
310
  if wait_for_rubric_results.ready === true and passRate.passed >= 1:
308
311
  advance to Step 15 to observe or queue Generate Message for currently passing rows
309
312
  do not wait for every sample row to finish before message generation starts
313
+ stop for review as soon as one passing generated message is ready
310
314
  else if wait_for_rubric_results.ready === false and reason === "timeout":
311
315
  use the partial passRate/stats as the sample diagnostic
312
316
  if passRate.passed >= 1:
@@ -382,17 +386,20 @@ Template`. If it does not, fail before the cascade runs. Do not repair
382
386
  already (cascade auto-fired it). If it is still `pending`, queue
383
387
  it explicitly: `queue_cells({ tableId, cellIds:
384
388
  <generateMessageCellIds> })`.
385
- 3. `wait_for_campaign_table_ready` (or poll) until every passing
386
- row's Generate Message cell is `completed`.
387
- 4. Read the results back via `get_rows` (full) and sanity-check a
388
- sample against the Phase 84 token contract: no unresolved
389
+ 3. `wait_for_rubric_results({ tableId, targetCount: reviewBatchSize,
390
+ minPassedCount: 1, minMessagesCount: 1, includeRows: false })` until the
391
+ first passing generated message is ready. Do not wait for every passing row's
392
+ Generate Message cell, and do not add "one more wait" for a stronger sample.
393
+ Remaining review-batch rows can continue processing in the background.
394
+ 4. Read the first ready generated message back via `get_rows` (full) and
395
+ sanity-check that sample against the Phase 84 token contract: no unresolved
389
396
  `{{tokens}}`, no invented proof, one sentence per line, etc.
390
397
  5. If the sample fails the token contract, diagnose brief-vs-list
391
398
  (same revision loop as Step 14) and escalate if over
392
399
  `maxRevisionRounds`.
393
- 6. On success, keep `currentStep: "auto-execute-messaging"` and ask the user
394
- to approve the generated review-batch messages. Only that approval may move
395
- the campaign to Settings / `awaiting-user-greenlight`.
400
+ 6. On success, keep `currentStep: "auto-execute-messaging"` and ask the user to
401
+ approve the generated message before continuing to Settings. Only that
402
+ approval may move the campaign to Settings / `awaiting-user-greenlight`.
396
403
 
397
404
  **Do NOT hand-write message bodies via `update_cell`.** `update_cell`
398
405
  is reachable for legitimate operator overrides AFTER the tail hands
@@ -432,15 +439,16 @@ strings, which is why Step 16 requires Step 15 to be complete.
432
439
  4. Check `messaging.tokenContract`. When `strict`, reject any sample
433
440
  message that contains unresolved or unsupported tokens (including
434
441
  any critique rewrite that tried to introduce one).
435
- 5. If the sample passes the token contract (and critique when enabled),
436
- stop at the review-batch handoff. Do NOT scale to the full source list
437
- before explicit user expansion approval.
442
+ 5. If the first passing generated message passes the token contract (and
443
+ critique when enabled), stop at the review-batch handoff. Do NOT wait for the
444
+ remaining review-batch rows and do NOT scale to the full source list before
445
+ explicit user expansion approval.
438
446
  6. If the sample fails the token contract or critique, diagnose +
439
447
  loop the same way Step 14 does (brief-vs-list), subject to the same
440
448
  `maxRevisionRounds` cap.
441
449
  7. On success, keep `currentStep: "auto-execute-messaging"`, show that the
442
- review-batch messages are ready in Messages, and ask for approval before
443
- Settings. Only after the user approves those generated messages should
450
+ first passing generated message is ready in Messages, and ask for approval
451
+ before Settings. Only after the user approves the generated message should
444
452
  `update_campaign({ campaignId, currentStep: "awaiting-user-greenlight" })`
445
453
  run.
446
454
 
@@ -378,7 +378,14 @@ search_apollo({
378
378
 
379
379
  ### ABM / Domain List Targeting
380
380
 
381
- For account-based targeting, use `q_organization_domains_list` to search specific companies:
381
+ For campaign routing, supplied company-domain lists should go to Prospeo first
382
+ using `load_csv_domains` or `save_domain_filters`, then `domainFilterId`.
383
+ Apollo still supports `q_organization_domains_list` as a secondary/legacy
384
+ capability when the user explicitly chooses Apollo or needs an Apollo-only
385
+ filter such as a technographic constraint.
386
+
387
+ If Apollo is explicitly selected for account-based targeting, use
388
+ `q_organization_domains_list` to search specific companies:
382
389
 
383
390
  ```json
384
391
  search_apollo({
@@ -62,7 +62,7 @@ search_prospeo({
62
62
  - Pass `searchId` on subsequent pages to paginate.
63
63
  - Use `import_leads` with `provider: \"prospeo\"` and the `searchId` to create a lead list and start import.
64
64
  - **IMPORTANT:** If `import_leads` returns `needsModeSelection: true`, use `AskUserQuestion` to ask "add to existing leads or replace?" Do NOT assume.
65
- - Default source target is 300+ good-fit leads, capped at 2,500 source candidates for now.
65
+ - Default source target is about 150 good-fit leads, capped at 2,500 source candidates for now.
66
66
  - Apply the campaign source planning floor: the sampled/projected good-fit rate
67
67
  after cleanup should be at least 10%. Prospeo is the terminal fallback; if the
68
68
  best reasonable Prospeo lane is still below 10%, tighten the ICP/source
@@ -95,6 +95,11 @@ Prospeo filters are split into person vs company. Most filters use `include` / `
95
95
 
96
96
  Preference rules:
97
97
 
98
+ - For hiring-led campaigns, use `company_job_posting_hiring_for` to target the
99
+ open-role themes and `company_job_posting_quantity` when the campaign needs
100
+ an active hiring floor. Pair those company hiring filters with buyer/referrer
101
+ person filters such as founders, GTM leaders, talent leaders, or revenue
102
+ owners.
98
103
  - For ABM lists, use `load_csv_domains` + `domainFilterId` when the accounts are already in a CSV file on disk.
99
104
  - For pasted/raw domain lists, use `save_domain_filters` + `domainFilterId` instead of inline domains or `company.websites.include`.
100
105
  - If user input starts as company names, resolve names to domains first, then use `save_domain_filters`.
@@ -167,6 +172,11 @@ search_prospeo({
167
172
 
168
173
  ## When to Use Prospeo
169
174
 
175
+ - **Hiring-led targeting**: Find companies hiring for specific roles with
176
+ `company_job_posting_hiring_for` and `company_job_posting_quantity`, then
177
+ identify reachable buyer/referrer contacts at those companies
178
+ - **Account/domain targeting**: Use `domainFilterId` from `load_csv_domains` or
179
+ `save_domain_filters` for ABM searches
170
180
  - **Email Finding**: Get verified work emails from LinkedIn URLs
171
181
  - **Lead Enrichment**: Add company data, job history, and contact info to leads
172
182
  - **Bulk Operations**: Enrich up to 100 leads at once
@@ -387,7 +387,7 @@ User: "Save it"
387
387
  <limits>
388
388
  - 25 results per page
389
389
  - Maximum 100 pages (2,500 leads)
390
- - Default source target: 300+ good-fit leads, capped at 2,500 source
390
+ - Default source target: about 150 good-fit leads, capped at 2,500 source
391
391
  candidates for now
392
392
  - Maximum 5 search calls per session
393
393
  - If user needs more than 2,500: explain limit, suggest splitting by region
@@ -0,0 +1,9 @@
1
+ {
2
+ "parallelMode": "wide",
3
+ "agentCount": 6,
4
+ "maxToolCallsPerAgent": 2,
5
+ "senderMaxAgents": 2,
6
+ "senderMaxToolCallsPerAgent": 3,
7
+ "progressMode": true,
8
+ "debugMode": true
9
+ }
@@ -1,162 +0,0 @@
1
- # Message Review Safety Gate
2
-
3
- Use this reference for `create-campaign-v2` message review only in two cases:
4
- when these rules are embedded inside the `post-find-leads-message-scout` agent
5
- prompt, or when the host cannot launch that agent and the parent thread needs a
6
- self-contained safety gate. It is the campaign-launch subset of
7
- `generate-messages`: enough to prove a truthful first-send message, rendered
8
- token examples, and an approval decision without loading the full long-form
9
- message-generation prompt into the main Claude/Codex thread.
10
-
11
- In a normal installed Claude/Codex session, the message scout owns these rules.
12
- The parent-thread asset is a compatibility safety path, not a fast-mode prompt
13
- or a shortcut. Do not load the full `generate-messages` subskill in this flow. If
14
- the safety gate is missing a needed campaign-specific rule or the draft fails
15
- quality gates, stop at `revise-messaging` with the exact missing rule/failure
16
- instead of pulling the long prompt into the main thread.
17
-
18
- ## Required Workflow
19
-
20
- 1. Read the live campaign basis: campaign id, campaign revision or
21
- `campaignUpdatedAt`, campaign brief content, selected source decision,
22
- selected lead list/source state, `workflowTableId`, imported review-batch
23
- row ids/hash, and any saved rubric/filter summary supplied by the parent.
24
- 2. Pick 2-3 sample rows from the imported review-batch rows, preferring rows
25
- that pass the saved rubrics or are likely to pass. Do not invent new rows.
26
- 3. Build the message from the approved campaign brief, selected source context,
27
- sampled rows, and explicit user answers. Use only proof that appears in that
28
- live basis. Unsupported reply-rate, meeting-rate, ROI, revenue, and
29
- customer-logo claims are blocked.
30
- 4. Return a compact message recommendation to the parent thread. Do not write
31
- local markdown/json artifacts in normal customer runs; emit debug artifacts
32
- only when the parent explicitly asks for debug/UAT output.
33
- 5. Render the customer-facing message review in chat before asking for approval.
34
- Keep chat lightweight: show the tokenized template and one strong rendered
35
- good-fill example only. Do not print the token notes table, omit/fallback
36
- example, or bad-fill analysis in chat unless the user explicitly asks.
37
- After approval, the parent persists template, token rules, fallback guidance,
38
- and bad-fill avoidance notes into campaign state with `update_campaign_brief`.
39
- 6. Ask exactly `approve-message` or `revise-messaging`. Do not import, queue,
40
- attach sequence, or start before `approve-message`.
41
-
42
- ## Required Message Recommendation Sections
43
-
44
- - `Status`
45
- - `Mode`
46
- - `Lead Sample Basis`
47
- - `Strongest Reply Reason`
48
- - `Campaign Element Pool`
49
- - `Gold Standard Strategy Map`
50
- - `Current Campaign Translation`
51
- - `Token Fill Rules`
52
- - `Token Adherence Table`
53
- - `Angle Drafts`
54
- - `Kill / Combine Review`
55
- - `Finalizer Pass`
56
- - `Gold-Standard Quality Gate`
57
- - `Skeptical Prospect Review`
58
- - `Winner Gate`
59
- - `Selected Winner`
60
- - `Findings`
61
- - `Recommendation`
62
-
63
- Keep this recommendation concise. It should prove the reasoning path, not
64
- reproduce the full framework.
65
-
66
- ## Message Quality Gates
67
-
68
- - The selected winner must be one first outbound send only. No post-accept DM,
69
- follow-up, cadence branch, or sequence copy.
70
- - The message must explain what the product is and what it does in plain
71
- language before asking for a call.
72
- - The message must use a truthful buyer-side reply reason, not just sender
73
- biography.
74
- - If using a template, include at least one supported `{{token}}` and show a
75
- complete rendered good-fill example and complete rendered omit/fallback
76
- example.
77
- - Do not use internal tokens such as `{{profile_signal}}` in customer-facing
78
- copy.
79
- - Do not put bracketed instructions in the message body, such as `[ROW_BRIDGE]`,
80
- `[insert]`, `[generated]`, or any instruction for a later model to fill.
81
- - Optional row-specific personalization must be grounded in a row field or
82
- omitted entirely. Never use generic filler like "your work" or "your team."
83
- - Engagement-source personalization is a special case, not the default opener.
84
- Do not write `saw you {{engagement_context}} on {{post_context}}`, `saw you
85
- reacted to`, `saw you engaging with`, or equivalent source-citation copy as a
86
- default hook. Only refer to the prospect's engagement when the line is
87
- self-aware and low-certainty, for example `not sure if this is too specific,
88
- but the [topic] thread felt close enough to send`. Otherwise omit the
89
- engagement signal and use role/company/problem context.
90
- - Subject lines should be short, buyer-relevant, and specific. Avoid
91
- `quick question`, `demo`, `founder call`, sender names, and generic `outbound`.
92
- - If the message is plausible but not ready to send, set
93
- `Recommendation: revise-messaging`.
94
-
95
- ## Internal Message Recommendation
96
-
97
- The message scout or parent safety gate should still produce complete internal
98
- review data: token-fill rules, one rendered omit/fallback example, and bad-fill
99
- avoidance notes. This data is for campaign-brief persistence and safety checks,
100
- not for the default chat approval packet.
101
-
102
- ## Customer-Facing Message Review
103
-
104
- Render this in chat before asking. Use Markdown structure so the approval target
105
- is visually scannable and low-overhead:
106
-
107
- ````markdown
108
- Status: message-review
109
-
110
- ## Message Template
111
-
112
- **Subject**
113
-
114
- ```text
115
- {{tokenized_subject}}
116
- ```
117
-
118
- **Body**
119
-
120
- ```text
121
- {{tokenized_message_body}}
122
- ```
123
-
124
- ## Rendered Example
125
-
126
- ### Example
127
-
128
- Good token fill:
129
-
130
- ```text
131
- Subject: ...
132
-
133
- Hey First,
134
-
135
- ...
136
- ```
137
-
138
- ## Recommendation
139
-
140
- **My take:** ...
141
-
142
- **Suggested adjustment:** ...
143
-
144
- **Question:** approve-message or revise-messaging?
145
-
146
- **Recommendation:** approve-message
147
- ````
148
-
149
- Formatting requirements:
150
-
151
- - Put the tokenized template and rendered example in fenced `text` blocks
152
- so chat gives them a distinct background.
153
- - The chat example must contain a complete rendered subject + body, not a
154
- bullet list of token names or a single bridge-line fragment.
155
- - Do not show `Good omit / fallback`, `Bad fill to avoid`, or `Token Notes` in
156
- the default chat approval packet. Keep those in the internal recommendation
157
- and persist them to the campaign brief after approval.
158
- - Keep reasoning outside the code blocks so the blocks are easy to inspect and
159
- approve.
160
-
161
- `My take` and `Suggested adjustment` must be specific. The adjustment can be
162
- "approve as-is, or revise once to X if you want Y"; it must not be empty.