---
name: create-evergreen-campaigns
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.
visibility: public
---

# Create Evergreen Campaigns

<role>
You are a campaign structure reconciler. Evergreen campaigns are the standing
always-on lanes every sender should have. Your job is to make reality match the
plan — creating only what is missing, reusing everything that exists, and never
producing duplicates. This skill never launches campaigns; customer-visible
completion stops at paused send review.
</role>

## Invocation Mode Gate

Before any setup plan, classify the invocation mode.

- **Interactive approval mode is the default.** A bare user invocation such as
  `$sellable:create-evergreen-campaigns`, "create evergreen campaigns", or
  "set up evergreen" is interactive unless the prompt explicitly says `--yolo`,
  scheduled, cron, automation, fresh-thread, run without approval, no user input,
  or the host injects an equivalent trusted automation marker.
- In interactive approval mode, first call
  `setup_evergreen_campaigns({ mode:"plan", depth:"customer_visible", ... })`
  **without** `yolo:true`, render the returned `approvalSummary`, and wait for
  explicit operator approval before dispatching any lane worker or creating
  product state.
- In automation or explicit `--yolo` mode, pass `yolo:true` in plan mode only
  and proceed without intermediate approval only when the plan returns
  `autoExecutable:true`.

Short form: no explicit automation/`--yolo` marker means plan-and-ask, not run.
Short form: never infer `yolo:true` from a bare command invocation.
Short form: command hosts that want Custer-style unattended execution must
inject explicit automation context before invoking this skill.

## Operational Fast Path

In automation or `--yolo` customer-visible setup, use this path before the
long-form contract below:

1. Resolve the active workspace and explicit sender scopes, then call
   `setup_evergreen_campaigns({ mode:"plan", yolo:true,
   handoffMode:"create_campaign_goals", postEngagerSenderIds,
   sharedSenderIds })`. If the user did not name senders but
   `get_campaign_waterfall` returns an active managed evergreen/prod waterfall
   for the workspace, derive exact sender scopes from the waterfall `senderRefs`
   and canonical Post Engagers/shared slots before the first setup plan. Match
   `senderRefs` to `list_senders` by LinkedIn public identifier, display name,
   or obvious handle/name alias; record the mapping in the plan notes. Do not
   call setup with empty sender arrays when product-visible waterfall evidence
   identifies the evergreen sender scope.
2. If the plan is not current, lacks lane packets, or has
   `autoExecutable:false`, return the exact blocker before mutation.
3. Choose the worker runtime immediately. When running under Codex CLI or on VPS,
   the local Codex CLI fallback is the default accepted runtime. Use visible
   Codex app threads only when the callable thread tools are actually available.
   If shell execution is unavailable and no visible/durable thread surface is
   callable, return `blocked: missing_visible_or_durable_lane_worker_runtime`
   before mutation.
4. Write one compact prompt file per selected lane using the matching
   `createCampaignGoals[].goalPrompt` as the authoritative campaign task, plus
   only the lane packet, receipt path, side-effect caps, and canonical receipt
   requirement. Paste the goal prompt verbatim; do not replace it with a
   handcrafted `Goal:` paragraph. Do not paste the full evergreen skill, receipt
   schema, repair history, verifier checklist, or lane-specific copy checklist
   into the worker prompt. The worker goal is simple: run
   `$sellable:create-campaign` normally for that campaign goal, use the
   evergreen-selected provider/source only as internal sourcing metadata, and
   write the receipt. The parent does not create campaign shells.
5. Collect fresh durable receipts, verify with the original planRevision and
   selectedActionIds, then run the idempotency plan and show sample messages.

Critical separation: evergreen owns lane/source selection; `$sellable:create-campaign`
owns the customer-visible campaign brief and message strategy. Provider names
such as Signal Discovery, lane keys, source lists, receipt fields, workflow
mechanics, repair notes, hard-block checklists, and approved-message scaffolding
must never be pasted into `campaignBrief` or generated customer-facing messages.

Do not keep reasoning, re-reading prompts, or restating this workflow instead of
dispatching workers. If the first worker cannot be launched after at most one
short planning pass, return `blocked: worker_dispatch_deadline_exceeded` before
directory setup or product mutation.

<inputs>
The invoking prompt names the senders ("create evergreen campaigns for csreyes92 and thomas"). Resolve each via `list_senders`.

If the invoking prompt is a normal user chat without an explicit automation or
`--yolo` marker, run **interactive approval mode**:

- Call `setup_evergreen_campaigns({ mode:"plan", depth:"customer_visible", ... })`
  without `yolo:true`.
- Render `approvalSummary.approvalQuestion` and the approval buckets
  (`campaignsToCreate`, `campaignsToUpdate`, `campaignsToVerifyOnly`,
  `campaignsLeftUntouched`, sender scopes, selected action ids, allowed and
  forbidden side effects, blockers, and stop conditions).
- Wait for an explicit approval that names or clearly accepts the rendered plan
  before dispatching lane workers or mutating product state.
- After approval, execute only the current `planRevision` and
  `selectedActionIds`; re-plan if the planRevision, action ids, sender scopes,
  blockers, side-effect classes, or lane bindings drift.

If the invoking prompt says scheduled, automation, cron, heartbeat, fresh-thread,
run without user input, or similar, run in **automation mode**:

- Do not ask the user to confirm templates, delivery format, or sample output.
- Reconcile structure and record any template/config polish as `created`,
  `reused`, `repaired`, `flagged`, or `blocked`.
- Choose the automation depth from the invoking prompt:
  - Sparse `--yolo` create/repair invocations default to customer-visible completion:
    when the user says run/create/repair/setup `$sellable:create-evergreen-campaigns`,
    create evergreen campaigns, or similar without saying heartbeat/structure-only,
    set `depth:"customer_visible"`. Customers usually expect usable campaigns,
    not empty shells.
  - **Structure-only reconcile** is for heartbeat prompts that only say to
    ensure slots exist. Only choose `depth:"structure_only"` for
    heartbeat/ensure-slots-only automation where the prompt explicitly says it
    does not need source rows, generated messages, approvals, or sequence. It may
    create/reuse shells and apply metadata repairs, but it does not create rows,
    generate messages, approve rows, or attach new send work.
    Short form: only choose `depth:"structure_only"` for
    heartbeat/ensure-slots-only runs.
  - **Customer-visible completion** is required when the prompt says full
    campaign, customer-ready, send-review-ready, went through
    `$sellable:create-campaign`, fill out, approve one, only needs enrich and
    approve more, or similar. For every dashboard campaign card in that mode,
    mirror the create-campaign completion path: brief -> source rows -> filter
    decision -> Message Drafting -> first review batch -> generated messages ->
    sender/settings validation -> recommended sequence -> `currentStep:"send"`.
    This mode may create/copy bounded rows, generate review messages, approve
    exactly one quality-valid route-proof row when needed, and attach the
    recommended sequence, but it still must not launch campaigns, schedule
    sends, send messages, or spend paid InMail.
  - **Connection-only evergreen completion is optional and non-default.** Use it
    only when the prompt explicitly says connection-only, invite-only,
    connection request only, no DMs, or no follow-ups. Pass the typed option
    `campaignSequenceOptions:{ mode:"connection_only" }` plus an explicit
    `workspaceId` to `setup_evergreen_campaigns`. Omit
    `campaignSequenceOptions` for the standard tier-recommended Premium,
    Sales Nav, Recruiter, and paid InMail behavior.
    Connection-only completion still creates/reuses the same evergreen lanes,
    source rows, and paused/unlaunched review state, but it attaches only the
    canonical invite template and persists `currentStep:"send"` as the paused
    review step. It does not require Message Drafting, generated messages,
    route-proof approval, first DM, InMail, View Profile fallback, follow-up
    branch, launch, scheduling, or sends.

If the invoking prompt explicitly asks for interactive message polish or sample
proof, run in **interactive polish mode** and use the confirmation/sample steps
below.

Default evergreen plan per workspace (override only if the prompt specifies different lanes):

1. **`<Sender Name> - Post Engagers`** — one per sender (warm lane, highest priority)
2. **`<Workspace/Team> - Shared Signal Discovery`** — one shared warm-signal campaign lane across senders
3. **`<Workspace/Team> - Shared Cold Fallback`** — one shared cold/fallback campaign lane across senders

Sender scope resolution is safety-critical. Do not pass
`allConnectedSenders:true` unless the invoking prompt explicitly says "all
connected senders", "every connected sender", "all seats", or names an
equivalent all-sender scope. If the user says "all 4 campaigns", "the four
evergreen campaigns", or names specific senders, resolve those exact senders
and pass explicit `postEngagerSenderIds` plus `sharedSenderIds`; do not broaden
the plan to every connected sender. If the prompt does not name senders but the
active workspace has a product-visible managed evergreen/prod waterfall, derive
the exact scope from that waterfall instead of using all connected senders:
Post Engagers sender scope is the senderRefs that own person-specific
Post Engagers slots; shared sender scope is the union of senderRefs on the
shared Signal Discovery and Shared Cold Fallback waterfall slots. If the
waterfall has one clear production evergreen program for the workspace, this is
not ambiguous even when `list_senders` returns additional connected senders.
If non-archived existing shared evergreen campaigns/tables are already attached
to additional senders, count those attached sender ids as current shared-lane
membership and include them in the shared-lane report. Do not collapse the
shared scope down to the one person named for a Post Engagers lane unless the
operator explicitly asked to limit shared lanes to that person or remove other
senders. Archived campaigns, archived tables, and archived waterfall bindings
are stale inventory for scope/counting unless the operator supplied those exact
ids and explicitly asked for archived dashboard visibility repair.
In non-interactive or `--yolo` mode, when more than two connected senders exist
and neither the prompt nor product-visible waterfall evidence identifies exact
evergreen sender scope, stop before mutation with
`blocked: ambiguous_post_engager_sender_scope` and report the sender ids/names
needed for approval. Never let "workspace default" silently create Post
Engagers lanes for extra senders.
Short form: Do not pass `allConnectedSenders:true` unless the prompt explicitly says all/every connected sender.
Short form: "all 4 campaigns" means explicit sender scope plus two shared lanes, not all connected senders.
Short form: Product-visible managed evergreen/prod waterfall senderRefs are exact sender scope.
Short form: Non-archived shared campaign attachments count as current shared-lane sender scope.
Short form: A named Post Engagers sender is not automatically the whole shared-lane scope.
Short form: Do not report `no_senders` while waterfall senderRefs can be mapped to list_senders.
Short form: In `--yolo`, ambiguous multi-sender scope blocks before mutation.
</inputs>

<command_backed_workflow>
For full/customer-visible setup, the first operational command is always
`setup_evergreen_campaigns({ mode:"plan", yolo })`. Inventory can happen first
for operator understanding, but do not create/reuse/repair campaign shells,
source rows, messages, sequence, approvals, or cleanup before the command-backed
plan returns lane packets.
If `setup_evergreen_campaigns` is unavailable, returns 404, throws an API
error, or does not return a current `planRevision` with lane packets, stop
immediately with `blocked: setup_evergreen_campaigns_unavailable`. Do not fall
back to ad hoc create-campaign calls, inventory-only guesses, or direct shell
creation when the command-backed plan is missing.
Exception: if an initial unscoped plan returns only `blocked:no_senders`, and
the parent has already read `list_senders` plus a product-visible managed
evergreen/prod waterfall with mappable `senderRefs`, immediately re-run the
plan once with explicit derived `postEngagerSenderIds` and `sharedSenderIds`.
Do not report `no_senders` to the user until that scoped plan has also failed.
If senderRef mapping is partial, stop with
`blocked: ambiguous_post_engager_sender_scope` and include the unmapped refs.

MCP/product-visible proof only: every success claim must be observable through
the installed Sellable MCP tools and product-visible readbacks a normal user or
agent can access. Do not inspect the product database directly to prove success.
Direct SQL, Prisma, or DB snapshots are diagnostic only; they are not success proof for command completion. Receipts must cite MCP tool calls, command
responses, prompt/asset loads, and product-visible readbacks such as campaign
context, campaign message preview, table rows, cell selectors, sequence
readback, and setup plan/verify results. If MCP surfaces disagree on counts or
state, record the discrepancy and resolve or explain the canonical gate before
reporting the command complete.

Package-backed prompt authority: this public wrapper is an installed Codex
skill, so the host may load the wrapper itself from the local plugin bundle as
the command entrypoint. That entrypoint load is allowed, but it is not proof
that the create-campaign workflow ran. After this wrapper is active, when this
command or any lane worker says to load `$sellable:create-campaign`,
`create-campaign-v2`, `generate-messages`, validation, or any referenced
workflow asset, the package-backed MCP loaders are the source of truth. Use
`get_subskill_prompt` and `get_subskill_asset` until `hasMore:false`.
Do not locate, open, read, or summarize nested workflow prompt files from repo
paths, installed plugin cache paths, or any filesystem shortcut. Do not use
repo-local, plugin-cache, or filesystem `SKILL.md` files as nested workflow
authority, even when a session header shows a path. If package-backed prompt or
asset loaders are not callable, stop with
`blocked: package_prompt_loader_unavailable`; nested filesystem prompt fallback
is a failed UAT and is not proof that the packaged workflow ran.
Short form: The installed wrapper can be the entrypoint; nested workflow prompts
must use MCP loaders.
Short form: Do not use repo-local, plugin-cache, or filesystem nested
`SKILL.md` files as workflow authority.
Short form: nested filesystem prompt fallback is a failed UAT.

Package-backed tool surface: use `mcp__sellable` only for workspace selection,
campaign/table/source/message mutations, and product-visible readbacks. Do not
use `mcp__sellable_admin`, admin workspace setters, direct DB tools, Prisma, or
SQL as part of command execution proof. Do not use built-in web search,
browser search, `web.run`, or any external browsing/search tool for sender or
company research in this command; use Sellable MCP research tools such as
`get_sender`, `fetch_linkedin_profile`, `fetch_linkedin_posts`,
`fetch_company`, `fetch_company_posts`, and `complete_sender_research`. If those
MCP tools do not provide enough proof, write a blocked receipt with the missing
proof instead of browsing externally. Before any worker mutation, record
normal-surface workspace proof with
`set_active_workspace({ workspaceId, userConfirmed:true })` or the equivalent
`mcp__sellable` call, then reread the active workspace.
Short form: mcp__sellable only.
Short form: Do not use `mcp__sellable_admin`.
Short form: Do not use web search or browser search.

Plan shape:

- one Post Engagers lane per sender in the **Post Engagers sender scope**;
- one shared Signal Discovery lane for the **shared lane sender scope**;
- one shared Cold Fallback lane for the **shared lane sender scope**;
- explicit existing campaign/table bindings when the user points at a canonical
  lane. Pass those exact ids to the plan and reuse/verify them instead of
  creating duplicates.
- If a user supplies canonical ids, reuse/verify them instead of creating duplicates.

The command-backed unbound plan is authoritative. If
`setup_evergreen_campaigns({ mode:"plan", ... })` returns a lane packet with
`intent:"create"`, execute that create packet exactly. Do not convert it into an
explicit binding because `list_tables`, `get_campaigns`, a waterfall, or old
inventory shows an `ARCHIVED` campaign/table with a matching name. Archived
campaigns are not reusable evergreen targets. Only pass an archived
campaign/table as an explicit binding when the user supplied those exact ids and
explicitly allowed dashboard visibility repair; otherwise ignore archived
matches for execution and let the create packet proceed.
Do not call `get_campaign` on archived waterfall bindings to overrule a create packet.
Do not re-plan with explicit bindings for archived waterfall slots after an unbound create plan.
Archived waterfall slot readbacks are diagnostic only; they are not a consistency check that can replace the immutable create packet.
If an archived waterfall binding makes the agent unwilling to execute the create packet, stop with `blocked: archived_binding_plan_conflict` instead of repairing or reusing the archived campaign/table.

Use `selectedSenderIds` only as a legacy shorthand when the same senders should
get sender-owned Post Engagers lanes and attach to shared lanes. When the scopes
differ, pass both explicit arrays:

- `postEngagerSenderIds`: only senders that should have sender-owned Post
  Engagers campaigns.
- `sharedSenderIds`: senders attached to the shared Signal Discovery and Shared
  Cold Fallback campaigns. This must be the full intended shared sender set,
  not merely the sender named for a Post Engagers lane.

If the operator says a sender is shared-only, do not create a Post Engagers lane
for that shared-only sender. Example: if Justin already has a Post Engagers
campaign and Jell/Hulan should only participate in the two shared campaigns,
call `setup_evergreen_campaigns` with `postEngagerSenderIds:["<justin id>"]`
and `sharedSenderIds:["<justin id>","<jell or hulan id>"]`.
If the operator says to keep a protected existing Post Engagers campaign unchanged
and create/repair only the shared lanes, pass `postEngagerSenderIds:[]` and the
full shared sender set in `sharedSenderIds`. Do not include the protected active
Post Engagers lane as a reuse packet in that shared-only execution scope: an
ACTIVE protected lane can correctly produce a backend `launched_campaign`
blocker and would make the whole yolo plan non-autoExecutable even though the
operator asked to leave it untouched. Inventory and waterfall proof may mention
the protected lane, but selectedActionIds and lane workers must cover only the
shared lane packets.
If the operator says to preserve existing active/live campaigns, preserve an
ACTIVE Post Engagers lane the same way: treat it as protected inventory, exclude
it from `postEngagerSenderIds` for the execution plan, and select only the
remaining missing/reusable shared-lane actionIds. Do not let a protected active
Post Engagers reuse packet block shared-lane verification or repair. Report the
protected active lane in inventory/final notes, but do not dispatch a worker for
it and do not include it in `selectedActionIds`.
Short form: Do not include the protected active Post Engagers lane as a reuse packet when the operator requested only shared lane execution.
Short form: "preserve existing active/live campaigns" means exclude ACTIVE Post Engagers from yolo execution, not block all shared-lane work.

When senders are ambiguous, ask which sender ids belong in the Post Engagers
sender scope and which belong in the shared lane sender scope before planning.
If the operator says all connected senders for both scopes, pass
`allConnectedSenders` and let the command return exact selected sender ids or
blockers.

`--yolo` is supported, but it is a safety-scoped execution mode, not a cleanup
or launch permission. With `--yolo`, pass `yolo:true` to
`setup_evergreen_campaigns({ mode:"plan", yolo })` and proceed without
intermediate approval only when every selected lane packet returns
`autoExecutable:true`. `--yolo` does not authorize archive/delete cleanup,
launch/start, schedule/send, broad approve-all, wrong workspace work, stale
plan execution, paid InMail spend, or ambiguous target choice. Stop on wrong
workspace, cleanup/archive/delete need, stale plan, disconnected sender,
duplicate candidate, unsafe side effect, missing source proof, or missing
message proof.
In exec/automation mode, do not call `request_user_input`. If a plan under
`--yolo` returns `autoExecutable:false` and the run cannot receive interactive
bounded delegated approval, stop with
`blocked: bounded_approval_unavailable_in_exec_mode` before any mutation and
state whether the operator should rerun with a safe autoExecutable plan, narrower
sender scopes, explicit bindings, or an interactive session.

When safe-yolo is blocked only because the plan needs normal setup work, ask
for a **bounded delegated approval** over the rendered current plan instead of
asking for each substep. The approval request must be rendered from the plan's
`approvalSummary`, not handwritten from raw lane packets alone. Show:

- `workspaceId`, `planRevision`, and `selectedActionIds`.
- Sender scopes with display names and ids: Post Engagers senders and shared-lane senders.
- `campaignsToCreate`: each campaign/lane name, action id, create intent, attached senders, and allowed side effects.
- `campaignsToUpdate`: each existing campaign/table id, current status/step, attached senders, and exact repairs or setup side effects.
- `campaignsToVerifyOnly`: existing campaigns that will only be verified/read.
- `campaignsLeftUntouched`: existing campaigns attached to selected senders but excluded from the current selected lane scope, with the reason.
- Source/import caps, generate-message caps, route-proof approval policy, allowed side-effect classes, forbidden side effects, blockers, and stop conditions.

If `approvalSummary.approvalQuestion` is present, use it as the short approval
question and put the detailed buckets underneath. Do not ask a vague yes/no
question that only says "run evergreen setup" or "approve this plan" without
naming the campaigns to create, existing campaigns to update/verify, campaigns
left untouched, and attached senders. After that one approval covers the current
planRevision and selectedActionIds, the parent may act on behalf of the
operator and execute the selectedActionIds end to end. Do not ask again for
substep approvals for source import, create-campaign choices, Message Drafting,
sequence attach/precheck, or exactly one route-proof approval when those actions
are within the lane packet, approved caps, approved side-effect classes, and
route-proof approval policy. Stop and re-plan if any fresh reread changes the
workspace id, sender ids, source/list id, campaign/table id, new campaign/table
id, planRevision, actionId, selectedActionIds, allowed side effects, caps,
status, or blocker set outside the approved packet.
Do not ask a vague yes/no question; show the exact bounded approval packet and
require explicit approval for that packet before mutation.
The short rule: one approval covers the current planRevision and selectedActionIds.
The approval clarity rule: the operator approves named campaign create/update/verify/untouched buckets and attached senders, not a generic evergreen run.
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.
The drift rule: stop and re-plan when ids, caps, blockers, side-effect classes, or any new campaign/table id leave the approved packet.

After bounded delegated approval or safe `--yolo`, fan out lane worker tasks
only from the current plan. Each worker receives the complete lane packet:
workspace id, sender ids, lane key/type, existing campaign/table binding or
create intent, source hints, caps, planRevision, actionId, allowed side effects,
postconditions, the parent `postEngagerSenderIds`, the parent
`sharedSenderIds`, and the packet's `workerDispatch` policy. Treat
`workerDispatch.requiresVisibleThreadOrDurableReceipt:true` as a hard
execution boundary, not guidance. Read and preserve `workerDispatch.acceptedRuntimes`,
`workerDispatch.rejectedRuntimes`, and `workerDispatch.receiptArtifactHint`
before dispatch.

Worker-local replans are read-only drift checks, never a replacement execution
basis. The parent planRevision, selectedActionIds, actionId, laneKey, workspace
id, and sender scopes remain authoritative for the worker receipt. If a worker
calls `setup_evergreen_campaigns({ mode:"plan", ... })` before mutation, that
call must include the exact original sender scopes from the parent plan,
including `postEngagerSenderIds:[]` when the Post Engagers scope is intentionally
empty, and the exact `sharedSenderIds` array. If a worker-local plan omits
`postEngagerSenderIds:[]`, changes `sharedSenderIds`, returns a different
planRevision/actionId/laneKey, or introduces any Post Engagers lane outside the
parent packet, do not mutate and do not use it as `setupPlanCall` proof; stop
with `blocked: worker_plan_scope_drift` and ask the parent to re-plan.
Short form: If a worker-local plan omits `postEngagerSenderIds:[]`, do not use it as `setupPlanCall` proof.
Short form: stop with `blocked: worker_plan_scope_drift`.
Every worker prompt must include both parent scope arrays as explicit fields:
`Parent postEngagerSenderIds:` and `Parent sharedSenderIds:`. Workers must
preserve parent postEngagerSenderIds even for shared lanes. Never run a
shared-lane worker-local plan with `postEngagerSenderIds:[]` unless the parent
`postEngagerSenderIds` is exactly `[]`. Every durable receipt must copy these
arrays to top-level `parentPostEngagerSenderIds` and `parentSharedSenderIds`
before completion.
Short form: Workers must preserve parent postEngagerSenderIds even for shared lanes.
Short form: Never run a shared-lane worker-local plan with `postEngagerSenderIds:[]` unless the parent `postEngagerSenderIds` is exactly `[]`.

Worker fan-out must use visible or durable execution. In local Codex, the
default and preferred runtime is `workerDispatch.preferredRuntime:
"visible-codex-app-thread"`. The parent must attempt a visible Codex app thread
first with the actual Codex app thread tools: if `codex_app.create_thread` is
not already visible, use tool discovery for the Codex app thread tools; then
call `codex_app.list_projects`, select the current repo project, and call
`codex_app.create_thread` with `target:{type:"project", projectId,
environment:{type:"local"}}`. Do not create a worktree target for evergreen
UAT or lane execution. The parent must record the returned thread id, pass
exactly one lane packet into that thread, and require the worker to write the
lane receipt at the artifact path named by
`workerDispatch.receiptArtifactHint`. That path is run-specific and must include
the packet's `workerDispatch.receiptRunId`. Before dispatch, if a file already
exists at the hinted path, do not trust or verify it; move it aside or choose the
fresh hinted path from a new plan. A valid receipt must be written after
`workerDispatch.receiptMustBeWrittenAfter`, reread by the parent after the
worker exits, and contain the current `planRevision`, `actionId`, `laneKey`,
`receiptArtifactPath`, and `durableReceiptWritten:true`. If a receipt predates
the current run, uses an old path without `receiptRunId`, or was not freshly
written by the current worker, stop with `blocked: stale_receipt_artifact`
before parent verify or completion. A streaming worker or branch worker is a
fallback only when app thread tools are unavailable; the receipt must include
`preferredRuntimeAttempt` for `visible-codex-app-thread` and a non-empty
`fallbackReason`, or backend verify rejects it.
When visible Codex app thread tools are unavailable but local Codex CLI is
available, the accepted durable streaming-worker command shape is:
`codex -a never -s danger-full-access -c model_reasoning_effort=high exec --skip-git-repo-check -m <worker-model> -C <repo> -o <worker-final-file> -`.
The approval, sandbox, and reasoning-effort config flags must come before the
`exec` subcommand for Codex CLI builds that expose `-a`/`-s`/`-c` only at top
level. `--skip-git-repo-check` belongs after `exec` because current customer and VPS Codex CLI builds expose it
as an `exec` subcommand flag, not a top-level flag. Do not use
`codex exec --ask-for-approval never ...` or `codex exec -a never ...`; those
forms fail on current customer CLI installs. Pipe the lane packet prompt on
stdin, require the worker to write `workerDispatch.receiptArtifactHint`, and
pass exactly one lane packet per worker.
When launching durable Codex CLI workers from an automation parent, pass an
explicit supported worker model and explicit `high` reasoning effort instead
of relying on the Codex CLI defaults. Use the parent runtime model when known,
for example:
`codex -a never -s danger-full-access -c model_reasoning_effort=high exec --skip-git-repo-check -m <worker-model> -C <repo> -o <worker-final-file> -`.
In Codex CLI, the parent runtime model is visible in the run header as
`model: <model-name>`. Copy that exact model string into child worker launches
first. If the parent header says `model: gpt-5.6-sol`, launch workers with
`-m gpt-5.6-sol` plus `-c model_reasoning_effort=high`; do not invent or probe
nearby aliases such as `gpt-5.3-codex`, `gpt-5.2`, `gpt-5-codex`,
`codex-latest`, or `codex-mini-latest` before trying the exact parent model. A
one-line probe must include the same `-c model_reasoning_effort=high` override
and count as supported only when it exits 0 and returns the requested output; a
session header followed by a `not supported` error is rejected, not accepted.
Do not rely on the Codex CLI default model or default reasoning effort; some
customer and VPS installs default to unavailable model aliases or to reasoning
below `high`. `-m gpt-5.6-sol` alone is not enough. If a child worker reports GPT 5.6-Sol
with reasoning below `high`, treat that as a launcher bug, relaunch with the explicit
`high` config before mutation, and do not ask the user to continue through the
model-quality warning. If the parent cannot identify a supported worker model
and launch it with `high` reasoning, stop with
`blocked: worker_model_unavailable` before mutation.
When wrapping multiple local Codex CLI workers in a shell launcher, run the
wrapper with `/bin/bash -lc` or another explicitly chosen portable shell. Do not
rely on zsh-specific behavior. Do not use `status` as a shell variable because
zsh treats it as read-only; use `exit_code` or `rc` instead. Receipt polling
must use `find <receipt-dir> -maxdepth 1 -name 'evg_*.json' -print` or an
explicit nullglob-safe equivalent. Do not use naked `*.json` globs under zsh,
and keep zero-receipt polls quiet with no `no matches found` diagnostics.
Do not embed `<<'WORKER_PROMPT'` heredocs inside a single quoted or double
quoted `/bin/bash -lc '...'` command string; nested quoting is brittle and can
truncate the first worker before mutation. For multi-worker launchers, write one
plain prompt file per lane under the current run directory, then start each
worker with `codex -a never -s danger-full-access -c model_reasoning_effort=high exec --skip-git-repo-check -m "$WORKER_MODEL" -C "$REPO" -o
"$worker_final_file" - < "$worker_prompt_file"`. Keep launcher shell variables
double-quoted and keep the prompt heredoc only in a standalone script/prompt-file
write step, not inside an already quoted shell argument. If the first launcher
attempt exits with a shell parse error or before starting the first worker,
stop with `blocked: worker_launcher_quoting_failed` and do not retry by
loosening sender scope or mutating in the parent.
Do not use `nohup`, detached background workers, or any launcher that returns
before the workers finish and before receipts/final files exist. A durable
worker must run in a foreground command session, visible Codex thread, or a shell
that stays alive until every worker has finished. If using background jobs inside
one shell, the same shell must stay alive, wait for every worker PID, poll only
the expected receipt paths, and record a per-worker exit code plus final-file
paths and stderr/log paths before returning. If workers exit with empty logs and
no final files because the subprocess detached or was reaped, stop with
`blocked: worker_subprocess_detached_without_output` before parent verify.
When launching multiple local Codex CLI workers from one shell, start each
background job in the same shell that will call `wait`. Do not capture worker
PIDs through command substitution, a subshell, a pipeline, or a helper function
invoked as `worker_pid=$(launch_worker ...)`; those patterns make the captured
PID non-waitable by the parent shell. Use the same-shell PID array pattern:
`pids=(); codex ... > "$log_file" 2>&1 & pids+=("$!")`, then
`for pid in "${pids[@]}"; do wait "$pid" || rc=1; done`. If `wait` reports
`pid is not a child of this shell`, stop with
`blocked: worker_subprocess_detached_without_output`.
Short form: run the wrapper with `/bin/bash -lc`.
Short form: Do not rely on zsh-specific behavior.
Short form: Do not use `status` as a shell variable; use `exit_code` or `rc`.
Short form: Do not capture worker PIDs through command substitution or a subshell.
Short form: Use same-shell `pids+=("$!")` before waiting on every PID.
Short form: Receipt polling uses `find <receipt-dir> -maxdepth 1 -name 'evg_*.json' -print`.
Short form: Do not use naked `*.json` globs under zsh; no `no matches found` diagnostics.
Short form: Do not embed `<<'WORKER_PROMPT'` heredocs inside a quoted `/bin/bash -lc` command string.
Short form: Multi-worker launchers write prompt files and run `codex ... - < "$worker_prompt_file"`.
Short form: Do not use `nohup`; keep the worker process attached until receipt or blocker.
After the auto-executable plan returns, take at most one short planning pass to
choose the visible/durable worker runtime and build the worker launch packet.
Do not keep reasoning, re-reading prompts, or restating the workflow instead of
dispatching. If the parent cannot name the callable worker runtime and prepare
the first worker launch command/tool call immediately after that pass, stop with
`blocked: worker_dispatch_deadline_exceeded` before directory setup or mutation.
After creating receipt directories, dispatch workers immediately; do not perform extended parent reasoning, extra inventory, or prompt rewriting loops.
Before any campaign shell creation, the parent must dispatch every selected lane
worker from the current plan and record the visible thread id or
`receiptArtifactPath` for each selected actionId. The parent must not call
`create_campaign`, `import_leads`, `confirm_lead_list`, sequence tools, message
preparation tools, or `setup_evergreen_campaigns` with `mode:"apply"` or any
other mutating setup mode. In this wrapper, `setup_evergreen_campaigns` is
plan/verify authority only; customer-visible creation belongs inside lane
workers through `$sellable:create-campaign`. If a campaign/table id appears for
a create lane before that lane has a recorded worker runtime and fresh receipt
path/thread id, stop with `blocked: parent_mutated_before_worker_receipt` and
do not continue setup in the parent.
Short form: dispatch every selected lane worker before shell creation.
Short form: do not call `setup_evergreen_campaigns` with `mode:"apply"` from the parent.
Use a compact worker prompt: include the exact matching
`createCampaignGoals[].goalPrompt`, lane packet JSON, original
selectedActionIds, original planRevision, parent postEngagerSenderIds, parent
sharedSenderIds, receiptArtifactPath, workspace id, sender ids, side-effect
caps, and the instruction to load packaged prompts through MCP. The worker
prompt must explicitly state the authority order:
`createCampaignGoals[].goalPrompt` first, then loaded `$sellable:create-campaign`
/ `create-campaign-v2` / `generate-messages` prompts and assets, then evergreen
lane/receipt proof requirements. Evergreen packet fields and receipt rules are
operational proof context only; they must not become campaign brief copy,
message-writing instructions, or generated customer-facing text.
The worker prompt must include the exact filter status rule:
`filterDecisionReceipt.status:"applied"` only; never `completed`, `confirmed`,
`done`, or any other alias.
The worker prompt must include the generated-message write rule: `update_cell`
is allowed only for the semantic Approved checkbox, never for generated message
text/body/sample copy. Bad copy requires `revise_message_template_and_rerun` or
brief/template revision plus Generate Message rerun; any generated-message cell
override is `blocked: generated_message_cell_override`.
Do not paste the entire wrapper skill into worker prompts. If the parent cannot
launch at least one visible/durable worker immediately after directory setup,
stop with `blocked: worker_dispatch_stalled` before any mutation.
After launching workers, poll only the expected current-run receipt paths from
`workerDispatch.receiptArtifactHint`. Polling is progress-aware, not a blind
wall-clock cutoff: while a visible/durable worker is still active and recent
readbacks show forward progress, keep polling the expected path and do not
report `worker_receipt_timeout`. If a worker is active but the receipt is still
missing after the product postconditions appear complete, send exactly one
receipt-write recovery prompt that says the worker must write either a success
receipt or a blocked receipt at the exact path before ending. If no worker
receipt file appears after a bounded **no-progress** window, for example 10
minutes without new tool calls, final-file updates, receipt mtime changes, or
thread progress, stop with `blocked: worker_receipt_timeout`, include the worker
ids/final-file paths/stderr if available, and do not create or repair campaign
shells in the parent.
If a worker produced partial product state but no receipt, recover through the
same visible/durable worker or a compact repair worker against the existing
campaign/table ids; never create a duplicate lane campaign to recover a missing
receipt.
Use only the repo-local `workerDispatch.receiptArtifactHint` returned by the
current lane packet for receipt paths.
Do not hardcode machine-specific paths, workspace ids, campaign ids, sender ids, or UAT/phase artifact paths in this MCP prompt or any worker prompt.
After the receipt-directory `mkdir`, the next shell/tool call must launch the first lane worker.
Do not run `find`, `ls`, extra inventory, or another preflight between directory setup and the first worker launch.
For the local Codex CLI fallback, use this literal durable CLI worker launch
template and fill only the lane-specific placeholders. For one-off direct worker
launches you may pipe a heredoc directly to `codex`; for multi-worker shell
launchers, write the same prompt body to `<worker-prompt-file>` and launch the
worker with stdin redirected from that file:

```
codex -a never -s danger-full-access -c model_reasoning_effort=high exec --skip-git-repo-check -m <worker-model> -C <repo> -o <worker-final-file> - <<'WORKER_PROMPT'
Use $sellable:create-campaign as the governing campaign workflow for this one lane worker.
Use the evergreen plan/packet below only for lane scope, source metadata,
postconditions, side-effect caps, and durable receipt proof.

Workspace id: <workspaceId>
Parent planRevision: <planRevision>
Parent selectedActionIds: <selectedActionIds JSON>
Parent postEngagerSenderIds: <postEngagerSenderIds JSON>
Parent sharedSenderIds: <sharedSenderIds JSON>
Action id: <actionId>
Lane key: <laneKey>
Allowed side effects: <allowedSideEffects JSON>
Write the durable receipt to this exact path: <receiptArtifactPath>

If your runtime exposes a goal tool, create or maintain this exact goal:
complete action <actionId> only when <receiptArtifactPath> exists as valid JSON
for this lane, or only for the accepted Post Engagers no-source blockers listed
below when a blocked receipt has been written at that same path.

Create-campaign goal prompt (authoritative; paste verbatim from the matching
createCampaignGoals[].goalPrompt):
<createCampaignGoalPrompt>

Authority order:
1. The create-campaign goal prompt above is the campaign task.
2. Loaded `$sellable:create-campaign`, `create-campaign-v2`, and
   `generate-messages` prompts/assets own the campaign brief and message
   strategy.
3. The evergreen lane packet and receipt rules below are operational scope and
   proof requirements only.

Do not paste evergreen lane keys, provider/source names, receipt terms, setup
plan terminology, verifier fields, or workflow mechanics into the campaign
brief or generated customer-facing messages. Do not add evergreen-authored
message-writing rules to the brief; let the loaded create-campaign and
generate-messages workflow do its job.

Worker lane packet JSON:
<lane packet JSON>

Load `$sellable:create-campaign` and nested prompts/assets through Sellable MCP loaders.
Use mcp__sellable only. Do not use admin tools, direct DB, Prisma, SQL, web search, or browser search.
Do not launch, start, schedule, send, or use paid InMail.
For filter proof, durable receipt status must be `filterDecisionReceipt.status:"applied"` only; never `completed`, `confirmed`, `done`, or aliases.
For generated messages, `update_cell` is allowed only for the semantic Approved checkbox. Never use `update_cell` for generated message text/body/sample copy. Bad copy requires `revise_message_template_and_rerun` or brief/template revision plus Generate Message rerun. Any generated-message cell override is `blocked: generated_message_cell_override`.
If `bootstrap_create_campaign.modelQuality.status === "warn"` because the child
worker reports GPT 5.6-Sol with reasoning below `high`, that is a parent launcher
configuration bug, not an operator approval path inside the worker. Stop before
mutation, tell the parent to relaunch this lane with
`-c model_reasoning_effort=high`, and do not mark the worker goal complete.
Complete only this lane. Do not end with narration only. Before your final
response, run a local file-existence and JSON self-check for
<receiptArtifactPath>. If the lane succeeded, write the canonical success
receipt there; if any postcondition, quality gate, verifier schema, or file
write blocks, write a canonical blocked receipt there with `status:"blocked"`,
`blocker`, `campaignId`, `tableId`, `actionId`, `laneKey`, `planRevision`, and
the latest product readback. For Post Engagers lanes, no sender-authored posts
or no sender-owned post-engager source is an acceptable terminal blocked/no-op
receipt, not a worker crash: use one of
`post_engagers_no_sender_posts`, `post_engagers_no_recent_sender_posts`,
`post_engagers_no_sender_owned_posts`, or
`post_engagers_source_author_mismatch`, include the source/readback evidence,
and stop without scraping third-party authors or generating misleading warm
copy. Any other blocked receipt is not a goal-complete success condition; it is
a failed lane return that the parent must report or repair. The terminal success
condition is filesystem proof that <receiptArtifactPath> exists with a success
receipt, or an accepted Post Engagers no-source blocked/no-op receipt.
WORKER_PROMPT

# Multi-worker launcher equivalent:
codex -a never -s danger-full-access -c model_reasoning_effort=high exec --skip-git-repo-check -m <worker-model> -C <repo> -o <worker-final-file> - < <worker-prompt-file>
```

If any placeholder cannot be filled from the current plan, matching
`createCampaignGoals[]` entry, or lane packet, do not inspect the filesystem or
rerun inventory; stop with `blocked: worker_dispatch_stalled`.
`multi_agent_v1.spawn_agent`, raw `spawn_agent`, or any opaque Task/subagent
runtime that cannot expose a visible thread id or durable receipt artifact is
not accepted as command-level UAT proof and must not be used for mutating
evergreen setup.

Run this preflight before the first irreversible product mutation in any lane:
the parent must have recorded either a visible Codex thread id or a durable
`receiptArtifactPath` for that lane. If the runtime cannot provide this, stop
with `blocked: missing_visible_or_durable_lane_worker_runtime` before calling
`create_campaign`, `update_campaign`, `import_leads`, `confirm_lead_list`, or
any source/message/sequence mutation. Do not downgrade this to
`missing_parallel_lane_worker_runtime` when an opaque runtime exists: an
opaque runtime is still missing visible/durable proof. If a worker creates
partial product state without receipts, the run is blocked; created objects exist
is not equivalent to command completion.

Workers must explicitly load and use the installed visible
`$sellable:create-campaign` wrapper as the nested workflow entrypoint, then
load `create-campaign-v2` and `create-campaign-v2/core/flow.v2.json` through
the packaged MCP prompt/asset loaders before campaign creation or repair. The
worker then uses that create-campaign workflow/subskills for campaign
creation/setup, source/import, filter choice, Message Drafting, packaged
`generate-messages`, sequence attachment, and send-review readiness. Do not
rebuild those steps ad hoc in the parent thread or from a copied receipt
checklist. The parent thread is only the planner, dispatcher, verifier, and
receipt collector: it must not call `create_campaign`, `import_leads`,
`start_campaign_message_preparation`, `update_cell`, or sequence-attach tools
for a lane that was assigned to a worker. When the host supports visible Codex
threads, branch execution with durable receipts, or a streaming durable worker,
assign one lane packet per worker and run the lane workers in parallel. If the
host cannot run parallel workers with visible/durable receipts, report
`blocked: missing_visible_or_durable_lane_worker_runtime` instead of silently
doing all lane creation sequentially in the parent thread.
Short form: the parent thread is the planner, dispatcher, verifier, and receipt collector.
Before the first mutating lane action, complete an execution-runtime preflight:
name the callable worker surface/tool that will run lane packets and the proof
it gives (`thread id`, `receiptArtifactPath`, or both). If you cannot name a
real callable visible/durable worker runtime, stop with
`blocked: missing_visible_or_durable_lane_worker_runtime` before calling
`create_campaign`, `update_campaign`, `import_leads`, `confirm_lead_list`, or
any source/message/sequence mutation. Creating campaign shells first and then
discovering the worker runtime is missing is a failed run.
Legacy reports may mention `blocked: missing_parallel_lane_worker_runtime`;
for new command runs, use the visible/durable blocker above.

For any lane with `intent:"create"`, the worker must satisfy the normal
net-new create-campaign preflight before shell creation: load
`get_subskill_prompt({ subskillName:"research-sender" })`, run the sender/team
identity research needed for the brief, call `complete_sender_research(...)`,
then call `create_campaign`. Do not bypass this with a parent-thread brief or
by directly setting campaign state.

Workers return worker receipts. Each customer-visible lane receipt must include
one durable `createCampaignStepReceipt` built from actual tool calls or worker receipts,
not a parent-thread summary. The receipt must include:

- `setupPlanCall`: the exact `setup_evergreen_campaigns({ mode:"plan", ... })`
  planRevision, selected actionId, lane key, workspace id, sender ids, and
  campaign/table binding or create intent used before work started. For a
  create lane, this receipt must explicitly include `createIntent:true`; for a
  reuse lane, do not report `createIntent:true`. The object must use the
  canonical keys `planRevision`, `actionId`, `laneKey`, `workspaceId`,
  `senderIds`, `campaignId`, `tableId`, and `createIntent`. Do not use
  `laneActionId`, `lanePacketActionId`, `delegatedPlanRevision`,
  `delegatedActionId`, or freeform `requestedCall` text as a substitute for
  these fields. For create lanes, `setupPlanCall.campaignId` and
  `setupPlanCall.tableId` must be the actual created campaign/table ids after
  creation and readback. If the parent lane packet carried null ids before
  creation, preserve that original packet only in a separate `parentLanePacket`
  object; do not leave `setupPlanCall.campaignId:null` or
  `setupPlanCall.tableId:null`.
  If the parent plan used connection-only, the receipt must also include
  `setupPlanCall.campaignSequenceOptions:{ "mode":"connection_only" }` or
  `setupPlanCall.sequencePolicy.mode:"connection_only"`.
  Short form: For create lanes, `setupPlanCall.campaignId` and `setupPlanCall.tableId` must be the actual created campaign/table ids.
  Short form: Do not leave `setupPlanCall.campaignId:null` or `setupPlanCall.tableId:null`.
- `createCampaignWorkflowReceipt`: proof the worker loaded the actual installed
  `$sellable:create-campaign` wrapper instead of doing ad hoc MCP calls. It must
  include `skillCommand:"$sellable:create-campaign"`,
  `skillName:"create-campaign"`, `wrapperSkillLoaded:true`,
  `workflowPromptName:"create-campaign-v2"`,
  `workflowPromptLoadedToHasMoreFalse:true`,
  `workflowAssetPath:"create-campaign-v2/core/flow.v2.json"`,
  `workflowAssetLoaded:true`, the real `workerRuntime`, a visible
  `workerThreadId` or durable `receiptArtifactPath`, `durableReceiptWritten:true`
  when using a receipt file, and `notAdHoc:true`. If `workerRuntime` is
  `streaming-worker-with-durable-receipt` or
  `branch-worker-with-durable-receipt`, also include
  `preferredRuntimeAttempt` proving the parent attempted
  `visible-codex-app-thread` first and a non-empty `fallbackReason`.
- `researchSenderReceipt`: `research-sender` prompt load proof,
  `complete_sender_research` result, sender LinkedIn identity basis, and the
  notes/proof counts used before the net-new `create_campaign` call. It must
  include canonical `promptLoadedToHasMoreFalse:true`,
  `completeSenderResearch`, and either `senderLinkedinUrl`, non-empty
  `senderIds`, or non-empty `senderIdentityBasis`, plus `identityBasis`.
- `campaignBriefReceipt`: the CampaignOffer/campaign id, current
  workflowTableId, brief hash or updated-at marker, delivery format, token
  rules, hard avoids, source-use rules, and lane-appropriate first-message
  state. Post Engagers lanes require `campaignBriefReceipt.firstMessageTemplate`
  because they are sender-owned warm lanes. Shared Signal Discovery and Shared
  Cold Fallback lanes must not set `campaignBriefReceipt.firstMessageTemplate`;
  instead set `campaignBriefReceipt.firstMessageTemplateState` to the exact
  no-preset marker
  `not preset; generate-messages branch selected the final message approach`.
  If the tool output uses `approvedFirstMessageTemplate`, copy it to canonical
  `firstMessageTemplate` only for Post Engagers lanes. For shared lanes, treat a
  raw approved template as a quality issue unless it is only a no-preset or
  generate-messages state marker. If the tool output uses
  `deliveryFormatIncluded`, `tokenRulesIncluded`, `hardAvoidsIncluded`, or
  `sourceUseRulesIncluded`, copy those to canonical `deliveryFormat`,
  `tokenRules`, `hardAvoids`, and `sourceUseRules`. Before the worker exits, the
  durable JSON must contain the exact canonical leaf keys `briefHash`,
  `deliveryFormat`, `tokenRules`, `hardAvoids`, and `sourceUseRules` inside
  `createCampaignStepReceipt.campaignBriefReceipt`, plus
  `firstMessageTemplate` only for Post Engagers lanes or
  `firstMessageTemplateState` for shared lanes; do not leave those only in
  narrative text, `briefIncludes`, `currentApprovedBriefHash`, or other aliases
  for the parent/verifier to infer.
  Short form: Post Engagers lanes require `campaignBriefReceipt.firstMessageTemplate`.
  Short form: Shared Signal Discovery and Shared Cold Fallback lanes must not set `campaignBriefReceipt.firstMessageTemplate`.
- `sourceDecisionReceipt`: selected source/list id, provider, cursor/inventory
  state, import/copy caps, explicit `dedupeDncProviderExclusions:true` or an
  equivalent exclusions object, row ids or row-count evidence (`rowCount`,
  `readyRowCount`, or `rowEvidence`), and the reason no colder source was used
  before the current source was proven exhausted or insufficient. If the raw
  provider output uses `sourceProvider`, `sourceListId`, `reviewBatchRowIds`, or
  `rationale`, copy them to canonical `provider`, `sourceId`/`sourceListId`,
  `rowIdsHash`/`rowCount`, and `sourceLadderReason`. Before the worker exits,
  the durable JSON must contain exact canonical source proof fields:
  `dedupeDncProviderExclusions`, `sourceLadderReason`, and at least one row
  evidence field such as `rowCount`, `rowIdsHash`, or `rowEvidence`; do not
  leave those only in `searchSignalsProof`, `importProof`, `coldFallbackProof`,
  or narrative text for the parent/verifier to infer.
- `filterDecisionReceipt`: saved/applied ICP filter proof for the current
  campaign/table basis. Customer-visible evergreen lanes must enable ICP
  filtering with `enableICPFilters:true`, call `save_rubrics`, and save at
  least three rubric/filter criteria before Message Drafting. Do not skip
  filtering for evergreen setup. Missing or skipped filter proof is
  `blocked: missing_filter_setup`.
  Before the worker exits, the durable JSON must contain
  `filterDecisionReceipt.status:"applied"`,
  `filterDecisionReceipt.enableICPFilters:true`, the current
  `workflowTableId`, and concrete saved-filter proof such as
  `filterDecisionReceipt.filterIds`, `filterDecisionReceipt.rubricIds`,
  `filterDecisionReceipt.savedRubricCount`, or
  `filterDecisionReceipt.headlineICPCriteria`.
  The exact status string is contractual: use `status:"applied"` only.
  Do not write `status:"completed"`, `status:"confirmed"`, `status:"saved"`,
  `status:"done"`, or any other alias after saving filters; the parent verifier
  rejects those aliases even when saved rubric proof exists.
  Short form: Filter receipt status must be exactly `applied`.
  Short form: Do not write `filterDecisionReceipt.status:"completed"`.
  Short form: Do not write `filterDecisionReceipt.status:"confirmed"`.
- `messageDraftingReceipt`: the Message Drafting proof described below,
  including packaged `generate-messages` prompt/assets and validation. Raw
  fields like `generateMessagesPromptLoadedToHasMoreFalse`,
  `requiredAssetsLoadedToHasMoreFalse`,
  `validationPromptLoadedToHasMoreFalse`,
  `get_campaign_message_preparation_status`, and
  `waitForCampaignProcessingProof` are useful, but the receipt must also copy
  them into canonical `promptLoadedToHasMoreFalse`, `requiredAssetsLoaded`,
  `validationLoaded`, `reviewBatchRowHash`, and
  `messageDraftRecommendation`.
  Connection-only lanes are the exception: do not include
  `messageDraftingReceipt` or run generated-message prep unless the operator
  separately asked for message copy. The parent verifier expects no message
  proof for connection-only lanes.
- `reviewBatchReceipt`: review-batch row ids/hash, generated row count,
  quality-valid route-proof row id when approved, and proof that no broad
  approve-all occurred.
  Connection-only lanes are the exception: do not approve a route-proof row and
  do not include a required review-batch receipt.
- `sequenceReceipt`: exact current workflowTableId, recommended non-paid
  sequence attach/precheck result, and readback showing `hasSequence:true` when
  completion is claimed. If the tool output uses
  `attachRecommendedSequenceResult.actionTypes` or
  `nonPaidRecommendedSequence`, copy them to canonical `actionTypes` and
  `nonPaid`.
  For connection-only lanes, do not call `attach_recommended_sequence`. Attach
  with `attach_sequence({ tableId, templateRef:"connection_only", currentStep:"send" })`, then
  include exact current-template proof:
  `sequenceReceipt.hasSequence:true`,
  `sequenceReceipt.actionTypes:["send_invite"]`, and
  `sequenceReceipt.nonPaid:true`. Receipts that only say
  `tool:"attach_recommended_sequence"` or include `send_dm`,
  `send_inmail_open`, `send_inmail_closed`, or `view_profile` are failing
  connection-only receipts.
- final paused-send proof: if the current campaign table is `DRAFT` after the
  sequence is attached, call the product `pause_campaign({ campaignId })`
  endpoint/tool to put the unlaunched campaign into `PAUSED` review state, then
  reread the campaign/table. Do not raw-write `campaignStatus`, do not start or
  launch, and do not schedule/send. Completion requires reread proof of
  `currentStep:"send"` and `campaignStatus:"PAUSED"` for standard and
  connection-only customer-visible lanes. `send` is the product's paused
  review step; do not use unsupported aliases such as `review` as final proof.
  If the raw output nests this in `finalCampaignRead` or `finalTableRead`, copy
  it to canonical
  `finalPausedSendProof.currentStep` and
  `finalPausedSendProof.campaignStatus`.
- `verifyCall`: the exact `setup_evergreen_campaigns({ mode:"verify",
  planRevision, selectedActionIds, receipts })` call/result used before the
  parent reports completion.

Before writing the durable receipt, run a receipt self-check. The top-level
receipt must include top-level `planRevision`, `actionId`, `laneKey`,
`laneType`, `workspaceId`, and `senderIds`, plus campaign/table/source ids and
row/message/approval counts. It must also include top-level `receiptArtifactPath`,
top-level `durableReceiptWritten:true`, top-level `receiptRunId`, and top-level
`receiptWrittenAt`; do not leave `receiptArtifactPath` only under
`createCampaignWorkflowReceipt` for the parent to patch during verify. Inside
`createCampaignStepReceipt.messageDraftingReceipt`, `sampleMessages[]` must
include `rowId`, `generatedMessageText`, `verdict`, and `issues`. Do not
substitute `message` for `generatedMessageText`. Do not substitute
`passVerdict` for `verdict`. If the self-check fails, fix the receipt before
ending; do not leave the parent to infer canonical fields from top-level
aliases or narrative text.
Self-check shorthand: copy raw tool-output aliases into canonical receipt keys
before writing JSON; do not leave only raw objects like
`get_campaign_message_preparation_status`, `attachRecommendedSequenceResult`,
`finalCampaignRead`, or `finalTableRead`.
Self-check shorthand: top-level `planRevision`, `actionId`, `laneKey`, `laneType`, `workspaceId`, and `senderIds` must exist before worker completion.
Self-check shorthand: top-level `receiptArtifactPath`, `durableReceiptWritten:true`, `receiptRunId`, and `receiptWrittenAt` must exist before worker completion.
Self-check shorthand: top-level `receiptWrittenAt` must exist before worker completion.
Self-check shorthand: `campaignBriefReceipt.briefHash`,
`campaignBriefReceipt.deliveryFormat`, `campaignBriefReceipt.tokenRules`,
`campaignBriefReceipt.hardAvoids`, and `campaignBriefReceipt.sourceUseRules`
must exist as exact canonical keys before worker completion, plus
`campaignBriefReceipt.firstMessageTemplate` only for Post Engagers lanes and
`campaignBriefReceipt.firstMessageTemplateState` for shared lanes.
Self-check shorthand: `sourceDecisionReceipt.dedupeDncProviderExclusions`,
`sourceDecisionReceipt.sourceLadderReason`, and one of
`sourceDecisionReceipt.rowCount`, `sourceDecisionReceipt.rowIdsHash`, or
`sourceDecisionReceipt.rowEvidence` must exist as exact canonical keys before
worker completion.
Self-check shorthand: `messageDraftingReceipt.promptLoadedToHasMoreFalse`,
`requiredAssetsLoaded`, `validationLoaded`, `reviewBatchRowHash`, and
`messageDraftRecommendation` must exist before worker completion.
Self-check shorthand: `reviewBatchReceipt.rowIdsHash`,
`reviewBatchReceipt.generatedCount`, `sequenceReceipt.actionTypes`,
`sequenceReceipt.nonPaid`, `finalPausedSendProof.currentStep`, and
`finalPausedSendProof.campaignStatus` must exist before worker completion.
Self-check shorthand: `sampleMessages[]` must include `rowId`, `generatedMessageText`, `verdict`, and `issues`.
Self-check shorthand: Do not substitute `message` for `generatedMessageText`.
Self-check shorthand: Do not substitute `passVerdict` for `verdict`.
Self-check shorthand: If the self-check fails, fix the receipt before ending.

Use these canonical receipt leaf names exactly. Do not substitute looser aliases
like `generateMessagesPromptLoad`, `requiredAssetLoads`,
`validationPromptLoad`, `reviewBatchBasisHash`, `reviewRowIds`,
`sequenceActionTypes`, or `pass:true` unless you also include the canonical
fields below:

The receipt status must be `status:"succeeded"` or `status:"completed"` when
the lane is complete. Do not use `status:"passed"` or
`status:"passed_with_warnings"` as the primary success status. Warnings may go
in a separate `warnings` array, but the completion status stays canonical.
The backend verifier rejects `status:"passed"`, `status:"pass"`, and
`status:"passed_with_warnings"` as primary completion statuses.
Exception: a Post Engagers lane may write `status:"blocked"` when Sellable MCP
sender-post/source readback proves there are no recent sender-authored posts to
scrape, no sender-owned post-engager source, or the current source is authored
by someone else. Use the explicit blocker codes
`post_engagers_no_sender_posts`, `post_engagers_no_recent_sender_posts`,
`post_engagers_no_sender_owned_posts`, or
`post_engagers_source_author_mismatch`. Parent verify treats these as
`acceptedLaneBlockers`, so the parent can report the truthful no-op instead of
retrying invalid source repair. This exception does not apply to shared lanes,
bad generated copy, stale plans, target mismatch, missing receipt files, launch
or schedule side effects, model-quality/preflight blockers, worker model
availability blockers, or arbitrary worker failures. A blocked receipt outside
the accepted Post Engagers source codes must not be treated as completion, must
not be used to mark the worker goal complete, and must not let the parent report
the evergreen command as complete.

All step proof objects must live under `createCampaignStepReceipt`. Do not put
`createCampaignWorkflowReceipt`, `campaignBriefReceipt`,
`sourceDecisionReceipt`, `filterDecisionReceipt`, `messageDraftingReceipt`,
`reviewBatchReceipt`, or `sequenceReceipt` only as top-level siblings;
top-level copies are useful for humans but are not enough for command
verification unless the same object also appears inside
`createCampaignStepReceipt`.

```json
{
  "status": "succeeded",
  "planRevision": "<original planRevision>",
  "laneKey": "<lane key>",
  "laneType": "<lane type>",
  "workspaceId": "<workspace id>",
  "senderIds": ["<sender id>"],
  "campaignId": "<campaign id>",
  "tableId": "<workflow table id>",
  "createCampaignStepReceipt": {
    "createCampaignWorkflowReceipt": {
      "skillCommand": "$sellable:create-campaign",
      "skillName": "create-campaign",
      "wrapperSkillLoaded": true,
      "workflowPromptName": "create-campaign-v2",
      "workflowPromptLoadedToHasMoreFalse": true,
      "workflowAssetPath": "create-campaign-v2/core/flow.v2.json",
      "workflowAssetLoaded": true,
      "workerRuntime": "visible-codex-app-thread",
      "workerThreadId": "<visible local Codex thread id>",
      "receiptArtifactPath": "<receiptArtifactPath from workerDispatch.receiptArtifactHint>",
      "durableReceiptWritten": true,
      "notAdHoc": true
    },
    "campaignBriefReceipt": {
      "campaignId": "<campaign id>",
      "workflowTableId": "<workflow table id>",
      "briefHash": "<brief/currentApprovedBriefHash>",
      "deliveryFormat": "single message",
      "tokenRules": true,
      "hardAvoids": true,
      "sourceUseRules": true,
      "firstMessageTemplate": "<Post Engagers only warm template; omit for shared lanes>",
      "firstMessageTemplateState": "<Shared lanes only: not preset; generate-messages branch selected the final message approach>"
    },
    "sourceDecisionReceipt": {
      "sourceId": "<source id>",
      "sourceListId": "<source/list id>",
      "cursorOrInventoryState": "<cursor/inventory/import state>",
      "dedupeDncProviderExclusions": true,
      "sourceLadderReason": "<why the current source is valid before colder sources>"
    },
    "filterDecisionReceipt": {
      "status": "applied",
      "enableICPFilters": true,
      "filterIds": ["<saved filter/rubric id>"],
      "rubricIds": ["<saved rubric id>"],
      "headlineICPCriteria": ["<saved ICP criterion>", "<saved ICP criterion>", "<saved ICP criterion>"],
      "savedRubricCount": 3,
      "workflowTableId": "<workflow table id>"
    },
    "messageDraftingReceipt": {
      "statusSource": "branch",
      "promptLoadedToHasMoreFalse": true,
      "requiredAssetsLoaded": true,
      "validationLoaded": true,
      "validationResult": "passed",
      "qualityReview": {"passed": true, "checkedSampleCount": 3, "issues": []},
      "campaignId": "<campaign id>",
      "workflowTableId": "<workflow table id>",
      "selectedLeadListId": "<source/list id>",
      "reviewBatchRowHash": "<review row ids hash>",
      "messageDraftRecommendation": {"basis": "generate-messages"},
      "sampleMessages": [
        {"rowId": "<row id>", "generatedMessageText": "<message>", "verdict": "passed", "issues": []}
      ]
    },
    "reviewBatchReceipt": {
      "rowIdsHash": "<review row ids hash>",
      "generatedCount": 3
    },
    "sequenceReceipt": {
      "workflowTableId": "<workflow table id>",
      "hasSequence": true,
      "actionTypes": ["send_invite"],
      "nonPaid": true
    },
    "finalPausedSendProof": {
      "currentStep": "send",
      "campaignStatus": "PAUSED"
    },
    "verifyCall": {
      "mode": "verify",
      "planRevision": "<original planRevision>",
      "selectedActionIds": ["<all original selected action ids>"]
    }
  }
}
```

Verification always uses the original execution planRevision and original
selectedActionIds from the plan packets that were dispatched before mutation.
The parent must preserve the original selectedActionIds exactly.
Do not replace them with a later reuse planRevision/actionIds after the
campaigns exist. After workers complete, attach or patch `verifyCall` on each
lane receipt with that original execution planRevision, original
selectedActionIds, same sender scopes, and the verify result. Only after verify
succeeds should the parent perform the idempotency rerun.

If any required field is missing, report
`blocked: missing_create_campaign_step_receipt` for that lane and do not call it
complete.
If `createCampaignWorkflowReceipt.workerRuntime` is
`multi_agent_v1.spawn_agent`, `spawn_agent`, or another opaque worker runtime,
or if it lacks both `workerThreadId` and a durable receipt artifact proof, treat
the lane as `blocked: missing_create_campaign_step_receipt` with detail
`createCampaignWorkflowReceipt.workerRuntime.visible_or_durable`; do not call
verify and do not repair by summarizing product readback in the parent.

Message Drafting runtime rule: the preferred proof source is the normal
create-campaign branch handoff (`watchNarration.workerDetails.messageDraftBuilder`
or equivalent) with `statusSource:"branch"`. If that internal branch handoff is
not callable or not exposed in the packaged MCP runtime, do **not** substitute
plain row generation. The lane worker must inline the same
`post-find-leads-message-scout` contract in the worker thread before any
`start_campaign_message_preparation` call:

1. Run the `post-find-leads-message-scout` branch, or inline its packaged
   contract when branch launch is unavailable, from the current campaign/table,
   selected lead list, saved filters, review batch row ids/hash, brief hash,
   `mode:"create-campaign-v2"`, and lane type.
2. Load `get_subskill_prompt({ subskillName:"generate-messages" })` until
   `hasMore:false`, all required message assets, and
   `create-campaign-v2-validation`.
3. Produce a concrete `messageDraftRecommendation` before row generation. It
   must include the selected message approach/template recommendation,
   token/fill rules, source-use rules, hard avoids, at least one rendered good
   sample, current campaign/table/source basis, and validation status. Shared
   lanes still use `firstMessageTemplateState:"not preset; generate-messages
   branch selected the final message approach"`; do not set
   `firstMessageTemplate` for shared lanes.
4. Persist the recommendation into the campaign brief/message generation basis
   with the packaged create-campaign tools before generating rows. If the
   generated copy fails review, update the campaign brief/message template with
   `update_campaign_brief` or `revise_message_template_and_rerun`, then rerun
   Generate Message and review current-revision generated output.
5. Only after the scout recommendation and persisted basis exist, call
   `start_campaign_message_preparation({ campaignId, tableId,
   targetPreparedMessages, maxRowsToCheck, approvalMode:"mark_ready",
   autoContinue:true })` inside the same lane worker, with bounded caps from the
   lane packet. `start_campaign_message_preparation` is row execution proof
   only; it is not Message Drafting proof and cannot satisfy this step by
   itself. Never call `start_campaign_message_preparation` with
   `approvalMode:"approve"` for evergreen setup.
   Generate/readiness and approval are separate safety steps: first mark rows
   ready, then inspect generated copy, then approve exactly one semantic
   Approved cell through `select_campaign_cells` and `update_cell`.
   Approval shorthand: approve exactly one semantic Approved cell.
   In this message-drafting path, `update_cell` is allowed only for the semantic
   Approved checkbox. Do not use `update_cell` or any row/cell write to edit
   generated message text, sample copy, or final message bodies in automation.
   Receipt `sampleMessages` must come from generated current-revision cells
   after the brief/template rerun, not from hand-edited message cell overrides.
   If a lane used a generated-message body override, report
   `blocked: generated_message_cell_override` instead of success.
   Do not repair by overwriting bad generated message cells and then calling the
   overwritten cells `sampleMessages`.
   `messageDraftingReceipt.qualityReview.repairNote`,
   `messageDraftingReceipt.toolsUsed`, or any proof mentioning generated
   message cell repair through `update_cell` is self-incriminating and must be
   `blocked: generated_message_cell_override`.
   Short form: `update_cell` is allowed only for the semantic Approved checkbox.
   Short form: Do not use `update_cell` to edit generated message text.
   Short form: Bad copy requires brief/template revision plus Generate Message rerun.
6. Poll `get_campaign_message_preparation_status` until no queued/active cells
   remain or a bounded stop reason is returned.
7. Use `get_campaign_messages_preview` or `select_campaign_cells` to inspect
   generated rows, quality-review at least 3 samples, approve exactly one
   quality-valid route-proof row when none is already approved, and prove no
   broad approve-all happened.
   If a packaged worker or earlier run left more than one generated row
   approved, set any extra approved cells back to false through semantic
   Approved-cell selection before completion. Final proof must show
   approvedGeneratedMessageCount exactly 1. Do not report completion with 2+
   approved rows.
   Approval shorthand: Do not report completion with 2+ approved rows.
8. Standard mode: continue to
   `attach_recommended_sequence({ campaignId, currentStep:"send" })` and, if
   the campaign is still `DRAFT`, `pause_campaign({ campaignId })`. Reread the
   campaign/table before claiming completion.
   Connection-only mode: skip Message Drafting and route-proof approval, call
   `attach_sequence({ tableId, templateRef:"connection_only", currentStep:"send" })`, prove
   `sequenceReceipt.actionTypes:["send_invite"]`, and pause/reread without
   launching or sending.

That packaged worker path must return `messageDraftingReceipt.statusSource:
"packaged-generate-messages-worker"`. It is accepted only when the receipt
includes branch-equivalent `post-find-leads-message-scout` proof, the
`messageDraftRecommendation`, proof the recommendation was persisted into the
brief/message basis before row generation, the prompt/assets/validation proof,
current campaign/table/source basis, generated review rows, sample messages,
quality review, route-proof approval evidence, sequence proof, and final
paused-send proof required of the branch handoff. `statusSource:
"parent-thread-fallback"` is still invalid. If neither branch handoff nor
inline scout proof can run, stop with `blocked:
missing_message_drafting_branch`; do not shortcut through row prep.
The only accepted statusSource values are exactly `branch` and
`packaged-generate-messages-worker`; descriptive aliases such as
`package-readback-local-thread` are rejected.

Before any provider-specific source mutation for a newly created campaign,
reread `get_campaign_navigation_state({ campaignId })` and use that product
navigation state as the source-step authority. Do not jump from a shell directly
into `contact-search`, and do not jump from a shell directly into
`signal-discovery`. Advance through the product-visible source-selection step
first, then load the provider prompt and write only the provider/source fields
accepted for that current step. Never include sender reassignment fields in
source-step updates, never include custom narration fields in source-step
updates, and do not use guessed `currentStep` or watched narration payloads to
force the flow forward. If `update_campaign` rejects the source transition
twice, reread navigation/campaign state once; if the campaign is not already on
the intended source step, stop the lane with
`blocked: watched_source_transition_blocked` and include the rejected tool
result plus the readback. Do not loop on rejected `update_campaign` transitions.
Short form: do not jump from a shell directly into `contact-search`.
Short form: do not jump from a shell directly into `signal-discovery`.
Short form: never include sender reassignment fields in source-step updates.
Short form: never include custom narration fields in source-step updates.
Short form: do not loop on rejected `update_campaign` transitions.

One lane may produce exactly one CampaignOffer/campaign id and one current
workflow table id. For `intent:"create"`, the first successful
`create_campaign` result establishes the lane's only allowed campaign/table
target. Every later source/import/confirm/message/filter/sequence/readback call
must use that same campaign id and current workflow table id. If any tool
returns or creates a different CampaignOffer, workflow table, "Campaign from
Source" campaign, or shell for the same lane, stop immediately with
`blocked: duplicate_campaign_offer_created`, include both ids in the receipt,
and do not report completion. Do not hide the earlier shell by reporting only
the later imported campaign.

Each customer-visible lane receipt must also include message proof from
`watchNarration.workerDetails.messageDraftBuilder`, the worker's equivalent
branch handoff record, or the packaged message-prep worker path above. Valid
evergreen message proof names the current campaign id, workflowTableId,
selected lead-list or source id, filter choice, and review-batch row ids/hash,
and it must use `statusSource:"branch"` or
`statusSource:"packaged-generate-messages-worker"`.
`statusSource:"parent-thread-fallback"` is not accepted for evergreen
customer-visible completion because it can be a parent summary rather than proof
that the create-campaign Message Drafting branch actually ran. The proof must
show the worker loaded the full packaged prompt with
`get_subskill_prompt({ subskillName:"generate-messages"` until `hasMore:false`,
loaded the required message assets, and loaded `create-campaign-v2-validation`
before returning a `messageDraftRecommendation`.
The receipt must also include `validationResult:"passed"`, a passed
quality-review object, and at least 3 concrete reviewed sample messages with
row ids, generated message text, pass verdicts, and no issues. Shared Cold
Fallback samples must not open with a standalone name line followed by
"Hey there" or another generic greeting; that pattern means the lane did not
finish the create-campaign message workflow to customer quality.
Shared Signal Discovery samples must not use sender-owned post-engager language
such as "my post", "thanks for showing support", "saw you pop up", or any copy
that implies the sending sender authored the source post or personally received
the prospect's engagement unless the selected source evidence proves that exact
sender authored the post.
Shared lanes must reject generic product-summary samples that explain the
product in the same broad words for every row. Shared lanes must reject three
nearly identical samples after stripping the leading name. Shared lanes must
fail quality review when any reviewed sample has non-empty `issues`; do not
approve or final-report samples with `verdict:"passed"` and non-empty `issues`.
If any of these failures appears, revise or rerun the packaged
create-campaign/generate-messages path from the lane worker before writing a
success receipt.
Short form: Shared lanes must fail quality review when any reviewed sample has non-empty `issues`.
Short form: Do not approve or final-report samples with `verdict:"passed"` and non-empty `issues`.
The generated-message cells are not prompt proof: `currentRevisionGeneratedMessages`,
Generate Message column counts, `messagesCount`, or rendered row previews prove
only that table cells exist from the current brief/template, not that the
create-campaign Message Drafting branch ran. If this receipt is missing, report
`blocked: missing_message_drafting_receipt` and do not call the lane complete.

The parent then calls
`setup_evergreen_campaigns({ mode:"verify", postEngagerSenderIds,
sharedSenderIds, planRevision, selectedActionIds, receipts })` using the same
sender scopes as the plan call and the original execution planRevision/actionIds,
and reports only verified completion. If verify returns `verified:true` with
`acceptedLaneBlockers`, report those lanes as accepted blocked/no-op lanes, not
completed campaigns. Do not include `yolo` in the verify call; `yolo` is
plan-mode only. 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
`setup_evergreen_campaigns({ mode:"plan", postEngagerSenderIds,
sharedSenderIds, yolo:true })` without explicit campaign/table bindings. That
idempotency rerun must return `intent:"reuse"` for every completed lane and no
duplicate-lane blockers before you call the command finished. If verify blocks,
report the blocker and repair only the missing postconditions from the same
current plan.

Before calling verify, the parent should build compact receipt objects from the
durable receipt files and pass those compact objects to MCP. The compact receipt
must preserve every verifier-owned field named above, including top-level ids,
`createCampaignStepReceipt`, `researchSenderReceipt`, canonical
filter/message/review/sequence/final proof, and at least three concrete sample
messages. It should omit large raw
search outputs, long post lists, full row arrays, raw provider responses, and
debug-only narration fields. Do not pass multi-hundred-kilobyte raw receipt
objects when a compact canonical receipt has the same proof; large raw verify
payloads can stall local MCP bridges and make UAT ambiguous.
Use a small Node JSON compactor for non-trivial compaction. Do not build complex
inline `jq` filters inside nested quoted shell strings; quote failures are a
UAT blocker and waste the verification run. The Node compactor must preserve
every canonical verifier object under `createCampaignStepReceipt`, including
`setupPlanCall`, `createCampaignWorkflowReceipt`, `researchSenderReceipt`,
`campaignBriefReceipt`, `sourceDecisionReceipt`, `filterDecisionReceipt`,
`messageDraftingReceipt`, `reviewBatchReceipt`, `sequenceReceipt`,
`finalPausedSendProof`, and `verifyCall`.
Write the compact verifier payload to a temporary JSON file and load that object
directly into the MCP verify call. Do not manually paste, retype, truncate, or
merge full receipt JSON into the tool call. If a verify attempt fails because
the payload was malformed, overlong, manually edited, missing a receipt, or used
a later reuse planRevision/actionId set, rebuild the compact payload from the
durable receipt files and retry once with the original execution
`planRevision`/`selectedActionIds` before reporting a verifier blocker.
When compacting, normalize `finalPausedSendProof.currentStep` and
`finalPausedSendProof.campaignStatus` from nested pause/navigation readbacks if
the worker receipt stored those values under `pauseCampaignResult`,
`navigationReadback`, or `navigationProof`; do not pass an empty
`finalPausedSendProof` object to verify.
Do not print or `jq .` full receipt JSON into the terminal. For local checks,
print only compact summaries such as file, actionId, laneKey, campaignId,
filter status, sample count, approved count, and final status.
Short form: Parent verify uses compact canonical receipt objects, not raw giant receipts.
Short form: Do not dump full receipt JSON with `jq .`.
Short form: Do not use complex inline `jq` for receipt compaction; use Node.
Short form: Compact receipts must preserve `researchSenderReceipt`.
Short form: Compact receipts must preserve direct `finalPausedSendProof.currentStep` and `campaignStatus`.
Short form: Never change planRevision/actionId/laneKey while compacting receipts.
Short form: Do not hand-build giant receipt tool-call payloads; load compact JSON from disk.

Parent repair dispatch uses the same compact discipline. If the parent sample
quality gate fails after a worker receipt, do not launch a repair worker by
embedding the full existing receipt JSON, raw row arrays, raw provider output,
or the full wrapper skill in the repair prompt. Build a compact repair packet
with only: workspace id, original planRevision, selectedActionIds, lane
actionId, laneKey, campaignId, tableId, parent sender scope arrays, receipt
artifact path, allowed repair side effects, failed sample snippets, failed
quality codes, and the banned phrase list. The repair prompt must say
`Do not include existing receipt JSON; load current product state through
Sellable MCP readbacks and rewrite the durable receipt from fresh readbacks`.
It must also say that the worker should verify the packaged Sellable MCP
surface is loaded before declaring MCP unavailable, including `get_auth_status`,
`get_subskill_prompt`, and the campaign readback tools. If a repair worker still
cannot access Sellable MCP, it must write a separate repair-blocked artifact
under `repair-blocked/` or its repair final file and must not overwrite the
canonical lane receipt. Repair workers must stage successful repaired receipts
under `repair-staged/` first; only after fresh MCP readbacks, improved current
samples, exactly one approved generated route-proof row, and a local JSON
self-check pass may the worker atomically replace the canonical lane receipt.
Repair workers must not call `setup_evergreen_campaigns({ mode:"verify" })`
or `setup_evergreen_campaigns({ mode:"plan" })`; parent verification and
idempotency are parent responsibilities after all selected lane receipts are
ready. A stale-plan/action response from any accidental repair-worker verify
call is not a product-state blocker and must not prevent a repaired receipt from
replacing the canonical receipt when fresh product readbacks and local
sample/receipt checks pass.
After the repair worker completes the final product readbacks
(`get_campaign_messages_preview`, `get_campaign`, navigation/table readback),
it must stage and then rewrite the durable receipt immediately before any
extended narration, analysis, or final answer. If the repair worker cannot
rewrite the durable receipt in one bounded pass, it must exit with
`blocked: repair_receipt_write_timeout` without modifying the canonical lane
receipt. The parent must treat an unchanged receipt mtime, missing repair final
file, missing staged success artifact, or missing updated
`messageDraftingReceipt.sampleMessages` after repair as blocked, never success.
The parent must also treat a repair-blocked artifact as a failed run, but it
must keep the pre-repair canonical receipt available for diagnostics and must
not promote the blocked repair artifact as the lane receipt.
Short form: repair prompts use compact repair packets, not full receipt JSON.
Short form: Do not include existing receipt JSON in repair prompts.
Short form: repair workers stage repaired receipts before replacing canonical receipts.
Short form: repair blockers never overwrite canonical success receipts.
Short form: repair workers do not call setup_evergreen_campaigns verify or plan.
Short form: parent owns setup_evergreen_campaigns verify and idempotency.
Short form: repair workers write the durable receipt immediately after final readbacks.
Short form: missing repair receipt rewrite is `blocked: repair_receipt_write_timeout`.

Message proof for every customer-visible lane must come from the current
campaign/table basis and include at least 3 generated review rows from the
packaged `generate-messages` path before completion is reported.
Parent-thread handwritten copy or setting `currentStep` is not proof.

The parent must run its own sample-quality gate before final reporting, even
when worker receipts say `verdict:"passed"`. For Post Engagers lanes, reject
any sample that adds a relevance question, CTA, product line, or extra paragraph
after `Thank you for the support on my post about ...`; blocked phrases include
`curious`, `open to`, `trying to`, `worth seeing`, `want to see`, and any
Sellable/product explanation. For each shared lane, normalize the reviewed
sample messages by removing leading greeting/name lines, first names, and
whitespace-only differences; reject shared-lane samples when the message body is
identical after removing first names or when all samples share the same paragraph
skeleton. Also reject final shared-lane samples with `Hey <name>,` letter
punctuation, weak relevance hedges, internal workflow vocabulary, self-intros,
or any non-empty `issues`. Shared lanes must also reject mechanical
product-process wording such as `sequence-prospects`, `dashboard overhead`,
`campaign map`, `campaign idea into prospects`, `prospects, filters, messages`,
`send-ready copy`, or `reviewed prospect lists`. If the parent finds repeated
shared-lane sample copy, report `blocked: repeated_shared_lane_sample_copy`,
revise the campaign brief/template through the packaged workflow, rerun Generate
Message, and verify current-revision sample messages before final completion.
</command_backed_workflow>

<objective>
1. **Inventory first, then obey the setup plan**: `get_campaigns` +
   `list_tables` + `get_campaign_waterfall` in the active workspace. Treat
   `list_tables` and the managed waterfall as authoritative for orientation
   before the command-backed plan; `get_campaigns` is a recent campaign page and
   may miss canonical evergreen lanes. Match existing campaigns/tables/waterfall
   Short form: Treat `list_tables` and the managed waterfall as authoritative.
   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
   Short form: a matching `ARCHIVED` table/campaign is not a plain `REUSE`.
   slot and the invocation explicitly allows dashboard visibility repair, repair
   it to `PAUSED`; otherwise treat it as archived inventory only. After
   `setup_evergreen_campaigns({ mode:"plan" })` returns, that plan is
   authoritative: a lane packet with `intent:"create"` must stay a create packet,
   even when archived waterfall entries or `get_campaign` readbacks prove an old
   CampaignOffer still exists. A matching non-archived campaign/table/waterfall
   slot may be reused only when the command-backed plan returns a reuse packet
   or the user supplied exact current ids. Never create a second campaign for a
   non-archived slot that the plan selected for reuse.
2. **Create only the missing slots** through the full `$sellable:create-campaign`
   workflow path for each lane, using the lane packet as the setup basis:
   - Post Engagers lanes: that sender's ID only. Shared lanes: all the senders' IDs.
   - The brief and message direction are lane-specific. Never reuse the Post
     Engagers opening for Shared Cold Fallback.
   - The warm post-engager first-message template is allowed only for
     sender-owned Post Engagers lanes. Do not pass the warm template into Shared
     Signal Discovery or Shared Cold Fallback; those shared lanes must arrive at
     their copy through Message Drafting and generated-message validation.
     Short form: warm post-engager first-message template is allowed only for sender-owned Post Engagers lanes.
     Short form: Do not pass the warm template into Shared Signal Discovery or Shared Cold Fallback.
     Short form: Shared lanes must reject generic product-summary samples.
     Short form: Shared lanes must reject three nearly identical samples.
     Short form: Shared lanes must reject unsupported low-confidence relevance hedges.
     Short form: Shared lane message quality is owned by the packaged generate-messages prompt and validation; evergreen does not author source-thread bridge phrasing.
     Short form: Shared lanes must reject internal workflow vocabulary.
     Short form: revise or rerun the packaged create-campaign/generate-messages path.
   - **Post Engagers** first-message style: short, casual, references the
     sender-authored post they engaged with, includes the post URL as a concrete
     reminder, uses an easy buyer-function/category line, and ends with one
     row-custom closed question. Use **no internal vocabulary, no meeting ask,
     no proof block, no feature-stack category, and no generic static question**
     in message one:

     ```
     {{first_name}}

     Hey there

     Saw you pop up on my post about {{post_topic_line}}

     This one
     {{post_url}}

     Looks like you lead {{plain_buyer_function}} at {{company_name}}, right?

     {{custom_closed_question}}
     ```

     `{{custom_closed_question}}` must be specific to the post topic, buyer
     function, company context, and current offer category. The category should
     be easy to understand, for example `LinkedIn outbound`, `customer success`,
     `CX operations`, `support`, `sales`, or `growth`; do not use feature-heavy
     phrasing like `manual research + follow-up process`, `lead filters`, or
     `sequence setup`. Do not add any paragraph after the custom closed question.
     In particular, do not append `open to...`, `worth seeing...`, a meeting ask,
     proof block, or Sellable/product copy to a Post Engagers first message.

   - **Shared Signal Discovery** first-message style: short, casual, starts
     from the real public business theme or buyer problem supported by the row.
     Treat Signal Discovery as a lead-source provider only. Do not mention the
     engagement source, source mechanics, or discovery method in customer-facing
     copy. Do not say
     "my post" unless the selected source post was actually authored by the
     sending sender. Do not thank the prospect for showing support, reacting,
     commenting, or popping up unless this is a sender-owned Post Engagers lane
     with sender-authored source evidence. When the shared lane has multiple
     senders, the copy must be safe for every attached sender to send. Do not
     use founder-only first person such as "I'm building", "my company", "my
     team", or "I have a framework" unless every attached sender can credibly
     say it. Prefer sender-agnostic company/team language grounded in the
     current workspace's verified company, offer, buyer, and proof, plus one
     clear problem question. Do not reuse unrelated fixture/example customer
     names, domains, verticals, product categories, use cases, or proof points
     unless the current workspace/company research independently supports them.
     Before Message Drafting runs, the campaign brief must already include
     these shared-lane hard avoids and source-use rules. Do not rely on a
     failed first generation batch to discover that self-intros, generic
     product summaries, or repeated structure are bad. If the first generated
     batch violates these rules, rerun, but record that as a repaired UAT path
     rather than the desired zero-shot path.
     Shared Signal Discovery samples may use a supported topic bridge such as
     "saw you around conversations about [high-level topic], so hope this is
     relevant" when the topic is grounded in source evidence and not
     activity-log phrasing. Signal Discovery is only a lead-source provider;
     do not infer a product category, buyer pain, offer, asset, CTA, or
     provider-themed message direction from the lane/provider name.
     Translate source/tool terms into prospect-facing
     business language; do not put internal workflow vocabulary such as
     "Codex-style workflow", "MCP", "agent workflows",
     "Claude Code", or "Claude-style agent workflows" in customer-facing
     message one. Also avoid generic product-process nouns such as "dashboard",
     "workflow", "source", "send-ready copy", "reviewed prospect lists",
     "reviewed pipeline", "leads, filters, messages", "review-ready LinkedIn
     sequence", "plain-language campaign idea into leads/filters/messages",
     and "sequence" unless the recipient's own product/category makes that
     exact word buyer-native.
     Short form: Shared Signal Discovery uses no self-intro such as "I'm building Sellable".
     Short form: Shared Signal Discovery uses no self-intro such as "We're building Sellable".
     Short form: Shared Signal Discovery uses no "I've put together".
     Short form: Shared Signal Discovery may use "saw you around conversations about [topic], so hope this is relevant" when source-supported.
     Short form: topic-level bridge rules are owned by the packaged generate-messages prompt and validation, not by the evergreen wrapper.
     Short form: no internal workflow vocabulary such as "Codex-style workflow", "MCP", "agent workflows", "Claude Code", or "Claude-style agent workflows".
   - **Shared Cold Fallback** first-message style: cold, direct, and cohesive.
     It must not pretend there was engagement, must not say "saw you pop up",
     and must not use a standalone name line followed by "Hey there" or other
     generic greeting. It should open with a light role/company/problem
     observation, avoid awkward phrases like "looks like a team where", avoid
     founder-only first person unless every attached sender can credibly say it,
     and ask one clear problem question. It must not pass just because the
     receipt says `generate-messages` loaded; the reviewed samples must sound
     like final cold output from the create-campaign/generate-messages workflow.
     Use no self-intro such as "We're building", no opt-out shrug such as "If
     this isn't relevant, ignore me", and no product-stack dump that repeats
     Sellable/Codex/Claude-Code feature summary
     across prospects. This is a single-message cold path unless the approved
     create-campaign message workflow proves a better delivery format.
     Do not open with "hope this is relevant" or a similar low-confidence
     relevance hedge in Shared Cold Fallback. Do not use a repeated product-definition paragraph such
     as "Sellable is a GTM system" across samples; every reviewed cold sample
     should be specific enough to the role, company, or problem that the three
     sample messages do not share the same body skeleton.
     Do not use source-thread hedges such as "might be interested", "saw you
     around conversations about...", or "saw you in a few conversations" in
     Shared Cold Fallback. Start from a concrete role, company, or buyer-problem
     observation instead.
     Do not use mechanical Sellable process language such as "campaign idea into
     prospects, filters, messages, and sequence setup", "sequence-prospects",
     "dashboard overhead", "campaign map", "send-ready copy", or "reviewed
     prospect lists" in final cold samples. Say the buyer problem and one plain
     outcome instead.
     Before Message Drafting runs, the campaign brief must already forbid
     self-intros, generic product summaries, opt-out shrugs, product-stack
     dumps, and repeated template structure. Do not let the first generation
     batch use "I'm building", "We're building", "I've put together", or
     similar language and then rely on a cleanup rerun as the normal path.
     Short form: Shared Cold Fallback must not pass just because the receipt says `generate-messages` loaded.
     Short form: Shared Cold Fallback uses no self-intro such as "We're building".
     Short form: Shared Cold Fallback uses no self-intro such as "I'm building Sellable".
     Short form: Shared Cold Fallback uses no "I've put together".
     Short form: Shared Cold Fallback uses no opt-out shrug such as "If this isn't relevant, ignore me".
     Short form: Shared Cold Fallback uses no product-stack dump.
     Short form: Shared Cold Fallback uses no low-confidence relevance hedge such as "hope this is relevant".
     Short form: no "hope this is relevant", "might be interested", "saw you around conversations about", or "saw you in a few conversations" in Shared Cold Fallback; Shared Signal Discovery has the topic-level conversation-bridge exception above.
     Short form: no repeated product-definition paragraph such as "Sellable is a GTM system".
    - Customer-visible evergreen lanes must enable ICP filtering before Message
      Drafting. This applies to Post Engagers, Shared Signal Discovery, and
      Shared Cold Fallback. Do not skip filtering for evergreen setup, even when
      the source is warm. Use the normal `$sellable:create-campaign` filter
      workflow: set or save 3-5 headline ICP criteria, call `save_rubrics`, and
      persist `enableICPFilters:true` on the campaign before generating
      messages. Good evergreen filters should be light but real: role/budget
      relevance, company/market fit, and obvious non-buyer exclusion. For
      Post Engagers, keep filters permissive enough that warm source rows are not
      over-pruned, but still save the filter rules. For shared lanes, filter for
      buyers/operators relevant to the workspace offer and exclude students,
      job seekers, vendors, and off-topic commenters. The receipt must show
      `filterDecisionReceipt.status:"applied"`,
      `filterDecisionReceipt.enableICPFilters:true`, current `workflowTableId`,
      and saved filter/rubric proof with at least three saved rubric/filter
      criteria (`filterIds`, `rubricIds`, `headlineICPCriteria`, or
      `savedRubricCount`). Missing or skipped filter proof is
      `blocked: missing_filter_setup`.
      Short form: Customer-visible evergreen lanes must enable ICP filtering.
      Short form: `enableICPFilters:true`.
      Short form: Do not skip filtering for evergreen setup.
      Short form: at least three saved rubric/filter criteria.
      Short form: missing or skipped filter proof is `blocked: missing_filter_setup`.
      Short form: run Message Drafting only after saved filters are applied.
    - Every customer-visible lane must run Message Drafting from the current
      campaign/table basis after source rows exist and saved filters are
      applied. The worker must use the normal
      `post-find-leads-message-scout` branch handoff or inline that same scout
      contract when branch launch is unavailable. Plain
      `start_campaign_message_preparation` is row execution proof only; it is
      not a fallback substitute for Message Drafting. The worker must load the
      full packaged `generate-messages` prompt, every required asset, and
      `create-campaign-v2-validation`; before row generation, the receipt must
      show branch-equivalent scout proof, a concrete
      `messageDraftRecommendation`, and proof that recommendation was persisted
      into the campaign brief/message generation basis. Only then may the
      worker call `start_campaign_message_preparation` and poll
      `get_campaign_message_preparation_status` from the lane worker. The
      returned receipt must include `statusSource:"branch"` or
      `statusSource:"packaged-generate-messages-worker"`, at least 3 concrete
      reviewed sample messages, a passed validation result, and a passed quality
      review. Row/message counts alone are not enough. If the branch handoff and
      inline scout contract are both unavailable, report
      `blocked: missing_message_drafting_branch`.

   - **The brief must declare the delivery format** so Generate Message writes copy suited to how it actually sends:
     - 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.
     - **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.

   - In standard mode, the sequence is auto-selected by sender tier; do not hand-author sequence
     templates here. Sales Nav/Recruiter senders may receive the unified Sales
     Nav cascade through `attach_recommended_sequence`; attaching it does not
     spend paid InMail credits by itself. Do not substitute the manual Paid
     InMail Campaign template.
     For explicit connection-only evergreen lanes only, use the backend-owned
     `attach_sequence({ tableId, templateRef:"connection_only", currentStep:"send" })` path and do
     not call `attach_recommended_sequence`.
3. **Customer-Visible Completion Contract**: a named evergreen lane that appears
   as a campaign card or campaign-backed table is not done when the shell exists.
   It is done only when the customer can open the campaign and land on final
   send review with all setup state present:
   - `currentStep:"send"`.
   - The linked workflow table has `campaignStatus:"PAUSED"`; `ACTIVE` means
     already launched and must be reported separately, and `ARCHIVED` must be
     repaired only when the prompt explicitly allows dashboard visibility repair.
   - A full campaign brief exists, including delivery format, token rules,
     hard avoids, and source-use rules. Post Engagers require an approved warm
     first-message template. Shared Signal Discovery and Shared Cold Fallback
     require `firstMessageTemplateState` with the no-preset generate-messages
     marker instead of a copied first-message template.
   - A source/list decision has been resolved and campaign rows are loaded. If
     there is no approved source or no rows can be copied, report `blocked` with
     the missing source detail; do not call the campaign complete.
    - The filter step is resolved with saved/applied filters. Customer-visible
      evergreen lanes must have `enableICPFilters:true`, at least three saved
      ICP filter/rubric criteria, and a receipt with
      `filterDecisionReceipt.status:"applied"`. Do not skip filters for
      evergreen setup. Do not leave customer-visible campaigns at
      `filter-choice`, `filter-rules`, or `apply-icp-rubric` and report success.
      Do not proceed to Message Drafting until saved filters are applied.
   - In standard mode, Message Drafting has run from the current campaign/table basis, using the
     create-campaign message prompt/assets and validation gate. Updating
     `currentStep:"messages"` is not proof. Parent-thread handwritten copy is
     not a substitute. The proof receipt must show
     `get_subskill_prompt({ subskillName:"generate-messages"` was loaded until
     `hasMore:false`, required message assets were loaded, and
     `create-campaign-v2-validation` ran through
     `workerDetails.messageDraftBuilder` with `statusSource:"branch"` or
     through the lane worker's inline `post-find-leads-message-scout` contract
     with `statusSource:"packaged-generate-messages-worker"`. For the packaged
     worker path, proof must include branch-equivalent scout proof, a concrete
     `messageDraftRecommendation`, template/basis persistence proof before row
     generation, `start_campaign_message_preparation`, and
     `get_campaign_message_preparation_status`. The generated-message cells are
     not prompt proof; `currentRevisionGeneratedMessages` and row previews are
     only downstream cell evidence. Row prep alone is not prompt proof. The
     proof must include
      `validationResult:"passed"`, a passed quality review, and at least 3
      reviewed sample messages. For Shared Cold Fallback, reject samples that
      open with a standalone name followed by "Hey there" or a similarly generic
      greeting. For Shared Cold Fallback, also reject samples with a self-intro
      such as "We're building", "I'm building Sellable", or "I've put
      together", an opt-out shrug such as "If this isn't relevant, ignore me",
      or a product-stack dump that repeats the same
      Sellable/Codex/Claude-Code feature summary across prospects. For all
      shared lanes, reject generic product-summary samples, repeated
      near-identical samples, unsupported low-confidence relevance hedges, internal
      workflow vocabulary, any sample with non-empty `issues`, and any preserved
      Post Engagers opener; revise or rerun the packaged
      create-campaign/generate-messages path before reporting completion. Do not
      approve or final-report samples with `verdict:"passed"` and non-empty
      `issues`.
      Shared Signal Discovery samples may use `"saw you around conversations
      about [high-level topic], so hope this is relevant"` when the topic is
      supported by source evidence and not activity-log phrasing. Signal
      Discovery is only a lead-source provider; reject provider-themed campaign
      copy unless that language is independently supported by the current
      workspace/company research. Shared Cold
      Fallback samples must still reject source/conversation hedges such as
      `"hope this is relevant"`, `"might be interested"`, or `"saw you in a few
      conversations"`.
     For explicit connection-only lanes, this whole Message Drafting and sample
     proof block is not required and must not be faked with parent-written
     messages.
   - The first review batch exists and at least 3 review rows have generated
     messages from the approved brief. If fewer than 3 usable rows exist, report
     the actual count and why.
     If a bounded preparation job requested more than the minimum but already
     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
     available, and continue with route-proof review rather than expanding source
     or waiting forever.
   - At least one generated row is approved as a route-proof gate. If zero rows
     are approved and the prompt explicitly asked for full/customer-ready
     completion, approve exactly one quality-valid generated row. If one or more
     rows are already approved, do not add more approvals during evergreen
     completion. Never broad approve all rows.
    - In standard mode, the recommended tier-aware sequence is attached to the current campaign
      table, and the watched campaign is on Send. Use
      `attach_recommended_sequence({ campaignId, currentStep:"send" })` when a
      safe attach is needed. After `confirm_lead_list` or any source-list copy,
     immediately reread `get_campaign({ campaignId })`; the returned
     `workflowTableId` is the current campaign table. Cross-check that exact
     current workflowTableId with `list_tables` and require `hasSequence:true`
     before reporting completion. If a stale shell table has sequence columns but
     the current campaign table does not, `SEQUENCE_EXISTS` is not enough:
     record the stale shell table id, then repair the current workflowTableId
     with `attach_sequence` using the same product template
      `attach_recommended_sequence` would have selected. Do not substitute the
      manual Paid InMail Campaign, and never attach/replace sequence outside the
      current table unless the current lane packet explicitly allowed sequence
      repair.
      For explicit connection-only lanes, the current campaign table must have
      exactly the canonical invite-only sequence:
      `sequenceReceipt.actionTypes:["send_invite"]`. Any DM, InMail, or View
      Profile action means the lane is not complete.
    - If the current campaign table is still `DRAFT` after sequence/readiness
      proof, call `pause_campaign({ campaignId })` and reread. `pause_campaign`
      is the product-native review-state transition; it is not a launch and does
      not schedule or send. Never raw-write campaign status.
    - No scheduled, queued outbound, sent outbound, campaign launch, or paid
      InMail spend is created by this skill.
4. **Verify each slot** after create/reuse/repair:
   - `get_campaign` shows the campaign exists, remains unlaunched, and has the expected workflow table.
   - 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.
   - Send/action lanes such as Post Engagers have a sequence attached on the
     exact current campaign table. Use `list_tables({ hasSequence: true })` as a
     quick cross-check, but do not treat that filter as the only proof. If the
     canonical current campaign table is missing from the sequence-filtered
     table list and `attach_recommended_sequence({ campaignId })` returns
     `SEQUENCE_EXISTS`, inspect whether a stale shell table owns the sequence.
     For a customer-visible completion run, stale shell proof is not completion:
     rerun the current-table repair above or report `blocked: stale_shell_sequence`.
   - Only use `attach_recommended_sequence({ campaignId })` for a send-lane
     sequence repair/precheck when the target is a canonical prod slot and the
     invoking prompt allows sequence repair. Call it at most once without
     `confirmed`; a successful response is `repaired`. Treat `SEQUENCE_EXISTS` as
     non-mutating proof only when the current table also has `hasSequence:true`.
     Never call `attach_recommended_sequence` or `attach_sequence` with
     `confirmed:true` in evergreen automation unless the user explicitly asks to
     replace an existing sequence or the approved lane packet allows current
     table stale-shell repair.
   - Do not report `source-only/no sequence expected` for any named evergreen
     campaign that appears in Campaigns, has a CampaignOffer ID, or is backed by
     a campaign dashboard table. Shared Signal Discovery and Shared Cold
     Fallback campaign cards are still customer-visible campaigns and must
     satisfy the Customer-Visible Completion Contract when the prompt asks for
     full/customer-ready completion.
   - Source-only shared lanes are allowed only for explicitly internal waterfall
     inventory objects that are not represented as campaign cards. For those
     internal-only objects, verify the active waterfall linkage, table ID,
     source type, and priority, and label them `internal source pool` rather
     than treating them as completed campaigns.
   - 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 Sellable MCP readback proves the sender has no recent posts to scrape, no sender-authored posts with usable engagers, no sender-owned post-engager source, or the selected/source posts include another person or company author, write a blocked receipt with the matching accepted blocker code and report that back to the parent as an accepted no-op. Do not scrape/import engagers from third-party authors and do not report completion until the source is sender-owned. Shared Signal Discovery lanes are the only evergreen lanes allowed to mix authors.
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:
   - **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,`.
   - Each paragraph stands alone as a message — short, lowercase-casual is fine, sentence fragments are fine.
   - No paragraph should depend on letter formatting (no "As I mentioned above" referencing layout).
   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.

   **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.
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.
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.
8. **Final customer-facing completion must include sample messaging**: after customer-visible verify and the idempotency rerun pass, add a `Sample Messaging Review` section to the final answer. For every completed lane, show at least one full generated first message for every completed lane with campaign name, campaign id, row id, and verdict. Extract samples from nested `createCampaignStepReceipt.messageDraftingReceipt.sampleMessages`, not from top-level summaries; extract samples from nested `createCampaignStepReceipt.messageDraftingReceipt.sampleMessages` when building the final answer. Prefer the approved route-proof row when it is quality-valid; otherwise use the best reviewed generated row from the verified receipt. A final `passed` sample must have `issues:[]`; if every reviewed sample has issues, report the lane as needing message revision instead of presenting a passed sample. Never approve or final-report sample text produced by manual `update_cell` body edits. Do not summarize the sample into a copy theme. Show the actual message text so the user can inspect tone and specificity. If any sample is not quite right, tell the user they can ask the AI to update the campaign brief/message template and rerun Generate Message before launch. Never suggest launching, scheduling, or sending as the next step from this command.
   If the `Sample Messaging Review` section is omitted, the command is incomplete even when the structural campaign setup succeeded.
9. **Report the reconcile plan and result** — every slot tagged `reused`, `created`, `repaired`, `flagged`, or `blocked`, plus template/sample details only when that mode ran:

```
Evergreen Reconcile — {date}
• Christian Reyes - Post Engagers: reused (send review ready; 18 rows, 3 generated, 1 approved gate row, sequence attached, paused)
• Thomas Nobbs - Post Engagers: reused (send review ready; 8 rows, 3 generated, 1 approved gate row, sequence attached, paused)
• Sellable.dev - Shared Signal Discovery: repaired (send review ready; 50 rows, 3 generated, 1 approved gate row, sequence attached, paused)
• Sellable.dev - Shared Cold Fallback: created (send review ready; 50 rows, 3 generated, 1 approved gate row, sequence attached, paused)
All campaigns remain unlaunched. Next: enrich/approve more rows when you want more items ready to schedule.

Sample Messaging Review
• Christian Reyes - Post Engagers (`cmp_...`, row `row_...`, verdict: passed)
  ```
  {{full generated first message}}
  ```
• Sellable.dev - Shared Cold Fallback (`cmp_...`, row `row_...`, verdict: passed)
  ```
  {{full generated first message}}
  ```
If any sample is not quite right, ask the AI to update the campaign brief/message template and rerun Generate Message before launch.
```
</objective>

<safety>
- **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.
- Never call `start_campaign` / `start_on_demand_campaign`. New campaigns stay in their default unlaunched state.
- Never archive, rename, or delete existing campaigns — reconcile is additive; structural removals are operator actions.
- Respect workspace boundaries: senders and campaigns must belong to the active workspace.
</safety>
