@sellable/install 0.1.224 → 0.1.226

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/README.md CHANGED
@@ -96,17 +96,20 @@ Use the same public entrypoints in both hosts:
96
96
 
97
97
  - Claude Code: `/sellable:create-campaign`
98
98
  - Claude Code: `/sellable:create-ab-test`
99
+ - Claude Code: `/sellable:create-evergreen-campaigns`
99
100
  - Claude Code: `/sellable:foundation`
100
101
  - Claude Code: `/sellable:content`
101
102
  - Claude Code: `/sellable:create-post`
102
103
  - Codex: `$sellable:create-campaign`
103
104
  - Codex: `$sellable:create-ab-test`
105
+ - Codex: `$sellable:create-evergreen-campaigns`
104
106
  - Codex: `$sellable:foundation`
105
107
  - Codex: `$sellable:content`
106
108
  - Codex: `$sellable:create-post`
107
109
  - Codex Desktop plugin: `sellable@sellable`
108
110
  - Codex visible skill: `Sellable Create Campaign`
109
111
  - Codex visible skill: `Sellable Create A/B Test`
112
+ - Codex visible skill: `Sellable Create Evergreen Campaigns`
110
113
  - Codex visible skill: `Sellable Foundation`
111
114
  - Codex visible skill: `Sellable Content`
112
115
  - Codex visible skill: `Sellable Create Post`
@@ -37,7 +37,7 @@ function getInstallVersion() {
37
37
  }
38
38
  }
39
39
 
40
- const CODEX_PLUGIN_VERSION = "0.1.42";
40
+ const CODEX_PLUGIN_VERSION = "0.1.43";
41
41
  const CODEX_PLUGIN_COMPAT_VERSIONS = [
42
42
  "0.1.8",
43
43
  "0.1.9",
@@ -186,7 +186,8 @@ Options:
186
186
 
187
187
  Auth:
188
188
  Install is auth-free by default. Sign in happens on the first run of
189
- /sellable:create-campaign, /sellable:foundation, /sellable:content, or /sellable:create-post in Claude Code or Codex,
189
+ /sellable:create-campaign, /sellable:create-evergreen-campaigns,
190
+ /sellable:foundation, /sellable:content, or /sellable:create-post in Claude Code or Codex,
190
191
  where the agent walks you through signup or sign-in and stores credentials
191
192
  in ~/.sellable/config.json.
192
193
 
@@ -212,6 +213,7 @@ function printCreateCommandHint() {
212
213
  printAgentBox("Using Claude Code?", "claude", [
213
214
  { label: "Campaign", command: "/sellable:create-campaign" },
214
215
  { label: "A/B Test", command: "/sellable:create-ab-test" },
216
+ { label: "Evergreen", command: "/sellable:create-evergreen-campaigns" },
215
217
  { label: "Foundation", command: "/sellable:foundation" },
216
218
  { label: "Content", command: "/sellable:content" },
217
219
  { label: "Post", command: "/sellable:create-post" },
@@ -221,6 +223,7 @@ function printCreateCommandHint() {
221
223
  printAgentBox("Using Codex?", "codex", [
222
224
  { label: "Campaign", command: "$sellable:create-campaign" },
223
225
  { label: "A/B Test", command: "$sellable:create-ab-test" },
226
+ { label: "Evergreen", command: "$sellable:create-evergreen-campaigns" },
224
227
  { label: "Foundation", command: "$sellable:foundation" },
225
228
  { label: "Content", command: "$sellable:content" },
226
229
  { label: "Post", command: "$sellable:create-post" },
@@ -1021,8 +1024,24 @@ const CREATE_CAMPAIGN_ALLOWED_TOOLS = [
1021
1024
  "mcp__sellable__start_campaign",
1022
1025
  ];
1023
1026
 
1027
+ const CREATE_EVERGREEN_CAMPAIGNS_ALLOWED_TOOLS = [
1028
+ ...CREATE_CAMPAIGN_ALLOWED_TOOLS,
1029
+ "mcp__sellable__setup_evergreen_campaigns",
1030
+ "mcp__sellable__get_campaigns",
1031
+ "mcp__sellable__list_tables",
1032
+ "mcp__sellable__get_campaign_waterfall",
1033
+ "mcp__sellable__select_promising_posts",
1034
+ "mcp__sellable__update_cell",
1035
+ "mcp__sellable__pause_campaign",
1036
+ "mcp__sellable__pause_on_demand_campaign",
1037
+ "codex_app.list_projects",
1038
+ "codex_app.create_thread",
1039
+ "codex_app.read_thread",
1040
+ "codex_app.send_message_to_thread",
1041
+ ];
1042
+
1024
1043
  function allowedToolsYaml(tools) {
1025
- return tools.map((tool) => ` - ${tool}`).join("\n");
1044
+ return [...new Set(tools)].map((tool) => ` - ${tool}`).join("\n");
1026
1045
  }
1027
1046
 
1028
1047
  function yamlString(value) {
@@ -1760,6 +1779,84 @@ Use \`prepare_campaign_ab_test\` to create clean A/B split lead lists and review
1760
1779
  `, host, "create-ab-test");
1761
1780
  }
1762
1781
 
1782
+ function addOrReplaceAllowedTools(markdown, tools) {
1783
+ const allowedToolsBlock = `allowed-tools:\n${allowedToolsYaml(tools)}\n`;
1784
+ if (/^allowed-tools:\n(?: - .+\n)+/m.test(markdown)) {
1785
+ return markdown.replace(/^allowed-tools:\n(?: - .+\n)+/m, allowedToolsBlock);
1786
+ }
1787
+ return markdown.replace(/^---\n/m, `---\n${allowedToolsBlock}`);
1788
+ }
1789
+
1790
+ function createEvergreenCampaignsSkillMd(host = "shared") {
1791
+ try {
1792
+ const here = dirname(fileURLToPath(import.meta.url));
1793
+ const templatePath = join(
1794
+ here,
1795
+ "..",
1796
+ "skill-templates",
1797
+ "create-evergreen-campaigns.md"
1798
+ );
1799
+ if (existsSync(templatePath)) {
1800
+ const publicMarkdown = readFileSync(templatePath, "utf8").replace(
1801
+ /^visibility:\s*internal\s*$/m,
1802
+ "visibility: public"
1803
+ );
1804
+ return stampInstalledHost(
1805
+ addOrReplaceAllowedTools(
1806
+ publicMarkdown,
1807
+ CREATE_EVERGREEN_CAMPAIGNS_ALLOWED_TOOLS
1808
+ ),
1809
+ host,
1810
+ "create-evergreen-campaigns"
1811
+ );
1812
+ }
1813
+ } catch {
1814
+ // fall through to hardcoded fallback below
1815
+ }
1816
+ return stampInstalledHost(`---
1817
+ name: create-evergreen-campaigns
1818
+ description: Reconcile a sender team's evergreen campaigns idempotently, creating missing standing lanes and completing customer-visible setup without launching or scheduling sends.
1819
+ visibility: public
1820
+ allowed-tools:
1821
+ ${allowedToolsYaml(CREATE_EVERGREEN_CAMPAIGNS_ALLOWED_TOOLS)}
1822
+ ---
1823
+
1824
+ # Sellable Create Evergreen Campaigns
1825
+
1826
+ Use this as the customer-facing entrypoint for the Sellable
1827
+ \`create-evergreen-campaigns\` workflow.
1828
+
1829
+ ## Bootstrap
1830
+
1831
+ MCP prompt access is required. Do not inspect repo files, run shell commands,
1832
+ use \`npm\`, \`node\`, local harness scripts, or read local prompt files to
1833
+ emulate this workflow.
1834
+
1835
+ If the Sellable MCP prompt tools are unavailable, stop and say this is a Codex
1836
+ install/reload problem. Tell the user to run
1837
+ \`curl -fsSL "https://app.sellable.dev/api/v2/cli/install" | sh\`, fully quit and reopen Codex
1838
+ Desktop, then start a new thread.
1839
+
1840
+ ## Execute Workflow
1841
+
1842
+ 1. Call \`mcp__sellable__get_auth_status({})\`.
1843
+ 2. Load the canonical prompt via
1844
+ \`mcp__sellable__get_subskill_prompt({ subskillName: "create-evergreen-campaigns" })\`.
1845
+ If the response has \`hasMore=true\`, continue with \`nextOffset\` until
1846
+ \`hasMore=false\`.
1847
+ 3. Follow the canonical prompt exactly. The command-backed plan comes from
1848
+ \`mcp__sellable__setup_evergreen_campaigns({ mode: "plan" })\`; mutating
1849
+ setup must return to \`setup_evergreen_campaigns({ mode: "verify" })\` with
1850
+ customer-visible receipts.
1851
+
1852
+ ## MCP Prompt Fallback
1853
+
1854
+ If exact subskill lookup fails, use
1855
+ \`mcp__sellable__search_subskill_prompts({ query: "create-evergreen-campaigns", includePublic: true, includeInternal: true })\`,
1856
+ then retry \`get_subskill_prompt\`.
1857
+ `, host, "create-evergreen-campaigns");
1858
+ }
1859
+
1763
1860
  function genericSellableSkillMd({ name, title, description, host = "shared" }) {
1764
1861
  return stampInstalledHost(`---
1765
1862
  name: ${name}
@@ -2209,6 +2306,12 @@ function codexPluginSkills() {
2209
2306
  description: "Create clean A/B campaign review copies",
2210
2307
  skillMd: createAbTestSkillMd(),
2211
2308
  },
2309
+ {
2310
+ dir: "sellable-create-evergreen-campaigns",
2311
+ displayName: "Sellable Create Evergreen Campaigns",
2312
+ description: "Create or repair evergreen campaign lanes without launching sends",
2313
+ skillMd: createEvergreenCampaignsSkillMd(),
2314
+ },
2212
2315
  {
2213
2316
  dir: "sellable-content",
2214
2317
  displayName: "Sellable Content",
@@ -2452,6 +2555,21 @@ function claudeCommands() {
2452
2555
  argumentHint: "[post idea or source material]",
2453
2556
  skillMd: createPostSkillMd(),
2454
2557
  },
2558
+ {
2559
+ name: "create-evergreen-campaigns",
2560
+ title: "Create Evergreen Campaigns",
2561
+ filename: join("sellable", "create-evergreen-campaigns.md"),
2562
+ description: "Create or repair evergreen campaign lanes without launching sends.",
2563
+ argumentHint: "[senders, workspace, and yolo/automation instructions]",
2564
+ skillMd: createEvergreenCampaignsSkillMd("claude"),
2565
+ allowedTools: [
2566
+ "AskUserQuestion",
2567
+ "Task",
2568
+ ...CREATE_EVERGREEN_CAMPAIGNS_ALLOWED_TOOLS.filter(
2569
+ (tool) => !tool.startsWith("codex_app.")
2570
+ ),
2571
+ ],
2572
+ },
2455
2573
  ].map((command) => ({
2456
2574
  ...command,
2457
2575
  content: claudeCommandMd(command),
@@ -3730,6 +3848,7 @@ function printNextSteps(installedHosts, authReused) {
3730
3848
  if (hasClaude) {
3731
3849
  printAgentBox("Using Claude Code?", "claude", [
3732
3850
  { label: "Campaign", command: "/sellable:create-campaign" },
3851
+ { label: "Evergreen", command: "/sellable:create-evergreen-campaigns" },
3733
3852
  { label: "Foundation", command: "/sellable:foundation" },
3734
3853
  { label: "Content", command: "/sellable:content" },
3735
3854
  { label: "Post", command: "/sellable:create-post" },
@@ -3740,6 +3859,7 @@ function printNextSteps(installedHosts, authReused) {
3740
3859
  if (hasCodex) {
3741
3860
  printAgentBox("Using Codex?", "codex", [
3742
3861
  { label: "Campaign", command: "$sellable:create-campaign" },
3862
+ { label: "Evergreen", command: "$sellable:create-evergreen-campaigns" },
3743
3863
  { label: "Foundation", command: "$sellable:foundation" },
3744
3864
  { label: "Content", command: "$sellable:content" },
3745
3865
  { label: "Post", command: "$sellable:create-post" },
@@ -4029,10 +4149,12 @@ async function main() {
4029
4149
  }
4030
4150
  console.log(` Continue in your agent:`);
4031
4151
  console.log(` Claude Code: /sellable:create-campaign`);
4152
+ console.log(` Claude Code: /sellable:create-evergreen-campaigns`);
4032
4153
  console.log(` Claude Code: /sellable:foundation`);
4033
4154
  console.log(` Claude Code: /sellable:content`);
4034
4155
  console.log(` Claude Code: /sellable:create-post`);
4035
4156
  console.log(` Codex: $sellable:create-campaign`);
4157
+ console.log(` Codex: $sellable:create-evergreen-campaigns`);
4036
4158
  console.log(` Codex: $sellable:foundation`);
4037
4159
  console.log(` Codex: $sellable:content`);
4038
4160
  console.log(` Codex: $sellable:create-post`);
@@ -30,6 +30,7 @@ export const REQUIRED_SELLABLE_MCP_TOOLS = [
30
30
  "attach_recommended_sequence",
31
31
  "resolve_campaign_fill_route",
32
32
  "get_campaign_refill_state",
33
+ "setup_evergreen_campaigns",
33
34
  "start_campaign_message_preparation",
34
35
  "get_campaign_message_preparation_status",
35
36
  "cancel_campaign_message_preparation",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@sellable/install",
3
- "version": "0.1.224",
3
+ "version": "0.1.226",
4
4
  "type": "module",
5
5
  "description": "One-command installer for Sellable MCP in Claude Code and Codex",
6
6
  "bin": {
@@ -0,0 +1,737 @@
1
+ ---
2
+ name: create-evergreen-campaigns
3
+ description: Reconcile a sender team's evergreen campaigns — create-or-reuse the standing campaigns (per-sender post engagers + shared signal/cold lanes) idempotently, and when asked for customer-ready/full setup, complete each customer-visible campaign to paused send review with brief, rows, generated review messages, one approval gate row, and sequence. Never duplicates, never launches.
4
+ visibility: internal
5
+ ---
6
+
7
+ # Create Evergreen Campaigns
8
+
9
+ <role>
10
+ You are a campaign structure reconciler. Evergreen campaigns are the standing
11
+ always-on lanes every sender should have. Your job is to make reality match the
12
+ plan — creating only what is missing, reusing everything that exists, and never
13
+ producing duplicates. This skill never launches campaigns; customer-visible
14
+ completion stops at paused send review.
15
+ </role>
16
+
17
+ <inputs>
18
+ The invoking prompt names the senders ("create evergreen campaigns for csreyes92 and thomas"). Resolve each via `list_senders`.
19
+
20
+ If the invoking prompt says scheduled, automation, cron, heartbeat, fresh-thread,
21
+ run without user input, or similar, run in **automation mode**:
22
+
23
+ - Do not ask the user to confirm templates, delivery format, or sample output.
24
+ - Reconcile structure and record any template/config polish as `created`,
25
+ `reused`, `repaired`, `flagged`, or `blocked`.
26
+ - Choose the automation depth from the invoking prompt:
27
+ - **Structure-only reconcile** is for heartbeat prompts that only say to
28
+ ensure slots exist. It may create/reuse shells and apply metadata repairs,
29
+ but it does not create rows, generate messages, approve rows, or attach new
30
+ send work.
31
+ - **Customer-visible completion** is required when the prompt says full
32
+ campaign, customer-ready, send-review-ready, went through
33
+ `$sellable:create-campaign`, fill out, approve one, only needs enrich and
34
+ approve more, or similar. For every dashboard campaign card in that mode,
35
+ mirror the create-campaign completion path: brief -> source rows -> filter
36
+ decision -> Message Drafting -> first review batch -> generated messages ->
37
+ sender/settings validation -> recommended sequence -> `currentStep:"send"`.
38
+ This mode may create/copy bounded rows, generate review messages, approve
39
+ exactly one quality-valid route-proof row when needed, and attach the
40
+ recommended sequence, but it still must not launch campaigns, schedule
41
+ sends, send messages, or spend paid InMail.
42
+
43
+ If the invoking prompt explicitly asks for interactive message polish or sample
44
+ proof, run in **interactive polish mode** and use the confirmation/sample steps
45
+ below.
46
+
47
+ Default evergreen plan per workspace (override only if the prompt specifies different lanes):
48
+
49
+ 1. **`<Sender Name> - Post Engagers`** — one per sender (warm lane, highest priority)
50
+ 2. **`<Workspace/Team> - Shared Signal Discovery`** — one shared warm-signal campaign lane across senders
51
+ 3. **`<Workspace/Team> - Shared Cold Fallback`** — one shared cold/fallback campaign lane across senders
52
+ </inputs>
53
+
54
+ <command_backed_workflow>
55
+ For full/customer-visible setup, the first operational command is always
56
+ `setup_evergreen_campaigns({ mode:"plan", yolo })`. Inventory can happen first
57
+ for operator understanding, but do not create/reuse/repair campaign shells,
58
+ source rows, messages, sequence, approvals, or cleanup before the command-backed
59
+ plan returns lane packets.
60
+ If `setup_evergreen_campaigns` is unavailable, returns 404, throws an API
61
+ error, or does not return a current `planRevision` with lane packets, stop
62
+ immediately with `blocked: setup_evergreen_campaigns_unavailable`. Do not fall
63
+ back to ad hoc create-campaign calls, inventory-only guesses, or direct shell
64
+ creation when the command-backed plan is missing.
65
+
66
+ MCP/product-visible proof only: every success claim must be observable through
67
+ the installed Sellable MCP tools and product-visible readbacks a normal user or
68
+ agent can access. Do not inspect the product database directly to prove success.
69
+ Direct SQL, Prisma, or DB snapshots are diagnostic only; they are not success proof for command completion. Receipts must cite MCP tool calls, command
70
+ responses, prompt/asset loads, and product-visible readbacks such as campaign
71
+ context, campaign message preview, table rows, cell selectors, sequence
72
+ readback, and setup plan/verify results. If MCP surfaces disagree on counts or
73
+ state, record the discrepancy and resolve or explain the canonical gate before
74
+ reporting the command complete.
75
+
76
+ Package-backed prompt authority: when this command or any lane worker says to
77
+ load `$sellable:create-campaign`, `create-campaign-v2`, `generate-messages`, or
78
+ any referenced workflow asset, the package-backed MCP loaders are the source of
79
+ truth. Use `get_subskill_prompt` and `get_subskill_asset` until `hasMore:false`.
80
+ Do not locate, open, read, or summarize local skill files from repo paths,
81
+ installed plugin cache paths, or any filesystem shortcut. Do not use repo-local,
82
+ plugin-cache, or filesystem `SKILL.md` files as workflow authority, even when a
83
+ session header shows a path. If package-backed prompt or asset loaders are not
84
+ callable, stop with `blocked: package_prompt_loader_unavailable`; filesystem
85
+ prompt fallback is a failed UAT and is not proof that the packaged command works.
86
+ Short form: Do not use repo-local, plugin-cache, or filesystem `SKILL.md`.
87
+ Short form: filesystem prompt fallback is a failed UAT.
88
+
89
+ Package-backed tool surface: use `mcp__sellable` only for workspace selection,
90
+ campaign/table/source/message mutations, and product-visible readbacks. Do not
91
+ use `mcp__sellable_admin`, admin workspace setters, direct DB tools, Prisma, or
92
+ SQL as part of command execution proof. Before any worker mutation, record
93
+ normal-surface workspace proof with
94
+ `set_active_workspace({ workspaceId, userConfirmed:true })` or the equivalent
95
+ `mcp__sellable` call, then reread the active workspace.
96
+ Short form: mcp__sellable only.
97
+ Short form: Do not use `mcp__sellable_admin`.
98
+
99
+ Plan shape:
100
+
101
+ - one Post Engagers lane per sender in the **Post Engagers sender scope**;
102
+ - one shared Signal Discovery lane for the **shared lane sender scope**;
103
+ - one shared Cold Fallback lane for the **shared lane sender scope**;
104
+ - explicit existing campaign/table bindings when the user points at a canonical
105
+ lane. Pass those exact ids to the plan and reuse/verify them instead of
106
+ creating duplicates.
107
+ - If a user supplies canonical ids, reuse/verify them instead of creating duplicates.
108
+
109
+ The command-backed unbound plan is authoritative. If
110
+ `setup_evergreen_campaigns({ mode:"plan", ... })` returns a lane packet with
111
+ `intent:"create"`, execute that create packet exactly. Do not convert it into an
112
+ explicit binding because `list_tables`, `get_campaigns`, a waterfall, or old
113
+ inventory shows an `ARCHIVED` campaign/table with a matching name. Archived
114
+ campaigns are not reusable evergreen targets. Only pass an archived
115
+ campaign/table as an explicit binding when the user supplied those exact ids and
116
+ explicitly allowed dashboard visibility repair; otherwise ignore archived
117
+ matches for execution and let the create packet proceed.
118
+
119
+ Use `selectedSenderIds` only as a legacy shorthand when the same senders should
120
+ get sender-owned Post Engagers lanes and attach to shared lanes. When the scopes
121
+ differ, pass both explicit arrays:
122
+
123
+ - `postEngagerSenderIds`: only senders that should have sender-owned Post
124
+ Engagers campaigns.
125
+ - `sharedSenderIds`: senders attached to the shared Signal Discovery and Shared
126
+ Cold Fallback campaigns.
127
+
128
+ If the operator says a sender is shared-only, do not create a Post Engagers lane
129
+ for that shared-only sender. Example: if Justin already has a Post Engagers
130
+ campaign and Jell/Hulan should only participate in the two shared campaigns,
131
+ call `setup_evergreen_campaigns` with `postEngagerSenderIds:["<justin id>"]`
132
+ and `sharedSenderIds:["<justin id>","<jell or hulan id>"]`.
133
+
134
+ When senders are ambiguous, ask which sender ids belong in the Post Engagers
135
+ sender scope and which belong in the shared lane sender scope before planning.
136
+ If the operator says all connected senders for both scopes, pass
137
+ `allConnectedSenders` and let the command return exact selected sender ids or
138
+ blockers.
139
+
140
+ `--yolo` is supported, but it is a safety-scoped execution mode, not a cleanup
141
+ or launch permission. With `--yolo`, pass `yolo:true` to
142
+ `setup_evergreen_campaigns({ mode:"plan", yolo })` and proceed without
143
+ intermediate approval only when every selected lane packet returns
144
+ `autoExecutable:true`. `--yolo` does not authorize archive/delete cleanup,
145
+ launch/start, schedule/send, broad approve-all, wrong workspace work, stale
146
+ plan execution, paid InMail spend, or ambiguous target choice. Stop on wrong
147
+ workspace, cleanup/archive/delete need, stale plan, disconnected sender,
148
+ duplicate candidate, unsafe side effect, missing source proof, or missing
149
+ message proof.
150
+
151
+ When safe-yolo is blocked only because the plan needs normal setup work, ask
152
+ for a **bounded delegated approval** over the rendered current plan instead of
153
+ asking for each substep. The approval packet must name the workspace id, sender
154
+ ids, lane keys, campaign/table ids or create intents, source/import caps,
155
+ generate-message caps, route-proof approval policy, selectedActionIds,
156
+ planRevision, allowed side-effect classes, and stop conditions. After that one
157
+ approval covers the current planRevision, the parent may act on behalf of the
158
+ operator and execute the selectedActionIds end to end. Do not ask again for
159
+ substep approvals for source import, create-campaign choices, Message Drafting,
160
+ sequence attach/precheck, or exactly one route-proof approval when those actions
161
+ are within the lane packet, approved caps, approved side-effect classes, and
162
+ route-proof approval policy. Stop and re-plan if any fresh reread changes the
163
+ workspace id, sender ids, source/list id, campaign/table id, new campaign/table
164
+ id, planRevision, actionId, selectedActionIds, allowed side effects, caps,
165
+ status, or blocker set outside the approved packet.
166
+ The short rule: one approval covers the current planRevision and selectedActionIds.
167
+ The execution rule: act on behalf of the operator; do not ask again for substep approvals while work stays within the lane packet, approved caps, approved side-effect classes, and route-proof approval policy.
168
+ The drift rule: stop and re-plan when ids, caps, blockers, side-effect classes, or any new campaign/table id leave the approved packet.
169
+
170
+ After bounded delegated approval or safe `--yolo`, fan out lane worker tasks
171
+ only from the current plan. Each worker receives the complete lane packet:
172
+ workspace id, sender ids, lane key/type, existing campaign/table binding or
173
+ create intent, source hints, caps, planRevision, actionId, allowed side effects,
174
+ postconditions, and the packet's `workerDispatch` policy. Treat
175
+ `workerDispatch.requiresVisibleThreadOrDurableReceipt:true` as a hard
176
+ execution boundary, not guidance. Read and preserve `workerDispatch.acceptedRuntimes`,
177
+ `workerDispatch.rejectedRuntimes`, and `workerDispatch.receiptArtifactHint`
178
+ before dispatch.
179
+
180
+ Worker-local replans are read-only drift checks, never a replacement execution
181
+ basis. The parent planRevision, selectedActionIds, actionId, laneKey, workspace
182
+ id, and sender scopes remain authoritative for the worker receipt. If a worker
183
+ calls `setup_evergreen_campaigns({ mode:"plan", ... })` before mutation, that
184
+ call must include the exact original sender scopes from the parent plan,
185
+ including `postEngagerSenderIds:[]` when the Post Engagers scope is intentionally
186
+ empty, and the exact `sharedSenderIds` array. If a worker-local plan omits
187
+ `postEngagerSenderIds:[]`, changes `sharedSenderIds`, returns a different
188
+ planRevision/actionId/laneKey, or introduces any Post Engagers lane outside the
189
+ parent packet, do not mutate and do not use it as `setupPlanCall` proof; stop
190
+ with `blocked: worker_plan_scope_drift` and ask the parent to re-plan.
191
+ Short form: If a worker-local plan omits `postEngagerSenderIds:[]`, do not use it as `setupPlanCall` proof.
192
+ Short form: stop with `blocked: worker_plan_scope_drift`.
193
+
194
+ Worker fan-out must use visible or durable execution. Preferred in local Codex
195
+ is a visible Codex thread created with the actual Codex app thread tools:
196
+ first call `codex_app.list_projects`, select the current repo project, then
197
+ call `codex_app.create_thread` with `target:{type:"project", projectId,
198
+ environment:{type:"local"}}`. Do not create a worktree target for evergreen
199
+ UAT or lane execution. The parent must record the returned thread id, pass
200
+ exactly one lane packet into that thread, and require the worker to write the
201
+ lane receipt at the artifact path named by
202
+ `workerDispatch.receiptArtifactHint`. A streaming worker or branch worker is
203
+ acceptable only when it writes a durable per-lane receipt artifact as it works.
204
+ `multi_agent_v1.spawn_agent`, raw `spawn_agent`, or any opaque Task/subagent
205
+ runtime that cannot expose a visible thread id or durable receipt artifact is
206
+ not accepted as command-level UAT proof and must not be used for mutating
207
+ evergreen setup.
208
+
209
+ Run this preflight before the first irreversible product mutation in any lane:
210
+ the parent must have recorded either a visible Codex thread id or a durable
211
+ `receiptArtifactPath` for that lane. If the runtime cannot provide this, stop
212
+ with `blocked: missing_visible_or_durable_lane_worker_runtime` before calling
213
+ `create_campaign`, `update_campaign`, `import_leads`, `confirm_lead_list`, or
214
+ any source/message/sequence mutation. Do not downgrade this to
215
+ `missing_parallel_lane_worker_runtime` when an opaque runtime exists: an
216
+ opaque runtime is still missing visible/durable proof. If a worker creates
217
+ partial product state without receipts, the run is blocked; created objects exist
218
+ is not equivalent to command completion.
219
+
220
+ Workers must explicitly load and use the installed visible
221
+ `$sellable:create-campaign` wrapper as the nested workflow entrypoint, then
222
+ load `create-campaign-v2` and `create-campaign-v2/core/flow.v2.json` through
223
+ the packaged MCP prompt/asset loaders before campaign creation or repair. The
224
+ worker then uses that create-campaign workflow/subskills for campaign
225
+ creation/setup, source/import, filter choice, Message Drafting, packaged
226
+ `generate-messages`, sequence attachment, and send-review readiness. Do not
227
+ rebuild those steps ad hoc in the parent thread or from a copied receipt
228
+ checklist. The parent thread is only the planner, dispatcher, verifier, and
229
+ receipt collector: it must not call `create_campaign`, `import_leads`,
230
+ `start_campaign_message_preparation`, `update_cell`, or sequence-attach tools
231
+ for a lane that was assigned to a worker. When the host supports visible Codex
232
+ threads, branch execution with durable receipts, or a streaming durable worker,
233
+ assign one lane packet per worker and run the lane workers in parallel. If the
234
+ host cannot run parallel workers with visible/durable receipts, report
235
+ `blocked: missing_visible_or_durable_lane_worker_runtime` instead of silently
236
+ doing all lane creation sequentially in the parent thread.
237
+ Short form: the parent thread is the planner, dispatcher, verifier, and receipt collector.
238
+ Before the first mutating lane action, complete an execution-runtime preflight:
239
+ name the callable worker surface/tool that will run lane packets and the proof
240
+ it gives (`thread id`, `receiptArtifactPath`, or both). If you cannot name a
241
+ real callable visible/durable worker runtime, stop with
242
+ `blocked: missing_visible_or_durable_lane_worker_runtime` before calling
243
+ `create_campaign`, `update_campaign`, `import_leads`, `confirm_lead_list`, or
244
+ any source/message/sequence mutation. Creating campaign shells first and then
245
+ discovering the worker runtime is missing is a failed run.
246
+ Legacy reports may mention `blocked: missing_parallel_lane_worker_runtime`;
247
+ for new command runs, use the visible/durable blocker above.
248
+
249
+ For any lane with `intent:"create"`, the worker must satisfy the normal
250
+ net-new create-campaign preflight before shell creation: load
251
+ `get_subskill_prompt({ subskillName:"research-sender" })`, run the sender/team
252
+ identity research needed for the brief, call `complete_sender_research(...)`,
253
+ then call `create_campaign`. Do not bypass this with a parent-thread brief or
254
+ by directly setting campaign state.
255
+
256
+ Workers return worker receipts. Each customer-visible lane receipt must include
257
+ one durable `createCampaignStepReceipt` built from actual tool calls or worker receipts,
258
+ not a parent-thread summary. The receipt must include:
259
+
260
+ - `setupPlanCall`: the exact `setup_evergreen_campaigns({ mode:"plan", ... })`
261
+ planRevision, selected actionId, lane key, workspace id, sender ids, and
262
+ campaign/table binding or create intent used before work started. For a
263
+ create lane, this receipt must explicitly include `createIntent:true`; for a
264
+ reuse lane, do not report `createIntent:true`. The object must use the
265
+ canonical keys `planRevision`, `actionId`, `laneKey`, `workspaceId`,
266
+ `senderIds`, `campaignId`, `tableId`, and `createIntent`. Do not use
267
+ `laneActionId`, `lanePacketActionId`, `delegatedPlanRevision`,
268
+ `delegatedActionId`, or freeform `requestedCall` text as a substitute for
269
+ these fields.
270
+ - `createCampaignWorkflowReceipt`: proof the worker loaded the actual installed
271
+ `$sellable:create-campaign` wrapper instead of doing ad hoc MCP calls. It must
272
+ include `skillCommand:"$sellable:create-campaign"`,
273
+ `skillName:"create-campaign"`, `wrapperSkillLoaded:true`,
274
+ `workflowPromptName:"create-campaign-v2"`,
275
+ `workflowPromptLoadedToHasMoreFalse:true`,
276
+ `workflowAssetPath:"create-campaign-v2/core/flow.v2.json"`,
277
+ `workflowAssetLoaded:true`, the real `workerRuntime`, a visible
278
+ `workerThreadId` or durable `receiptArtifactPath`, `durableReceiptWritten:true`
279
+ when using a receipt file, and `notAdHoc:true`.
280
+ - `researchSenderReceipt`: `research-sender` prompt load proof,
281
+ `complete_sender_research` result, sender LinkedIn identity basis, and the
282
+ notes/proof counts used before the net-new `create_campaign` call.
283
+ - `campaignBriefReceipt`: the CampaignOffer/campaign id, current
284
+ workflowTableId, brief hash or updated-at marker, delivery format, token
285
+ rules, hard avoids, source-use rules, and first-message template state.
286
+ - `sourceDecisionReceipt`: selected source/list id, provider, cursor/inventory
287
+ state, import/copy caps, explicit `dedupeDncProviderExclusions:true` or an
288
+ equivalent exclusions object, row ids or row-count evidence (`rowCount`,
289
+ `readyRowCount`, or `rowEvidence`), and the reason no colder source was used
290
+ before the current source was proven exhausted or insufficient.
291
+ - `filterDecisionReceipt`: saved/applied filter ids or explicit skip-filter
292
+ decision with current campaign/table basis.
293
+ - `messageDraftingReceipt`: the Message Drafting proof described below,
294
+ including packaged `generate-messages` prompt/assets and validation.
295
+ - `reviewBatchReceipt`: review-batch row ids/hash, generated row count,
296
+ quality-valid route-proof row id when approved, and proof that no broad
297
+ approve-all occurred.
298
+ - `sequenceReceipt`: exact current workflowTableId, recommended non-paid
299
+ sequence attach/precheck result, and readback showing `hasSequence:true` when
300
+ completion is claimed.
301
+ - final paused-send proof: if the current campaign table is `DRAFT` after the
302
+ sequence is attached, call the product `pause_campaign({ campaignId })`
303
+ endpoint/tool to put the unlaunched campaign into `PAUSED` review state, then
304
+ reread the campaign/table. Do not raw-write `campaignStatus`, do not start or
305
+ launch, and do not schedule/send. Completion requires reread proof of
306
+ `currentStep:"send"` and `campaignStatus:"PAUSED"`.
307
+ - `verifyCall`: the exact `setup_evergreen_campaigns({ mode:"verify",
308
+ planRevision, selectedActionIds, receipts })` call/result used before the
309
+ parent reports completion.
310
+
311
+ Before writing the durable receipt, run a receipt self-check. The top-level
312
+ receipt must include top-level `planRevision`, `actionId`, `laneKey`,
313
+ `laneType`, `workspaceId`, and `senderIds`, plus campaign/table/source ids and
314
+ row/message/approval counts. Inside
315
+ `createCampaignStepReceipt.messageDraftingReceipt`, `sampleMessages[]` must
316
+ include `rowId`, `generatedMessageText`, `verdict`, and `issues`. Do not
317
+ substitute `message` for `generatedMessageText`. Do not substitute
318
+ `passVerdict` for `verdict`. If the self-check fails, fix the receipt before
319
+ ending; do not leave the parent to infer canonical fields from top-level
320
+ aliases or narrative text.
321
+ Self-check shorthand: top-level `planRevision`, `actionId`, `laneKey`, `laneType`, `workspaceId`, and `senderIds` must exist before worker completion.
322
+ Self-check shorthand: `sampleMessages[]` must include `rowId`, `generatedMessageText`, `verdict`, and `issues`.
323
+ Self-check shorthand: Do not substitute `message` for `generatedMessageText`.
324
+ Self-check shorthand: Do not substitute `passVerdict` for `verdict`.
325
+ Self-check shorthand: If the self-check fails, fix the receipt before ending.
326
+
327
+ Use these canonical receipt leaf names exactly. Do not substitute looser aliases
328
+ like `generateMessagesPromptLoad`, `requiredAssetLoads`,
329
+ `validationPromptLoad`, `reviewBatchBasisHash`, `reviewRowIds`,
330
+ `sequenceActionTypes`, or `pass:true` unless you also include the canonical
331
+ fields below:
332
+
333
+ The receipt status must be `status:"succeeded"` or `status:"completed"` when
334
+ the lane is complete. Do not use `status:"passed"` or
335
+ `status:"passed_with_warnings"` as the primary success status. Warnings may go
336
+ in a separate `warnings` array, but the completion status stays canonical.
337
+ The backend verifier rejects `status:"passed"`, `status:"pass"`, and
338
+ `status:"passed_with_warnings"` as primary completion statuses.
339
+
340
+ All step proof objects must live under `createCampaignStepReceipt`. Do not put
341
+ `createCampaignWorkflowReceipt`, `campaignBriefReceipt`,
342
+ `sourceDecisionReceipt`, `filterDecisionReceipt`, `messageDraftingReceipt`,
343
+ `reviewBatchReceipt`, or `sequenceReceipt` only as top-level siblings;
344
+ top-level copies are useful for humans but are not enough for command
345
+ verification unless the same object also appears inside
346
+ `createCampaignStepReceipt`.
347
+
348
+ ```json
349
+ {
350
+ "status": "succeeded",
351
+ "planRevision": "<original planRevision>",
352
+ "laneKey": "<lane key>",
353
+ "laneType": "<lane type>",
354
+ "workspaceId": "<workspace id>",
355
+ "senderIds": ["<sender id>"],
356
+ "campaignId": "<campaign id>",
357
+ "tableId": "<workflow table id>",
358
+ "createCampaignStepReceipt": {
359
+ "createCampaignWorkflowReceipt": {
360
+ "skillCommand": "$sellable:create-campaign",
361
+ "skillName": "create-campaign",
362
+ "wrapperSkillLoaded": true,
363
+ "workflowPromptName": "create-campaign-v2",
364
+ "workflowPromptLoadedToHasMoreFalse": true,
365
+ "workflowAssetPath": "create-campaign-v2/core/flow.v2.json",
366
+ "workflowAssetLoaded": true,
367
+ "workerRuntime": "separate-codex-thread",
368
+ "workerThreadId": "<visible local Codex thread id>",
369
+ "receiptArtifactPath": ".planning/artifacts/uat/69/lane-receipts/<planRevision>/<actionId>-<laneKey>.json",
370
+ "durableReceiptWritten": true,
371
+ "notAdHoc": true
372
+ },
373
+ "campaignBriefReceipt": {
374
+ "campaignId": "<campaign id>",
375
+ "workflowTableId": "<workflow table id>",
376
+ "briefHash": "<brief/currentApprovedBriefHash>",
377
+ "deliveryFormat": "single message",
378
+ "tokenRules": true,
379
+ "hardAvoids": true,
380
+ "sourceUseRules": true,
381
+ "firstMessageTemplate": "<template or template state>"
382
+ },
383
+ "sourceDecisionReceipt": {
384
+ "sourceId": "<source id>",
385
+ "sourceListId": "<source/list id>",
386
+ "cursorOrInventoryState": "<cursor/inventory/import state>",
387
+ "dedupeDncProviderExclusions": true,
388
+ "sourceLadderReason": "<why the current source is valid before colder sources>"
389
+ },
390
+ "filterDecisionReceipt": {
391
+ "status": "skipped",
392
+ "workflowTableId": "<workflow table id>"
393
+ },
394
+ "messageDraftingReceipt": {
395
+ "statusSource": "branch",
396
+ "promptLoadedToHasMoreFalse": true,
397
+ "requiredAssetsLoaded": true,
398
+ "validationLoaded": true,
399
+ "validationResult": "passed",
400
+ "qualityReview": {"passed": true, "checkedSampleCount": 3, "issues": []},
401
+ "campaignId": "<campaign id>",
402
+ "workflowTableId": "<workflow table id>",
403
+ "selectedLeadListId": "<source/list id>",
404
+ "reviewBatchRowHash": "<review row ids hash>",
405
+ "messageDraftRecommendation": {"basis": "generate-messages"},
406
+ "sampleMessages[].verdict": "pass",
407
+ "sampleMessages": [
408
+ {"rowId": "<row id>", "generatedMessageText": "<message>", "verdict": "pass"}
409
+ ]
410
+ },
411
+ "reviewBatchReceipt": {
412
+ "rowIdsHash": "<review row ids hash>",
413
+ "generatedCount": 3
414
+ },
415
+ "sequenceReceipt": {
416
+ "workflowTableId": "<workflow table id>",
417
+ "hasSequence": true,
418
+ "sequenceReceipt.actionTypes": ["send_invite"],
419
+ "sequenceReceipt.nonPaid": true,
420
+ "actionTypes": ["send_invite"],
421
+ "nonPaid": true
422
+ },
423
+ "finalPausedSendProof": {
424
+ "currentStep": "send",
425
+ "campaignStatus": "PAUSED"
426
+ },
427
+ "verifyCall": {
428
+ "mode": "verify",
429
+ "planRevision": "<original planRevision>",
430
+ "selectedActionIds": ["<all original selected action ids>"]
431
+ }
432
+ }
433
+ }
434
+ ```
435
+
436
+ Verification always uses the original execution planRevision and original
437
+ selectedActionIds from the plan packets that were dispatched before mutation.
438
+ The parent must preserve the original selectedActionIds exactly.
439
+ Do not replace them with a later reuse planRevision/actionIds after the
440
+ campaigns exist. After workers complete, attach or patch `verifyCall` on each
441
+ lane receipt with that original execution planRevision, original
442
+ selectedActionIds, same sender scopes, and the verify result. Only after verify
443
+ succeeds should the parent perform the idempotency rerun.
444
+
445
+ If any required field is missing, report
446
+ `blocked: missing_create_campaign_step_receipt` for that lane and do not call it
447
+ complete.
448
+ If `createCampaignWorkflowReceipt.workerRuntime` is
449
+ `multi_agent_v1.spawn_agent`, `spawn_agent`, or another opaque worker runtime,
450
+ or if it lacks both `workerThreadId` and a durable receipt artifact proof, treat
451
+ the lane as `blocked: missing_create_campaign_step_receipt` with detail
452
+ `createCampaignWorkflowReceipt.workerRuntime.visible_or_durable`; do not call
453
+ verify and do not repair by summarizing product readback in the parent.
454
+
455
+ Message Drafting runtime rule: the preferred proof source is the normal
456
+ create-campaign branch handoff (`watchNarration.workerDetails.messageDraftBuilder`
457
+ or equivalent) with `statusSource:"branch"`. If that internal branch handoff is
458
+ not callable or not exposed in the packaged MCP runtime, do **not** stop after
459
+ creating/importing rows. The lane worker must run the packaged message-drafting
460
+ path itself in the worker thread:
461
+
462
+ 1. Load `get_subskill_prompt({ subskillName:"generate-messages" })` until
463
+ `hasMore:false`.
464
+ 2. Load the required message assets and `create-campaign-v2-validation`.
465
+ 3. Call `start_campaign_message_preparation({ campaignId, tableId,
466
+ targetPreparedMessages, maxRowsToCheck, approvalMode:"mark_ready",
467
+ autoContinue:true })` inside the same lane worker, with bounded caps from the
468
+ lane packet.
469
+ Never call `start_campaign_message_preparation` with `approvalMode:"approve"`
470
+ for evergreen setup. Generate/readiness and approval are separate safety
471
+ steps: first mark rows ready, then inspect generated copy, then approve
472
+ exactly one semantic Approved cell through `select_campaign_cells` and
473
+ `update_cell`.
474
+ Approval shorthand: approve exactly one semantic Approved cell.
475
+ 4. Poll `get_campaign_message_preparation_status` until no queued/active cells
476
+ remain or a bounded stop reason is returned.
477
+ 5. Use `get_campaign_messages_preview` or `select_campaign_cells` to inspect
478
+ generated rows, quality-review at least 3 samples, approve exactly one
479
+ quality-valid route-proof row when none is already approved, and prove no
480
+ broad approve-all happened.
481
+ If a packaged worker or earlier run left more than one generated row
482
+ approved, set any extra approved cells back to false through semantic
483
+ Approved-cell selection before completion. Final proof must show
484
+ approvedGeneratedMessageCount exactly 1. Do not report completion with 2+
485
+ approved rows.
486
+ Approval shorthand: Do not report completion with 2+ approved rows.
487
+ 6. Continue to `attach_recommended_sequence({ campaignId, currentStep:"send" })`
488
+ and, if the campaign is still `DRAFT`, `pause_campaign({ campaignId })`.
489
+ Reread the campaign/table before claiming completion.
490
+
491
+ That packaged worker path must return `messageDraftingReceipt.statusSource:
492
+ "packaged-generate-messages-worker"`. It is accepted only when the receipt
493
+ includes the same prompt/assets/validation proof, current campaign/table/source
494
+ basis, generated review rows, sample messages, quality review, route-proof
495
+ approval evidence, sequence proof, and final paused-send proof required of the
496
+ branch handoff. `statusSource:"parent-thread-fallback"` is still invalid.
497
+ The only accepted statusSource values are exactly `branch` and
498
+ `packaged-generate-messages-worker`; descriptive aliases such as
499
+ `package-readback-local-thread` are rejected.
500
+
501
+ One lane may produce exactly one CampaignOffer/campaign id and one current
502
+ workflow table id. For `intent:"create"`, the first successful
503
+ `create_campaign` result establishes the lane's only allowed campaign/table
504
+ target. Every later source/import/confirm/message/filter/sequence/readback call
505
+ must use that same campaign id and current workflow table id. If any tool
506
+ returns or creates a different CampaignOffer, workflow table, "Campaign from
507
+ Source" campaign, or shell for the same lane, stop immediately with
508
+ `blocked: duplicate_campaign_offer_created`, include both ids in the receipt,
509
+ and do not report completion. Do not hide the earlier shell by reporting only
510
+ the later imported campaign.
511
+
512
+ Each customer-visible lane receipt must also include message proof from
513
+ `watchNarration.workerDetails.messageDraftBuilder`, the worker's equivalent
514
+ branch handoff record, or the packaged message-prep worker path above. Valid
515
+ evergreen message proof names the current campaign id, workflowTableId,
516
+ selected lead-list or source id, filter choice, and review-batch row ids/hash,
517
+ and it must use `statusSource:"branch"` or
518
+ `statusSource:"packaged-generate-messages-worker"`.
519
+ `statusSource:"parent-thread-fallback"` is not accepted for evergreen
520
+ customer-visible completion because it can be a parent summary rather than proof
521
+ that the create-campaign Message Drafting branch actually ran. The proof must
522
+ show the worker loaded the full packaged prompt with
523
+ `get_subskill_prompt({ subskillName:"generate-messages"` until `hasMore:false`,
524
+ loaded the required message assets, and loaded `create-campaign-v2-validation`
525
+ before returning a `messageDraftRecommendation`.
526
+ The receipt must also include `validationResult:"passed"`, a passed
527
+ quality-review object, and at least 3 concrete reviewed sample messages with
528
+ row ids, generated message text, pass verdicts, and no issues. Shared Cold
529
+ Fallback samples must not open with a standalone name line followed by
530
+ "Hey there" or another generic greeting; that pattern means the lane did not
531
+ finish the create-campaign message workflow to customer quality.
532
+ Shared Signal Discovery samples must not use sender-owned post-engager language
533
+ such as "my post", "thanks for showing support", "saw you pop up", or any copy
534
+ that implies the sending sender authored the source post or personally received
535
+ the prospect's engagement unless the selected source evidence proves that exact
536
+ sender authored the post.
537
+ The generated-message cells are not prompt proof: `currentRevisionGeneratedMessages`,
538
+ Generate Message column counts, `messagesCount`, or rendered row previews prove
539
+ only that table cells exist from the current brief/template, not that the
540
+ create-campaign Message Drafting branch ran. If this receipt is missing, report
541
+ `blocked: missing_message_drafting_receipt` and do not call the lane complete.
542
+
543
+ The parent then calls
544
+ `setup_evergreen_campaigns({ mode:"verify", postEngagerSenderIds,
545
+ sharedSenderIds, planRevision, selectedActionIds, receipts })` using the same
546
+ sender scopes as the plan call and the original execution planRevision/actionIds,
547
+ and reports only verified completion. A verify call without the same sender scopes can recompute an empty or different plan and is invalid proof. A verify call that uses a later reuse planRevision instead of the original execution planRevision is also invalid proof. Only after verify succeeds, rerun
548
+ `setup_evergreen_campaigns({ mode:"plan", postEngagerSenderIds,
549
+ sharedSenderIds, yolo:true })` without explicit campaign/table bindings. That
550
+ idempotency rerun must return `intent:"reuse"` for every completed lane and no
551
+ duplicate-lane blockers before you call the command finished. If verify blocks,
552
+ report the blocker and repair only the missing postconditions from the same
553
+ current plan.
554
+
555
+ Message proof for every customer-visible lane must come from the current
556
+ campaign/table basis and include at least 3 generated review rows from the
557
+ packaged `generate-messages` path before completion is reported.
558
+ Parent-thread handwritten copy or setting `currentStep` is not proof.
559
+ </command_backed_workflow>
560
+
561
+ <objective>
562
+ 1. **Inventory first**: `get_campaigns` + `list_tables` + `get_campaign_waterfall` in the active workspace. Treat `list_tables` and the managed waterfall as authoritative for older managed slots; `get_campaigns` is a recent campaign page and may miss canonical evergreen lanes. Match existing campaigns/tables/waterfall slots to the plan by name (case-insensitive, ignore suffixes like "(Copy)") and stored slot identity. `list_tables.campaignStatus` and `list_tables.dashboardBucket` are part of the identity check: a matching `ARCHIVED` table/campaign is not a plain `REUSE`. If it is the canonical prod slot and the invocation explicitly allows dashboard visibility repair, repair it to `PAUSED`; otherwise mark it `flagged`/`blocked` and do not create a duplicate. A matching non-archived campaign/table/waterfall slot = REUSE; record it and move on. Never create a second campaign for a slot that already has one in any of those inventories.
563
+ 2. **Create only the missing slots** through the full `$sellable:create-campaign`
564
+ workflow path for each lane, using the lane packet as the setup basis:
565
+ - Post Engagers lanes: that sender's ID only. Shared lanes: all the senders' IDs.
566
+ - The brief and message direction are lane-specific. Never reuse the Post
567
+ Engagers opening for Shared Cold Fallback.
568
+ - **Post Engagers** first-message style: short, casual, references the
569
+ sender-authored post they engaged with, closed question, **no internal
570
+ vocabulary, no pitch, no meeting ask** in message one:
571
+
572
+ ```
573
+ {{first_name}}
574
+
575
+ Hey there
576
+
577
+ Saw you pop up on my post about {{post_topic_line}}
578
+ ```
579
+
580
+ - **Shared Signal Discovery** first-message style: short, casual, references
581
+ the real observed signal/theme or public conversation. It may mention the
582
+ engagement source only when the source evidence supports it. Do not say
583
+ "my post" unless the selected source post was actually authored by the
584
+ sending sender. Do not thank the prospect for showing support, reacting,
585
+ commenting, or popping up unless this is a sender-owned Post Engagers lane
586
+ with sender-authored source evidence.
587
+ - **Shared Cold Fallback** first-message style: cold, direct, and cohesive.
588
+ It must not pretend there was engagement, must not say "saw you pop up",
589
+ and must not use a standalone name line followed by "Hey there" or other
590
+ generic greeting. It should open with a light role/company/problem
591
+ observation, avoid awkward phrases like "looks like a team where", and ask
592
+ one clear problem question. This is a single-message
593
+ cold path unless the approved create-campaign message workflow proves a
594
+ better delivery format.
595
+ - Every customer-visible lane must run Message Drafting from the current
596
+ campaign/table basis after source rows exist. The worker must load the full
597
+ packaged `generate-messages` prompt, every required asset, and
598
+ `create-campaign-v2-validation`; if the internal branch handoff is not
599
+ callable in the packaged runtime, the worker must call
600
+ `start_campaign_message_preparation` and poll
601
+ `get_campaign_message_preparation_status` from the lane worker. The
602
+ returned receipt must include `statusSource:"branch"` or
603
+ `statusSource:"packaged-generate-messages-worker"`, at least 3 concrete
604
+ reviewed sample messages, a passed validation result, and a passed quality
605
+ review. Row/message counts alone are not enough.
606
+
607
+ - **The brief must declare the delivery format** so Generate Message writes copy suited to how it actually sends:
608
+ - DM lanes: add a `Delivery format:` line — either `multiline (each paragraph sends as its own DM message)` or `single message`. When multiline, the template's blank-line paragraphs ARE the message boundaries — write each one as a standalone typed message.
609
+ - **InMail lanes can never be multiline**: an InMail is one message and the recipient must reply before anything else can be sent. InMail-bound templates must read as one cohesive message — declare `Delivery format: single message (InMail — no follow-up until reply)` and never structure the copy to depend on multi-message pacing.
610
+
611
+ - The sequence is auto-selected by sender tier; do not hand-author sequence templates here. Never select a paid-InMail template.
612
+ 3. **Customer-Visible Completion Contract**: a named evergreen lane that appears
613
+ as a campaign card or campaign-backed table is not done when the shell exists.
614
+ It is done only when the customer can open the campaign and land on final
615
+ send review with all setup state present:
616
+ - `currentStep:"send"`.
617
+ - The linked workflow table has `campaignStatus:"PAUSED"`; `ACTIVE` means
618
+ already launched and must be reported separately, and `ARCHIVED` must be
619
+ repaired only when the prompt explicitly allows dashboard visibility repair.
620
+ - A full campaign brief exists, including delivery format, token rules,
621
+ hard avoids, source-use rules, and the approved first-message template.
622
+ - A source/list decision has been resolved and campaign rows are loaded. If
623
+ there is no approved source or no rows can be copied, report `blocked` with
624
+ the missing source detail; do not call the campaign complete.
625
+ - The filter step is resolved: either saved/applied filters are present, or
626
+ the campaign explicitly skipped filters. Do not leave customer-visible
627
+ campaigns at `filter-choice`, `filter-rules`, or `apply-icp-rubric` and
628
+ report success.
629
+ - Message Drafting has run from the current campaign/table basis, using the
630
+ create-campaign message prompt/assets and validation gate. Updating
631
+ `currentStep:"messages"` is not proof. Parent-thread handwritten copy is
632
+ not a substitute. The proof receipt must show
633
+ `get_subskill_prompt({ subskillName:"generate-messages"` was loaded until
634
+ `hasMore:false`, required message assets were loaded, and
635
+ `create-campaign-v2-validation` ran through
636
+ `workerDetails.messageDraftBuilder` with `statusSource:"branch"` or
637
+ through the lane worker's packaged message-prep path with
638
+ `statusSource:"packaged-generate-messages-worker"`,
639
+ `start_campaign_message_preparation`, and
640
+ `get_campaign_message_preparation_status`. The generated-message cells are
641
+ not prompt proof; `currentRevisionGeneratedMessages` and row previews are
642
+ only downstream cell evidence. The proof must include
643
+ `validationResult:"passed"`, a passed quality review, and at least 3
644
+ reviewed sample messages. For Shared Cold Fallback, reject samples that
645
+ open with a standalone name followed by "Hey there" or a similarly generic
646
+ greeting.
647
+ - The first review batch exists and at least 3 review rows have generated
648
+ messages from the approved brief. If fewer than 3 usable rows exist, report
649
+ the actual count and why.
650
+ If a bounded preparation job requested more than the minimum but already
651
+ met the minimum generated-message floor, do not stall on a single bad review row once a fresh status/readiness check shows no queued, processing, or active cells; record the shortfall, name the failed/empty-row reason when
652
+ available, and continue with route-proof review rather than expanding source
653
+ or waiting forever.
654
+ - At least one generated row is approved as a route-proof gate. If zero rows
655
+ are approved and the prompt explicitly asked for full/customer-ready
656
+ completion, approve exactly one quality-valid generated row. If one or more
657
+ rows are already approved, do not add more approvals during evergreen
658
+ completion. Never broad approve all rows.
659
+ - The recommended non-paid sequence is attached to the current campaign
660
+ table, and the watched campaign is on Send. Use
661
+ `attach_recommended_sequence({ campaignId, currentStep:"send" })` when a
662
+ safe attach is needed. After `confirm_lead_list` or any source-list copy,
663
+ immediately reread `get_campaign({ campaignId })`; the returned
664
+ `workflowTableId` is the current campaign table. Cross-check that exact
665
+ current workflowTableId with `list_tables` and require `hasSequence:true`
666
+ before reporting completion. If a stale shell table has sequence columns but
667
+ the current campaign table does not, `SEQUENCE_EXISTS` is not enough:
668
+ record the stale shell table id, then repair the current workflowTableId
669
+ with `attach_sequence` using the same non-paid product template
670
+ `attach_recommended_sequence` would have selected. Never use a paid-InMail
671
+ template and never attach/replace sequence outside the current table unless
672
+ the current lane packet explicitly allowed sequence repair.
673
+ - If the current campaign table is still `DRAFT` after sequence/readiness
674
+ proof, call `pause_campaign({ campaignId })` and reread. `pause_campaign`
675
+ is the product-native review-state transition; it is not a launch and does
676
+ not schedule or send. Never raw-write campaign status.
677
+ - No scheduled, queued outbound, sent outbound, campaign launch, or paid
678
+ InMail spend is created by this skill.
679
+ 4. **Verify each slot** after create/reuse/repair:
680
+ - `get_campaign` shows the campaign exists, remains unlaunched, and has the expected workflow table.
681
+ - Builder truth must match dashboard truth. `currentStep:"running"` is valid only when the linked table has `campaignStatus:"ACTIVE"`. For a `PAUSED` or `ARCHIVED` campaign/table, repair or flag stale `currentStep:"running"` back to launch review (`send` / review-ready) before reporting the slot done. Never call `start_campaign` just to make a stale `running` step true.
682
+ - Send/action lanes such as Post Engagers have a sequence attached on the
683
+ exact current campaign table. Use `list_tables({ hasSequence: true })` as a
684
+ quick cross-check, but do not treat that filter as the only proof. If the
685
+ canonical current campaign table is missing from the sequence-filtered
686
+ table list and `attach_recommended_sequence({ campaignId })` returns
687
+ `SEQUENCE_EXISTS`, inspect whether a stale shell table owns the sequence.
688
+ For a customer-visible completion run, stale shell proof is not completion:
689
+ rerun the current-table repair above or report `blocked: stale_shell_sequence`.
690
+ - Only use `attach_recommended_sequence({ campaignId })` for a send-lane
691
+ sequence repair/precheck when the target is a canonical prod slot and the
692
+ invoking prompt allows sequence repair. Call it at most once without
693
+ `confirmed`; a successful response is `repaired`. Treat `SEQUENCE_EXISTS` as
694
+ non-mutating proof only when the current table also has `hasSequence:true`.
695
+ Never call `attach_recommended_sequence` or `attach_sequence` with
696
+ `confirmed:true` in evergreen automation unless the user explicitly asks to
697
+ replace an existing sequence or the approved lane packet allows current
698
+ table stale-shell repair.
699
+ - Do not report `source-only/no sequence expected` for any named evergreen
700
+ campaign that appears in Campaigns, has a CampaignOffer ID, or is backed by
701
+ a campaign dashboard table. Shared Signal Discovery and Shared Cold
702
+ Fallback campaign cards are still customer-visible campaigns and must
703
+ satisfy the Customer-Visible Completion Contract when the prompt asks for
704
+ full/customer-ready completion.
705
+ - Source-only shared lanes are allowed only for explicitly internal waterfall
706
+ inventory objects that are not represented as campaign cards. For those
707
+ internal-only objects, verify the active waterfall linkage, table ID,
708
+ source type, and priority, and label them `internal source pool` rather
709
+ than treating them as completed campaigns.
710
+ - Post Engagers lanes are sender-owned source lanes. The selected/source LinkedIn posts must be authored by that exact sender (for example, Thomas post-engager source posts must visibly be Thomas-authored posts). If selected/source posts include another person or company author, mark the slot `blocked`/`flagged` for source repair; do not scrape/import engagers or report completion until the source is sender-owned. Shared Signal Discovery lanes are the only evergreen lanes allowed to mix authors.
711
+ 5. **Interactive polish mode only: confirm the message template with the user — and check it reads chat-native.** Show the exact first-message template each created campaign's brief carries and ask the user to confirm or adjust it before moving on. Because DM copy may send paragraph-by-paragraph (each blank-line block becomes its own message), every paragraph must read like something a human literally typed as a separate chat message:
712
+ - **No letter punctuation.** `Hey {{first_name}}` — never `Hey {{first_name}},` (nobody types a trailing comma and hits send). No `Dear`, no sign-offs, no `Best,`.
713
+ - Each paragraph stands alone as a message — short, lowercase-casual is fine, sentence fragments are fine.
714
+ - No paragraph should depend on letter formatting (no "As I mentioned above" referencing layout).
715
+ If the template violates these, propose the chat-native version and ask; on approval, update the brief via `update_campaign_brief` and show the final version.
716
+
717
+ **Also confirm the delivery format and keep config in sync.** Ask the user whether DM lanes should send multiline (paragraph-per-message) or as a single message. Record the answer as the brief's `Delivery format:` line, and when multiline is chosen, set `actionConfig.sendEachParagraphAsMessage: true` on that campaign's `send_dm` column via `update_column` (config edits run no cells and nothing sends from unlaunched campaigns). Never set the paragraph-split flag on InMail columns — the option does not apply to InMail.
718
+ 6. **Interactive polish mode only: prove the template on one real row.** For ONE campaign that has at least one lead row (add one via `add_on_demand_leads` if every lane is empty and the user provides/approves a test lead), generate a message for exactly one row (`queue_campaign_cells` with `columnRole: "generateMessage"`, `rowSelector: { type: "reviewBatch", limit: 1 }` or the single row's ID), wait for it, then show the user the generated message next to the template — AND show how it would split if paragraph-per-message sending is enabled (list each paragraph as `msg 1:`, `msg 2:`, …) so the user confirms each one reads like a real typed message. Check alignment: tone, structure, no internal vocabulary, correct token substitution, no letter punctuation. Do NOT approve the row or generate for more rows — one sample only.
719
+ 7. **Automation mode template/config audit**: inspect existing send-lane briefs for a `Delivery format:` line and chat-native warm template. If a send lane is missing the line and the prompt allows cleanup, update only the campaign brief metadata with the safest default for that lane (`Delivery format: single message (LinkedIn DM; line breaks remain inside one generated message; no follow-up until reply)`). If cleanup is not allowed, flag it. Do not generate a sample row in automation mode unless the invocation explicitly asks for sample proof. In structure-only automation, do not generate a sample row unless the invocation explicitly asks for sample proof. In customer-visible completion, follow the Customer-Visible Completion Contract instead.
720
+ 8. **Report the reconcile plan and result** — every slot tagged `reused`, `created`, `repaired`, `flagged`, or `blocked`, plus template/sample details only when that mode ran:
721
+
722
+ ```
723
+ Evergreen Reconcile — {date}
724
+ • Christian Reyes - Post Engagers: reused (send review ready; 18 rows, 3 generated, 1 approved gate row, sequence attached, paused)
725
+ • Thomas Nobbs - Post Engagers: reused (send review ready; 8 rows, 3 generated, 1 approved gate row, sequence attached, paused)
726
+ • Sellable.dev - Shared Signal Discovery: repaired (send review ready; 50 rows, 3 generated, 1 approved gate row, sequence attached, paused)
727
+ • Sellable.dev - Shared Cold Fallback: created (send review ready; 50 rows, 3 generated, 1 approved gate row, sequence attached, paused)
728
+ All campaigns remain unlaunched. Next: enrich/approve more rows when you want more items ready to schedule.
729
+ ```
730
+ </objective>
731
+
732
+ <safety>
733
+ - **Idempotent by construction**: rerunning on a schedule must produce all-`reused` and zero new campaigns. If a name matches ambiguously (two candidates), reuse the most recently updated and flag the duplicate for the operator instead of creating a third.
734
+ - Never call `start_campaign` / `start_on_demand_campaign`. New campaigns stay in their default unlaunched state.
735
+ - Never archive, rename, or delete existing campaigns — reconcile is additive; structural removals are operator actions.
736
+ - Respect workspace boundaries: senders and campaigns must belong to the active workspace.
737
+ </safety>