@sellable/mcp 0.1.189 → 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.
- package/agents/post-find-leads-message-scout.md +8 -7
- package/agents/registry.json +2 -2
- package/dist/index-dev.js +0 -0
- package/dist/index.js +0 -0
- package/dist/server.js +22 -0
- package/dist/tools/campaign-processing.d.ts +383 -0
- package/dist/tools/campaign-processing.js +304 -0
- package/dist/tools/leads.d.ts +63 -19
- package/dist/tools/leads.js +59 -8
- package/dist/tools/prompts.js +2 -2
- package/dist/tools/registry.d.ts +289 -37
- package/dist/tools/registry.js +2 -0
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +17 -12
- package/skills/create-campaign-v2/SKILL.md +17 -18
- package/skills/create-campaign-v2/SOUL.md +11 -4
- package/skills/create-campaign-v2/core/flow.v2.json +1 -1
- package/skills/create-campaign-v2/core/policy.md +3 -3
- package/skills/create-campaign-v2/references/approval-gate-framing.md +5 -5
- package/skills/create-campaign-v2/references/filter-leads.md +5 -3
- package/skills/create-campaign-v2/references/parallel-critique-protocol.md +2 -2
- package/skills/create-campaign-v2/references/sample-validation-loop.md +46 -66
- package/skills/create-campaign-v2/references/step-15-re-cascade.md +20 -13
- package/skills/create-campaign-v2-tail/SKILL.md +52 -65
- package/skills/find-leads/SKILL.md +2 -1
- package/skills/generate-messages/SKILL.md +11 -0
- package/skills/generate-messages-compact/SKILL.md +100 -0
- package/skills/generate-messages-compact/references/examples-critique-revision.md +37 -0
- 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
|
-
(`
|
|
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.
|
|
45
|
-
|
|
46
|
-
|
|
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
|
|
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
|
-
|
|
53
|
+
queue_campaign_cells({
|
|
55
54
|
tableId,
|
|
56
|
-
|
|
55
|
+
columnRole: "generateMessage",
|
|
56
|
+
rowSelector: { type: "needsGeneratedMessage" }
|
|
57
57
|
})
|
|
58
58
|
|
|
59
|
-
4.
|
|
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
|
-
|
|
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
|
|
95
|
+
Step 15 must explicitly queue `needsGeneratedMessage` cells.
|
|
90
96
|
- Re-cascade iteration cap = 3. Trip escalates.
|
|
91
|
-
- `
|
|
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
|
-
|
|
49
|
-
|
|
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.
|
|
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
|
-
|
|
62
|
-
|
|
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
|
|
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
|
-
- `
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
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
|
-
`
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
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.
|
|
145
|
-
|
|
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.
|
|
269
|
-
15-row internal execution slice
|
|
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`, `
|
|
277
|
-
`
|
|
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
|
-
|
|
303
|
-
|
|
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
|
|
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
|
|
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
|
-
(
|
|
327
|
-
|
|
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
|
|
365
|
-
|
|
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.
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
387
|
-
|
|
388
|
-
|
|
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,
|
|
44
|
-
reactions and
|
|
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
|