@sellable/mcp 0.1.188 → 0.1.190

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 (29) hide show
  1. package/agents/post-find-leads-message-scout.md +8 -7
  2. package/agents/registry.json +2 -2
  3. package/dist/index-dev.js +0 -0
  4. package/dist/index.js +0 -0
  5. package/dist/server.js +22 -0
  6. package/dist/tools/campaign-processing.d.ts +383 -0
  7. package/dist/tools/campaign-processing.js +304 -0
  8. package/dist/tools/leads.d.ts +63 -19
  9. package/dist/tools/leads.js +59 -8
  10. package/dist/tools/prompts.js +2 -2
  11. package/dist/tools/registry.d.ts +289 -37
  12. package/dist/tools/registry.js +2 -0
  13. package/package.json +1 -1
  14. package/skills/create-campaign/SKILL.md +17 -12
  15. package/skills/create-campaign-v2/SKILL.md +23 -25
  16. package/skills/create-campaign-v2/SOUL.md +11 -4
  17. package/skills/create-campaign-v2/core/flow.v2.json +1 -1
  18. package/skills/create-campaign-v2/core/policy.md +3 -3
  19. package/skills/create-campaign-v2/references/approval-gate-framing.md +5 -5
  20. package/skills/create-campaign-v2/references/filter-leads.md +5 -3
  21. package/skills/create-campaign-v2/references/parallel-critique-protocol.md +2 -2
  22. package/skills/create-campaign-v2/references/sample-validation-loop.md +46 -66
  23. package/skills/create-campaign-v2/references/step-15-re-cascade.md +20 -13
  24. package/skills/create-campaign-v2-tail/SKILL.md +52 -65
  25. package/skills/find-leads/SKILL.md +2 -1
  26. package/skills/generate-messages/SKILL.md +11 -0
  27. package/skills/generate-messages-compact/SKILL.md +100 -0
  28. package/skills/generate-messages-compact/references/examples-critique-revision.md +37 -0
  29. package/skills/providers/signal-discovery.md +9 -3
@@ -27,8 +27,8 @@ Re-cascade runs whenever ALL of the following are true:
27
27
  to continue processing more campaign rows.
28
28
  2. A subsequent check of rubric state shows rows that were pending at
29
29
  first message-generation pass are now passed.
30
- 3. Those newly-passed rows do NOT yet have generated messages
31
- (`messagesCount` flat relative to pre-cascade).
30
+ 3. Those newly-passed rows do NOT yet have current-revision generated messages
31
+ (`currentRevisionGeneratedMessagesCount` flat relative to pre-cascade).
32
32
  4. Step 15 has NOT yet transitioned to `awaiting-user-greenlight`.
33
33
 
34
34
  Before that first generated-message approval, do not run this loop just to wait
@@ -41,25 +41,31 @@ rows graduated.
41
41
  ## Re-Cascade Loop Shape
42
42
 
43
43
  ```text
44
- 1. paginate campaign table (get_rows_minimal, strip carryData) to
45
- collect rowIds where:
46
- - rubricStatus = "passed"
47
- - messageStatus = "pending" OR messagesCount = 0
44
+ 1. use selector dry-run only if debugging:
45
+ `select_campaign_cells({ columnRole: "generateMessage", rowSelector: { type: "needsGeneratedMessage" } })`
46
+ The normal path can skip this and queue directly.
48
47
 
49
- 2. if newlyPassedRowIds.length == 0:
48
+ 2. if selectedCellCount == 0:
50
49
  proceed to awaiting-user-greenlight
51
50
  EXIT re-cascade loop
52
51
 
53
52
  3. else:
54
- queue_cells({
53
+ queue_campaign_cells({
55
54
  tableId,
56
- cellIds: newlyPassedGenerateMessageCellIds
55
+ columnRole: "generateMessage",
56
+ rowSelector: { type: "needsGeneratedMessage" }
57
57
  })
58
58
 
59
- 4. re-check token contract on the cascaded subset (strict mode default)
59
+ 4. wait_for_campaign_processing({
60
+ tableId,
61
+ minGeneratedMessages: 1,
62
+ templateRevision: "current"
63
+ })
64
+
65
+ 5. re-check token contract on the cascaded subset (strict mode default)
60
66
  — see references/thomas-revision-filters.md + gold-standard-message-patterns.md
61
67
 
62
- 5. go back to step 1 until newlyPassedRowIds == 0
68
+ 6. go back to step 1 until selectedCellCount == 0
63
69
  ```
64
70
 
65
71
  ## Cap
@@ -86,9 +92,10 @@ silently skipped.
86
92
  ## Hard Rules
87
93
 
88
94
  - Generate Message cells do not always auto-cascade on late-passed rows —
89
- Step 15 must explicitly queue newly-pending Generate Message cells.
95
+ Step 15 must explicitly queue `needsGeneratedMessage` cells.
90
96
  - Re-cascade iteration cap = 3. Trip escalates.
91
- - `messagesCount` flat + rubric graduating rows ⇒ re-cascade required.
97
+ - `currentRevisionGeneratedMessagesCount` flat + rubric graduating rows ⇒
98
+ re-cascade required.
92
99
  - Token contract enforcement applies per-cascade, not once-for-all.
93
100
  - Re-cascade NEVER re-runs `import_leads`, `check_rubric`, or
94
101
  `enrich_with_prospeo`. Those are Step 13 / Step 14 actions.
@@ -30,9 +30,8 @@ Step 13 — materialize source list + confirm initial campaign slice
30
30
  materialize/reuse approved source with campaignOfferId
31
31
  import_leads(source-list target; SalesNav/Prospeo default ~1000, Signal Discovery default ~1500 engagers)
32
32
  wait_for_lead_list_ready
33
- confirm_lead_list(reviewBatchLimit=15)
33
+ confirm_lead_list(reviewBatchLimit=15) # compact reviewBatch metadata is stored on the campaign table
34
34
  wait_for_campaign_table_ready # campaign table exists
35
- get_rows_minimal # read initial campaign-table execution slice
36
35
  update_campaign(currentStep=filter-choice)
37
36
 
38
37
  Post-import main thread
@@ -45,22 +44,17 @@ Post-import main thread
45
44
  only then start the initial-slice cascade
46
45
 
47
46
  Step 14 — kick initial-slice cascade + observe campaign rows
48
- queue_cells(cellIds=<first 15 campaign-table execution-slice Enrich Prospect cells only>) <-- starts bounded chain
49
- wait_for_campaign_table_ready # wait until sample cascade starts returning filter results
50
- get_rows_minimal # read passesRubric + message cell status per row
51
- wait_for_rubric_results(minPassedCount=1, includeRows=false)
47
+ queue_campaign_cells(columnRole=enrich, rowSelector=reviewBatch) <-- starts bounded chain
48
+ wait_for_campaign_processing(minPassedCount=1)
52
49
  if at least one row passes: update_campaign(currentStep=auto-execute-messaging)
53
50
  compute projectedPass for later reporting / revision decisions
54
- if zero rows pass: diagnose brief-vs-list; if brief: update_campaign_brief + re-queue + wait
51
+ if zero rows pass: diagnose brief-vs-list; if brief: update_campaign_brief + re-queue + wait with changed args
55
52
  (check_rubric / bulk_enrich_with_prospeo are NOT called here —
56
- cascade already did them. wait_for_rubric_results is OK as a
57
- read-only observation helper if you need to block until the first
58
- passing filtered row exists.)
53
+ cascade already did them.)
59
54
 
60
55
  Step 15 — observe messaging
61
- wait_for_rubric_results({ minPassedCount: 1, minMessagesCount: 1, includeRows: false })
62
- get_rows_minimal # confirm the first passing generated message exists
63
- (rare) queue_cells on any pending Generate Message cells
56
+ queue_campaign_cells(columnRole=generateMessage, rowSelector=needsGeneratedMessage)
57
+ wait_for_campaign_processing({ minGeneratedMessages: 1, templateRevision: "current" })
64
58
  token-contract spot check via get_rows
65
59
  update_campaign(currentStep=auto-execute-messaging) with review-ready narration
66
60
  ask the user to approve the generated message before Settings
@@ -99,19 +93,14 @@ Message` column's http_request writes those cells via the cascade.
99
93
  - Do NOT call `check_rubric`, `enrich_with_prospeo`, or
100
94
  `bulk_enrich_with_prospeo` in the tail. Those are direct-API
101
95
  mutation tools that fetch data to the caller without writing to
102
- the workflow table cells. Step 13's `queue_cells` already
96
+ the workflow table cells. Step 14's `queue_campaign_cells` call
103
97
  triggers the column-level enrichment that populates those cells.
104
98
  Running these tools in the tail produces duplicate cost with no
105
99
  cell side effect.
106
- - `wait_for_rubric_results` is read-only and OK to use as an
107
- observation helper when you need to block on rubric completion
108
- it does not mutate cells. Prefer `get_rows_minimal` +
109
- `wait_for_campaign_table_ready` for the primary cascade-observation
110
- path, but use `wait_for_rubric_results({ targetCount: cohortSize })`
111
- when the table-level wait returns before rubric cells finish. In
112
- create-campaign-v2 Step 14, pass `minPassedCount: 1`; one passing
113
- filtered row is enough to start observing Generate Message, even if
114
- other sample rows are still processing.
100
+ - `wait_for_campaign_processing` is the stats-only observation helper for the
101
+ tail. In Step 14 pass `minPassedCount: 1`; in Step 15 pass
102
+ `minGeneratedMessages: 1` and `templateRevision: "current"`. Do not repoll
103
+ identical args after a timeout.
115
104
  - `start_campaign` is FORBIDDEN in the autonomous tail. It belongs only
116
105
  in the Claude-greenlight path, AFTER the user signals "start". See
117
106
  `references/final-handoff-contract.md`.
@@ -121,15 +110,16 @@ Message` column's http_request writes those cells via the cascade.
121
110
  - You MAY NOT call `start_campaign` while ANY passing row has an
122
111
  empty `Generate Message` cell. If you discover empty message cells in
123
112
  the greenlight turn, ABORT greenlight and return to Step 15 first.
124
- - You MAY NOT call `attach_sequence` while ANY passing row has an
125
- empty `Generate Message` cell. Before `attach_sequence`, call
126
- `get_rows_minimal` and confirm every row where `passesRubric=true`
127
- has a `completed` Generate Message cell with non-empty `result`. If
128
- any are pending, call `queue_cells` on those generateMessageCellIds
129
- and wait. If rows truly won't message, ESCALATE.
130
- - You MAY NOT advance past Step 13 without calling `queue_cells` on
131
- the initial-slice Enrich Prospect cells. Without it, every downstream
132
- cell stays `pending` and the campaign ships empty.
113
+ - You MAY NOT call `attach_sequence` while ANY reviewed passing row has an
114
+ empty or stale `Generate Message` cell. Before `attach_sequence`, call
115
+ `select_campaign_cells({ tableId, columnRole: "generateMessage",
116
+ rowSelector: { type: "needsGeneratedMessage" } })`. If any cells are
117
+ selected, call `queue_campaign_cells` with the same selector and wait with
118
+ `wait_for_campaign_processing({ minGeneratedMessages: 1,
119
+ templateRevision: "current" })`. If rows truly won't message, ESCALATE.
120
+ - You MAY NOT advance past Step 14 without calling `queue_campaign_cells` for
121
+ `{ columnRole: "enrich", rowSelector: { type: "reviewBatch" } }`. Without it,
122
+ every downstream cell stays `pending` and the campaign ships empty.
133
123
  - You MAY NOT queue enrichment for rows outside the configured internal
134
124
  execution slice before the user approves expansion. Full-list enrichment/message
135
125
  generation is a credit-spend decision and must happen after the user
@@ -141,9 +131,8 @@ All subsequent steps read the already-parsed config. Do not re-load mid-run,
141
131
  and do not read repo-local config files; packaged Claude Code and Codex runs
142
132
  must use the MCP asset loader.
143
133
  Load each subskill prompt (`create-campaign-v2`, `research-sender`,
144
- `generate-messages`) at most once per run. A multi-call chunk sequence counts
145
- as one load. If a tool result already told you to load a prompt, load all
146
- chunks once and remember; do not restart the same prompt from offset 0 later.
134
+ `generate-messages-compact`) at most once per run. If a tool result already told
135
+ you to load a prompt, remember it; do not restart the same prompt later.
147
136
 
148
137
  After every `update_campaign({ currentStep: ... })` in the tail, narrate
149
138
  what changed and orient the user to what the already-open app will show next —
@@ -265,16 +254,17 @@ searchId, targetLeadCount: 1000 })`.
265
254
  the source list and `workflowTableId` is the campaign table.
266
255
  4. `wait_for_campaign_table_ready` until the initial campaign-table execution slice rows are
267
256
  available in the campaign table.
268
- 5. Call `get_rows_minimal({ tableId: workflowTableId })` and confirm the
269
- 15-row internal execution slice is present. Do not queue cells in Step 13.
257
+ 5. Confirm the compact `reviewBatch` returned by `confirm_lead_list` has the
258
+ expected 15-row internal execution slice metadata. Do not fetch row payloads
259
+ or queue cells in Step 13.
270
260
  6. If the import returns zero usable leads, ESCALATE per
271
261
  `references/escalation-ladder.md` (hard fail).
272
262
  7. `update_campaign({ campaignId, currentStep: "filter-choice" })`.
273
263
  8. Describe the filter-choice screen now visible in the already-open app. Do not
274
264
  repeat the watch URL.
275
265
 
276
- **Do NOT call `check_rubric`, `wait_for_rubric_results`,
277
- `queue_cells`, `enrich_with_prospeo`, or `bulk_enrich_with_prospeo` in Step 13.**
266
+ **Do NOT call `check_rubric`, `wait_for_campaign_processing`,
267
+ `queue_campaign_cells`, `enrich_with_prospeo`, or `bulk_enrich_with_prospeo` in Step 13.**
278
268
  Those are direct-API tools that fetch enrichment/scoring data to the
279
269
  caller or start the workflow-table cascade too early. The cascade starts in
280
270
  Step 14 only after `save_rubrics` and `update_campaign_brief` have both
@@ -299,18 +289,16 @@ message is ready. It does NOT call `check_rubric`,
299
289
  Shape:
300
290
 
301
291
  ```text
302
- queue_cells({ tableId: workflowTableId, cellIds: reviewBatchEnrichCellIds })
303
- wait_for_campaign_table_ready({ tableId: workflowTableId })
304
- get_rows_minimal({ tableId: workflowTableId })
305
- wait_for_rubric_results({ tableId: workflowTableId, targetCount: cohortSize, minPassedCount: 1, includeRows: false })
292
+ queue_campaign_cells({ tableId: workflowTableId, columnRole: "enrich", rowSelector: { type: "reviewBatch" } })
293
+ wait_for_campaign_processing({ tableId: workflowTableId, minPassedCount: 1 })
306
294
  passInSample = count of first sampleSize review/process sample rows with passesRubric === true
307
295
  projectedPass = round(passInSample / sampleSize * importLimit)
308
296
 
309
- if wait_for_rubric_results.ready === true and passRate.passed >= 1:
297
+ if wait_for_campaign_processing.ready === true and passRate.passed >= 1:
310
298
  advance to Step 15 to observe or queue Generate Message for currently passing rows
311
299
  do not wait for every sample row to finish before message generation starts
312
300
  stop for review as soon as one passing generated message is ready
313
- else if wait_for_rubric_results.ready === false and reason === "timeout":
301
+ else if wait_for_campaign_processing.ready === false and reason === "timeout":
314
302
  use the partial passRate/stats as the sample diagnostic
315
303
  if passRate.passed >= 1:
316
304
  advance to Step 15 to observe/queue Generate Message for passing rows
@@ -322,9 +310,10 @@ else if wait_for_rubric_results.ready === false and reason === "timeout":
322
310
  ask whether to revise source, revise filter/rubric, or wait once only if still processing
323
311
  else:
324
312
  diagnose brief-vs-list per sample-validation-loop.md
325
- - brief: autonomous update_campaign_brief, then re-kick cascade
326
- (queue_cells on enrichCellIds) and wait for the new
327
- results
313
+ - brief: autonomous update_campaign_brief, then re-kick cascade with
314
+ queue_campaign_cells({ columnRole: "enrich", rowSelector:
315
+ { type: "reviewBatch" }, forceRerun: true }) and wait for the
316
+ new results
328
317
  - list: ESCALATE (no auto-revision of leads)
329
318
  - unknown: ESCALATE
330
319
  revisionRound += 1
@@ -361,8 +350,8 @@ Entered on `CampaignOffer.currentStep === "auto-execute-messaging"`.
361
350
 
362
351
  **Messages are produced by the `Generate Message` column cascade, not
363
352
  by a separate tool.** Do NOT call a `generate_messages` MCP tool —
364
- that tool does not exist; `generate-messages` is a subskill prompt
365
- loaded during live messaging drafts. In the autonomous tail, messages
353
+ that tool does not exist; `generate-messages-compact` is the Message Draft
354
+ Builder subskill prompt for the approved template. In the autonomous tail, messages
366
355
  flow through the column pipeline: `Enrich Prospect` →
367
356
  `DNC Check` → `ICP Score` → `Passes Rubric` → `Generate Message`.
368
357
  Each column's http_request auto-fires when its upstream dependency
@@ -373,21 +362,19 @@ the output — not to generate messages manually.
373
362
 
374
363
  **What Step 15 does:**
375
364
 
376
- 1. `get_rows_minimal` to read `passesRubric` + `generateMessageCellId`
377
- - the Generate Message cell's `status` and `result` per row.
378
- Before queueing or waiting on Generate Message cells, confirm the
379
- minted campaign brief still contains `{{...}}` in `## Approved Message
380
- Template`. If it does not, fail before the cascade runs. Do not repair
381
- after mint; the template must be present during mint so Step 15 never
382
- starts in freeform generation mode.
383
- 2. For every row where `passesRubric === true`:
384
- - The Generate Message cell should be `running` or `completed`
385
- already (cascade auto-fired it). If it is still `pending`, queue
386
- it explicitly: `queue_cells({ tableId, cellIds:
387
- <generateMessageCellIds> })`.
388
- 3. `wait_for_rubric_results({ tableId, targetCount: reviewBatchSize,
389
- minPassedCount: 1, minMessagesCount: 1, includeRows: false })` until the
390
- first passing generated message is ready. Do not wait for every passing row's
365
+ 1. Before queueing or waiting on Generate Message cells, confirm the minted
366
+ campaign brief still contains `{{...}}` in `## Approved Message Template`.
367
+ If it does not, fail before the cascade runs. Do not repair after mint; the
368
+ template must be present during mint so Step 15 never starts in freeform
369
+ generation mode.
370
+ 2. Use `select_campaign_cells({ tableId, columnRole: "generateMessage",
371
+ rowSelector: { type: "needsGeneratedMessage" } })` only if you need a dry-run
372
+ count. If any passing row has a pending, empty, or stale Generate Message
373
+ cell, queue it explicitly with `queue_campaign_cells({ tableId, columnRole:
374
+ "generateMessage", rowSelector: { type: "needsGeneratedMessage" } })`.
375
+ 3. `wait_for_campaign_processing({ tableId, minGeneratedMessages: 1,
376
+ templateRevision: "current" })` until the first current-revision generated
377
+ message is ready. Do not wait for every passing row's
391
378
  Generate Message cell, and do not add "one more wait" for a stronger sample.
392
379
  Remaining review/process sample rows can continue processing in the background.
393
380
  4. Read the first ready generated message back via `get_rows` (full) and
@@ -135,7 +135,8 @@ The kickoff doc is the resume surface. Re-open it before repeating discovery wor
135
135
  place to start, why the right buyers are likely to be there, what signs the
136
136
  next search will check, where you'll look next if the first place is too thin,
137
137
  and that approval only lets you look for the best places to find buyers. No
138
- one is added to the campaign yet.
138
+ one is added to the campaign yet. The approval must come after the visible
139
+ plan; do not reuse a prior brief approval or generic approval panel.
139
140
  - When enough context exists, try 1-2 alternate hypotheses if the first lane is too weak or noisy.
140
141
  - Directional preview does not require a sender, campaign, or selected lead list. Start with count/sample exploration first; only attach searches to a campaign when the user is ready to import.
141
142
  - If the user already has a LinkedIn-profile CSV, treat that as a direct lead-list path and skip discovery.
@@ -1112,6 +1112,17 @@ unit, either compress it into that first send unit or omit it. Do not move it to
1112
1112
  a later message. Preserve the idea as an internal `Findings` note only if it is
1113
1113
  useful after campaign mint.
1114
1114
 
1115
+ ### Message Review Revision Loop
1116
+
1117
+ When the user gives edits to an already rendered template, treat that feedback
1118
+ as the next drafting input. Produce a revised selected template, rendered
1119
+ example, and short change summary before asking for approval again. Do not only
1120
+ acknowledge the feedback or ask whether a new version is better without showing
1121
+ the new version. In live campaign mode, save with `update_campaign_brief` only
1122
+ after the user approves the revised template.
1123
+ Do not call `request_user_input` or `AskUserQuestion` from the drafting or
1124
+ revision step; approval questions belong after the revised copy is visible.
1125
+
1115
1126
  Before returning `Status: confirmed`, run this explicit check:
1116
1127
 
1117
1128
  `Single send unit: PASS | BLOCKED — reason`
@@ -0,0 +1,100 @@
1
+ ---
2
+ name: generate-messages-compact
3
+ description: Compact required Message Draft Builder prompt for create-campaign-v2; load lazy references only for examples, critique, or revision.
4
+ visibility: internal
5
+ ---
6
+
7
+ # Generate Messages Compact
8
+
9
+ Use this prompt for create-campaign-v2 Message Draft Builder. It replaces the
10
+ old normal-path requirement to load the full long-form `generate-messages`
11
+ prompt before writing a reusable template. The full prompt remains available for
12
+ legacy dry-mode validation and deep debugging.
13
+
14
+ ## Mode
15
+
16
+ Draft one reusable first-message template for a live campaign after:
17
+
18
+ - `confirm_lead_list` copied a non-empty source into the campaign table
19
+ - the first review batch exists in campaign state
20
+ - the parent recorded the filter choice
21
+
22
+ This is not the product Generate Message cell path. Do not write row messages,
23
+ queue cells, update the campaign brief, attach sequence, or launch.
24
+
25
+ ## Source Of Truth
26
+
27
+ Use only live campaign inputs supplied by the parent or scoped MCP tools:
28
+
29
+ - `campaignId`
30
+ - campaign revision or `updatedAt`
31
+ - campaign brief content and brief hash
32
+ - selected source decision and `selectedLeadListId`
33
+ - `workflowTableId`
34
+ - review-batch row ids/hash and compact sample rows
35
+ - filter choice and saved rubric summary when available
36
+ - explicit user/operator message direction
37
+
38
+ Reject as `blocked` or `stale` if campaign id, selected list, table id, brief
39
+ hash, or review-batch basis do not match. Do not reconstruct state from local
40
+ markdown/json files, database reads, or stale tool output.
41
+
42
+ ## Required Output
43
+
44
+ Return compact structured output to the parent:
45
+
46
+ - `templateRecommendation`: tokenized first-send template
47
+ - `tokenFillRules`: safe fill rules, fallbacks, and unsupported fills
48
+ - `renderedSample`: one good passing-row example
49
+ - `omitSample`: one example where weak personalization is omitted
50
+ - `concerns`: quality risks or blockers
51
+ - `status`: `ready`, `blocked`, `retry-needed`, or `stale`
52
+ - `basisStatus`, `basisToken`, `outputAt`, `outputHash`
53
+
54
+ The parent owns approval and `update_campaign_brief`.
55
+
56
+ ## Drafting Rules
57
+
58
+ - Keep it clear, concrete, and product-specific.
59
+ - Use simple language and short lines.
60
+ - Default to one blank line per sentence in the body.
61
+ - Explain what the product is before what it does.
62
+ - Use proof only when the campaign brief provides it.
63
+ - Use the sender's voice, not third-person notes about the sender.
64
+ - Use supported tokens such as `{{first_name}}` only.
65
+ - Prefer omitting weak personalization over forcing a creepy line.
66
+ - Do not claim the prospect needs outbound, automation, AI, or the product.
67
+ - Do not claim private intent from a reaction/comment.
68
+ - Do not write "saw you engaging", "clearly focused", or similar certainty.
69
+ - Do not include bracketed instructions in the outbound message body.
70
+ - Do not include sequence, follow-up, post-accept DM, launch, or sender setup.
71
+
72
+ Engagement-source personalization is allowed only when it is cautious and
73
+ self-aware, for example: `you might not remember the thread, but I found you
74
+ through a [topic] discussion and your [role/company context] looked close to
75
+ [problem]`. Otherwise omit the source signal and start from role, company,
76
+ problem, or product relevance.
77
+
78
+ ## Quality Gate
79
+
80
+ Before returning `ready`, verify:
81
+
82
+ - first name and company/person match
83
+ - tokens have deterministic fallbacks
84
+ - proof claims are in the brief or omitted
85
+ - the prospect angle follows from role/source context
86
+ - unsupported claims are removed
87
+ - the template is usable for the whole review batch, not only one row
88
+ - at least one rendered example reads like a message a real sender would send
89
+
90
+ ## Lazy References
91
+
92
+ Load `references/examples-critique-revision.md` only when:
93
+
94
+ - the parent explicitly asks for a critique pass
95
+ - the first draft fails the quality gate
96
+ - the user asks to revise the message template
97
+ - you need more examples to resolve a close call
98
+
99
+ Do not load the old full `generate-messages` prompt in the normal create-campaign
100
+ path unless the parent asks for legacy dry-mode validation or deep debugging.
@@ -0,0 +1,37 @@
1
+ # Examples, Critique, And Revision
2
+
3
+ Load this only when the compact prompt is insufficient.
4
+
5
+ ## Good Pattern
6
+
7
+ ```text
8
+ Hey {{first_name}},
9
+
10
+ You might not remember the thread, but I found you through a Claude GTM
11
+ discussion and your founder/operator background looked close enough to send this.
12
+
13
+ I am building Sellable so Claude Code can run LinkedIn outbound without
14
+ stitching together Clay, Apollo, HeyReach, and spreadsheets.
15
+
16
+ It finds people from live LinkedIn signals.
17
+
18
+ It filters for fit.
19
+
20
+ It writes the sequence and keeps the campaign moving from chat.
21
+
22
+ Open to seeing what this would look like for your own outbound workflow?
23
+ ```
24
+
25
+ ## Bad Patterns To Block
26
+
27
+ - "Saw you engaging with Debbie's post, so AI-GTM is clearly on your mind."
28
+ - "You need help with outbound."
29
+ - "I noticed you are struggling with..."
30
+ - "[PERSONALIZATION_LINE]"
31
+ - "Christian's approach is..."
32
+
33
+ ## Revision Rule
34
+
35
+ When the user asks to change the message after generation, update the approved
36
+ template in the campaign brief and rerun Generate Message cells. Do not directly
37
+ overwrite generated row messages.
@@ -40,8 +40,14 @@ When the user asks to find posts or start searching, **IMMEDIATELY begin Round 1
40
40
 
41
41
  Production Signal Discovery uses Harvest for post comments and reactions.
42
42
  Harvest returns about 100 comments or reactions per page. The source scrape
43
- pages through Harvest up to the configured per-post caps, currently 1,500
44
- reactions and 300 comments per selected post.
43
+ pages through Harvest up to the configured per-post caps, currently 1,000
44
+ reactions and 1,000 top-level comments per selected post.
45
+
46
+ LinkedIn search/post-detail counts are visible public counts. LinkedIn can
47
+ include replies in the visible comment number, while Signal Discovery imports
48
+ top-level commenters only. Treat pre-import people-to-check math as an upper
49
+ bound. The exact unique people count is known only after the comments/reactions
50
+ scrape completes and duplicate people are merged.
45
51
 
46
52
  Important interpretation rule: a source list row count is the number of
47
53
  headline-passing candidates inserted after scraping and filtering, not the raw
@@ -243,7 +249,7 @@ Examples of BAD keywords:
243
249
 
244
250
  Look for posts that:
245
251
 
246
- - Have meaningful engagement (minimum 30 likes OR 15 comments)
252
+ - Have meaningful visible engagement (minimum 30 likes OR 15 visible comments)
247
253
  - Are from authors in your ICP's community
248
254
  - Discuss specific, tactical topics (not generic thought leadership)
249
255
  - Generate discussion, not just likes