@sellable/mcp 0.1.0

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 (200) hide show
  1. package/.claude-plugin/plugin.json +12 -0
  2. package/.mcp.json +9 -0
  3. package/README.md +355 -0
  4. package/dist/api.d.ts +21 -0
  5. package/dist/api.js +73 -0
  6. package/dist/auth.d.ts +60 -0
  7. package/dist/auth.js +246 -0
  8. package/dist/engage-memory.d.ts +63 -0
  9. package/dist/engage-memory.js +354 -0
  10. package/dist/index-dev.d.ts +2 -0
  11. package/dist/index-dev.js +17 -0
  12. package/dist/index.d.ts +7 -0
  13. package/dist/index.js +8 -0
  14. package/dist/server.d.ts +1 -0
  15. package/dist/server.js +499 -0
  16. package/dist/skills.d.ts +11 -0
  17. package/dist/skills.js +97 -0
  18. package/dist/tools/auth.d.ts +30 -0
  19. package/dist/tools/auth.js +124 -0
  20. package/dist/tools/blueprint-commit.d.ts +174 -0
  21. package/dist/tools/blueprint-commit.js +286 -0
  22. package/dist/tools/bootstrap.d.ts +64 -0
  23. package/dist/tools/bootstrap.js +246 -0
  24. package/dist/tools/campaigns.d.ts +589 -0
  25. package/dist/tools/campaigns.js +892 -0
  26. package/dist/tools/cells.d.ts +58 -0
  27. package/dist/tools/cells.js +48 -0
  28. package/dist/tools/context.d.ts +88 -0
  29. package/dist/tools/context.js +271 -0
  30. package/dist/tools/csv-domains.d.ts +73 -0
  31. package/dist/tools/csv-domains.js +464 -0
  32. package/dist/tools/csv-linkedin.d.ts +102 -0
  33. package/dist/tools/csv-linkedin.js +712 -0
  34. package/dist/tools/direct-campaigns.d.ts +240 -0
  35. package/dist/tools/direct-campaigns.js +250 -0
  36. package/dist/tools/engage-bootstrap.d.ts +94 -0
  37. package/dist/tools/engage-bootstrap.js +205 -0
  38. package/dist/tools/engage-discovery.d.ts +78 -0
  39. package/dist/tools/engage-discovery.js +150 -0
  40. package/dist/tools/engage-memory.d.ts +181 -0
  41. package/dist/tools/engage-memory.js +143 -0
  42. package/dist/tools/engage-state.d.ts +72 -0
  43. package/dist/tools/engage-state.js +62 -0
  44. package/dist/tools/enrichment.d.ts +167 -0
  45. package/dist/tools/enrichment.js +174 -0
  46. package/dist/tools/flow-preflight.d.ts +68 -0
  47. package/dist/tools/flow-preflight.js +138 -0
  48. package/dist/tools/framework.d.ts +44 -0
  49. package/dist/tools/framework.js +153 -0
  50. package/dist/tools/interaction-mode.d.ts +27 -0
  51. package/dist/tools/interaction-mode.js +102 -0
  52. package/dist/tools/leads.d.ts +2417 -0
  53. package/dist/tools/leads.js +2307 -0
  54. package/dist/tools/linkedin.d.ts +210 -0
  55. package/dist/tools/linkedin.js +229 -0
  56. package/dist/tools/navigation.d.ts +91 -0
  57. package/dist/tools/navigation.js +381 -0
  58. package/dist/tools/one-off.d.ts +229 -0
  59. package/dist/tools/one-off.js +273 -0
  60. package/dist/tools/processing.d.ts +70 -0
  61. package/dist/tools/processing.js +56 -0
  62. package/dist/tools/prompts.d.ts +211 -0
  63. package/dist/tools/prompts.js +210 -0
  64. package/dist/tools/provider-preflight.d.ts +21 -0
  65. package/dist/tools/provider-preflight.js +59 -0
  66. package/dist/tools/readiness.d.ts +261 -0
  67. package/dist/tools/readiness.js +510 -0
  68. package/dist/tools/rows.d.ts +126 -0
  69. package/dist/tools/rows.js +105 -0
  70. package/dist/tools/rubrics.d.ts +497 -0
  71. package/dist/tools/rubrics.js +681 -0
  72. package/dist/tools/senders.d.ts +44 -0
  73. package/dist/tools/senders.js +69 -0
  74. package/dist/tools/sequencer.d.ts +127 -0
  75. package/dist/tools/sequencer.js +194 -0
  76. package/dist/tools/tables.d.ts +35 -0
  77. package/dist/tools/tables.js +36 -0
  78. package/dist/tools/verify-row.d.ts +36 -0
  79. package/dist/tools/verify-row.js +38 -0
  80. package/dist/tools/workspaces.d.ts +140 -0
  81. package/dist/tools/workspaces.js +139 -0
  82. package/dist/utils/workspace-root.d.ts +1 -0
  83. package/dist/utils/workspace-root.js +39 -0
  84. package/package.json +46 -0
  85. package/skills/building-gtm-tables/SKILL.md +216 -0
  86. package/skills/building-gtm-tables/core/auto-execute.yaml +19 -0
  87. package/skills/building-gtm-tables/core/blueprint-schema.json +72 -0
  88. package/skills/building-gtm-tables/references/brief-to-blueprint.md +334 -0
  89. package/skills/building-gtm-tables/references/column-type-catalog.md +318 -0
  90. package/skills/building-gtm-tables/references/common-blueprints.fixtures.ts +199 -0
  91. package/skills/building-gtm-tables/references/common-blueprints.md +44 -0
  92. package/skills/building-gtm-tables/references/failure-taxonomy.md +197 -0
  93. package/skills/building-gtm-tables/references/uat-seed-prompts.md +37 -0
  94. package/skills/building-gtm-tables/references/verify-loop.md +74 -0
  95. package/skills/campaign-messages/SKILL.md +173 -0
  96. package/skills/campaign-messages/flow.v1.json +75 -0
  97. package/skills/craft-message/SKILL.md +401 -0
  98. package/skills/create-campaign/ARCHITECTURE.md +232 -0
  99. package/skills/create-campaign/DISCUSS.md +296 -0
  100. package/skills/create-campaign/FLOW_ASCII.md +240 -0
  101. package/skills/create-campaign/HOST-PARITY-CHECKLIST.md +49 -0
  102. package/skills/create-campaign/README.md +142 -0
  103. package/skills/create-campaign/SKILL.md +286 -0
  104. package/skills/create-campaign/context/README.md +67 -0
  105. package/skills/create-campaign/context/_TEMPLATE.md +12 -0
  106. package/skills/create-campaign/context/context.md +35 -0
  107. package/skills/create-campaign/context/learnings.md +16 -0
  108. package/skills/create-campaign/context/registry.json +19 -0
  109. package/skills/create-campaign/core/flow.v1.json +217 -0
  110. package/skills/create-campaign/core/policy.md +191 -0
  111. package/skills/create-campaign/core/providers/apollo.json +35 -0
  112. package/skills/create-campaign/core/providers/prospeo.json +34 -0
  113. package/skills/create-campaign/core/providers/registry.json +31 -0
  114. package/skills/create-campaign/core/providers/sales-nav.json +37 -0
  115. package/skills/create-campaign/core/providers/signal-discovery.json +42 -0
  116. package/skills/create-campaign/references/brief-template.md +64 -0
  117. package/skills/create-campaign/references/campaign-quality.md +84 -0
  118. package/skills/create-campaign/references/copy-calibration-examples.md +120 -0
  119. package/skills/create-campaign/references/offer-patterns.md +108 -0
  120. package/skills/create-campaign/references/provider-selection-strategy.md +212 -0
  121. package/skills/create-campaign/references/question-examples.md +167 -0
  122. package/skills/create-campaign/references/token-fill-examples.md +81 -0
  123. package/skills/create-campaign-brief/ARCHITECTURE.md +72 -0
  124. package/skills/create-campaign-brief/DISCUSS.md +64 -0
  125. package/skills/create-campaign-brief/README.md +176 -0
  126. package/skills/create-campaign-brief/SKILL.md +537 -0
  127. package/skills/create-campaign-brief/references/brief-synthesis-rules.md +100 -0
  128. package/skills/create-campaign-brief/references/brief-template.md +220 -0
  129. package/skills/create-campaign-brief/references/campaign-idea-options.md +30 -0
  130. package/skills/create-campaign-brief/references/copy-appendix-template.md +62 -0
  131. package/skills/create-campaign-brief/references/draft-lifecycle.md +23 -0
  132. package/skills/create-campaign-brief/references/examples/MANIFEST.json +89 -0
  133. package/skills/create-campaign-brief/references/examples/briefs/clover.md +223 -0
  134. package/skills/create-campaign-brief/references/examples/briefs/galley.md +222 -0
  135. package/skills/create-campaign-brief/references/examples/briefs/gelee.md +220 -0
  136. package/skills/create-campaign-brief/references/examples/briefs/hey-digital.md +234 -0
  137. package/skills/create-campaign-brief/references/examples/briefs/persona.md +231 -0
  138. package/skills/create-campaign-brief/references/examples/briefs/revvix.md +220 -0
  139. package/skills/create-campaign-brief/references/examples/briefs/sellable-dev.md +220 -0
  140. package/skills/create-campaign-brief/references/examples/briefs/superposition.md +233 -0
  141. package/skills/create-campaign-brief/references/examples/briefs/superpower.md +219 -0
  142. package/skills/create-campaign-brief/references/examples/briefs/westpark-villas.md +220 -0
  143. package/skills/create-campaign-brief/references/icp-lock-question-bank.md +43 -0
  144. package/skills/create-campaign-brief/references/messaging-inputs.md +58 -0
  145. package/skills/create-campaign-brief/references/output-acceptance-rubric.md +62 -0
  146. package/skills/create-campaign-brief/references/phase75-active-runtime-message-pack.md +248 -0
  147. package/skills/create-campaign-brief/references/phase75-canonical-brief-template.md +319 -0
  148. package/skills/create-campaign-brief/references/phase75-good-brief-and-messaging-examples.md +445 -0
  149. package/skills/create-campaign-brief/references/quick-research-protocol.md +39 -0
  150. package/skills/create-campaign-brief/references/reference-sheet-protocol.md +60 -0
  151. package/skills/create-campaign-brief/references/zero-shot-iteration-rules.md +66 -0
  152. package/skills/create-campaign-v2/SKILL.md +1619 -0
  153. package/skills/create-campaign-v2/core/auto-execute.README.md +219 -0
  154. package/skills/create-campaign-v2/core/auto-execute.yaml +121 -0
  155. package/skills/create-campaign-v2/core/flow.v2.json +1643 -0
  156. package/skills/create-campaign-v2/core/policy.md +82 -0
  157. package/skills/create-campaign-v2/references/ai-tells.md +253 -0
  158. package/skills/create-campaign-v2/references/approval-gate-framing.md +346 -0
  159. package/skills/create-campaign-v2/references/draft-lifecycle.md +110 -0
  160. package/skills/create-campaign-v2/references/escalation-ladder.md +119 -0
  161. package/skills/create-campaign-v2/references/filter-leads.md +495 -0
  162. package/skills/create-campaign-v2/references/final-handoff-contract.md +176 -0
  163. package/skills/create-campaign-v2/references/gold-standard-message-examples.md +394 -0
  164. package/skills/create-campaign-v2/references/gold-standard-message-patterns.md +314 -0
  165. package/skills/create-campaign-v2/references/gold-standard-message-validation-example.md +212 -0
  166. package/skills/create-campaign-v2/references/lead-validation-preview.md +172 -0
  167. package/skills/create-campaign-v2/references/parallel-critique-protocol.md +368 -0
  168. package/skills/create-campaign-v2/references/sample-validation-loop.md +289 -0
  169. package/skills/create-campaign-v2/references/step-13-import-leads.md +151 -0
  170. package/skills/create-campaign-v2/references/step-15-re-cascade.md +90 -0
  171. package/skills/create-campaign-v2/references/thomas-revision-filters.md +521 -0
  172. package/skills/create-campaign-v2/references/thomas-variant-selection.md +202 -0
  173. package/skills/create-campaign-v2/references/tier-routing-matrix.md +66 -0
  174. package/skills/create-campaign-v2/references/validation-criteria.md +367 -0
  175. package/skills/create-campaign-v2/references/watch-link-handoff.md +106 -0
  176. package/skills/create-campaign-v2-validation/SKILL.md +296 -0
  177. package/skills/create-post/SKILL.md +1308 -0
  178. package/skills/create-rubric/SKILL.md +251 -0
  179. package/skills/engage/SKILL.md +549 -0
  180. package/skills/engage/core/README.md +23 -0
  181. package/skills/engage/core/proven-searches.json +11 -0
  182. package/skills/engage/core/style-guide.template.md +47 -0
  183. package/skills/engage/core/tracked-people.json +10 -0
  184. package/skills/enrich-prospects/SKILL.md +97 -0
  185. package/skills/find-leads/SKILL.md +467 -0
  186. package/skills/generate-messages/SKILL.md +2361 -0
  187. package/skills/interview/SKILL.md +132 -0
  188. package/skills/interview/core/ENGAGE_STYLE_GUIDE.template.md +54 -0
  189. package/skills/interview/core/ICP.template.md +54 -0
  190. package/skills/interview/core/VOICE_PROFILE.template.md +101 -0
  191. package/skills/providers/apollo.md +520 -0
  192. package/skills/providers/prospeo.md +398 -0
  193. package/skills/providers/sales-nav.md +372 -0
  194. package/skills/providers/signal-discovery.md +495 -0
  195. package/skills/research/SKILL.md +258 -0
  196. package/skills/research/config.json +9 -0
  197. package/skills/research/override.md +13 -0
  198. package/skills/research-prospect/SKILL.md +99 -0
  199. package/skills/research-sender/SKILL.md +158 -0
  200. package/skills/workflow-sequences/SKILL.md +85 -0
@@ -0,0 +1,1619 @@
1
+ ---
2
+ name: create-campaign-v2
3
+ description: Execute the JSON-gated from-scratch campaign flow: create the brief, validate chained Phase 84 artifacts, then atomically mint a live campaign after explicit user approval at the commit gate.
4
+ visibility: internal
5
+ ---
6
+
7
+ # Create Campaign v2
8
+
9
+ <role>
10
+ You are the create-campaign-v2 orchestrator. Your job is to execute the
11
+ configured `core/flow.v2.json` state machine from scratch: (1) interview the
12
+ user and create `brief-v1`, (2) validate the chained Phase 84 draft artifacts,
13
+ (3) run a message-review gate, show the user an approval packet and commit
14
+ gate, and (4) if the user
15
+ approves, atomically mint a real `CampaignOffer` and hand off to the bounded
16
+ review-batch tail (Plan 85-02).
17
+ </role>
18
+
19
+ <objective>
20
+ Run the configured JSON flow in durable stages:
21
+
22
+ 0. create-campaign-brief
23
+ 1. find leads
24
+ 2. filter leads
25
+ 3. generate message
26
+ 4. message review gate
27
+ 5. approval packet + commit gate
28
+ 6. atomic mint
29
+ 7. review-batch tail to `awaiting-user-greenlight`
30
+
31
+ The JSON flow is the source of truth for stage order, `requiredArtifacts`,
32
+ `producesArtifacts`, `allowedTools`, `doNotAllow`, `waitFor`, and
33
+ `transitions`. This prompt explains how to execute those gates; it does not
34
+ replace them.
35
+
36
+ Each stage writes its own artifact into the draft directory before the next
37
+ stage starts. `brief-v1.md` is the customer-facing brief output; `brief.md` is
38
+ the canonical downstream campaign thesis input.
39
+
40
+ After validation artifacts are complete, show the user a commit gate with 6
41
+ choices (approve / revise-brief / revise-leads / revise-rubric /
42
+ revise-messaging / abort). Only `approve` triggers DB mutation. On `approve`,
43
+ atomically mint the campaign (`create_campaign` + `save_rubrics`), capture the
44
+ `watchUrl` from the `create_campaign` response, and surface it to the user only
45
+ after both tool calls succeed.
46
+ </objective>
47
+
48
+ <files>
49
+
50
+ Validated draft directory:
51
+
52
+ ```text
53
+ .sellable/create-campaign-v2/drafts/{workspace-slug}/{campaign-slug}/
54
+ brief-v1.md
55
+ brief.md
56
+ lead-review.md
57
+ lead-sample.json
58
+ lead-filter.md
59
+ message-prep.md # optional speed artifact
60
+ message-candidate-drafts.md # optional provisional message artifact
61
+ message-validation.md
62
+ message-review.md
63
+ message-review-decision.md
64
+ rubric.json # optional implementation artifact only
65
+ approval-packet.md
66
+ customer-roleplay.md
67
+ commit-gate-decision.md
68
+ ```
69
+
70
+ </files>
71
+
72
+ <rules>
73
+
74
+ - Net-new runs start at `bootstrap` -> `brief-interview` in
75
+ `core/flow.v2.json`. Do not start at `validate-artifacts` unless resuming a
76
+ compatibility run where all upstream artifacts already exist.
77
+ - Fast Intake Mode is mandatory for hosted/rehearsal net-new runs. Ask the
78
+ first founder strategy/source question packet in under 60 seconds. Before
79
+ that first packet, the first assistant turn may only call
80
+ `bootstrap_create_campaign`, load this workflow prompt with
81
+ `get_subskill_prompt({ subskillName: "create-campaign-v2" })`, optionally run
82
+ one lightweight identity lookup, then call `AskUserQuestion`. If the user
83
+ supplied a company website/domain, call exactly one of `fetch_company`,
84
+ `WebFetch`, or `WebSearch` to identify what the company actually does before
85
+ generating intake options. If the user supplied a LinkedIn profile URL, call
86
+ `fetch_linkedin_profile` before generating intake options. Do not infer the
87
+ product category from the company name alone. If no domain, website, or
88
+ LinkedIn profile is supplied, use neutral/custom intake options rather than
89
+ guessed vertical-specific options. Before the first AskUserQuestion, do not run
90
+ sender research, `list_senders`, source discovery, Sales Nav, Prospeo,
91
+ Signals, Bash, Read, Write, Edit, Glob, Grep, full company research, or
92
+ draft-directory inspection/creation. Do draft-directory setup only after the
93
+ founder answers. The first intake packet must ask buyer, offer/CTA, proof, and
94
+ lead source, and all four questions must include an `Other / custom` option.
95
+ - After the founder answers the first strategy/source packet, explain the next
96
+ stage only: campaign brief creation and brief approval. Use this shape:
97
+
98
+ ```text
99
+ Got it. I'll turn this into a campaign brief first (~1-2 min), then show it to you so you can approve it or tell me what to change before I source leads.
100
+ ```
101
+
102
+ Do not mention internal artifact names in this preamble.
103
+
104
+ - After rendering the brief summary, ask for brief approval when there is a real
105
+ strategic choice or the user has not already made the direction obvious. The
106
+ user-facing choice should be approve/revise language, not "looks good".
107
+ - After the brief is approved or auto-confirmed, show the next progress line:
108
+ `Cool. Now I'm going to find people who are both a good fit and likely to
109
+ reply on LinkedIn. I'll compare source paths by expected volume, likely
110
+ connection acceptance rate, likely reply rate, signal quality, and tradeoffs.
111
+ This usually takes ~3-5 min, and I'll show you the source decision + sample
112
+ before anything goes live.`
113
+ - After the lead sample/source decision is ready and approved or auto-confirmed,
114
+ show the next progress line:
115
+ `Lead source is set. Now I'll do two things from this sample: apply the fit
116
+ filter to clean the list down to great fits, and draft the first message we
117
+ should test for this campaign. Those can run in parallel and usually take
118
+ ~2-3 min.`
119
+ - During long post-intake work, show concise progress checkpoints before the
120
+ next expensive stage: source being checked, source switch/tradeoff if any,
121
+ lead sample usable, filter/message drafting, and full message prompt loading.
122
+ Each checkpoint should include a rough remaining time when useful.
123
+ - In hosted/rehearsal runs, `Bash` is only for safe local draft-directory
124
+ housekeeping before approval: `mkdir`, `ls`, `find`, `test`, `pwd`, `echo`,
125
+ `cat`, or copying `brief-v1.md` to `brief.md` inside the repo. Do not use
126
+ Bash to run interpreters/scripts (`python`, `node`, `npm`, `pnpm`, `yarn`,
127
+ `npx`), call APIs, query databases, inspect prompt dumps, run git, or
128
+ synthesize artifact content. Use `Read`/`Write`/`Edit` for artifact files and
129
+ MCP tools for product actions.
130
+ - `brief-v1.md` is the user-facing brief. `brief.md` is the stable downstream
131
+ input and remains the thesis source.
132
+ - `lead-review.md` and `lead-sample.json` are the required outputs of `find leads`.
133
+ - `lead-filter.md` is the primary output of `filter leads`.
134
+ - `rubric.json` is optional and secondary to `lead-filter.md`.
135
+ - `message-prep.md` is an optional speed artifact produced by the explicit
136
+ message path after find-leads. It can prepare proof inventory, buyer
137
+ objections, CTA options, gold-standard strategy maps, and candidate angles
138
+ from `brief.md`, `lead-review.md`, and `lead-sample.json`.
139
+ - `message-candidate-drafts.md` is an optional provisional speed artifact from
140
+ sample rows that already look like probable good fits. It can contain rough
141
+ candidate messages and element tests, but it cannot select the final winner
142
+ and cannot override `lead-filter.md`.
143
+ - `message-validation.md` is the normal output of `generate message`.
144
+ - `message-review.md` and `message-review-decision.md` are the mandatory
145
+ message quality gate outputs between `message-validation.md` and
146
+ `approval-packet.md`.
147
+ - Run the dependency chain as a DAG: `create-campaign-brief` -> `find leads`;
148
+ once `lead-review.md` and `lead-sample.json` exist, run `filter leads` and
149
+ the explicit `generate message` validation as parallel branches from the same
150
+ basis (`brief.md`, `lead-review.md`, `lead-sample.json`). Approval waits for
151
+ both `lead-filter.md` and `message-validation.md`, then reconciles that the
152
+ selected message basis rows still pass the final filter.
153
+ - Never run a downstream stage until the active `flow.v2.json` step's
154
+ `requiredArtifacts` exist.
155
+ - Never call a tool outside the active step's `allowedTools`, and never call a
156
+ tool listed in the active step's `doNotAllow`.
157
+ - Before the user chooses `approve`, the mutating live-campaign tools are
158
+ forbidden: `create_campaign`, `save_rubrics`, `import_leads`,
159
+ `confirm_lead_list`, `update_campaign`, `queue_cells`, and `start_campaign`.
160
+ - Resume state is based on the presence and completeness of the chained artifacts,
161
+ not on inline validation blocks inside `brief.md`.
162
+ - Preserve the Phase 83 thesis. Do not rewrite product, ICP, offer, or message
163
+ hypothesis sections during preview validation.
164
+ - Exception: brief-validated may rewrite §3 Campaign Thesis AND §12 Next Steps
165
+ when Phase-84 lead-yield data clearly contradicts a Phase-83 UNVALIDATED
166
+ decision on primary lead-source. "Clearly contradicts" = the Phase-83
167
+ primary source yields 0 FIT while an alternative source yields ≥ 5 FIT in
168
+ the same ~25-row sample (or an equivalent ≥ 5× ratio at larger samples).
169
+ Every rewrite MUST cite specific yield numbers from `lead-review.md`. All
170
+ other Phase-83 brief sections remain read-only. See
171
+ `references/filter-leads.md` § Brief-Validated Rewrite Authority for the
172
+ full evidence shape.
173
+ - Every artifact write uses `tmp + rename` so a failed write leaves the prior
174
+ artifact intact.
175
+ - If a required upstream artifact is missing, stop and route back to the
176
+ missing step instead of guessing.
177
+ - Before asking for commit approval, persist a customer-facing
178
+ `message-review.md` and `message-review-decision.md` so the message
179
+ approval decision can be inspected later.
180
+ - Before asking for commit approval, persist a customer-facing
181
+ `approval-packet.md` so the exact packet can be inspected later.
182
+ - Persist `customer-roleplay.md` when a customer/operator roleplay critique is
183
+ run. This critique can recommend a decision, but it can never authorize mint.
184
+ - Persist `commit-gate-decision.md` with exactly one of the six gate choices.
185
+ Only an explicit `approve` value in this decision artifact authorizes DB
186
+ mutation.
187
+
188
+ </rules>
189
+
190
+ <conditional_gates>
191
+
192
+ There are two human gates:
193
+
194
+ - `message-review` is the mandatory pre-packet message quality gate. It asks
195
+ only whether to proceed with the selected message (`approve-message`) or run
196
+ one more messaging revision (`revise-messaging`). It does not authorize DB
197
+ mutation.
198
+ - The commit gate is the only mandatory user approval before DB mutation.
199
+ Earlier artifact gates are interrupt-only unless this contract explicitly
200
+ names the `message-review` gate.
201
+
202
+ Auto-continue without `AskUserQuestion` when the artifact says `Status:
203
+ confirmed` (or equivalent), confidence is high/medium, volume is viable, and
204
+ there is no strategic tradeoff.
205
+
206
+ Ask the user only when one of these is true:
207
+
208
+ - `Status` is rejected, unclear, revise-find-leads, revise-filter, or
209
+ confirm-with-user.
210
+ - Projected usable leads are below the campaign floor for the market, or the
211
+ sample pass rate suggests heavy filtering will make the campaign miss.
212
+ - The source path contradicts the brief, removes the buyer segment that made
213
+ the campaign compelling, or proves the provider is matching titles but not
214
+ actual buyers.
215
+ - Filter rules are not production-rubric-translatable from row data, enrichment,
216
+ or normal public research.
217
+ - Message validation has a blocking skeptical-prospect issue, no selected
218
+ winner, weak token plan, or a recommendation to revise. Even when it is
219
+ confirmed, render the `message-review` gate before creating the approval
220
+ packet so the founder can approve or revise the message deliberately.
221
+
222
+ When asking subjective strategy questions (buyer scope, first ask, proof
223
+ emphasis, tone, lead-source preference), always make it clear the user can give
224
+ a custom answer. Add an explicit `Other / custom` option to each subjective
225
+ question. Do not rely on prose like "you can add detail" as the only custom
226
+ path. Do not add custom options to the final six-choice commit gate.
227
+ Use customer-facing question wording:
228
+
229
+ - target prospects: `Who should be the target prospects for this campaign? Pick one or combine.`
230
+ - main CTA / offer: `What should the main CTA or offer be? Pick one or combine.`
231
+ - proof emphasis: `Which proof point would most increase this buyer's confidence in {{company}}? Multi-select fine.`
232
+ - lead source: `How should we get the people for this campaign?`
233
+
234
+ Ask the lead-source question as the last question in the first strategy
235
+ batch, after buyer, offer/ask, and proof/safety are understood. Frame supplied
236
+ lists as optional, not required. The three visible options are exactly:
237
+
238
+ 1. `Find people for me (recommended if you don't already have your own list)`
239
+ 2. `I have a CSV of LinkedIn profiles`
240
+ 3. `I have a CSV of company domains`
241
+
242
+ Keep `Other / custom` available for freeform answers such as a pasted list,
243
+ an existing Sellable lead list, or another source idea. Do not put existing
244
+ Sellable lead lists in the main three-option first batch; support them through
245
+ custom/freeform input. If the user pastes up to 100 LinkedIn profile URLs or
246
+ company domains, normalize the paste into a temporary local CSV and continue
247
+ through the matching CSV preview path. Mixed, ambiguous, malformed, or oversized
248
+ pastes should ask for a real CSV file instead of guessing.
249
+
250
+ Avoid internal wording like `Which proof points should the message be allowed
251
+ to lean on?` because it describes the artifact, not the founder decision.
252
+
253
+ When auto-continuing, show one concise progress line and immediately continue.
254
+ Do not create a review question whose only useful answer is "looks good".
255
+
256
+ </conditional_gates>
257
+
258
+ <step_contracts>
259
+
260
+ ## Step 1: Find Leads
261
+
262
+ Use existing `find-leads` campaignless preview behavior to validate ICP and
263
+ volume with a real sample.
264
+
265
+ Write:
266
+
267
+ - `lead-review.md`
268
+ - `lead-sample.json`
269
+ - optional `lead-source-intake.json` when the user intentionally supplied a
270
+ source
271
+
272
+ Required behavior:
273
+
274
+ - use campaignless preview mode
275
+ - do not pass `campaignOfferId` (Step 1 only — this is the one place
276
+ where campaignless is the right choice; every post-mint provider
277
+ search — Prospeo, Sales Nav, Apollo, Signal Discovery, or any
278
+ future provider — MUST include `campaignOfferId` so the search
279
+ persists to the campaign and shows up in the UI's Contact Search
280
+ tab. Omitting `campaignOfferId` after mint orphans the search.)
281
+ - do not import leads
282
+ - do not set `selectedLeadListId`
283
+ - do not create lead-list rows
284
+ - do not mutate DB-backed campaign state
285
+ - run a parallel source scout when the source is not supplied, but choose the
286
+ lanes from the sourcing approach rather than running every provider by
287
+ default. For Signals-first campaigns, search multiple Signals keyword lanes
288
+ and fetch top-post engagers in parallel. For Sales Nav-first or
289
+ Prospeo/account-first campaigns, run multiple preview lanes for that provider
290
+ in parallel and use Signals only as a warmth/quality check when relevant
291
+ - for Signals-first campaigns, raw post search volume is only inventory, not
292
+ lead volume. `492 post results` means matching LinkedIn posts found across
293
+ keyword lanes; it does not mean 492 prospects. The source decision must name
294
+ the actual posts we would use, show why they won, and estimate usable engagers
295
+ from those posts after headline/sample filtering
296
+ - Signals source decisions should prefer fresh posts. Default to posts from the
297
+ last 30 days, prefer the last 7-14 days when quality is comparable, and call
298
+ out any older post as a deliberate tradeoff. Do not hide post age inside the
299
+ raw search count
300
+ - default source quality target is 300-500+ likely usable leads; accept
301
+ 150-300 only for an explicit beta/design-partner motion and name that
302
+ volume tradeoff in `lead-review.md` and the approval packet
303
+ - if `lead-source-intake.json` is present, read `sourceType`,
304
+ `sourceInputMode`, file path or existing lead-list ID, selected columns,
305
+ confirmation token, normalized counts, and any preview-created
306
+ `domainFilterId`
307
+ - supplied LinkedIn profile CSVs call `load_csv_linkedin_leads` in preview mode
308
+ only before approval; do not pass `confirmed: true`, `campaignOfferId`,
309
+ `currentStep`, `leadListId`, `sourceLeadListId`, or provider-import params
310
+ - supplied company/domain CSVs call `load_csv_domains`; pre-approval
311
+ confirmation is allowed only without `campaignOfferId` or `currentStep` and
312
+ only to produce a standalone `domainFilterId` for campaignless
313
+ account-constrained people sampling
314
+ - existing Sellable lead lists skip provider discovery and sample from the
315
+ existing rows before approval; do not clone/import the list until after
316
+ approval
317
+ - do not call `save_domain_filters` in the pre-approval create-campaign path
318
+
319
+ `lead-review.md` must state:
320
+
321
+ - validation status: `confirmed`, `rejected`, or `unclear`
322
+ - confidence
323
+ - provider path used
324
+ - supplied source type when applicable (`normal-discovery`,
325
+ `supplied-linkedin-profiles`, `supplied-domains`, or `existing-lead-list`)
326
+ - row/domain counts, invalid counts, duplicate counts, and sample method for
327
+ supplied sources
328
+ - preview count
329
+ - ICP match rate
330
+ - volume comparison
331
+ - expected LinkedIn funnel: likely connection acceptance range, likely reply
332
+ range, and whether the estimate is sample-backed, historical, founder-supplied,
333
+ or directional
334
+ - for Signals-first paths: top candidate posts reviewed, selected post URLs,
335
+ post author, post topic/excerpt, post age or posted date, engagement count,
336
+ sampled engager count per selected post, sampled fit count per selected post,
337
+ estimated usable engagers per selected post, and why each selected post is
338
+ better than discarded posts
339
+ - source decision: best path, why it won, pros, cons/tradeoffs, and discarded
340
+ source paths with the reason each lost
341
+ - repeated false-positive patterns
342
+ - message handoff: 2-5 sample rows that look like strong/probable good fits,
343
+ with row identifiers and the reason each is safe enough for message thinking
344
+ - suggested next action or revision
345
+
346
+ For normal LinkedIn discovery, `lead-review.md` must include these
347
+ customer-visible sections with literal headings:
348
+
349
+ - `## Source Decision`
350
+ - `## Selected Signal Posts` for Signals-first campaigns
351
+ - `## Expected LinkedIn Funnel`
352
+ - `## Sample Leads` for Signals-first campaigns
353
+ - `## Pros`
354
+ - `## Tradeoffs`
355
+ - `## Discarded Paths`
356
+
357
+ For Signals-first campaigns, `## Selected Signal Posts` must include a compact
358
+ table with one row per selected or finalist post:
359
+
360
+ - post URL
361
+ - post author
362
+ - posted date or recency
363
+ - topic/excerpt
364
+ - total engagement or estimated engagers
365
+ - sampled engagers
366
+ - sampled fits
367
+ - estimated usable leads
368
+ - why use / why discard
369
+
370
+ For Signals-first campaigns, `## Sample Leads` must group representative sample
371
+ rows by source post when possible, so the user can see not just that the search
372
+ found posts, but which posts produce believable prospects.
373
+
374
+ `## Expected LinkedIn Funnel` must include expected source volume, expected
375
+ usable leads after filtering, likely connection acceptance range, likely reply
376
+ range, and estimate basis. If exact performance data is unavailable, use a
377
+ directional range and label it `directional`, not definitive.
378
+
379
+ When showing `lead-review.md` to the user, render the customer-visible sections
380
+ inline. Do not compress it to a short summary or artifact links only. The
381
+ visible response must include `## Source Decision`, `## Expected LinkedIn
382
+ Funnel`, `## Pros`, `## Tradeoffs`, and `## Discarded Paths`. For
383
+ Signals-first campaigns it must also include `## Selected Signal Posts` and
384
+ `## Sample Leads`.
385
+
386
+ For supplied profile CSVs and existing lead lists, `lead-review.md` must not
387
+ describe a generic TAM estimate or pretend the rows came from Sales Nav/Prospeo
388
+ discovery. For supplied domain/account lists, explain that domains are account
389
+ constraints and include the actual people sampled from those accounts. If the
390
+ domain-constrained people sample returns zero or too few usable rows, route to
391
+ `revise-leads` / `confirm-with-user`; never silently remove the domain
392
+ constraint.
393
+
394
+ `lead-sample.json` must be a machine-readable sample that downstream filter
395
+ validation can inspect directly.
396
+
397
+ If preview returns zero usable leads, write the rejection evidence, stop the
398
+ chain, and route to `revise-leads`.
399
+
400
+ ## Step 2: Filter Leads
401
+
402
+ Read `brief.md`, `lead-review.md`, and `lead-sample.json`.
403
+
404
+ This branch can run in parallel with Step 3 once `lead-review.md` and
405
+ `lead-sample.json` exist. It owns lead quality and production rubrics only.
406
+
407
+ Write:
408
+
409
+ - `lead-filter.md`
410
+ - optional `rubric.json`
411
+
412
+ Required behavior:
413
+
414
+ - preserve recurring keep/exclude filter families that show up across campaign
415
+ history: buyer role, wrong-function exclusions, company-type exclusions,
416
+ competitor/vendor exclusions, geography, company size, and active-role status
417
+ - use the actual lead sample to identify repeated false positives
418
+ - prefer required keep/exclude rules over a broad scoring stack
419
+ - allow at most one optional/supporting rule when it materially helps later
420
+ messaging or prioritization
421
+ - judge each proposed rule against the sample, report pass rate, and call out
422
+ whether the rule is truly necessary or should be removed
423
+ - make every accepted filter directly translatable into production
424
+ `LeadScoringRubric` rows (`checkName`, `description`, `criterion`, `reason`,
425
+ `isRequiredCheck`, `allowPartialCredit`, `strictMatching`)
426
+ - do not accept a filter that cannot be evaluated from `lead-sample.json`,
427
+ provider row fields, enrichment, or normal public research
428
+ - derive `rubric.json` from the final `lead-filter.md` rules only when a
429
+ machine-readable sidecar is needed downstream
430
+ - continue with `lead-filter.md` as the source of truth when `rubric.json`
431
+ cannot be written or parsed
432
+ - if `rubric.json` is omitted, keep the filter concise enough that a 2-5 item
433
+ production rubric can be compiled from it without inventing new rules
434
+ - write `lead-filter.md` user-facing first: decision, who we keep, who we
435
+ exclude, what the sample showed, pass rate, recommendation
436
+ - include `Implementation Details` inside `lead-filter.md` whenever the status
437
+ is confirmed; this is where production rubric fields belong
438
+ - `Implementation Details` must be a fenced JSON object with
439
+ `leadScoringRubrics` so downstream can parse/save the rules without
440
+ inference
441
+ - do not write message artifacts from the filter path; filter-leads owns lead
442
+ quality and production rubrics only
443
+
444
+ `lead-filter.md` must contain:
445
+
446
+ - `Status`
447
+ - `Decision`
448
+ - `Who We'll Keep`
449
+ - `Who We'll Exclude`
450
+ - `Sample False Positives`
451
+ - `Optional Supporting Rule` only when one is clearly justified
452
+ - `Pass Rate`
453
+ - `Recommendation`
454
+ - `Implementation Details`
455
+
456
+ When `rubric.json` is emitted, it must use the production rubric shape, not a
457
+ custom sidecar schema:
458
+
459
+ - `leadScoringRubrics`
460
+ - `checkName`
461
+ - `description`
462
+ - `criterion`
463
+ - `reason`
464
+ - `isRequiredCheck`
465
+ - `allowPartialCredit`
466
+ - `strictMatching`
467
+
468
+ `Implementation Details` must contain a fenced JSON object with
469
+ `leadScoringRubrics`, and that array must contain 2-5 production rubric items
470
+ total. Do not create one rubric row per keep/exclude bullet. Bundle related
471
+ false-positive families into one exclusion criterion, and keep role fit as its
472
+ own explicit criterion. Keep raw rubric flags out of the top user-facing
473
+ sections.
474
+
475
+ Do not:
476
+
477
+ - call `check_rubric`
478
+ - call `save_rubrics`
479
+ - create a second independent scoring design in `rubric.json`
480
+ - emit more than one optional/supporting rule
481
+
482
+ ## Step 3: Generate Message
483
+
484
+ Read `brief.md`, `lead-review.md`, `lead-sample.json`, optional
485
+ `lead-filter.md`, optional `message-prep.md`, and optional
486
+ `message-candidate-drafts.md`.
487
+
488
+ This branch can run in parallel with Step 2 once `lead-review.md` and
489
+ `lead-sample.json` exist. If `lead-filter.md` is not ready yet, draft from the
490
+ lead-review message handoff rows, then reconcile after the filter lands.
491
+
492
+ Write:
493
+
494
+ - `message-validation.md`
495
+
496
+ Run the `generate-messages` skill in caller-declared `DRY MODE`. Its SKILL.md
497
+ holds the full drafting contract (retrieval, proof inventory, candidates,
498
+ finalizer pass, voice rules, safety). This step only covers orchestration.
499
+ This is not optional: before any write to `message-validation.md`,
500
+ `message-review.md`, `approval-packet.md`, or a commit-gate question, the current run must load the
501
+ full `generate-messages` prompt. In hosted/from-scratch runs, load it with
502
+ chunked `get_subskill_prompt({ subskillName: "generate-messages", offset,
503
+ limit })` calls so every tool result stays small enough for the streamed
504
+ harness. Start with `offset: 0, limit: 12000`, then keep calling with
505
+ `offset: nextOffset` until `hasMore` is false.
506
+ In Codex-hosted runs, this is a quality gate, not just provenance: if the
507
+ model cannot retrieve the complete prompt or cannot follow the required
508
+ gold-standard deliberation flow, stop at `message-review` and ask for
509
+ `revise-messaging`. Do not mint the campaign to compensate for weak copy.
510
+ Do not hand-write `message-validation.md` from `message-prep.md` or
511
+ `message-candidate-drafts.md`. Those files are planning inputs only; the final
512
+ message-validation artifact must come from the actual `generate-messages`
513
+ prompt path (`get_message_prompt` / `generate-messages`).
514
+
515
+ Orchestration requirements:
516
+
517
+ - start once real leads exist: `brief.md`, `lead-review.md`, and
518
+ `lead-sample.json` are enough to run generate-message
519
+ - use only the find-leads message handoff rows as basis examples, e.g.
520
+ "prospects 1, 5, and 6 are solid enough to think from"
521
+ - if `lead-filter.md` is ready, use it; if it arrives after message drafting,
522
+ reconcile before approval and rerun or revise messaging if the selected
523
+ winner depended on rows that fail the final filter
524
+ - never write `message-validation.md`, render a message review, render an
525
+ approval packet, or ask the commit gate until 100% of the real
526
+ `generate-messages` prompt has been read
527
+ - before `message-review` can recommend `approve-message`, verify
528
+ `message-validation.md` contains the full generate-messages shape:
529
+ `Gold Standard Strategy Map`, `Proof Inventory`, `Token Fill Rules`,
530
+ `Token Adherence Table`, `Angle Drafts`, `Kill / Combine Review`,
531
+ `Finalists`, `Finalizer Pass`, `Gold-Standard Quality Gate`,
532
+ `Skeptical Prospect Review`, `Winner Gate`, and a raw sendable
533
+ `Selected Winner`. If any are missing, recommend `revise-messaging` and do
534
+ not continue to approval or mint.
535
+ - pass no `campaignId`
536
+ - read only `brief.md`, `lead-review.md`, `lead-sample.json`, optional
537
+ `lead-filter.md`, and the `gold-standard-*` references. If
538
+ `message-prep.md` exists, use it as a
539
+ planning cache instead of redoing the same element inventory from scratch. If
540
+ `message-candidate-drafts.md` exists, use it as provisional draft input only
541
+ after checking its basis rows against the completed `lead-filter.md`, when
542
+ available.
543
+ - generate 2-3 sample messages inline
544
+ - start output with `Mode: DRY MODE (no DB mutation)`
545
+ - treat the archived examples as the **quality bar**, not a paste source;
546
+ write messages that could plausibly belong in the archive for this motion
547
+ - exact-template preservation only applies when the archived winner is the
548
+ same company as the brief
549
+ - draft 3 internal candidates and run a Finalizer Pass that combines the
550
+ best opener, proof sentence, bridge, and CTA across them into one winner
551
+ - if all finalists use the same first substantive line, treat the opener
552
+ test as failed; compare materially different opener jobs before selecting
553
+ a winner, or route to `revise-message`
554
+ - the Finalizer Pass must block adjacent repeated copy. Do not let a pain
555
+ line and mechanism line reuse the same noun stack unless the second line
556
+ adds plainly new buyer value. Merge, translate, bullet, or cut instead.
557
+ - the selected winner subject/body must not contain semicolons.
558
+ - the selected winner subject must not be generic category copy like
559
+ `first LinkedIn campaign`, `quick question`, `outbound`, or `intro`. Prefer the
560
+ original gold-standard A + B + C subject shape: a concrete row/company token
561
+ plus a concrete workflow/object plus the offer/result, for example
562
+ `{{company}} + LinkedIn outbound + Claude/Codex` or
563
+ `{{source_post_topic}} + active leads + first message test`. Keep it short,
564
+ buyer-native, and free of abstract category nouns.
565
+ - A + B + C is a quality shape, not permission to stuff tokens into the
566
+ subject. The subject must pique buyer interest and name a buyer-relevant
567
+ problem, workflow, or useful artifact. Do not use sender names, founder names,
568
+ "Austin's review", "founder call", "demo", "quick call", or similar
569
+ sender-centered language in the subject. Do not use a row token in the subject
570
+ unless the filled version is natural, lowercase/common-word where appropriate,
571
+ and more interesting than the non-tokenized version.
572
+ - the selected winner must not contain sender-intro fragments like
573
+ `Usama here`, `derm here`, `board-cert derm here`, `[credential] here`,
574
+ or `[role] at [company] here`.
575
+ - the selected winner must not use generic signal tokens such as
576
+ `{{recentSignal}}`, `{{recent_signal}}`, or `{{recent_signal_quote}}`.
577
+ Row personalization must use concrete enriched-row fields such as
578
+ `{{post_context}}`, `{{comment_summary}}`, `{{profile_summary}}`,
579
+ `{{source_post_topic}}`, `{{headline}}`, or `{{row_proof_note}}`, and it
580
+ must avoid source-citation phrases like `caught my eye`.
581
+ - do not default to founder-to-founder, MD-to-MD, doctor-to-doctor,
582
+ peer-call, compare-notes, or similar identity-call CTA framing unless
583
+ the user explicitly selected that route or the brief makes it the
584
+ approved offer. Even then, the CTA must name the useful return artifact,
585
+ preview, teardown, or working session.
586
+ - if the message is selling or introducing a product, the selected winner
587
+ must make the product plain before asking for time, but the opener must
588
+ not sound like homepage copy. A line shaped like `[Product] is a
589
+ [category] platform -- feature, feature, feature` is blocked when it reads
590
+ like product copy instead of a human note. For command-native products,
591
+ describe the buyer job (`launch a LinkedIn campaign from Claude/Codex`)
592
+ before category nouns like `platform` or `MCP`.
593
+ - pass the Thomas revision filters before writing findings
594
+ - immediately after `message-validation.md` is confirmed and reconciled with
595
+ `lead-filter.md`, write `message-review.md` and render it inline starting
596
+ with `Status: message-review` as the first visible line. Do not put a
597
+ markdown heading, preface, or summary before that status line. This is a
598
+ customer checkpoint, not an audit report: show the approved campaign message
599
+ template first, then show rendered examples that prove the tokens fill well
600
+ and can be omitted cleanly when row data is weak. Keep the detailed
601
+ gold-standard/rule audit inside `message-validation.md`, not in the
602
+ user-facing review. The review must use this exact label shape so the gate can
603
+ be parsed: `Subject: ...`, `Message: ...`, `Rendered examples: ...`,
604
+ `Good token fill: ...`, `Good omit: ...`, `Token notes: ...`,
605
+ `My take: ...`, `Suggested adjustment: ...`,
606
+ `Question: approve-message or revise-messaging?`, `Recommendation:
607
+ approve-message|revise-messaging`.
608
+ - `My take:` and `Suggested adjustment:` are mandatory customer-facing decision
609
+ fields, not optional summary text. They must appear after `Token notes:` and
610
+ before the question in every rendered message review. They must each be
611
+ non-empty, specific to the current message, and useful for one last manual
612
+ feedback pass. Never render a message review that jumps straight from token
613
+ notes to `Question:` or `Recommendation:`.
614
+ - `Message:` in `message-review.md` must be a tokenized template with supported
615
+ enriched-row tokens such as `{{first_name}}`, `{{company}}`,
616
+ `{{workflow_context}}`, `{{headline}}`, `{{source_post_topic}}`, or
617
+ `{{row_proof_note}}`. It must not be a one-row-only raw sample. It must
618
+ include at least one literal `{{...}}` token. Keep only tokens that future
619
+ enriched rows can actually fill or safely omit. If you cannot produce a
620
+ tokenized template from supported enriched-row fields, stop with
621
+ `Recommendation: revise-messaging`.
622
+ - `{{profile_signal}}` is never a supported customer-facing token. It is an
623
+ internal enrichment label and must not appear in `Subject:`, `Message:`,
624
+ rendered examples, `Token notes:`, `message-validation.md` selected copy, or
625
+ `approval-packet.md`. If a profile-derived signal matters, translate it into
626
+ a buyer-readable derived token with a clear fill rule, such as
627
+ `{{workflow_context}}` or `{{row_proof_note}}`; otherwise omit the line.
628
+ - `Rendered examples:` must include at least one `Good token fill:` rendered
629
+ message where the row has a clean signal and one `Good omit:` rendered message
630
+ where an optional token line is omitted instead of forced. Each example must
631
+ be a complete rendered subject + body, not a bullet list of token names or a
632
+ single bridge-line fragment. Do not use bracketed instructions, deferred row
633
+ notes, or phrases like `approve the selected winner above`.
634
+ - rendered examples may only use token values that exist in `lead-sample.json`,
635
+ `lead-review.md`, `lead-filter.md`, or the selected winner's documented
636
+ supported-token plan. Do not invent example values to make the template look
637
+ better. If a token is absent, weak, or not a supported enriched-row field,
638
+ show the `Good omit:` version instead of filling it.
639
+ - token casing must look intentional. Keep proper nouns, recipient names,
640
+ company names, product names, acronyms, and the pronoun `I` capitalized in
641
+ both the template examples and rendered output. Casual lowercase is allowed
642
+ only for static common words when the chosen message voice uses it. Do not
643
+ lowercase token values or invent transform syntax like `{{company_lower}}`,
644
+ `{{first_name | lower}}`, or bracketed casing instructions.
645
+ - Token usage must be intelligent, not decorative. If a tokenized opener reads
646
+ like mail merge (`Reaching out because {{reporting_context}} sits in...`,
647
+ `your {{role_context}} work`, `saw your {{topic}}`, or similar), rewrite the
648
+ base line so the token either sounds like a natural noun phrase or is omitted.
649
+ Prefer row-derived tokens that change the reply reason; otherwise use a clean
650
+ segment-level line and document why the row token is omitted.
651
+ - founder/sender names should not be used as the hook. Mention a founder name
652
+ only when the buyer already has a reason to care about that person or when the
653
+ user explicitly wants named-founder branding. In normal first-touch copy,
654
+ frame the CTA around the useful return artifact (`map one reporting problem`,
655
+ `review one dashboard`, `pressure-test one workflow`) instead of "call with
656
+ Austin" or another founder-name CTA.
657
+ - message-review hard-fail preflight: before rendering `Status: message-review`,
658
+ scan the subject, tokenized template, good-fill example, good-omit example,
659
+ `My take`, `Suggested adjustment`, and recommendation. If any of them contain
660
+ `felt close enough to send`, `close enough to send`,
661
+ `thought this was relevant enough`, `seemed close
662
+ enough`, `{{profile_signal}}`, `profile signal`, `is why I thought this might
663
+ be relevant`, `Austin's`, a sender/founder name in the subject, a one-row raw
664
+ sample under `Message:`, rendered examples without full copy, a
665
+ mail-merge-sounding tokenized opener, missing or empty `My take:`, missing or
666
+ empty `Suggested adjustment:`, `My take:` that only says `looks good`,
667
+ `strong`, or `approved`, `Suggested adjustment:` that only says `none`, or no
668
+ literal `{{...}}` token in `Message:`, revise the message before showing it.
669
+ Do not output
670
+ `Recommendation: approve-message` when any hard-fail preflight item is
671
+ present.
672
+ - `My take` must be 1-3 short bullets or sentences, focused on what a founder
673
+ needs to decide: approve as-is, revise once, make it shorter, make it more
674
+ specific, change proof, change CTA, or preserve the current length because
675
+ cutting would remove reply reason. If a shorter version is recommended,
676
+ `Suggested adjustment` must say exactly what to remove or compress. Do not
677
+ include a long `What works`, `Gold-standard/rule check`, or internal checklist
678
+ in this checkpoint.
679
+ - `Suggested adjustment` must give the founder a concrete final-edit option even
680
+ when the recommendation is `approve-message`. Use a useful shape like
681
+ `Approve as-is, or revise once to [specific edit] if you want [effect]`. It
682
+ must invite the user to give manual feedback before minting, for example:
683
+ `Reply revise-messaging with the exact line change, proof change, CTA change,
684
+ or tone feedback you want before I build the approval packet.` Do not write
685
+ `none`.
686
+ - if the message is merely plausible, generic, template-shaped, or weaker than
687
+ the loaded gold-standard examples, set `Recommendation: revise-messaging`.
688
+ The next Codex UAT goal is to prove message quality before mint, so no
689
+ approval packet or campaign mutation should happen just because the mechanics
690
+ are working.
691
+ - immediately after rendering `message-review.md`, call `AskUserQuestion` with
692
+ exactly two choices, in this order: `approve-message`, `revise-messaging`.
693
+ Stop after the question. Do not write `approval-packet.md`,
694
+ `customer-roleplay.md`, or a commit-gate question until
695
+ `message-review-decision.md` contains `approve-message`.
696
+ - if the selected winner or PS uses named customer/logo proof, quantified proof,
697
+ team credentials, or investor/accelerator proof that is not explicitly
698
+ supported by `brief.md`, `lead-review.md`, `lead-sample.json`, or loaded
699
+ reference material, the review must set `Recommendation: revise-messaging`.
700
+ Do not approve speculative credibility claims.
701
+ - if the selected winner or PS uses vague proof wrappers like `spoken publicly
702
+ about`, `publicly talked about`, `trusted by`, `worked with`, `used by`, or
703
+ bare customer-logo lists without naming a concrete buyer-relevant result,
704
+ mechanism, or risk reducer, the review must set `Recommendation:
705
+ revise-messaging`. Supported logos are not automatically body-worthy proof.
706
+ Translate to a concrete proof line or omit the proof from the message.
707
+ - if customer/logo proof is useful mainly for credibility, prefer a concise PS
708
+ over interrupting the body. The PS must name what the customers used the
709
+ product/service for, not just list logos. If the body already carries buyer
710
+ pain, mechanism, and CTA clearly, adding concrete social proof in the PS is
711
+ allowed when it lowers vendor risk.
712
+ - the selected winner or PS must not use internal labels like `p.s. relevant
713
+ proof:`, `p.s. useful proof:`, `p.s. proof:`, or `p.s. social proof:`.
714
+ Rewrite as a natural aside before asking the user to approve.
715
+ - if the user chooses `revise-messaging`, write `message-review-decision.md`
716
+ with `revise-messaging`, preserve upstream artifacts, delete/regenerate
717
+ only `message-validation.md`, `message-review.md`,
718
+ `message-review-decision.md`, `customer-roleplay.md`, and
719
+ `approval-packet.md`, then rerun this message step.
720
+ - after `message-review-decision.md` contains `approve-message`, write
721
+ `approval-packet.md` in customer-facing order and render the same approval
722
+ packet inline before the commit gate. The approval packet and six-choice
723
+ commit question must appear in the same customer-visible turn. Do not emit a
724
+ standalone commit-gate question. Include a clear `## Message Review` section
725
+ inside the approval packet before `## Approved Message Template`. It should
726
+ summarize the customer checkpoint from `message-review.md` and preserve the
727
+ same literal `My take:`, `Suggested adjustment:`, and `Recommendation:`
728
+ labels.
729
+ - if `message-validation.md` contains an extractable `Selected Winner`, use
730
+ that exact winner as the `## Approved Message Template` in
731
+ `approval-packet.md` and in the campaign brief passed to `create_campaign`.
732
+ Only substitute concrete enriched-prospect-row `{{tokens}}`; do not rewrite
733
+ the copy inline at approval time. Do not use abstract slot tokens like
734
+ `{{hookLine}}`, `{{painLine}}`, `{{productLine}}`, `{{closeLine}}`, or
735
+ `{{psLine}}`, and do not use `{{recent_signal_quote}}`; use fields that exist
736
+ on the enriched prospect row instead.
737
+ - the live body under `## Approved Message Template` must be sender-ready copy.
738
+ Do not include bracketed instruction placeholders such as `[ROW BRIDGE ...]`,
739
+ `[insert ...]`, `[generated ...]`, or prose that tells a later step to
740
+ paraphrase/fill a line. Put per-row generation rules in `### Token Fill Rules`
741
+ with concrete enriched-row fields, or route to `revise-messaging`.
742
+ - `message-validation.md` must not call the winner a canonical template or
743
+ hide per-row generation in bracketed body text. `## Selected Winner` must
744
+ be a real sendable message that could be approved as-is.
745
+ - block awkward bridge phrasing like `felt close enough to send this`,
746
+ `thought this was relevant enough`, or `seemed close enough`. Replace with a
747
+ crisp row-backed bridge using a buyer-readable row-derived token, such as
748
+ `That {{workflow_context}} work is where teams usually need cleaner reporting
749
+ ownership.` If the signal is weak or absent, omit the bridge line entirely.
750
+ - if subject, tokenized template, or rendered examples do not meet the bar,
751
+ `message-review.md` must recommend `revise-messaging`, not `approve-message`.
752
+
753
+ Do not:
754
+
755
+ - call `get_campaign`
756
+ - call `get_rows`
757
+ - call `update_cell`
758
+ - call `update_campaign_brief`
759
+ - fetch fresh web or LinkedIn research
760
+ - mutate DB-backed campaign state
761
+
762
+ `message-validation.md` must contain:
763
+
764
+ - `Status`
765
+ - `Mode`
766
+ - `Template Used`
767
+ - `Primary Example`
768
+ - `Secondary Influence`
769
+ - `Lead Sample Basis`
770
+ - `Strongest Reply Reason`
771
+ - `Pre-Draft Buyer-Role Analysis`
772
+ - `Campaign Element Pool`
773
+ - `Gold Standard Strategy Map`
774
+ - `Current Campaign Translation`
775
+ - `Element Scoring`
776
+ - `Proof Inventory`
777
+ - `Token Fill Rules`
778
+ - `Token Adherence Table`
779
+ - `Angle Drafts`
780
+ - `Kill / Combine Review`
781
+ - `Finalists`
782
+ - `Candidate Messages`
783
+ - `Finalizer Pass`
784
+ - `Gold-Standard Quality Gate`
785
+ - `Skeptical Prospect Review`
786
+ - `Winner Gate`
787
+ - `Selected Winner`
788
+ - `Findings`
789
+ - `Recommendation`
790
+
791
+ `approval-packet.md` must contain:
792
+
793
+ - campaign direction
794
+ - lead source and sample
795
+ - filters and rubrics
796
+ - selected message(s)
797
+ - `## Message Review` with literal `My take:`, `Suggested adjustment:`, and
798
+ `Recommendation:` (`approve-message` or `revise-messaging`). If the message
799
+ is only okay, say so plainly so the user can choose `revise-messaging` at the
800
+ commit gate.
801
+ - `## Approved Message Template`; when `message-validation.md` has a selected
802
+ winner, this section must preserve that selected winner rather than a newly
803
+ drafted approval-template variant
804
+ - risks / caveats
805
+ - next action: the six-choice commit gate
806
+
807
+ `message-review.md` must contain:
808
+
809
+ - `Status: message-review`
810
+ - `Subject:` using the approved tokenized A + B + C subject shape
811
+ - `Message:` followed by the tokenized approved message template
812
+ - `Rendered examples:`
813
+ - `Good token fill:` with a complete concrete rendered subject + body
814
+ - `Good omit:` with a complete concrete rendered subject + body where optional
815
+ row signal is omitted cleanly
816
+ - `Token notes:` naming which tokens are safe to fill, which optional token line
817
+ should be omitted when absent, and whether the message uses standard sentence
818
+ case or casual lowercase static words
819
+ - `My take:`
820
+ - `Suggested adjustment:` with a concrete final-edit option and explicit
821
+ invitation to give manual feedback before approval
822
+ - `Question: approve-message or revise-messaging?`
823
+ - `Recommendation:` exactly `approve-message` or `revise-messaging`
824
+ - The two-choice `AskUserQuestion` immediately after the rendered review
825
+
826
+ `message-review-decision.md` must contain exactly one selected route:
827
+ `approve-message` or `revise-messaging`. It does not authorize DB mutation.
828
+
829
+ If token sourcing is weak, revise token fill rules or route back to
830
+ `revise-filter` or `revise-message` instead of guessing.
831
+
832
+ </step_contracts>
833
+
834
+ <resume_rules>
835
+
836
+ Phase 84 validation resume:
837
+
838
+ - No `brief-v1.md` / `brief.md` present -> run `create-campaign-brief`
839
+ - `brief-v1.md` present but no `brief.md` -> copy `brief-v1.md` to `brief.md`,
840
+ then run `find leads`
841
+ - Only `brief.md` present -> compatibility resume: run `find leads`
842
+ - `lead-review.md` + `lead-sample.json` present, but no `lead-filter.md` and
843
+ no `message-validation.md` -> run `filter leads` and `generate message`
844
+ from the same find-leads basis; do not ask the user between them when
845
+ lead-review is confirmed
846
+ - `lead-review.md` + `lead-sample.json` present, but no `lead-filter.md` -> run
847
+ `filter leads`
848
+ - `lead-review.md` + `lead-sample.json` present, but no
849
+ `message-validation.md` -> run `generate message`
850
+ - `lead-filter.md` + `message-validation.md` present -> reconcile message
851
+ basis rows against the final filter, then write/render `message-review.md`
852
+ and ask the two-choice message review gate
853
+ - `message-validation.md` present but no `message-review-decision.md` -> write
854
+ `message-review.md`, render it inline starting with `Status:
855
+ message-review`, then ask `approve-message` vs `revise-messaging`
856
+ - `message-review-decision.md` contains `revise-messaging` -> preserve
857
+ upstream artifacts; delete only `message-validation.md`,
858
+ `message-review.md`, `message-review-decision.md`,
859
+ `customer-roleplay.md`, and `approval-packet.md`; rerun `generate message`
860
+ - `message-review-decision.md` contains `approve-message` but no
861
+ `approval-packet.md` -> write `approval-packet.md`, render that packet inline
862
+ starting with `Status: approval-packet`, then ask the six-choice commit gate
863
+ in the same turn
864
+ - `message-validation.md` + `message-review-decision.md` +
865
+ `approval-packet.md` present -> validation complete,
866
+ render `approval-packet.md` inline starting with `Status: approval-packet`,
867
+ then ask the six-choice commit gate in the same turn
868
+ - `lead-review.md` without `lead-sample.json`, or vice versa -> stop with a
869
+ contract violation
870
+ - `lead-filter.md` without upstream lead artifacts -> stop with a contract violation
871
+
872
+ Phase 85 commit-gate + atomic-mint resume:
873
+
874
+ - All validation artifacts present and `message-review-decision.md` contains
875
+ `approve-message` but no `approval-packet.md` yet -> write the approval
876
+ packet, render it inline, then show the commit gate in the same turn (see
877
+ `<commit_gate>` below)
878
+ - `approval-packet.md` present but no `commit-gate-decision.md` -> show the
879
+ approval packet inline and then the commit gate in the same turn; an existing
880
+ positive `customer-roleplay.md` is advisory only
881
+ - `commit-gate-decision.md` is a revision choice or `abort` -> route without
882
+ DB mutation
883
+ - `commit-gate-decision.md` contains `approve` and no `CampaignOffer` minted
884
+ yet -> run atomic mint
885
+ - `CampaignOffer` exists and `currentStep = "auto-execute-leads"` -> resume
886
+ via `create_campaign({ campaignId })` to re-fetch state + `watchUrl`,
887
+ re-surface the watch link using `references/watch-link-handoff.md`, and
888
+ continue into the autonomous tail at Step 13
889
+
890
+ Phase 85 autonomous-tail resume (Plan 85-02):
891
+
892
+ - `CampaignOffer.currentStep === "auto-execute-leads"` -> run Step 13
893
+ (import) then advance to `validate-sample`
894
+ - `CampaignOffer.currentStep === "validate-sample"` -> run Step 14 sample
895
+ validation loop. Read the persisted `revisionRound` counter and do NOT
896
+ reset it to 0 on resume
897
+ - `CampaignOffer.currentStep === "auto-execute-messaging"` -> run Step 15
898
+ messaging scale-up
899
+ - `CampaignOffer.currentStep === "awaiting-user-greenlight"` -> Step 16:
900
+ re-surface `watchUrl` + `handoff.orientation` and STOP. Do not call
901
+ `start_campaign` on resume. Wait for UI start or a Claude greenlight
902
+ turn
903
+ - `CampaignOffer.currentStep === "running"` -> campaign is live. Surface
904
+ a "campaign is live" confirmation + `watchUrl`. Do not re-start
905
+
906
+ On every tail resume, re-surface the `watchUrl` and the current step's
907
+ orientation string (v1 create-campaign watch-mode pattern). Load the
908
+ relevant reference for the active step:
909
+
910
+ - Step 14 -> `references/sample-validation-loop.md`
911
+ - any escalation path -> `references/escalation-ladder.md`
912
+ - Step 16 -> `references/final-handoff-contract.md`
913
+
914
+ </resume_rules>
915
+
916
+ <commit_gate>
917
+
918
+ After Phase 84 artifacts are complete, show the user a commit gate before any
919
+ DB mutation. Load `references/approval-gate-framing.md` for the full
920
+ framing — especially the approval packet ordering and per-choice draft
921
+ directory effects.
922
+
923
+ Preconditions (block the gate when missing):
924
+
925
+ - `brief-v1.md` should exist for from-scratch runs
926
+ - `brief.md` must exist
927
+ - `lead-review.md` must exist
928
+ - `lead-sample.json` must exist
929
+ - `lead-filter.md` must exist
930
+ - `message-validation.md` must exist
931
+ - `message-review.md` must exist
932
+ - `message-review-decision.md` must exist and contain `approve-message`
933
+ - `approval-packet.md` must be written before asking for a commit decision
934
+ - `rubric.json` is optional but strongly preferred
935
+
936
+ Anchor validation (block `approve` when empty):
937
+
938
+ - `brief.md` has a non-empty `## Message Thesis`
939
+ - `lead-review.md` status is `confirmed`
940
+ - `lead-sample.json` parses and contains at least one row
941
+ - `lead-filter.md` has `Decision`, `Who We'll Keep`, `Who We'll Exclude`,
942
+ `Pass Rate`, `Recommendation`, and `Implementation Details`
943
+ - `message-validation.md` has `Status: confirmed`, `Mode: DRY MODE (no DB
944
+ mutation)`, a `Selected Winner`, and a non-empty `Token Adherence Table`
945
+ - `message-review.md` has `Status: message-review`, `Subject:`, `Message:`
946
+ with at least one literal `{{...}}` token, `Rendered examples:`,
947
+ `Good token fill:` with a complete rendered subject + body, `Good omit:` with
948
+ a complete rendered subject + body, `Token notes:`, `My take:`, `Suggested
949
+ adjustment:`, `Question: approve-message or revise-messaging?`, and
950
+ `Recommendation: approve-message`
951
+ - `message-review-decision.md` is exactly `approve-message`
952
+
953
+ Present the approval packet in user-facing order: brief, lead sample, lead
954
+ filter, message validation. Do not dump raw JSON or opaque validation
955
+ anchors at the user.
956
+
957
+ When rendering the approval packet inline, start it with exactly
958
+ `Status: approval-packet`. Write the same content to `approval-packet.md`
959
+ before showing the gate. The same assistant turn must then call
960
+ `AskUserQuestion` with the six commit choices below. A commit-gate question
961
+ without the inline `Status: approval-packet` packet is invalid UX. If a
962
+ customer roleplay is performed, write `customer-roleplay.md` before the final
963
+ choice. A roleplay result that says "looks good", "approve", or equivalent is
964
+ only critique; it does not satisfy the gate. Write `commit-gate-decision.md`
965
+ only after the user/operator makes one explicit choice.
966
+
967
+ Use `AskUserQuestion` with exactly these 6 choices, in this order:
968
+
969
+ ```text
970
+ approve
971
+ revise-brief
972
+ revise-leads
973
+ revise-rubric
974
+ revise-messaging
975
+ abort
976
+ ```
977
+
978
+ Do not collapse, reorder, or add choices. Do not auto-select.
979
+
980
+ If the host UI caps options per question, preserve the same six routes with a
981
+ two-question gate: Q1 = `approve`, `revise`, `abort`; Q2 =
982
+ `revise-brief`, `revise-leads`, `revise-rubric`, `revise-messaging`. Do not
983
+ tell the customer this is because of schema/tool limits. In the split form,
984
+ `revise` is only the parent choice for the second question, not a new route.
985
+
986
+ </commit_gate>
987
+
988
+ <sender_resolution>
989
+
990
+ Before atomic mint, the skill needs a concrete sender identity. Resolve
991
+ it in this order — stop at the first step that yields a confirmed
992
+ sender, and NEVER invent / guess / brute-force LinkedIn URLs:
993
+
994
+ 1. **If the fixture/caller provides `senderIdentity.senderId`**, use it
995
+ directly. Call `get_sender({ senderId })` to confirm it exists in
996
+ the active workspace, then call `enrich_sender({ senderId })` ONCE
997
+ to populate `companySnapshot` + `proofDigest`. Do not call
998
+ `list_senders`, do not guess alternate URLs.
999
+ 2. **Else if the fixture/caller provides a confirmed
1000
+ `senderIdentity.linkedinUrl`**, call `enrich_sender({ linkedinUrl
1001
+ })` ONCE. If it errors, DO NOT retry with a variation of the URL.
1002
+ Fall through to step 3.
1003
+ 3. **Else if the caller provides `senderIdentity.name` +
1004
+ `companyDomain` (or workspace context), do ONE `WebSearch` pass**
1005
+ for the authoritative LinkedIn profile URL ("`{name}` `{company}`
1006
+ linkedin"). Use ONLY a URL that appears verbatim in a web search
1007
+ result. Never synthesize a URL from the name. If WebSearch returns
1008
+ no clean match, escalate to step 4.
1009
+ 4. **Else escalate.** Print the available sender context and ask the
1010
+ user to resolve (senderId or confirmed linkedinUrl). Do not proceed
1011
+ with atomic mint.
1012
+
1013
+ Hard rules:
1014
+
1015
+ - At most ONE `enrich_sender` retry per run, only when the first
1016
+ response was a transient (5xx) error. Do not retry on 400/404 by
1017
+ changing the URL.
1018
+ - At most ONE `WebSearch` pass per run for sender resolution.
1019
+ - NEVER call `enrich_sender` on a lead's LinkedIn URL. `enrich_sender`
1020
+ is for the single sender entity only. Lead enrichment happens via
1021
+ the Enrich Prospect column in the workflow table.
1022
+ - If the fixture's `mustMatchWorkspaceSender` flag is true and the
1023
+ resolved sender isn't in the active workspace, ABORT the run with an
1024
+ explicit mismatch error.
1025
+
1026
+ </sender_resolution>
1027
+
1028
+ <atomic_mint>
1029
+
1030
+ Only run this sequence when the user picks `approve`. Load
1031
+ `references/watch-link-handoff.md` before starting; it owns the watch-link
1032
+ surfacing protocol and the partial-mint recovery rules.
1033
+
1034
+ Exact sequence:
1035
+
1036
+ 1. Read `brief.md` and `rubric.json` from the draft directory.
1037
+ 2. Validate anchors one more time. An empty anchor blocks the approve path.
1038
+ The brief must include an `## Approved Message Template` section with
1039
+ the exact live message template and at least one supported `{{token}}`
1040
+ such as `{{first_name}}`. Rendered examples are not enough. The
1041
+ Generate Message column switches to template mode only when
1042
+ `campaignBrief.content` contains `{{...}}`; without this marker it will
1043
+ use full-generation mode and may rewrite the approved message.
1044
+ The live template body must contain only sender-ready copy plus supported
1045
+ `{{tokens}}`; no bracketed instructions or placeholder prose may appear in
1046
+ the body.
1047
+ Include `### Token Fill Rules` for every enriched-prospect-row token in the
1048
+ template. Tokens may be row fields such as `{{first_name}}`,
1049
+ `{{company}}`, `{{role}}`, `{{headline}}`, `{{profile_summary}}`,
1050
+ `{{post_context}}`, `{{comment_summary}}`, `{{source_post_topic}}`, or
1051
+ `{{row_proof_note}}` when those exact fields are supported by the
1052
+ row/enrichment data. Do not document static sender identity, product name,
1053
+ proof points, casing style, `{{recent_signal_quote}}`, or abstract slot
1054
+ tokens such as `{{hookLine}}`, `{{painLine}}`, `{{productLine}}`,
1055
+ `{{closeLine}}`, or `{{psLine}}`.
1056
+ 3. Call `bootstrap_create_campaign({ flowVersion: "v2" })`. Respect
1057
+ `safeToProceed`; on blocking errors, surface and stop.
1058
+ 4. Call
1059
+ `create_campaign({ campaignBrief, currentStep: "auto-execute-leads" })`.
1060
+ The `campaignBrief` argument must preserve the approved message
1061
+ template section verbatim, including the `{{tokens}}`.
1062
+ If the brief has no supported `{{token}}`, do not call
1063
+ `create_campaign`. Fail the approve path and ask for a corrected
1064
+ template, unless the user explicitly approved freeform AI-generated
1065
+ messages and the tool call sets `messageGenerationMode:
1066
+ "ai-generated"`.
1067
+ Include `senderIds` when a workspace sender is available. If the
1068
+ customer-named sender is not configured in the current UAT workspace,
1069
+ use an available workspace sender for the rehearsal and state that
1070
+ mismatch in the final runtime notes. Do not silently mint without a
1071
+ sender because sequence selection depends on sender tier/settings.
1072
+ Capture `{ campaignId, watchUrl }` from the response.
1073
+ - If the call errors, surface the error and stop. No rollback needed; no
1074
+ campaign exists.
1075
+ - If the response is missing `watchUrl`, treat it as a recoverable
1076
+ failure. Stop before `save_rubrics`. Do not print a watch link.
1077
+ Surface the contract violation explicitly — a missing `watchUrl` is a
1078
+ plumbing bug, not a silent skip.
1079
+ 5. Call `save_rubrics({ campaignOfferId: campaignId, rubric })` using the
1080
+ payload derived from `rubric.json` (or from `lead-filter.md` if
1081
+ `rubric.json` is absent).
1082
+ - If this call fails after `create_campaign` succeeded, either rollback
1083
+ the `CampaignOffer` row, or mark an explicit recoverable failure state
1084
+ so the user can retry. Never leave a silent partial campaign. Do NOT
1085
+ print the watch link during this recovery window.
1086
+ 6. Flip the brief status in the draft to `committed`. Move the draft
1087
+ directory from
1088
+ `.sellable/create-campaign-v2/drafts/{workspace-slug}/{campaign-slug}/`
1089
+ to
1090
+ `.sellable/create-campaign-v2/drafts/{workspace-slug}/.committed/{campaign-slug}/`.
1091
+ 7. Surface the `watchUrl` using the exact block defined in
1092
+ `references/watch-link-handoff.md`. Only print after both
1093
+ `create_campaign` AND `save_rubrics` have succeeded.
1094
+ 8. Hand off to the autonomous tail (Plan 85-02). Do not run Step 13 lead
1095
+ import here.
1096
+
1097
+ If the approved lead source exists only as a provider lane in
1098
+ `lead-review.md` (for example a Sales Navigator filter/search URL) and no
1099
+ live `searchId` or `sourceLeadListId` is present, recreate the approved
1100
+ source by calling the matching provider search tool first, then import from
1101
+ the returned id. This is replaying the approved lane, not inventing a new
1102
+ source. Do not skip import/messages on that basis.
1103
+
1104
+ Do NOT call `import_leads` during atomic mint. Full lead sourcing and import
1105
+ happen in Plan 85-02 Step 13 after mint.
1106
+
1107
+ </atomic_mint>
1108
+
1109
+ <revision_routing>
1110
+
1111
+ When the user picks a revision choice, do not mint a campaign. Do not print
1112
+ a watch link. Route back to the relevant upstream step and reset the draft
1113
+ directory per `references/approval-gate-framing.md`:
1114
+
1115
+ - `revise-brief` -> back to Phase 83 or Phase 84 depending on what changed.
1116
+ Preserve `brief.md`; delete all downstream artifacts.
1117
+ - `revise-leads` -> back to Phase 84 `find leads`. Preserve `brief.md`;
1118
+ delete lead and downstream artifacts.
1119
+ - `revise-rubric` -> back to Phase 84 `filter leads`. Preserve `brief.md`,
1120
+ `lead-review.md`, `lead-sample.json`; delete `lead-filter.md`,
1121
+ `message-validation.md`, and `rubric.json`.
1122
+ - `revise-messaging` -> back to Phase 84 `generate message`. Preserve
1123
+ upstream artifacts; delete only `message-validation.md`,
1124
+ `message-review.md`, `message-review-decision.md`,
1125
+ `customer-roleplay.md`, and `approval-packet.md`.
1126
+ - `abort` -> move the draft directory to
1127
+ `.sellable/create-campaign-v2/drafts/{workspace-slug}/.aborted/{campaign-slug}-{timestamp}/`.
1128
+ No DB rows touched.
1129
+
1130
+ Revisions must produce zero DB rows.
1131
+
1132
+ </revision_routing>
1133
+
1134
+ <boundaries>
1135
+
1136
+ Phase 84 (pre-commit-gate) boundary:
1137
+
1138
+ Do not create live campaign state during validation. Specifically, do not:
1139
+
1140
+ - mint a campaign
1141
+ - import leads
1142
+ - persist selected lead lists
1143
+ - attach downstream assets
1144
+ - save remote rubric state
1145
+ - move the user into a live execution step
1146
+
1147
+ Phase 85 (commit-gate and beyond) boundary:
1148
+
1149
+ - The commit gate is the only place in this skill where DB mutation is
1150
+ authorized. Do not call `create_campaign`, `save_rubrics`,
1151
+ `update_campaign`, or any other mutating tool before the user picks
1152
+ `approve`.
1153
+ - Revision choices and `abort` must never call a mutating tool.
1154
+ - `import_leads` is not part of atomic mint. Defer it to Plan 85-02 Step 13.
1155
+ - Never print the `watchUrl` until both `create_campaign` and `save_rubrics`
1156
+ have succeeded.
1157
+ - Never fabricate or reconstruct the `watchUrl` — capture it from the
1158
+ `create_campaign` response.
1159
+ - Once a campaign has been minted, every provider search and every
1160
+ provider-import call from this skill MUST include the minted
1161
+ `campaignOfferId`. This is universal — Prospeo, Sales Nav, Apollo,
1162
+ Signal Discovery, and any future provider. The only place a
1163
+ campaignless search is correct is the pre-mint Phase 84 `find leads`
1164
+ discovery sample. Post-mint expansion, scale-up imports, alternate
1165
+ lane explorations, account-based reruns, signal-monitor lanes, and
1166
+ operator-driven follow-up searches all persist to the campaign and
1167
+ must carry `campaignOfferId`. A search without `campaignOfferId`
1168
+ after mint orphans the search from the campaign UI's Contact
1169
+ Search panel and the campaign's `Searches` history, even when the
1170
+ search produced rows that later get imported. If the search
1171
+ helper enforces a `confirmed: true` gate, pass that with
1172
+ `campaignOfferId` rather than dropping it.
1173
+
1174
+ </boundaries>
1175
+
1176
+ <autonomous_tail>
1177
+
1178
+ The review-batch tail runs AFTER the atomic mint succeeds. It is driven by
1179
+ `core/auto-execute.yaml` and progresses through four steps — Step 13
1180
+ (import review batch), Step 14 (validate-sample), Step 15
1181
+ (auto-execute-messaging for the review batch), Step 16
1182
+ (awaiting-user-greenlight) — before stopping and waiting for a user
1183
+ greenlight.
1184
+
1185
+ Cost boundary: the commit gate approves creating a campaign and producing a
1186
+ real review batch. It does NOT approve spending enrichment/message credits on
1187
+ hundreds of leads. Two separate scales apply post-mint and they MUST be kept
1188
+ distinct in your head:
1189
+
1190
+ - **Import scale** (how many rows land in the campaign table). Defaults to
1191
+ `import.importLimit` (25). The user MAY explicitly raise this at or before
1192
+ mint with phrasing like "import all of them", "pull the full search", "all
1193
+ ~2,000 from this lane", or a numeric target. When the user has stated the
1194
+ full-scale import intent, pass that count as `targetLeadCount` so the table
1195
+ carries the full pool. Importing rows is cheap (no enrichment runs yet);
1196
+ storing them up front lets the user kick remaining enrichment manually
1197
+ without re-importing later.
1198
+ - **Cascade scale** (how many rows get enriched/scored/messaged before user
1199
+ approval). ALWAYS stays at the configured review batch (`sample.sampleSize`
1200
+ or `import.importLimit` — default 25) until the user explicitly approves the
1201
+ sample. This is the cost gate. Even when the user said "import all", you
1202
+ queue Enrich Prospect cells for only the first review-batch rows. The
1203
+ remaining rows sit `pending` in the table for the user to kick manually
1204
+ after they've eyeballed the sample.
1205
+
1206
+ A pre-stated full-import intent is NOT a pre-approval to cascade everything.
1207
+ Re-confirm before queueing enrichment beyond the review batch.
1208
+
1209
+ If the source lane contains hundreds of leads and the user has not stated a
1210
+ full-import intent, leave the rest untouched in the source — do not import
1211
+ beyond `importLimit` until the user asks to expand.
1212
+
1213
+ ## MANDATORY TOOL ORDER (read this BEFORE any tail step)
1214
+
1215
+ Every tail run MUST call these tools in this exact order. The tail is
1216
+ **review-batch cascade-driven**: you kick off Enrich Prospect only for
1217
+ the imported review batch, and the workflow engine chains DNC Check →
1218
+ ICP Score → Passes Rubric → Generate Message automatically. Your job is
1219
+ to START the bounded cascade, WAIT for it to drain, OBSERVE the results,
1220
+ and stop for review.
1221
+ Do NOT manually run rubric-check, enrich, or message-generation
1222
+ tools — the cascade already does them.
1223
+
1224
+ ```text
1225
+ Step 13 — import review batch + kick bounded cascade
1226
+ import_leads(targetLeadCount=importLimit)
1227
+ wait_for_lead_list_ready
1228
+ confirm_lead_list
1229
+ get_rows_minimal # capture enrichCellIds
1230
+ queue_cells(cellIds=<review-batch Enrich Prospect cells only>) <-- starts bounded chain
1231
+ wait_for_campaign_table_ready # review-batch cascade drains here
1232
+ update_campaign(currentStep=validate-sample)
1233
+
1234
+ Step 14 — observe sample
1235
+ get_rows_minimal # read passesRubric + message cell status per row
1236
+ compute projectedPass
1237
+ if OK: update_campaign(currentStep=auto-execute-messaging)
1238
+ else: diagnose brief-vs-list; if brief: update_campaign_brief + re-queue + wait
1239
+ (check_rubric / bulk_enrich_with_prospeo are NOT called here —
1240
+ cascade already did them. wait_for_rubric_results is OK as a
1241
+ read-only observation helper if you need to block on completion.)
1242
+
1243
+ Step 15 — observe messaging
1244
+ get_rows_minimal # confirm passing rows have completed Generate Message cells
1245
+ (rare) queue_cells on any pending Generate Message cells
1246
+ token-contract spot check via get_rows
1247
+ update_campaign(currentStep=awaiting-user-greenlight)
1248
+ (generate_messages is NOT an MCP tool; messages come from the cascade)
1249
+
1250
+ Step 16 — awaiting-user-greenlight
1251
+ attach_recommended_sequence({ campaignId }) # tier-aware: premium/SN -> If Open Profile->INMAIL_OPEN, else INVITE->accepted->DM
1252
+ re-surface watchUrl + review-batch orientation
1253
+ STOP. DO NOT call start_campaign. DO NOT auto-advance currentStep.
1254
+ ```
1255
+
1256
+ **Prefer `attach_recommended_sequence` over `attach_sequence` in the
1257
+ autonomous tail.** The recommended tool takes only `campaignId` and
1258
+ delegates template selection to the backend's tier-aware selector
1259
+ (`selectTemplateForTiers`). `attach_sequence` requires you to
1260
+ hand-author a `version: 2` template with nodes, branches, and
1261
+ entryNodeId — that's error-prone mid-long-context (galley-off UAT
1262
+ `20260420T195732Z` failed because Claude hit "Invalid node type" and
1263
+ tried to debug via forbidden Bash/Glob calls). Use `attach_sequence`
1264
+ only when the caller explicitly needs a custom non-recommended cadence.
1265
+
1266
+ Hard gates — if you find yourself about to violate any of these, stop
1267
+ first:
1268
+
1269
+ - Do NOT use `update_cell` to write message bodies. The `Generate
1270
+ Message` column's http_request writes those cells via the cascade.
1271
+ `update_cell` remains reachable for legitimate operator overrides
1272
+ AFTER the tail hands off.
1273
+ - Do NOT call `check_rubric`, `enrich_with_prospeo`, or
1274
+ `bulk_enrich_with_prospeo` in the tail. Those are direct-API
1275
+ mutation tools that fetch data to the caller without writing to
1276
+ the workflow table cells. Step 13's `queue_cells` already
1277
+ triggers the column-level enrichment that populates those cells.
1278
+ Running these tools in the tail produces duplicate cost with no
1279
+ cell side effect.
1280
+ - `wait_for_rubric_results` is read-only and OK to use as an
1281
+ observation helper when you need to block on rubric completion —
1282
+ it does not mutate cells. Prefer `get_rows_minimal` +
1283
+ `wait_for_campaign_table_ready` for the primary cascade-drain
1284
+ path, but use `wait_for_rubric_results({ targetCount: cohortSize })`
1285
+ when the table-level wait returns before rubric cells finish.
1286
+ - `start_campaign` is FORBIDDEN in the autonomous tail. It belongs only
1287
+ in the Claude-greenlight path, AFTER the user signals "start". See
1288
+ `references/final-handoff-contract.md`.
1289
+ - You MAY NOT call `start_campaign` without a prior successful
1290
+ `attach_recommended_sequence` (or `attach_sequence` if a custom
1291
+ cadence is explicitly required) in the same run.
1292
+ - You MAY NOT call `start_campaign` while ANY passing row has an
1293
+ empty `Generate Message` cell. If you discover empty message cells in
1294
+ the greenlight turn, ABORT greenlight and return to Step 15 first.
1295
+ - You MAY NOT call `attach_sequence` while ANY passing row has an
1296
+ empty `Generate Message` cell. Before `attach_sequence`, call
1297
+ `get_rows_minimal` and confirm every row where `passesRubric=true`
1298
+ has a `completed` Generate Message cell with non-empty `result`. If
1299
+ any are pending, call `queue_cells` on those generateMessageCellIds
1300
+ and wait. If rows truly won't message, ESCALATE.
1301
+ - You MAY NOT advance past Step 13 without calling `queue_cells` on
1302
+ the review-batch Enrich Prospect cells. Without it, every downstream
1303
+ cell stays `pending` and the campaign ships empty.
1304
+ - You MAY NOT queue enrichment for rows outside the configured review
1305
+ batch before the user approves expansion. Full-list enrichment/message
1306
+ generation is a credit-spend decision and must happen after the user
1307
+ has reviewed the sample.
1308
+
1309
+ Load `core/auto-execute.yaml` exactly once at the start of Step 13. All
1310
+ subsequent steps read the already-parsed config. Do not re-load mid-run.
1311
+ Load each subskill prompt (`create-campaign-v2`, `research-sender`,
1312
+ `generate-messages`) at most once per run — if a tool result already
1313
+ told you to load a prompt, load it and remember; do not re-request the
1314
+ same prompt later.
1315
+
1316
+ After every `update_campaign({ currentStep: ... })` in the tail, narrate
1317
+ what changed and orient the user to what the watch link will show next —
1318
+ reuse the v1 `create-campaign` watch-mode pattern verbatim.
1319
+
1320
+ ## Step 13: auto-execute-leads
1321
+
1322
+ Entered on `CampaignOffer.currentStep === "auto-execute-leads"` (set by
1323
+ atomic mint).
1324
+
1325
+ > Reminder: every provider search you run from this point forward — the
1326
+ > review-batch source rerun in Step 13, any expansion search after the
1327
+ > review batch, alternate-lane probes, account-based reruns, and operator
1328
+ > follow-ups — MUST include `campaignOfferId`. This applies to
1329
+ > `search_prospeo`, `search_sales_nav`, `search_apollo`,
1330
+ > `search_signals`, and any other provider search tool added later.
1331
+ > Campaignless searches after mint orphan results from the campaign UI.
1332
+
1333
+ 1. Load `core/auto-execute.yaml`. Capture `import.importLimit`,
1334
+ `sample.sampleSize`, `sample.minProjectedPass`,
1335
+ `sample.maxRevisionRounds`, `messaging.tokenContract`,
1336
+ `messaging.critique.enabled`, `handoff.autoStart`,
1337
+ `handoff.orientation`, `retry.sameToolSameError`,
1338
+ `logging.logEveryThresholdTrip`.
1339
+ 2. Resolve the approved source before importing. If there is no
1340
+ `lead-source-intake.json`, keep the existing discovered-provider path:
1341
+ `import_leads({ campaignOfferId, targetLeadCount: importLimit })`.
1342
+ If a manifest exists, branch by `sourceType`:
1343
+ - `supplied-linkedin-profiles`: revalidate file metadata and confirmation
1344
+ token, confirm `load_csv_linkedin_leads` only after approval, persist the
1345
+ returned `leadListId`, then call
1346
+ `confirm_lead_list({ sourceLeadListId: leadListId, targetLeadCount:
1347
+ importLimit })`.
1348
+ - `existing-lead-list`: revalidate that the lead list still exists in the
1349
+ same workspace, reuse it as `sourceLeadListId`, then call
1350
+ `confirm_lead_list({ sourceLeadListId, targetLeadCount: importLimit })`.
1351
+ - `supplied-domains`: reuse or confirm `load_csv_domains`, preserving
1352
+ `domainFilterId`; run a campaign-associated Prospeo people search with
1353
+ `campaignOfferId`, provider prompt preflight, and that `domainFilterId`;
1354
+ then import with `targetLeadCount: importLimit`.
1355
+ Never exceed `importLimit`. This is a review batch, not a full
1356
+ campaign-scale import. If a source search/list contains more rows than
1357
+ the cap, import only the first `importLimit` rows and leave the rest for
1358
+ a later expansion step.
1359
+ Persist or recover materialized IDs on resume: `leadListId`,
1360
+ `domainFilterId`, `searchId`, `campaignTableId`, imported row IDs, and queued
1361
+ cell IDs. If the source file changed after preview, or an existing lead list
1362
+ is missing/wrong-workspace, escalate before materialization instead of
1363
+ silently applying stale preview data. Retrying Step 13 must not duplicate
1364
+ lead lists, searches, campaign rows, or queued cells.
1365
+ 3. `wait_for_lead_list_ready` and `confirm_lead_list`.
1366
+ 4. **Verify the table is set up properly before queuing.** Call
1367
+ `get_rows_minimal({ tableId })` and confirm:
1368
+ - `Enrich Prospect` column exists on the table and every imported
1369
+ row has a corresponding cell with `status === "pending"`.
1370
+ - `DNC Check`, `ICP Score`, `Passes Rubric`, and `Generate Message`
1371
+ columns all exist too (the cascade depends on all of them).
1372
+ - If any are missing, or the cell is in a non-`pending` terminal
1373
+ state from an earlier run, ESCALATE with an explicit "table not
1374
+ configured for autonomous tail" diagnosis. Do NOT attempt to
1375
+ patch the table shape from the tail.
1376
+ 5. **Kick off the bounded review-batch cascade.** `queue_cells({ tableId,
1377
+ cellIds: <pending enrichCellIds for the imported review batch only> })`.
1378
+ You queue ONLY the review-batch `Enrich Prospect` cells; the cascade
1379
+ auto-dispatches `DNC Check`, `ICP Score`, `Passes Rubric`, and (for rows
1380
+ where `Passes Rubric === true`) `Generate Message` as each upstream
1381
+ completes. Do not queue cells for any rows beyond `importLimit` before
1382
+ user expansion approval.
1383
+ 6. `wait_for_campaign_table_ready` — waits for the review-batch cascade to
1384
+ drain.
1385
+ If it times out with most cells still `pending`, ESCALATE: the
1386
+ engine is blocked.
1387
+ 7. If the import returns zero usable leads, ESCALATE per
1388
+ `references/escalation-ladder.md` (hard fail).
1389
+ 8. `update_campaign({ campaignId, currentStep: "validate-sample" })`.
1390
+ 9. Orient user on the watch link (same pattern as every other step
1391
+ change).
1392
+
1393
+ **Do NOT call `check_rubric`, `wait_for_rubric_results`,
1394
+ `enrich_with_prospeo`, or `bulk_enrich_with_prospeo` in Step 13.**
1395
+ Those are direct-API tools that fetch enrichment/scoring data to the
1396
+ caller; they do not write to the workflow table cells. The cascade
1397
+ triggered by `queue_cells` runs the `Enrich Prospect` column's
1398
+ http_request, which writes the enrichment into the cell, which
1399
+ triggers the next column, and so on. Running the direct-API tools
1400
+ is duplicate work with no cell-population side effect.
1401
+
1402
+ ## Step 14: validate-sample (loop)
1403
+
1404
+ Entered on `CampaignOffer.currentStep === "validate-sample"`. Full
1405
+ decision tree lives in `references/sample-validation-loop.md`.
1406
+
1407
+ **Step 14 is pure observation.** The bounded review-batch cascade that Step
1408
+ 13 kicked off (via `queue_cells` on Enrich Prospect) has already written ICP
1409
+ scores and Passes Rubric values back into the workflow table. Step 14 reads
1410
+ those review-batch cell results — it does NOT call `check_rubric`,
1411
+ `wait_for_rubric_results`, `bulk_enrich_with_prospeo`, or any other
1412
+ direct enrichment/scoring tool. All of that already happened in the
1413
+ cascade; running them again is wasted work and can overwrite the
1414
+ real cell results with unrelated data.
1415
+
1416
+ Shape:
1417
+
1418
+ ```text
1419
+ get_rows_minimal({ tableId })
1420
+ passInSample = count of first sampleSize review-batch rows with passesRubric === true
1421
+ projectedPass = round(passInSample / sampleSize * importLimit)
1422
+
1423
+ if projectedPass >= minProjectedPass:
1424
+ advance to Step 15
1425
+ else:
1426
+ diagnose brief-vs-list per sample-validation-loop.md
1427
+ - brief: autonomous update_campaign_brief, then re-kick cascade
1428
+ (queue_cells on enrichCellIds) and wait for the new
1429
+ results
1430
+ - list: ESCALATE (no auto-revision of leads)
1431
+ - unknown: ESCALATE
1432
+ revisionRound += 1
1433
+ if revisionRound > maxRevisionRounds:
1434
+ ESCALATE
1435
+ ```
1436
+
1437
+ Persisted counter rule: `revisionRound` persists across skill resume.
1438
+ Do NOT reset to 0 on resume — a stale resume must still respect the
1439
+ `maxRevisionRounds` cap.
1440
+
1441
+ A "list problem" diagnosis NEVER triggers autonomous
1442
+ `update_campaign_brief`. Escalate instead.
1443
+
1444
+ When the sample passes the projected-pass floor, call
1445
+ `update_campaign({ campaignId, currentStep: "auto-execute-messaging" })`
1446
+ and orient the user that messaging will complete for the review batch only.
1447
+
1448
+ ## Step 15: auto-execute-messaging
1449
+
1450
+ Entered on `CampaignOffer.currentStep === "auto-execute-messaging"`.
1451
+
1452
+ **Messages are produced by the `Generate Message` column cascade, not
1453
+ by a separate tool.** Do NOT call a `generate_messages` MCP tool —
1454
+ that tool does not exist; `generate-messages` is a subskill prompt
1455
+ loaded during live messaging drafts. In the autonomous tail, messages
1456
+ flow through the column pipeline: `Enrich Prospect` →
1457
+ `DNC Check` → `ICP Score` → `Passes Rubric` → `Generate Message`.
1458
+ Each column's http_request auto-fires when its upstream dependency
1459
+ completes AND passes gate (e.g. `Passes Rubric === true` before
1460
+ `Generate Message`). Your job in Step 15 is to WAIT for the review-batch
1461
+ cascade to reach `Generate Message` for rows that passed ICP, and verify
1462
+ the output — not to generate messages manually.
1463
+
1464
+ **What Step 15 does:**
1465
+
1466
+ 1. `get_rows_minimal` to read `passesRubric` + `generateMessageCellId`
1467
+ - the Generate Message cell's `status` and `result` per row.
1468
+ Before queueing or waiting on Generate Message cells, confirm the
1469
+ minted campaign brief still contains `{{...}}` in `## Approved Message
1470
+ Template`. If it does not, fail before the cascade runs. Do not repair
1471
+ after mint; the template must be present during mint so Step 15 never
1472
+ starts in freeform generation mode.
1473
+ 2. For every row where `passesRubric === true`:
1474
+ - The Generate Message cell should be `running` or `completed`
1475
+ already (cascade auto-fired it). If it is still `pending`, queue
1476
+ it explicitly: `queue_cells({ tableId, cellIds:
1477
+ <generateMessageCellIds> })`.
1478
+ 3. `wait_for_campaign_table_ready` (or poll) until every passing
1479
+ row's Generate Message cell is `completed`.
1480
+ 4. Read the results back via `get_rows` (full) and sanity-check a
1481
+ sample against the Phase 84 token contract: no unresolved
1482
+ `{{tokens}}`, no invented proof, one sentence per line, etc.
1483
+ 5. If the sample fails the token contract, diagnose brief-vs-list
1484
+ (same revision loop as Step 14) and escalate if over
1485
+ `maxRevisionRounds`.
1486
+ 6. On success, `update_campaign({ campaignId, currentStep:
1487
+ "awaiting-user-greenlight" })`.
1488
+
1489
+ **Do NOT hand-write message bodies via `update_cell`.** `update_cell`
1490
+ is reachable for legitimate operator overrides AFTER the tail hands
1491
+ off, but during autonomous tail the cascade owns the writes. If you
1492
+ find yourself drafting a message body and about to push it through
1493
+ `update_cell`, you have lost state — stop, read the cells, and let
1494
+ the pipeline finish.
1495
+
1496
+ **What `attach_sequence` does (Step 16, not here):** binds the
1497
+ DM/InMail cadence template to the workflow table. Completely different
1498
+ job from messaging. Does NOT generate or write message text. A
1499
+ sequence with no `Generate Message` outputs would try to send empty
1500
+ strings, which is why Step 16 requires Step 15 to be complete.
1501
+
1502
+ 1. Observe the review-batch messages on the same sample that passed
1503
+ validation.
1504
+ 2. If `messaging.critique.enabled` is true (Plan 85-03), run the
1505
+ bounded critique pass on the sample output per
1506
+ `references/parallel-critique-protocol.md`. The pass runs on at
1507
+ most `messaging.critique.sampleSize` rows, sends each row through
1508
+ three parallel critics (targeting / copy / voice) that return
1509
+ structured JSON, and merges the voices in a synthesis step that
1510
+ enforces the Phase 84 token contract verbatim and re-runs the
1511
+ finalizer pass. Any rewrite that invents proof or uses an
1512
+ unsupported token falls back to the plain generated message. A
1513
+ trip of `messaging.critique.budgetUsdCap` HALTS critique for the
1514
+ remaining sample and continues the plain tail. Plan 85-02 reads
1515
+ the flag but never flips it; critique stays off until the flag is
1516
+ deliberately enabled.
1517
+ 3. If `messaging.critique.opus.enabled` is also true, route the
1518
+ highest-value subset through the Opus / craft-message path per
1519
+ `references/thomas-variant-selection.md`. Opus is capped by
1520
+ `messaging.critique.opus.maxMessagesPerPass` and
1521
+ `messaging.critique.opus.budgetUsdCap`; tripping either cap keeps
1522
+ the remaining rows on the non-Opus synthesizer. Opus is bound by
1523
+ the same token contract as every other rewrite path.
1524
+ 4. Check `messaging.tokenContract`. When `strict`, reject any sample
1525
+ message that contains unresolved or unsupported tokens (including
1526
+ any critique rewrite that tried to introduce one).
1527
+ 5. If the sample passes the token contract (and critique when enabled),
1528
+ stop at the review-batch handoff. Do NOT scale to the full source list
1529
+ before explicit user expansion approval.
1530
+ 6. If the sample fails the token contract or critique, diagnose +
1531
+ loop the same way Step 14 does (brief-vs-list), subject to the same
1532
+ `maxRevisionRounds` cap.
1533
+ 7. On success, `update_campaign({ campaignId, currentStep:
1534
+ "awaiting-user-greenlight" })` and orient the user.
1535
+
1536
+ Critique failure modes NEVER escalate. A critic timeout, a total
1537
+ timeout, a budget trip, a fake-proof rejection, or an unsupported-
1538
+ token rejection all fall back to the plain generated message for that
1539
+ row. Step 15 does not stall on critique.
1540
+
1541
+ ## Step 16: awaiting-user-greenlight
1542
+
1543
+ Entered on `CampaignOffer.currentStep === "awaiting-user-greenlight"`.
1544
+ Full contract lives in `references/final-handoff-contract.md`.
1545
+
1546
+ Shape:
1547
+
1548
+ 1. `attach_sequence` (bind the committed sequence to the campaign).
1549
+ 2. Re-surface the `watchUrl` using `references/watch-link-handoff.md`.
1550
+ 3. Surface the `handoff.orientation` string from `auto-execute.yaml`
1551
+ alongside the link.
1552
+ 4. STOP. Do NOT call `start_campaign`. Do NOT auto-advance
1553
+ `currentStep`. The autonomous tail ends here.
1554
+ 5. Make the credit boundary explicit in the handoff: only the first review
1555
+ batch has been enriched/messaged; expanding to more leads requires a
1556
+ separate user instruction.
1557
+
1558
+ Two equally valid greenlight channels take Step 16 into a running
1559
+ campaign:
1560
+
1561
+ - **UI path** — user clicks "Approve all" and "Start Campaign" in the
1562
+ watch-link UI. The skill stays idle. On the next resume, it observes
1563
+ the running state and shifts to a "campaign is live" confirmation.
1564
+ - **Claude greenlight path** — user replies with an affirmative ("yeah
1565
+ start" / "looks good, start" / "ship it"). The skill then performs,
1566
+ in order: (a) bulk-approve queued messages via the EXISTING endpoint
1567
+ `POST /api/v3/workflow-tables/cells/approve-batch` (do NOT invent a
1568
+ new tool/endpoint), (b) `start_campaign({ campaignId })`, (c)
1569
+ `update_campaign({ campaignId, currentStep: "running" })`, (d)
1570
+ re-surface `watchUrl` plus a "campaign is live" confirmation.
1571
+
1572
+ A Claude greenlight on an already-running campaign is a no-op
1573
+ confirmation, NOT a duplicate start. Detect via `get_campaign` before
1574
+ calling any mutating tool; if the campaign is already running, skip
1575
+ approve-batch / start_campaign / update_campaign and just confirm.
1576
+
1577
+ Workspace/sender mismatch at greenlight time aborts the start with the
1578
+ same invariant Phase 84 enforces.
1579
+
1580
+ ## Threshold Trips and Logging
1581
+
1582
+ When `logging.logEveryThresholdTrip === true` (default), log every
1583
+ threshold trip: `projectedPass` computation, retry cap, revision cap,
1584
+ hard-fail. These logs drive `auto-execute.yaml` tuning after 3-5 real
1585
+ runs.
1586
+
1587
+ ## Tail Hard Rules
1588
+
1589
+ - Review-batch sourcing/import happens in Step 13, not during atomic mint.
1590
+ - Full-list expansion is not part of the default tail. It requires user
1591
+ approval after the review batch.
1592
+ Import must never exceed `importLimit`.
1593
+ - The tail NEVER calls `start_campaign` on its own.
1594
+ - The tail NEVER auto-revises leads. Brief revision is autonomous; lead
1595
+ revision is always operator-gated.
1596
+ - `revisionRound` persists across resume — no silent reset.
1597
+ - The bulk-approve action in the Claude greenlight path uses the
1598
+ existing `POST /api/v3/workflow-tables/cells/approve-batch` endpoint
1599
+ — do NOT create a new tool or endpoint for this.
1600
+ - Threshold trips are logged for calibration.
1601
+
1602
+ </autonomous_tail>
1603
+
1604
+ <references_index>
1605
+
1606
+ | File | Load when |
1607
+ | ------------------------------------------ | ---------------------------------------------------------------------- |
1608
+ | `references/approval-gate-framing.md` | Phase 85 commit gate, before showing the approval packet |
1609
+ | `references/watch-link-handoff.md` | Atomic mint + every tail resume, before surfacing watchUrl |
1610
+ | `references/sample-validation-loop.md` | Step 14, before enriching + scoring the sample |
1611
+ | `references/escalation-ladder.md` | Any tail step that needs to decide retry / revise / escalate |
1612
+ | `references/final-handoff-contract.md` | Step 16, and every Claude greenlight turn |
1613
+ | `references/parallel-critique-protocol.md` | Step 15 when `messaging.critique.enabled` is true |
1614
+ | `references/thomas-variant-selection.md` | Step 15 when `messaging.critique.opus.enabled` is true |
1615
+ | `references/thomas-revision-filters.md` | Any critique rewrite, before persisting |
1616
+ | `core/auto-execute.yaml` | Start of Step 13, load once; all tail steps read parsed values |
1617
+ | `core/auto-execute.README.md` | When tuning `auto-execute.yaml` knobs or reviewing threshold-trip logs |
1618
+
1619
+ </references_index>