@sellable/mcp 0.1.80 → 0.1.82
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-filter-scout.md +19 -8
- package/agents/post-find-leads-message-scout.md +21 -6
- package/agents/registry.json +33 -18
- package/agents/source-scout-linkedin-engagement.md +10 -2
- package/agents/source-scout-prospeo-contact.md +9 -2
- package/agents/source-scout-sales-nav.md +10 -2
- package/dist/server.js +7 -1
- package/dist/tools/bootstrap.js +2 -2
- package/dist/tools/campaigns.d.ts +12 -0
- package/dist/tools/campaigns.js +40 -5
- package/dist/tools/context.d.ts +10 -4
- package/dist/tools/context.js +8 -4
- package/dist/tools/leads.d.ts +6 -3
- package/dist/tools/leads.js +39 -43
- package/dist/tools/navigation.d.ts +28 -1
- package/dist/tools/navigation.js +220 -10
- package/dist/tools/prompts.js +1 -1
- package/dist/tools/provider-preflight.js +2 -3
- package/dist/tools/readiness.d.ts +31 -6
- package/dist/tools/readiness.js +6 -1
- package/dist/tools/rubrics.d.ts +2 -0
- package/dist/tools/rubrics.js +2 -0
- package/package.json +1 -1
- package/skills/create-campaign/SKILL.md +24 -12
- package/skills/create-campaign-v2/SKILL.md +166 -114
- package/skills/create-campaign-v2/SOUL.md +19 -11
- package/skills/create-campaign-v2/core/auto-execute.yaml +14 -13
- package/skills/create-campaign-v2/core/flow.v2.json +964 -310
- package/skills/create-campaign-v2/references/approval-gate-framing.md +64 -32
- package/skills/create-campaign-v2/references/filter-leads.md +8 -0
- package/skills/create-campaign-v2/references/final-handoff-contract.md +55 -20
- package/skills/create-campaign-v2/references/lead-validation-preview.md +30 -17
- package/skills/create-campaign-v2/references/step-13-import-leads.md +62 -17
- package/skills/create-campaign-v2/references/validation-criteria.md +10 -5
- package/skills/create-campaign-v2/references/watch-link-handoff.md +72 -72
- package/skills/create-campaign-v2-tail/SKILL.md +123 -92
|
@@ -1,110 +1,110 @@
|
|
|
1
1
|
# Watch Link Handoff
|
|
2
2
|
|
|
3
|
-
This reference governs how create-campaign-v2 surfaces the watch link
|
|
4
|
-
|
|
5
|
-
|
|
6
|
-
|
|
3
|
+
This reference governs how create-campaign-v2 surfaces the watch link in the
|
|
4
|
+
shell-first flow. Load it before showing the link and on every resume where the
|
|
5
|
+
link needs to be re-surfaced.
|
|
6
|
+
|
|
7
|
+
CampaignOffer state is canonical; disk artifacts are a debug trail. All 14 disk
|
|
8
|
+
artifacts remain as debug outputs, but resume, gating, and handoff read campaign
|
|
9
|
+
state first.
|
|
7
10
|
|
|
8
11
|
## Plumbing Reuse
|
|
9
12
|
|
|
10
13
|
`create_campaign` already returns a signed `watchUrl` on the response
|
|
11
14
|
(`CampaignDetail.watchUrl` in `mcp/sellable/src/tools/campaigns.ts`). V2 does
|
|
12
15
|
NOT mint a new token, does NOT call a different route, and does NOT construct
|
|
13
|
-
the URL locally.
|
|
14
|
-
|
|
16
|
+
the URL locally. Capture whatever `watchUrl` the existing tool returns and
|
|
17
|
+
surface it verbatim.
|
|
15
18
|
|
|
16
|
-
|
|
17
|
-
step change, the skill names what changed and orients the user to what the
|
|
18
|
-
watch link will show next. V2 adopts that pattern verbatim — the only
|
|
19
|
-
difference is V2 surfaces the link once, at the atomic mint boundary, and
|
|
20
|
-
then re-surfaces it on resume.
|
|
19
|
+
## Shell-First Link
|
|
21
20
|
|
|
22
|
-
|
|
21
|
+
The active flow creates the campaign shell after the v1 brief has been rendered
|
|
22
|
+
and persisted. The link is surfaced only after `create_campaign` returns both:
|
|
23
23
|
|
|
24
|
-
|
|
25
|
-
|
|
24
|
+
- `campaignId`
|
|
25
|
+
- `watchUrl`
|
|
26
26
|
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
2. `save_rubrics({ campaignOfferId, rubric })`
|
|
30
|
-
→ must return success
|
|
27
|
+
The shell must already contain the v1 campaign brief, so the user lands on a
|
|
28
|
+
useful brief instead of an empty campaign.
|
|
31
29
|
|
|
32
|
-
|
|
33
|
-
between the two calls. Never print the link if either call fails.
|
|
30
|
+
Example skeleton:
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
```text
|
|
33
|
+
I created the campaign shell with the brief already in it.
|
|
34
|
+
You can watch the lead source, filters, and messages fill in from here.
|
|
36
35
|
|
|
37
|
-
|
|
38
|
-
must contain:
|
|
36
|
+
No leads import and nothing sends yet.
|
|
39
37
|
|
|
40
|
-
|
|
41
|
-
saved
|
|
42
|
-
- a one-line orientation sentence: `Open this to watch lead sourcing, rubric
|
|
43
|
-
scoring, and messaging populate live.`
|
|
44
|
-
- the `watchUrl` captured from the `create_campaign` response
|
|
38
|
+
Watch link: {watchUrl}
|
|
45
39
|
|
|
46
|
-
|
|
40
|
+
Cool, let's find leads.
|
|
41
|
+
```
|
|
47
42
|
|
|
48
|
-
|
|
49
|
-
Campaign created. I saved the approved brief too.
|
|
43
|
+
Atomic-mint legacy orientation:
|
|
50
44
|
|
|
45
|
+
```text
|
|
51
46
|
Open this to watch lead sourcing, rubric scoring, and messaging populate live.
|
|
52
|
-
|
|
53
|
-
Watch link: {watchUrl}
|
|
54
47
|
```
|
|
55
48
|
|
|
56
|
-
The exact wording may be adapted for tone, but the
|
|
57
|
-
|
|
58
|
-
line with the link inline — the user needs to see the orientation clearly
|
|
59
|
-
before they click.
|
|
49
|
+
The exact wording may be adapted for tone, but the copy must make three things
|
|
50
|
+
clear:
|
|
60
51
|
|
|
61
|
-
|
|
52
|
+
- the campaign shell exists now
|
|
53
|
+
- the brief is already visible in the campaign
|
|
54
|
+
- import, sequence, and start are still blocked
|
|
62
55
|
|
|
63
|
-
|
|
56
|
+
If shell creation fails or the response is missing `watchUrl`, stop and surface
|
|
57
|
+
the error. Do not continue into campaignless source scouting in the active
|
|
58
|
+
shell-first flow.
|
|
59
|
+
If a legacy atomic-mint response is missing `watchUrl`, it is not a silent skip.
|
|
60
|
+
Stop before `save_rubrics` and recover the missing signed URL first.
|
|
64
61
|
|
|
65
|
-
|
|
66
|
-
surface the underlying error, do not proceed to `save_rubrics`. No
|
|
67
|
-
rollback needed.
|
|
68
|
-
- `create_campaign` succeeds but the response is missing `watchUrl` → treat
|
|
69
|
-
as a recoverable failure. Stop before `save_rubrics`. Do not print a link.
|
|
70
|
-
Surface an explicit error that names the contract violation (the backend
|
|
71
|
-
tool should always return `watchUrl`; a missing value is a plumbing bug,
|
|
72
|
-
not a silent skip).
|
|
73
|
-
- `save_rubrics` fails after `create_campaign` succeeds → either rollback the
|
|
74
|
-
`CampaignOffer` row, or mark an explicit recoverable failure state so the
|
|
75
|
-
user can retry rubric save. Never leave a silent partial campaign with no
|
|
76
|
-
rubrics. Do NOT print the watch link during this recovery window.
|
|
62
|
+
## Step Orientation
|
|
77
63
|
|
|
78
|
-
|
|
79
|
-
the
|
|
64
|
+
After the shell exists, re-surface the same watch link only when it helps orient
|
|
65
|
+
the user after a meaningful step change:
|
|
80
66
|
|
|
81
|
-
|
|
67
|
+
- source selected: the campaign should point at the primary provider step
|
|
68
|
+
- rubrics saved: the campaign should show filter/rubric state
|
|
69
|
+
- message set approved: the campaign should show message state
|
|
70
|
+
- 10-lead test batch imported/generated: the campaign should show the test rows
|
|
71
|
+
- settings handoff: the campaign should open to Settings for sender attachment
|
|
72
|
+
- running: the campaign is live
|
|
82
73
|
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
without digging through prior transcript.
|
|
74
|
+
Do not spam the link between internal tool calls.
|
|
75
|
+
|
|
76
|
+
## Resume Re-Surface
|
|
87
77
|
|
|
88
78
|
On resume:
|
|
89
79
|
|
|
90
80
|
1. Load the `CampaignOffer` for the campaign being resumed.
|
|
91
|
-
2.
|
|
92
|
-
`create_campaign({ campaignId })
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
81
|
+
2. Recover the signed link through the existing resume path
|
|
82
|
+
(`create_campaign({ campaignId })` when available).
|
|
83
|
+
3. Inspect `currentStep` and print a short orientation for where the user will
|
|
84
|
+
land now.
|
|
85
|
+
4. Print the recovered `watchUrl`.
|
|
96
86
|
|
|
97
87
|
The re-surface must use the signed `watchUrl` from the tool response, not a
|
|
98
88
|
cached or reconstructed URL.
|
|
99
89
|
|
|
100
90
|
## Hard Rules
|
|
101
91
|
|
|
102
|
-
-
|
|
103
|
-
|
|
104
|
-
-
|
|
105
|
-
|
|
106
|
-
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
92
|
+
- Never fabricate or reconstruct the URL locally.
|
|
93
|
+
- Missing `watchUrl` is an error, not a silent skip.
|
|
94
|
+
- The first watch link must be shown only after the v1 brief is on the campaign.
|
|
95
|
+
- A watch link is not approval to import, attach sequence, or start.
|
|
96
|
+
- Import/enrichment remains blocked until rubrics are saved and the approved
|
|
97
|
+
message set is synced into the campaign brief.
|
|
98
|
+
|
|
99
|
+
## Partial-Mint Recovery
|
|
100
|
+
|
|
101
|
+
If `save_rubrics` fails after `create_campaign` succeeds, prefer a recoverable
|
|
102
|
+
failure state with explicit retry. Rollback is an operator escape hatch, not the
|
|
103
|
+
default. Never leave a silent partial campaign.
|
|
104
|
+
Use rollback only as an explicit operator escape hatch.
|
|
105
|
+
Store a recoverable failure state for retry before any watch link is shown.
|
|
106
|
+
|
|
107
|
+
Do NOT print the watch link during this recovery window. A partial campaign link
|
|
108
|
+
is misleading and must not be shown; block link surfacing and surface the
|
|
109
|
+
recovery path instead.
|
|
110
|
+
During partial mint, block link surfacing and surface the recovery path.
|
|
@@ -8,6 +8,11 @@ visibility: internal
|
|
|
8
8
|
|
|
9
9
|
This is the tail detail extracted from the main create-campaign-v2 SKILL.md to keep the entry prompt slim. The agent loads this on-demand BEFORE entering the auto-execute or validate-sample steps. Follow this verbatim.
|
|
10
10
|
|
|
11
|
+
CampaignOffer state is canonical; disk artifacts are a debug trail. All 14 disk
|
|
12
|
+
artifacts remain as debug outputs, but resume, gating, and handoff read campaign
|
|
13
|
+
state first. selectedLeadListId remains the source list; workflowTableId is the
|
|
14
|
+
campaign table.
|
|
15
|
+
|
|
11
16
|
## MANDATORY TOOL ORDER (read this BEFORE any tail step)
|
|
12
17
|
|
|
13
18
|
Every tail run MUST call these tools in this exact order. The tail is
|
|
@@ -20,16 +25,24 @@ Do NOT manually run rubric-check, enrich, or message-generation
|
|
|
20
25
|
tools — the cascade already does them.
|
|
21
26
|
|
|
22
27
|
```text
|
|
23
|
-
Step 13 — import review batch
|
|
28
|
+
Step 13 — import/confirm review batch only
|
|
29
|
+
materialize/reuse approved source with campaignOfferId
|
|
24
30
|
import_leads(targetLeadCount=importLimit)
|
|
25
31
|
wait_for_lead_list_ready
|
|
26
32
|
confirm_lead_list
|
|
27
|
-
|
|
33
|
+
wait_for_campaign_table_ready # campaign table exists
|
|
34
|
+
get_rows_minimal # read imported review batch
|
|
35
|
+
update_campaign(currentStep=filter-choice)
|
|
36
|
+
|
|
37
|
+
Post-import main thread
|
|
38
|
+
launch Lead Fit Builder + Message Draft Builder after workflowTableId exists
|
|
39
|
+
save_rubrics({ campaignOfferId, leadScoringRubrics }) after the campaign table exists
|
|
40
|
+
show readable filters + one concrete sample message
|
|
41
|
+
after approval, update_campaign_brief writes `## Approved Message Template` with `{{...}}` tokens
|
|
42
|
+
|
|
43
|
+
Step 14 — kick bounded cascade + observe sample
|
|
28
44
|
queue_cells(cellIds=<review-batch Enrich Prospect cells only>) <-- starts bounded chain
|
|
29
45
|
wait_for_campaign_table_ready # review-batch cascade drains here
|
|
30
|
-
update_campaign(currentStep=validate-sample)
|
|
31
|
-
|
|
32
|
-
Step 14 — observe sample
|
|
33
46
|
get_rows_minimal # read passesRubric + message cell status per row
|
|
34
47
|
compute projectedPass
|
|
35
48
|
if OK: update_campaign(currentStep=auto-execute-messaging)
|
|
@@ -46,7 +59,9 @@ Step 15 — observe messaging
|
|
|
46
59
|
(generate_messages is NOT an MCP tool; messages come from the cascade)
|
|
47
60
|
|
|
48
61
|
Step 16 — awaiting-user-greenlight
|
|
49
|
-
|
|
62
|
+
get_campaign + list_senders
|
|
63
|
+
if no sender attached: surface campaign settings link and STOP
|
|
64
|
+
if sender attached: attach_recommended_sequence({ campaignId }) # tier-aware: premium/SN -> If Open Profile->INMAIL_OPEN, else INVITE->accepted->DM
|
|
50
65
|
re-surface watchUrl + review-batch orientation
|
|
51
66
|
STOP. DO NOT call start_campaign. DO NOT auto-advance currentStep.
|
|
52
67
|
```
|
|
@@ -118,10 +133,17 @@ After every `update_campaign({ currentStep: ... })` in the tail, narrate
|
|
|
118
133
|
what changed and orient the user to what the watch link will show next —
|
|
119
134
|
reuse the v1 `create-campaign` watch-mode pattern verbatim.
|
|
120
135
|
|
|
136
|
+
Resume currentStep names covered by this tail: `"auto-execute-leads"`,
|
|
137
|
+
`"validate-sample"`, `"auto-execute-messaging"`,
|
|
138
|
+
`"awaiting-user-greenlight"`, and `"running"`. New mint-early runs normally
|
|
139
|
+
enter Step 13 from `"confirm-lead-list"`; the `"auto-execute-leads"` string is
|
|
140
|
+
kept for compatibility resumes.
|
|
141
|
+
|
|
121
142
|
## Step 13: auto-execute-leads
|
|
122
143
|
|
|
123
|
-
Entered
|
|
124
|
-
|
|
144
|
+
Entered from the lead-source confirmation path, with
|
|
145
|
+
`CampaignOffer.currentStep === "confirm-lead-list"` or an equivalent source
|
|
146
|
+
import milestone.
|
|
125
147
|
|
|
126
148
|
> Reminder: every provider search you run from this point forward — the
|
|
127
149
|
> review-batch source rerun in Step 13, any expansion search after the
|
|
@@ -139,24 +161,16 @@ atomic mint).
|
|
|
139
161
|
> - the **campaign workflow table** (`CampaignOffer.workflowTableId`,
|
|
140
162
|
> also returned as `campaignTableId` from `confirm_lead_list`).
|
|
141
163
|
>
|
|
142
|
-
> `confirm_lead_list`
|
|
143
|
-
>
|
|
144
|
-
> campaign-table id is NOT a valid
|
|
145
|
-
> `import_leads`
|
|
146
|
-
>
|
|
147
|
-
> When you scale up the import, `mode: "add"` requires the ORIGINAL
|
|
148
|
-
> lead-list id (the one returned by the first `import_leads`), never the
|
|
149
|
-
> campaign-table id. Passing the campaign-table id silently writes
|
|
150
|
-
> straight into the campaign workflow table, bypassing the lead list
|
|
151
|
-
> entirely; the lead list stays at its original size and the
|
|
152
|
-
> campaign-builder `/leads` UI throws "Lead list not found" because it
|
|
153
|
-
> expects `selectedLeadListId` to resolve as a lead list.
|
|
164
|
+
> `confirm_lead_list` must preserve the source lead-list id as
|
|
165
|
+
> `CampaignOffer.selectedLeadListId` and store the campaign workflow table as
|
|
166
|
+
> `CampaignOffer.workflowTableId`. The campaign-table id is NOT a valid
|
|
167
|
+
> `sourceLeadListId` for future `import_leads(mode: "add")` calls.
|
|
154
168
|
>
|
|
155
|
-
> Capture the original `leadListId` from the FIRST `import_leads`
|
|
156
|
-
>
|
|
157
|
-
>
|
|
158
|
-
>
|
|
159
|
-
>
|
|
169
|
+
> Capture the original `leadListId` from the FIRST `import_leads` response and
|
|
170
|
+
> reuse it for every scale-up `import_leads(mode: "add")` call on the same
|
|
171
|
+
> campaign. If the original id is unknown, query for the lead-list table by
|
|
172
|
+
> name or skip the `mode: "add"` path; do not pass the campaign-table id as
|
|
173
|
+
> `sourceLeadListId`.
|
|
160
174
|
|
|
161
175
|
1. Load `core/auto-execute.yaml` through
|
|
162
176
|
`get_subskill_asset({ subskillName: "create-campaign-v2", assetPath: "core/auto-execute.yaml" })`.
|
|
@@ -165,12 +179,16 @@ atomic mint).
|
|
|
165
179
|
`messaging.critique.enabled`, `handoff.autoStart`,
|
|
166
180
|
`handoff.orientation`, `retry.sameToolSameError`,
|
|
167
181
|
`logging.logEveryThresholdTrip`.
|
|
168
|
-
2. Resolve the approved source before importing.
|
|
169
|
-
`
|
|
170
|
-
|
|
182
|
+
2. Resolve and materialize the approved source before importing. Load
|
|
183
|
+
`references/post-mint-source-materialization.md` when the source is normal
|
|
184
|
+
discovery or when a legacy no-shell approval fixture must be replayed. If
|
|
185
|
+
CampaignOffer state already attached searches/selections to this campaign,
|
|
186
|
+
reuse them. If there is no `lead-source-intake.json`, replay the approved
|
|
187
|
+
provider recipe from `lead-review.md` with `campaignOfferId` before
|
|
188
|
+
`import_leads`.
|
|
171
189
|
If a manifest exists, branch by `sourceType`:
|
|
172
190
|
- `supplied-linkedin-profiles`: revalidate file metadata and confirmation
|
|
173
|
-
token, confirm `load_csv_linkedin_leads`
|
|
191
|
+
token, confirm `load_csv_linkedin_leads` as source-list materialization to batch the
|
|
174
192
|
supplied CSV into a Sellable lead list, persist the returned `leadListId`,
|
|
175
193
|
then call `confirm_lead_list({ sourceLeadListId: leadListId,
|
|
176
194
|
targetLeadCount: importLimit })`. Do not call `import_leads` for this
|
|
@@ -188,66 +206,66 @@ targetLeadCount: importLimit })`. Do not call `import_leads` for this
|
|
|
188
206
|
the cap, import only the first `importLimit` rows and leave the rest for
|
|
189
207
|
a later expansion step.
|
|
190
208
|
Persist or recover materialized IDs on resume: `leadListId`,
|
|
191
|
-
`domainFilterId`, `searchId`, `
|
|
192
|
-
|
|
209
|
+
`domainFilterId`, `searchId`, `selectedLeadListId`, `workflowTableId`, and
|
|
210
|
+
imported row IDs. If the source file changed after preview, or an existing lead list
|
|
193
211
|
is missing/wrong-workspace, escalate before materialization instead of
|
|
194
212
|
silently applying stale preview data. Retrying Step 13 must not duplicate
|
|
195
|
-
lead lists, searches, campaign rows
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
-
|
|
204
|
-
|
|
205
|
-
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
213
|
+
lead lists, searches, or campaign rows.
|
|
214
|
+
Normal discovery provider order:
|
|
215
|
+
- Signal Discovery: `get_provider_prompt({ provider: "signal-discovery",
|
|
216
|
+
campaignOfferId, confirmed: true })` -> `search_signals({ campaignOfferId,
|
|
217
|
+
...approved recipe })` -> `select_promising_posts({ campaignOfferId,
|
|
218
|
+
selectionMode: "replace", selections, headlineICPCriteria })` ->
|
|
219
|
+
`import_leads({ campaignOfferId, provider: "signal-discovery",
|
|
220
|
+
targetLeadCount: importLimit })`.
|
|
221
|
+
- Sales Nav: `get_provider_prompt({ provider: "sales-nav", campaignOfferId,
|
|
222
|
+
confirmed: true })` -> rebuild/verify filter IDs -> `search_sales_nav({
|
|
223
|
+
campaignOfferId, filters, confirmed: true })` -> `import_leads({
|
|
224
|
+
campaignOfferId, provider: "sales-nav", searchId, targetLeadCount:
|
|
225
|
+
importLimit })`.
|
|
226
|
+
- Prospeo: `get_provider_prompt({ provider: "prospeo", campaignOfferId,
|
|
227
|
+
confirmed: true })` -> reuse `domainFilterId` when present ->
|
|
228
|
+
`search_prospeo({ campaignOfferId, filters, domainFilterId, confirmed:
|
|
229
|
+
true })` -> `import_leads({ campaignOfferId, provider: "prospeo",
|
|
230
|
+
searchId, targetLeadCount: importLimit })`.
|
|
231
|
+
3. `wait_for_lead_list_ready` when a provider import job exists, then
|
|
232
|
+
`confirm_lead_list`. Persist both identifiers: `selectedLeadListId` remains
|
|
233
|
+
the source list and `workflowTableId` is the campaign table.
|
|
234
|
+
4. `wait_for_campaign_table_ready` until the bounded review-batch rows are
|
|
235
|
+
available in the campaign table.
|
|
236
|
+
5. Call `get_rows_minimal({ tableId: workflowTableId })` and confirm the first
|
|
237
|
+
review batch is present. Do not queue cells in Step 13.
|
|
238
|
+
6. If the import returns zero usable leads, ESCALATE per
|
|
219
239
|
`references/escalation-ladder.md` (hard fail).
|
|
220
|
-
|
|
221
|
-
|
|
240
|
+
7. `update_campaign({ campaignId, currentStep: "filter-choice" })`.
|
|
241
|
+
8. Orient user on the watch link (same pattern as every other step
|
|
222
242
|
change).
|
|
223
243
|
|
|
224
244
|
**Do NOT call `check_rubric`, `wait_for_rubric_results`,
|
|
225
|
-
`enrich_with_prospeo`, or `bulk_enrich_with_prospeo` in Step 13.**
|
|
245
|
+
`queue_cells`, `enrich_with_prospeo`, or `bulk_enrich_with_prospeo` in Step 13.**
|
|
226
246
|
Those are direct-API tools that fetch enrichment/scoring data to the
|
|
227
|
-
caller
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
triggers the next column, and so on. Running the direct-API tools
|
|
231
|
-
is duplicate work with no cell-population side effect.
|
|
247
|
+
caller or start the workflow-table cascade too early. The cascade starts in
|
|
248
|
+
Step 14 only after `save_rubrics` and `update_campaign_brief` have both
|
|
249
|
+
succeeded.
|
|
232
250
|
|
|
233
251
|
## Step 14: validate-sample (loop)
|
|
234
252
|
|
|
235
253
|
Entered on `CampaignOffer.currentStep === "validate-sample"`. Full
|
|
236
254
|
decision tree lives in `references/sample-validation-loop.md`.
|
|
237
255
|
|
|
238
|
-
**Step 14
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
`
|
|
243
|
-
direct enrichment/scoring tool.
|
|
244
|
-
cascade; running them again is wasted work and can overwrite the
|
|
245
|
-
real cell results with unrelated data.
|
|
256
|
+
**Step 14 starts the bounded cascade, then observes it.** Step 13 imported the
|
|
257
|
+
review batch only. After `save_rubrics` and the approved message template are
|
|
258
|
+
persisted, Step 14 queues the review-batch Enrich Prospect cells, waits for the
|
|
259
|
+
table cascade to drain, then reads the resulting ICP scores and Passes Rubric
|
|
260
|
+
values. It does NOT call `check_rubric`, `bulk_enrich_with_prospeo`, or any
|
|
261
|
+
other direct enrichment/scoring tool.
|
|
246
262
|
|
|
247
263
|
Shape:
|
|
248
264
|
|
|
249
265
|
```text
|
|
250
|
-
|
|
266
|
+
queue_cells({ tableId: workflowTableId, cellIds: reviewBatchEnrichCellIds })
|
|
267
|
+
wait_for_campaign_table_ready({ tableId: workflowTableId })
|
|
268
|
+
get_rows_minimal({ tableId: workflowTableId })
|
|
251
269
|
passInSample = count of first sampleSize review-batch rows with passesRubric === true
|
|
252
270
|
projectedPass = round(passInSample / sampleSize * importLimit)
|
|
253
271
|
|
|
@@ -376,13 +394,20 @@ Full contract lives in `references/final-handoff-contract.md`.
|
|
|
376
394
|
|
|
377
395
|
Shape:
|
|
378
396
|
|
|
379
|
-
1. `
|
|
380
|
-
2.
|
|
381
|
-
|
|
397
|
+
1. Call `get_campaign({ campaignId })` and inspect sender/sequence state.
|
|
398
|
+
2. Call `list_senders()` so you can surface connected sender options if the
|
|
399
|
+
campaign has no attached sender.
|
|
400
|
+
3. If the campaign has no attached sender, surface a direct settings link:
|
|
401
|
+
`/campaign-builder/{campaignId}/settings?mode=claude`. Tell the user to attach
|
|
402
|
+
a sender in Settings, then return here. STOP before sequence attach/start.
|
|
403
|
+
4. If a sender is attached, call `attach_recommended_sequence({ campaignId })`
|
|
404
|
+
(bind the tier-recommended sequence to the campaign).
|
|
405
|
+
5. Re-surface the `watchUrl` using `references/watch-link-handoff.md`.
|
|
406
|
+
6. Surface the `handoff.orientation` string from `auto-execute.yaml`
|
|
382
407
|
alongside the link.
|
|
383
|
-
|
|
408
|
+
7. STOP. Do NOT call `start_campaign`. Do NOT auto-advance
|
|
384
409
|
`currentStep`. The autonomous tail ends here.
|
|
385
|
-
|
|
410
|
+
8. Make the credit boundary explicit in the handoff: only the first review
|
|
386
411
|
batch has been enriched/messaged; expanding to more leads requires a
|
|
387
412
|
separate user instruction.
|
|
388
413
|
|
|
@@ -394,10 +419,12 @@ campaign:
|
|
|
394
419
|
the running state and shifts to a "campaign is live" confirmation.
|
|
395
420
|
- **Claude greenlight path** — user replies with an affirmative ("yeah
|
|
396
421
|
start" / "looks good, start" / "ship it"). The skill then performs,
|
|
397
|
-
in order: (a)
|
|
422
|
+
in order: (a) `get_campaign` and verify a sender is attached, (b) if no
|
|
423
|
+
sequence is attached, `attach_recommended_sequence({ campaignId })`, (c)
|
|
424
|
+
bulk-approve queued messages via the EXISTING endpoint
|
|
398
425
|
`POST /api/v3/workflow-tables/cells/approve-batch` (do NOT invent a
|
|
399
|
-
new tool/endpoint), (
|
|
400
|
-
`update_campaign({ campaignId, currentStep: "running" })`, (
|
|
426
|
+
new tool/endpoint), (d) `start_campaign({ campaignId })`, (e)
|
|
427
|
+
`update_campaign({ campaignId, currentStep: "running" })`, (f)
|
|
401
428
|
re-surface `watchUrl` plus a "campaign is live" confirmation.
|
|
402
429
|
|
|
403
430
|
A Claude greenlight on an already-running campaign is a no-op
|
|
@@ -418,6 +445,9 @@ runs.
|
|
|
418
445
|
## Tail Hard Rules
|
|
419
446
|
|
|
420
447
|
- Review-batch sourcing/import happens in Step 13, not during atomic mint.
|
|
448
|
+
- Step 13 materializes/reuses the approved source with `campaignOfferId` before
|
|
449
|
+
import. Do not import a legacy campaignless Signal source until selected posts
|
|
450
|
+
exist on the campaign.
|
|
421
451
|
- Full-list expansion is not part of the default tail. It requires user
|
|
422
452
|
approval after the review batch.
|
|
423
453
|
Import must never exceed `importLimit`.
|
|
@@ -434,18 +464,19 @@ runs.
|
|
|
434
464
|
|
|
435
465
|
<references_index>
|
|
436
466
|
|
|
437
|
-
| File
|
|
438
|
-
|
|
|
439
|
-
| `references/approval-gate-framing.md`
|
|
440
|
-
| `references/watch-link-handoff.md`
|
|
441
|
-
| `references/
|
|
442
|
-
| `references/
|
|
443
|
-
| `references/
|
|
444
|
-
| `references/
|
|
445
|
-
| `references/
|
|
446
|
-
| `references/thomas-
|
|
447
|
-
| `
|
|
448
|
-
| `core/auto-execute.
|
|
467
|
+
| File | Load when |
|
|
468
|
+
| ------------------------------------------------ | ------------------------------------------------------------------------- |
|
|
469
|
+
| `references/approval-gate-framing.md` | Phase 85 commit gate, before showing the approval packet |
|
|
470
|
+
| `references/watch-link-handoff.md` | Atomic mint + every tail resume, before surfacing watchUrl |
|
|
471
|
+
| `references/post-mint-source-materialization.md` | Step 13, before importing normal-discovery or legacy campaignless sources |
|
|
472
|
+
| `references/sample-validation-loop.md` | Step 14, before enriching + scoring the sample |
|
|
473
|
+
| `references/escalation-ladder.md` | Any tail step that needs to decide retry / revise / escalate |
|
|
474
|
+
| `references/final-handoff-contract.md` | Step 16, and every Claude greenlight turn |
|
|
475
|
+
| `references/parallel-critique-protocol.md` | Step 15 when `messaging.critique.enabled` is true |
|
|
476
|
+
| `references/thomas-variant-selection.md` | Step 15 when `messaging.critique.opus.enabled` is true |
|
|
477
|
+
| `references/thomas-revision-filters.md` | Any critique rewrite, before persisting |
|
|
478
|
+
| `core/auto-execute.yaml` | Start of Step 13, load once; all tail steps read parsed values |
|
|
479
|
+
| `core/auto-execute.README.md` | When tuning `auto-execute.yaml` knobs or reviewing threshold-trip logs |
|
|
449
480
|
|
|
450
481
|
Load every file in this table with:
|
|
451
482
|
|