@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.
- package/.claude-plugin/plugin.json +12 -0
- package/.mcp.json +9 -0
- package/README.md +355 -0
- package/dist/api.d.ts +21 -0
- package/dist/api.js +73 -0
- package/dist/auth.d.ts +60 -0
- package/dist/auth.js +246 -0
- package/dist/engage-memory.d.ts +63 -0
- package/dist/engage-memory.js +354 -0
- package/dist/index-dev.d.ts +2 -0
- package/dist/index-dev.js +17 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +499 -0
- package/dist/skills.d.ts +11 -0
- package/dist/skills.js +97 -0
- package/dist/tools/auth.d.ts +30 -0
- package/dist/tools/auth.js +124 -0
- package/dist/tools/blueprint-commit.d.ts +174 -0
- package/dist/tools/blueprint-commit.js +286 -0
- package/dist/tools/bootstrap.d.ts +64 -0
- package/dist/tools/bootstrap.js +246 -0
- package/dist/tools/campaigns.d.ts +589 -0
- package/dist/tools/campaigns.js +892 -0
- package/dist/tools/cells.d.ts +58 -0
- package/dist/tools/cells.js +48 -0
- package/dist/tools/context.d.ts +88 -0
- package/dist/tools/context.js +271 -0
- package/dist/tools/csv-domains.d.ts +73 -0
- package/dist/tools/csv-domains.js +464 -0
- package/dist/tools/csv-linkedin.d.ts +102 -0
- package/dist/tools/csv-linkedin.js +712 -0
- package/dist/tools/direct-campaigns.d.ts +240 -0
- package/dist/tools/direct-campaigns.js +250 -0
- package/dist/tools/engage-bootstrap.d.ts +94 -0
- package/dist/tools/engage-bootstrap.js +205 -0
- package/dist/tools/engage-discovery.d.ts +78 -0
- package/dist/tools/engage-discovery.js +150 -0
- package/dist/tools/engage-memory.d.ts +181 -0
- package/dist/tools/engage-memory.js +143 -0
- package/dist/tools/engage-state.d.ts +72 -0
- package/dist/tools/engage-state.js +62 -0
- package/dist/tools/enrichment.d.ts +167 -0
- package/dist/tools/enrichment.js +174 -0
- package/dist/tools/flow-preflight.d.ts +68 -0
- package/dist/tools/flow-preflight.js +138 -0
- package/dist/tools/framework.d.ts +44 -0
- package/dist/tools/framework.js +153 -0
- package/dist/tools/interaction-mode.d.ts +27 -0
- package/dist/tools/interaction-mode.js +102 -0
- package/dist/tools/leads.d.ts +2417 -0
- package/dist/tools/leads.js +2307 -0
- package/dist/tools/linkedin.d.ts +210 -0
- package/dist/tools/linkedin.js +229 -0
- package/dist/tools/navigation.d.ts +91 -0
- package/dist/tools/navigation.js +381 -0
- package/dist/tools/one-off.d.ts +229 -0
- package/dist/tools/one-off.js +273 -0
- package/dist/tools/processing.d.ts +70 -0
- package/dist/tools/processing.js +56 -0
- package/dist/tools/prompts.d.ts +211 -0
- package/dist/tools/prompts.js +210 -0
- package/dist/tools/provider-preflight.d.ts +21 -0
- package/dist/tools/provider-preflight.js +59 -0
- package/dist/tools/readiness.d.ts +261 -0
- package/dist/tools/readiness.js +510 -0
- package/dist/tools/rows.d.ts +126 -0
- package/dist/tools/rows.js +105 -0
- package/dist/tools/rubrics.d.ts +497 -0
- package/dist/tools/rubrics.js +681 -0
- package/dist/tools/senders.d.ts +44 -0
- package/dist/tools/senders.js +69 -0
- package/dist/tools/sequencer.d.ts +127 -0
- package/dist/tools/sequencer.js +194 -0
- package/dist/tools/tables.d.ts +35 -0
- package/dist/tools/tables.js +36 -0
- package/dist/tools/verify-row.d.ts +36 -0
- package/dist/tools/verify-row.js +38 -0
- package/dist/tools/workspaces.d.ts +140 -0
- package/dist/tools/workspaces.js +139 -0
- package/dist/utils/workspace-root.d.ts +1 -0
- package/dist/utils/workspace-root.js +39 -0
- package/package.json +46 -0
- package/skills/building-gtm-tables/SKILL.md +216 -0
- package/skills/building-gtm-tables/core/auto-execute.yaml +19 -0
- package/skills/building-gtm-tables/core/blueprint-schema.json +72 -0
- package/skills/building-gtm-tables/references/brief-to-blueprint.md +334 -0
- package/skills/building-gtm-tables/references/column-type-catalog.md +318 -0
- package/skills/building-gtm-tables/references/common-blueprints.fixtures.ts +199 -0
- package/skills/building-gtm-tables/references/common-blueprints.md +44 -0
- package/skills/building-gtm-tables/references/failure-taxonomy.md +197 -0
- package/skills/building-gtm-tables/references/uat-seed-prompts.md +37 -0
- package/skills/building-gtm-tables/references/verify-loop.md +74 -0
- package/skills/campaign-messages/SKILL.md +173 -0
- package/skills/campaign-messages/flow.v1.json +75 -0
- package/skills/craft-message/SKILL.md +401 -0
- package/skills/create-campaign/ARCHITECTURE.md +232 -0
- package/skills/create-campaign/DISCUSS.md +296 -0
- package/skills/create-campaign/FLOW_ASCII.md +240 -0
- package/skills/create-campaign/HOST-PARITY-CHECKLIST.md +49 -0
- package/skills/create-campaign/README.md +142 -0
- package/skills/create-campaign/SKILL.md +286 -0
- package/skills/create-campaign/context/README.md +67 -0
- package/skills/create-campaign/context/_TEMPLATE.md +12 -0
- package/skills/create-campaign/context/context.md +35 -0
- package/skills/create-campaign/context/learnings.md +16 -0
- package/skills/create-campaign/context/registry.json +19 -0
- package/skills/create-campaign/core/flow.v1.json +217 -0
- package/skills/create-campaign/core/policy.md +191 -0
- package/skills/create-campaign/core/providers/apollo.json +35 -0
- package/skills/create-campaign/core/providers/prospeo.json +34 -0
- package/skills/create-campaign/core/providers/registry.json +31 -0
- package/skills/create-campaign/core/providers/sales-nav.json +37 -0
- package/skills/create-campaign/core/providers/signal-discovery.json +42 -0
- package/skills/create-campaign/references/brief-template.md +64 -0
- package/skills/create-campaign/references/campaign-quality.md +84 -0
- package/skills/create-campaign/references/copy-calibration-examples.md +120 -0
- package/skills/create-campaign/references/offer-patterns.md +108 -0
- package/skills/create-campaign/references/provider-selection-strategy.md +212 -0
- package/skills/create-campaign/references/question-examples.md +167 -0
- package/skills/create-campaign/references/token-fill-examples.md +81 -0
- package/skills/create-campaign-brief/ARCHITECTURE.md +72 -0
- package/skills/create-campaign-brief/DISCUSS.md +64 -0
- package/skills/create-campaign-brief/README.md +176 -0
- package/skills/create-campaign-brief/SKILL.md +537 -0
- package/skills/create-campaign-brief/references/brief-synthesis-rules.md +100 -0
- package/skills/create-campaign-brief/references/brief-template.md +220 -0
- package/skills/create-campaign-brief/references/campaign-idea-options.md +30 -0
- package/skills/create-campaign-brief/references/copy-appendix-template.md +62 -0
- package/skills/create-campaign-brief/references/draft-lifecycle.md +23 -0
- package/skills/create-campaign-brief/references/examples/MANIFEST.json +89 -0
- package/skills/create-campaign-brief/references/examples/briefs/clover.md +223 -0
- package/skills/create-campaign-brief/references/examples/briefs/galley.md +222 -0
- package/skills/create-campaign-brief/references/examples/briefs/gelee.md +220 -0
- package/skills/create-campaign-brief/references/examples/briefs/hey-digital.md +234 -0
- package/skills/create-campaign-brief/references/examples/briefs/persona.md +231 -0
- package/skills/create-campaign-brief/references/examples/briefs/revvix.md +220 -0
- package/skills/create-campaign-brief/references/examples/briefs/sellable-dev.md +220 -0
- package/skills/create-campaign-brief/references/examples/briefs/superposition.md +233 -0
- package/skills/create-campaign-brief/references/examples/briefs/superpower.md +219 -0
- package/skills/create-campaign-brief/references/examples/briefs/westpark-villas.md +220 -0
- package/skills/create-campaign-brief/references/icp-lock-question-bank.md +43 -0
- package/skills/create-campaign-brief/references/messaging-inputs.md +58 -0
- package/skills/create-campaign-brief/references/output-acceptance-rubric.md +62 -0
- package/skills/create-campaign-brief/references/phase75-active-runtime-message-pack.md +248 -0
- package/skills/create-campaign-brief/references/phase75-canonical-brief-template.md +319 -0
- package/skills/create-campaign-brief/references/phase75-good-brief-and-messaging-examples.md +445 -0
- package/skills/create-campaign-brief/references/quick-research-protocol.md +39 -0
- package/skills/create-campaign-brief/references/reference-sheet-protocol.md +60 -0
- package/skills/create-campaign-brief/references/zero-shot-iteration-rules.md +66 -0
- package/skills/create-campaign-v2/SKILL.md +1619 -0
- package/skills/create-campaign-v2/core/auto-execute.README.md +219 -0
- package/skills/create-campaign-v2/core/auto-execute.yaml +121 -0
- package/skills/create-campaign-v2/core/flow.v2.json +1643 -0
- package/skills/create-campaign-v2/core/policy.md +82 -0
- package/skills/create-campaign-v2/references/ai-tells.md +253 -0
- package/skills/create-campaign-v2/references/approval-gate-framing.md +346 -0
- package/skills/create-campaign-v2/references/draft-lifecycle.md +110 -0
- package/skills/create-campaign-v2/references/escalation-ladder.md +119 -0
- package/skills/create-campaign-v2/references/filter-leads.md +495 -0
- package/skills/create-campaign-v2/references/final-handoff-contract.md +176 -0
- package/skills/create-campaign-v2/references/gold-standard-message-examples.md +394 -0
- package/skills/create-campaign-v2/references/gold-standard-message-patterns.md +314 -0
- package/skills/create-campaign-v2/references/gold-standard-message-validation-example.md +212 -0
- package/skills/create-campaign-v2/references/lead-validation-preview.md +172 -0
- package/skills/create-campaign-v2/references/parallel-critique-protocol.md +368 -0
- package/skills/create-campaign-v2/references/sample-validation-loop.md +289 -0
- package/skills/create-campaign-v2/references/step-13-import-leads.md +151 -0
- package/skills/create-campaign-v2/references/step-15-re-cascade.md +90 -0
- package/skills/create-campaign-v2/references/thomas-revision-filters.md +521 -0
- package/skills/create-campaign-v2/references/thomas-variant-selection.md +202 -0
- package/skills/create-campaign-v2/references/tier-routing-matrix.md +66 -0
- package/skills/create-campaign-v2/references/validation-criteria.md +367 -0
- package/skills/create-campaign-v2/references/watch-link-handoff.md +106 -0
- package/skills/create-campaign-v2-validation/SKILL.md +296 -0
- package/skills/create-post/SKILL.md +1308 -0
- package/skills/create-rubric/SKILL.md +251 -0
- package/skills/engage/SKILL.md +549 -0
- package/skills/engage/core/README.md +23 -0
- package/skills/engage/core/proven-searches.json +11 -0
- package/skills/engage/core/style-guide.template.md +47 -0
- package/skills/engage/core/tracked-people.json +10 -0
- package/skills/enrich-prospects/SKILL.md +97 -0
- package/skills/find-leads/SKILL.md +467 -0
- package/skills/generate-messages/SKILL.md +2361 -0
- package/skills/interview/SKILL.md +132 -0
- package/skills/interview/core/ENGAGE_STYLE_GUIDE.template.md +54 -0
- package/skills/interview/core/ICP.template.md +54 -0
- package/skills/interview/core/VOICE_PROFILE.template.md +101 -0
- package/skills/providers/apollo.md +520 -0
- package/skills/providers/prospeo.md +398 -0
- package/skills/providers/sales-nav.md +372 -0
- package/skills/providers/signal-discovery.md +495 -0
- package/skills/research/SKILL.md +258 -0
- package/skills/research/config.json +9 -0
- package/skills/research/override.md +13 -0
- package/skills/research-prospect/SKILL.md +99 -0
- package/skills/research-sender/SKILL.md +158 -0
- 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>
|