UNPKG

@sellable/install

Version:

One-command installer for Sellable MCP in Claude Code, Codex, and Hermes

848 lines (752 loc) • 49.8 kB
--- name: refill-sends description: Refill projected sends across a workspace or selected senders through the canonical fenced coordinator. visibility: public allowed-tools: - mcp__sellable__refill_sends - mcp__sellable__refill_v3_advance - mcp__sellable__refill_v3_world_state - mcp__sellable__refill_v3_continue - mcp__sellable__get_campaigns - mcp__sellable__get_refill_target_plan - mcp__sellable__get_scheduler_fill_capacity - mcp__sellable__get_subskill_asset - mcp__sellable__get_auth_status - mcp__sellable__start_cli_login - mcp__sellable__wait_for_cli_login - mcp__sellable__get_active_workspace - mcp__sellable__list_workspaces - mcp__sellable__get_subskill_prompt - mcp__sellable__search_subskill_prompts - mcp__sellable__list_senders - mcp__sellable__select_campaign_cells - mcp__sellable__wait_for_campaign_processing --- # Refill Sends Use this skill for “fill”, “refill sends”, “max out sends”, “load everyone up”, or “fill horizon sends”. Exactly one coordinator owns execution: `refill_v3_advance` on the V3 route, `refill_sends` on the V1 route. The host resolves the request, invokes that coordinator, and follows its exact continuation; it never reconstructs the refill ladder from low-level tools. Host commands: - Claude Code: `/sellable:refill-sends` - Codex: `$sellable:refill-sends` ## Writing to the customer Everything you write to the customer (Slack messages, questions, progress notes, summaries, the campaign brief) follows ASD-STE100 Simplified Technical English at about 80%: - One idea per sentence. One instruction per sentence. - An instruction has at most 20 words; any other sentence at most 25. - Active voice: say who does the action ("I added 60 people"). - Simple tenses only: present, simple past, and "will". - Common words with one meaning. Use the same word for the same thing every time. - No idioms, slang or phrasal verbs where one verb works: "start", not "kick off". - An instruction starts with its verb: "Reply to Priya.", "Connect your LinkedIn." - Keep small words such as "the", "a" and "to". Use a list for more than two items. Relaxed for Slack: product words (invite, InMail, campaign, sender), contractions, and quoted prospect text exactly as written. This rule is for text to the customer only. Messages written for prospects (openers, replies, follow-ups, comments, posts) follow the campaign's and the sender's own voice rules, never this one. ## Scheduled customer-program entry A scheduled invocation is one downstream action of the existing customer program. It does not create a second refill loop or choose a new campaign. The program wrapper supplies one already-claimed home workspace plus its exact campaign/sender cohort and one to three sender-local dates. Use that exact scope, pass `yolo:true` to the same canonical Refill V3 advance/continue flow below, and retain the server-issued run continuity until the run settles. Never switch an Admin runtime to another workspace, widen the cohort, add dates, or create a child schedule. Reuse a persisted refill enrollment and its approved campaign, exclusion, source, message, and sender rules. A human pause, a missing enrollment, a never-launched campaign, legacy PAUSED state with no provenance, or generation drift is a hold. Return the one exact decision or action the customer must take; do not restart the campaign, invent permission, or ask for the same permission every morning. An explicit authenticated Start/resume remains human-owned. Scheduled results use five product meanings: - `settled`: independent readback proves the exact scheduled additions. - `partial`: some additions are proven and the remaining exact gaps/blockers are named. - `pending`: the same run or scheduler receipt is still active; retain and poll that identity. - `blocked`: no allowed next mutation exists; name the precise human or provider action and safe retry condition. - `quiet`: coverage was already healthy or routine maintenance settled with no customer action. Make the result available to the daily review; do not post a second routine message. Customer fixes. A blocked row's `reason.codes` names what only the customer can fix. Say it once per sender in plain words, with the next step, and keep filling every other sender and lane: - `sender_disconnected`: that sender's LinkedIn account is disconnected. Next step: reconnect it in Sellable (LinkedIn accounts), or remove the sender if it is no longer used. - `sales_nav_disconnected` or `paid_inmail_connection_health_recheck_required`: Sellable cannot read that sender's Sales Navigator InMail credits. Next step: reconnect Sales Navigator for that sender (or renew it if it lapsed). Connection requests continue meanwhile. Retryable provider codes (`paid_inmail_credit_refresh_failed`, timeouts) are not customer tasks; never ask the customer to retry them. Interactive `refill-sends --yolo` always receives its actual result immediately. State connection invitations and paid InMail separately: invitation capacity is not paid InMail credit, and paid InMail coverage is never proof of invitation coverage. “Added” means scheduler-owned rows with a non-null scheduled time; it never means LinkedIn delivered them. ## Route selection — do this FIRST Decide the route before parsing scope, before loading any flow asset, and before any tool call other than auth/workspace resolution. The predicate is MECHANICAL and observable in the connected Sellable MCP tool list: > **Is `refill_v3_advance` one of the tools this server exposes?** - **Yes → take the Refill V3 route.** This is the default for every workspace on a server that exposes the tool. Go to [Refill V3](#refill-v3) and follow it; the V1 ladder in the rest of this document does not apply. - **No → take the V1 route.** The installed server predates Refill V3, so `refill_sends` is the only coordinator available. Follow this document from [Entry and exact scope](#entry-and-exact-scope) onward. Never infer the route from a workspace flag, a campaign field, a tool result, or the operator's wording: no such cutover signal exists, and guessing at one is what makes an ordinary refill request silently take the wrong route. Report the selected route explicitly before the first coordinator call. ## Entry and exact scope Everything from here down to [Refill V3](#refill-v3) is the **V1 route**. Skip it entirely when route selection chose V3. On this route `refill_sends` is the only execution owner and the host follows its exact continuation; it never reconstructs the refill ladder from low-level tools. Accepted request fields are `--yolo`, `workspaceId`, `--sender`/`senderIds`/ `senderNames`, `actionTypes`, `--until`/`untilDate`, `--target-date`/ `targetDate`, and compatibility `horizonSendDays`. Omitted dates mean the scheduler-forward 48-hour window. `untilDate` is sender-local and inclusive; `targetDate` is one sender-local scheduler-fillable date. Skip no-send days and never extend beyond the requested date. Finish the full D1 reread before D2. Ordinary refill defaults to `yolo:true`; only an explicit `yolo:false` or a clear request for review-first/manual execution opts out. ```text refill_sends({ yolo?: boolean, executionMode?: "manual" | "scheduled" | "yolo", requireWorkspace?: boolean, workspaceId?: string, senders?: string[], senderIds?: string[], senderNames?: string[], actionTypes?: ("send_invite" | "send_inmail_closed")[], horizonSendDays?: number, untilDate?: "YYYY-MM-DD", targetDate?: "YYYY-MM-DD", runHandle?: RefillRunHandleV1, targetConfig?: RefillTargetConfigV1, reportingContext?: RefillReportingContextV2, messageTemplateRevision?: MessageTemplateRevisionV1 }) ``` ```mermaid flowchart TD A["Parse the operator request"] --> AUTH{"Sellable auth valid?"} AUTH -- No --> LOGIN["Run the returned login flow and wait"] LOGIN --> AUTH AUTH -- Yes --> WS["Resolve exact workspace by ID, exact name, or install mapping"] WS --> WSG{"One accessible workspace match?"} WSG -- No --> WB["Output: blocked_retryable / WORKSPACE_REQUIRED or workspace_ambiguous"] WSG -- Yes --> SENDERS["Resolve each sender inside that workspace by ID, exact name, or unambiguous prefix"] SENDERS --> SG{"Every selector resolves exactly?"} SG -- No --> SB["Output: blocked_retryable / sender_selector_unresolved with candidates"] SG -- Yes --> DATE["Normalize 48h, exact targetDate, or inclusive untilDate in sender-local time"] DATE --> ROUTE{"Does this server expose refill_v3_advance?"} ROUTE -- Yes --> V3["Refill V3 route: load core/flow.v3.json and call refill_v3_advance"] ROUTE -- No --> D1["D1: call get_refill_target_plan read-only with exact scope"] D1 --> RENDER["Render sender/campaign waterfall, coverage ledger, first action, side effects, forbidden actions, and stop condition"] RENDER --> D2["D2: call refill_sends with workspaceCoordinator true plus displayed targetShapeRevision and actionKey"] ``` Scheduled and `--yolo` execution require an explicit `workspaceId` on every call. Never change the shared active workspace to control an automation, never fall back to its full fleet, and never mutate the workspace that merely happens to be active. If the native tool is unavailable, use only the host’s exact configured Sellable MCP transport; otherwise stop with `sellable_mcp_unavailable` instead of manually reproducing the workflow. Before product work, load the closed workflow asset completely and verify it: ```text get_subskill_prompt({ subskillName: "refill-sends-workflow" }) get_subskill_asset({ subskillName: "refill-sends-workflow", assetPath: "core/flow.v1.json" }) ``` Continue chunks until `hasMore:false`; require `workflow:"refill-sends-workflow"` and a COMPATIBLE version: same major (`v1`) and minor at or above the minimum `v1.9` — so `v1.9`, `v1.10` pass while `v1.8` and `v2.x` do not. Report the verified version before D1. A missing, unparseable, wrong-major, or below-minimum version is `workflow_version_mismatch`: stop before `get_refill_target_plan` or mutation. Do NOT require exact string equality: the flow asset ships in the npm package while this skill is installed separately, so an exact pin makes every additive contract change a breaking one. Route intent is immutable: ordinary refill uses `intent:"auto"` from first plan through terminal; explicit `yolo:false` uses the review-first path. ## Mandatory visible plan before execution Every refill in every mode begins with D1, a read-only `get_refill_target_plan` with the exact workspace, sender/action selectors, and date envelope. For default yolo execution, the first D1 call must explicitly pass `approvalMode:"approve"`; never rely on the planner's `mark_ready` default and then discover the scope mismatch at D2. Invoke D1 exactly once, retain and parse that response in the same host composition, and never call D1 again only to format or render it. Render its plan in normal chat before any mutation. At minimum, show one row per selected sender/campaign lane with sender, campaign, lane/source family, planned first action, target/cap, sent, scheduled, ready, still needing preparation, fallback order, and blocker/skip reason. Then show the bounded side effects, forbidden actions, stop condition, `targetShapeRevision`, and first `globalActionQueue[0].actionKey`. If a stale or missing paid-credit fact prevents a certified fillable cap, show `pending credit refresh`, never bare `unknown`. The target remains visible and yolo executes the displayed refresh action before replanning the numeric cap. After rendering D1, explicit `yolo:false` asks Accept or Decline and invokes only the returned `planned_manual_run` after Accept. Default `yolo:true` does not ask: call `refill_sends` D2 with `workspaceCoordinator:true`, the identical scope, and both `expectedTargetShapeRevision` and `expectedActionKey` copied from the displayed packet. The server may itself return `status:"plan_ready"`; render that returned plan before invoking `planned_manual_run` or `planned_workspace_run`. Thus the modes share the exact same visible plan and revision fence. The only difference is that non-yolo pauses for decisions while `--yolo` auto-accepts the displayed packet and autonomously traverses every planner-ranked existing campaign, lane, source, safe rubric trial, and scheduler handoff until terminal. If either pin drifts, render the fresh read-only replacement packet; do not mutate from the stale plan. No credit refresh, fence, or product mutation may exist before this render. ## Coordinator lifecycle ```mermaid flowchart TD A["Fresh read-only workspace refill plan rendered to the user"] --> FULL{"Projected coverage sent + scheduled is full?"} FULL -- Yes --> COMPLETE["Terminal: complete"] FULL -- No --> ACTIVE{"Active exact fenced run exists?"} ACTIVE -- Yes --> RESUME["Resume only issued runId, fence, targetConfig, reportingContext"] ACTIVE -- No --> PICK["Select globalActionQueue 0"] PICK --> PREFLIGHT["Exact campaign, table, sender, action, lane preflight"] PREFLIGHT --> ACT["Execute one bounded planner action"] RESUME --> ACT ACT --> OUTCOME{"Canonical outcome?"} OUTCOME -- "Committed or canonical no-op" --> A OUTCOME -- "Still running" --> WAIT["Bounded read-only wait or receipt reconciliation"] WAIT --> A OUTCOME -- "Source exhausted" --> NEXT{"Another planner-ranked existing target?"} NEXT -- Yes --> HANDOFF["Terminalize exact fence as next_exact_target"] HANDOFF --> A NEXT -- No --> NC["Terminal: new_campaign_required; create it under autopilot, else ask"] OUTCOME -- "Provider unavailable after one retry" --> RETRY["Terminal: blocked_retryable; universeExhausted false"] OUTCOME -- "Scope, config, readiness, or receipt drift" --> BLOCK["Terminal: blocked_retryable with exact blocker"] OUTCOME -- "Planner marker done" --> DONE["Terminalize marker; never dispatch it"] ``` If `status:"plan_ready"`, render its full plan packet first. Non-yolo asks for Accept or Decline before `planned_manual_run`; `--yolo` invokes `planned_workspace_run` automatically. If `status:"in_progress"`, invoke only the returned `continuation.arguments`. `targetConfig` and `runHandle` are separate and remain unchanged except for a server-issued takeover fence. `active_exact_run` resumes that fence; `next_exact_target` carries no stale run fence. Never open a second or overlapping run, ask the user to type “continue”, redispatch a control marker, or turn `done`, `next_campaign`, or a continuation object into a product action. ## Per-sender campaign and lane waterfall Use managed evergreen order when healthy. Otherwise build the same waterfall on the fly from existing eligible campaigns the sender is already attached to. This makes regular and evergreen campaigns differ only in where ordering comes from, not in how refill proceeds. ```mermaid flowchart TD A["All existing campaign-backed sequences attached to one sender"] --> FILTER["Exclude wrong workspace/sender, archived, completed, direct, disconnected, or sequence-invalid targets"] FILTER --> STALE{"Managed waterfall complete and live?"} STALE -- Yes --> MANAGED["Use configured priority order"] STALE -- No --> DERIVE["Derive deterministic order from dashboard-active campaigns"] DERIVE --> P1["Prefer canonical source/lane priority"] P1 --> P2["Then future scheduled and ready inventory"] P2 --> P3["Then recent successful sends and source health"] P3 --> P4["Then active before exact start-eligible paused, recency, stable ID"] MANAGED --> PIN["Pin exact per-sender and per-action lane chain for this run"] P4 --> PIN PIN --> LANE["Select highest-priority non-exhausted lane"] LANE --> REFILL["Run row lifecycle"] REFILL --> EX{"Lane structurally exhausted?"} EX -- No --> REFILL EX -- Yes --> MORE{"Another pinned existing lane or source family?"} MORE -- Yes --> LANE MORE -- No --> END["Output: lanes_exhausted, then new_campaign_required if coverage still short"] ``` Campaign enrollment/removal, missing pinned targets, or loss of eligibility is typed scope drift; a fresh planner may rederive, but the host cannot silently substitute a target. A current dashboard-active `PAUSED` campaign is eligible only when the planner names that exact campaign as start-eligible. Starting it may let the product scheduler schedule/send approved sequence actions and must be reported. DMs are follow-ups, not refill horizon targets. Mixed sequences keep distinct exact target paths; do not choose campaign-union behavior. ## Connection and InMail capacity overlay ```mermaid flowchart TD S["Sender capacity and exact selected days"] --> C{"Connection slots available now or later in rolling week?"} S --> F{"Paid-InMail credit facts fresh?"} F -- No --> RF["Refresh exact selected sender once per run, then replan"] RF --> P{"Credits meet existing threshold, normally 5?"} F -- Yes --> P C -- Yes --> INV["Enable existing invite lanes"] C -- No --> INVCAP["Cap invite lane with timing evidence"] P -- Yes --> MAIL["Enable existing paid-InMail or cascade lane"] P -- No --> MAILCAP["Skip paid lane; never lower threshold"] INV --> UNION["Rank enabled existing lanes per sender"] MAIL --> UNION INVCAP --> ALT{"Paid lane enabled?"} MAILCAP --> ALT2{"Invite or same-campaign connection fallback enabled?"} ALT -- Yes --> UNION ALT -- No --> CAP["Output: blocked_retryable / capacity or window"] ALT2 -- Yes --> UNION ALT2 -- No --> CAP UNION --> POOL["Pool shared-campaign prepared inventory; never double-enrich"] ``` `rollingWeeklyInvite.capacityFreedDuringWindow:true` means later capacity is schedulable; use its timing fields rather than treating the opening gate as a full-day blocker. Credit freshness precedes scheduler wait. Fresh facts below threshold authorize only an already-eligible existing connection fallback or another existing campaign—not a threshold change or new campaign. ## Row lifecycle For every selected lane, use the smallest action that can reduce its gap. ```mermaid flowchart TD A["Lane still has projected gap"] --> ACTIVE{"Active import, preparation, or stuck work?"} ACTIVE -- "Healthy active" --> WAIT["Bounded read-only wait; honor wait.deadlineAt"] WAIT --> A ACTIVE -- "Stuck or anomalous" --> REPAIR["Run only planner-bounded repairable cells or return blocker"] REPAIR --> A ACTIVE -- No --> APPROVE{"Generated rows can be approved?"} APPROVE -- Yes --> AP["Approve exact bounded cohort with readiness authority"] AP --> REPLAN["Record receipt and full authoritative replan"] APPROVE -- No --> ENRICH{"Existing rows can be enriched or prepared?"} ENRICH -- Yes --> PREP["Enrich, generate, or rerun exact bounded cohort"] PREP --> REPLAN ENRICH -- No --> SOURCE{"Selected source can add qualified rows?"} SOURCE -- Yes --> ADD["Copy/import bounded rows from exact selected source"] ADD --> REPLAN SOURCE -- No --> EXPAND["Run source-family acquisition flow"] EXPAND --> REPLAN REPLAN --> A ``` Structural exhaustion requires receipt-proven `hasMoreFrontierRows:false`, zero `approvalCandidates`, no fresh active prep, no `stuckActiveCells`, and no non-terminal `approvedNotDispatched` work. A dry preparation receipt is run-scoped and prevents the identical no-op from repeating. `USER_ADDED_ROWS_LIMIT_EXCEEDED` may use a bounded same-source split through `load_csv_linkedin_leads` into the exact review batch; a true campaign table hard cap returns a capacity blocker and never deletes rows. ## Sender Post Engagers acquisition ```mermaid flowchart TD A["Sender Post Engagers lane needs rows"] --> UNUSED{"Unused selected post/engager rows exist?"} UNUSED -- Yes --> USE["Import/copy bounded unused rows"] UNUSED -- No --> REFRESH["Refresh this sender's latest recent authored posts"] REFRESH --> SELECT["Select new unprocessed relevant high-engagement posts"] SELECT --> FETCH["Fetch/import their engagers and dedupe"] FETCH --> YIELD{"Usable qualified yield?"} YIELD -- Yes --> USE YIELD -- No --> EX["Exhaust only this Post Engagers lane"] EX --> NEXT["Advance to Shared Signal Discovery or next pinned existing lane"] ``` The campaign must be sender-owned: exactly that sender, authored posts only. Never invent keywords, search third-party posts, or run general Signal Discovery against the Post Engagers campaign. ## Shared Signal Discovery acquisition ```mermaid flowchart TD A["Shared Signal lane needs rows"] --> UNUSED{"Unused selected rows or unprocessed posts exist?"} UNUSED -- Yes --> USE["Import/copy bounded rows"] UNUSED -- No --> STALE["Re-search stale existing topics for newly published posts"] STALE --> SY{"Projected qualified yield meets min remaining gap or 100?"} SY -- Yes --> USE SY -- No --> DERIVE["Derive up to five concise positive-ICP topics from approved campaign intent"] DERIVE --> DY{"Projected qualified yield meets floor?"} DY -- Yes --> USE DY -- No --> AGENT["Request 3-5 new agent-supplied topics through the same fenced continuation"] AGENT --> SEARCH["Search once, excluding searchedKeywords, then project actual returned candidates"] SEARCH --> PROVIDER{"All LinkedIn providers failed?"} PROVIDER -- Yes --> RETRY["Retry the same bounded search once"] RETRY --> RECOVER{"Provider recovered?"} RECOVER -- No --> BLOCK["Output: blocked_retryable / provider_unavailable; universeExhausted false"] RECOVER -- Yes --> AY{"Actual qualified yield meets floor?"} PROVIDER -- No --> AY AY -- Yes --> USE AY -- No --> EX["Receipt-proven signal_yield_below_floor"] EX --> NEXT["Advance to next pinned existing lane"] ``` Provider outage never consumes a keyword tier or proves exhaustion. Temporary derived search state is run-scoped; later runs may rediscover new posts. Agent keywords are supplied only through the exact run continuation, never by user steering or a second run. ## Sales Nav and Prospeo acquisition ```mermaid flowchart TD A["Cold-provider lane needs rows"] --> UNUSED{"Unused exact-source rows exist?"} UNUSED -- Yes --> USE["Import/copy bounded rows"] UNUSED -- No --> DEEP["Continue latest/unfetched pages of exact saved Sales Nav or Prospeo search"] DEEP --> DY{"Qualified yield?"} DY -- Yes --> USE DY -- No --> OPTIONAL{"Known activity/recency filter exists?"} OPTIONAL -- No --> NEXT["Advance to next pinned existing lane"] OPTIONAL -- Yes --> CLONE["Clone exact approved search; remove one optional activity/recency filter"] CLONE --> PROOF{"Original unchanged and every hard-fit filter fingerprint preserved?"} PROOF -- No --> BLOCK["Output: blocked_retryable / provider_search_drift"] PROOF -- Yes --> TRIAL["Import bounded trial candidates into existing gated source"] TRIAL --> GATE["Run current ICP, exclusion, DNC, persona, geography, and company-fit gates"] GATE --> GY{"Qualified yield?"} GY -- Yes --> USE GY -- No --> QUARANTINE["Rejects remain unsendable; advance lane"] QUARANTINE --> NEXT ``` The original saved search is immutable. An automatic trial may remove only a known activity/recency signal; role, seniority, geography, industry, company size, domain, include/exclude, DNC, and every other hard-fit constraint remain byte-for-byte. Imported candidates are not approvals: current campaign gates quarantine bad fits. If rubric rejects dominate — including a receipt-proven `low_yield` cohort below the 10% planning floor, not only zero-prepared — enter the bounded sample-quality trial below before another source add. Do not repeat a spent trial or switch outside planner order. ## Provider prompt before any new provider search Draining an already-imported source list needs no provider context. But before any action that runs a NEW provider search — a `broaden_signal_search` round (especially when supplying `agentKeywords`), a Sales Nav or Prospeo cloned- search broadening, or any source add that dispatches a fresh search — load the matching specialized prompt first with `get_provider_prompt({ provider, campaignOfferId })` and apply its discipline: sample-first fit checks against the headline ICP criteria, the 10% planning floor, sample math (target ÷ observed pass rate) for sizing, and the keyword- quality guidance for Signal Discovery. Inventing broaden keywords or sizing a scrape without the provider prompt in context repeats the blind-import failure the sampling discipline exists to prevent. The search tools also enforce this preflight server-side; loading it late wastes a fenced round on a typed refusal. ## Rubric sample-quality trial Rubric rejection may mean the source is poor or that one or several required checks are unnecessarily narrow. Decide from an exact sample, not pass-rate alone. ```mermaid flowchart TD A["Rows fail ICP/rubric gate"] --> STALE{"Score stale, errored, or computed under mismatched authority?"} STALE -- Yes --> RESCORE["Rerun exact bounded cohort under unchanged current rubric"] RESCORE --> RESULT{"Now qualified?"} RESULT -- Yes --> CONTINUE["Continue row lifecycle"] RESULT -- No --> SAMPLE STALE -- No --> SAMPLE["Take exact representative rejected sample from prep receipt"] SAMPLE --> SNAP["Snapshot complete rubric and prior digest"] SNAP --> PROPOSE["Propose smallest 1-3 required-to-advisory bundle that creates useful new passes"] PROPOSE --> HARD{"Touches employment, DNC/opt-out, legal/compliance, or explicit exclusion?"} HARD -- Yes --> REJECT["Reject proposal; preserve complete prior rubric"] HARD -- No --> SIM["Simulate bundle from recorded per-prospect evaluations; no mutation"] SIM --> DELTA{"Any newly passing prospects?"} DELTA -- No --> REJECT DELTA -- Yes --> QUALITY["Review only newly passing profiles in campaign and customer context"] QUALITY --> REPLY{"If these prospects replied, would the customer be comfortable and consider the replies relevant?"} REPLY -- No --> REJECT REPLY -- Yes --> DRIFT{"Live rubric digest still equals prior digest?"} DRIFT -- No --> BLOCK["Output: blocked_retryable / rubric config drift"] DRIFT -- Yes --> APPLY["Guarded apply entire required-to-advisory bundle"] APPLY --> EXACT["Force exact bounded ICP rescore for accepted sample"] EXACT --> VERIFY{"Dispatch and accepted quality receipt valid?"} VERIFY -- Yes --> KEEP["Keep bundle; receipt before/after digests, changed checks, sample counts, and verdict"] VERIFY -- No --> ROLLBACK["Restore complete prior snapshot and receipt rollback"] REJECT --> BETTER["Keep rows unsendable; continue better supply or next existing lane"] ROLLBACK --> BETTER ``` `--yolo` may change more than one required rubric at a time only through this single bounded reversible bundle. The change demotes selected required checks to advisory scoring; it does not erase their evidence. The acceptance test is the newly passing delta: if any reviewed prospect would make the customer unhappy, surprised, or consider the reply irrelevant, keep the complete prior rubric and continue the source waterfall. Never expose raw prospect profiles in the public receipt. ## Scheduler and uncertain-receipt recovery ```mermaid flowchart TD A["Ready buffer covers remaining projected gap"] --> CREDIT{"Every selected paid lane has fresh credit facts?"} CREDIT -- No --> RF["Refresh each exact selected sender once, then full replan"] RF --> A CREDIT -- Yes --> DATE{"Exact targetDate?"} DATE -- Yes --> SWEEP["Dispatch one request-scoped product scheduler sweep"] DATE -- No --> WAIT["Enter coordinator-owned bounded scheduler wait"] SWEEP --> RECEIPT{"Receipt state?"} RECEIPT -- Terminal --> READ["Canonical target-plan and request/effect readback"] RECEIPT -- Active --> POLL["Return in_progress and poll only the same request key"] RECEIPT -- uncertain_outcome --> RECON["Reconcile same request key read-only; never redispatch"] POLL --> RECEIPT RECON --> TERM["Terminal: blocked_retryable with request identity intact"] WAIT --> READ READ --> FULL{"Projected coverage full?"} FULL -- Yes --> COMPLETE["Terminal: complete"] FULL -- No --> READY{"Ready buffer still covers gap?"} READY -- Yes --> OTHER{"Any other workspace target still needs preparation or an executable gate?"} OTHER -- Yes --> PREP["Continue next planner-ranked exact target"] OTHER -- No --> BLOCK["Terminal: receipt-backed blocked_retryable scheduler gap"] READY -- No --> PREP["Return to row lifecycle"] ``` A scheduler sweep is a visible workspace-wide scheduling side effect: existing product gates may schedule unrelated eligible cells in that workspace/date. It never sends directly or raw-writes scheduler fields. `cellsConsidered is allocation-attempt count`; `readyCellsFound` is prefilter inventory. Inspect `campaignScopeSummary`, `prefiltered`, `skipped`, and `deferred`. For ready closed-InMail cells with stale paid-credit evidence, use `refresh_paid_inmail_credits_then_rerun`. `wait_for_capacity_or_window` means do not source or prep more rows; `no_ready_cells_continue_refill_prep` returns to the row lifecycle. `cellsScheduled:0` alone is not failure. Once a sweep is dispatched, a host wait budget yields an `in_progress` continuation carrying the same run identity. Keep polling that request key until its official terminal receipt, then reread the full target plan. Never convert an active sweep into `loaded_awaiting_scheduler`. ## Explicit message-template revision Ordinary refill never changes copy. Only a literal user-approved revision envelope may enter this side path. ```mermaid flowchart TD A["Literal messageTemplateRevision with approved markdown, digests, 1-500 row IDs, request/effect IDs"] --> DIGEST{"Current prior authority digest present?"} DIGEST -- No --> FENCE["Acquire exact run and read reportingContext source templateAuthorityDigest"] FENCE --> DG{"Digest returned?"} DG -- No --> BLOCK["Terminal: blocked_retryable / template_authority_missing"] DG -- Yes --> REVISE["Apply revision to exact cohort"] DIGEST -- Yes --> REVISE REVISE --> AUDIT["select_campaign_cells in batches <=20 with includeGeneratedMessageReview:true"] AUDIT --> MATCH{"Every authoritativeGeneratedMessageReview complete, exact text/digest, matchesCurrentTemplateAuthority true?"} MATCH -- No --> FAIL["Stop before approval: regenerated_copy_integrity_failed"] MATCH -- Yes --> APPROVE["Approve only exact rowIds using compiled readiness authority"] APPROVE --> RECEIPT{"Bounded preparation receipt includes laneScope?"} RECEIPT -- No --> FAIL RECEIPT -- Yes --> PLAN["Return to canonical refill plan"] ``` Formula Message/Subject columns, previews, exports, and counts are not copy authority. `forceRerun:true` never implies a revision or approval. ## Manual approval and `--yolo` ```mermaid flowchart TD A["Render fresh bounded packet in normal chat as Markdown"] --> MODE{"--yolo present?"} MODE -- Yes --> AUTO["Auto-accept all planner-ranked transitions among existing campaigns, lanes, sources, and safe refill primitives"] MODE -- No --> ASK["Open host-native structured question with exactly Accept and Decline"] ASK --> DECISION{"Operator choice?"} DECISION -- Accept --> RUN["Execute only rendered envelope"] DECISION -- Decline --> STOP["Stop with no mutation"] AUTO --> RUN ``` Use `request_user_input` in Codex and `AskUserQuestion` in Claude Code. The full packet belongs in the normal chat message immediately before the question: workspace, sender scope, campaign-by-campaign plan table, action, target/cap, source, blocker/skip reason, exact IDs, expected side effects, forbidden actions, and stop condition. The question body must be compact and refer back to the posted packet instead of duplicating it. `--yolo` may switch among every fresh planner-ranked existing campaign/lane/source family and start the exact selected `PAUSED` campaign. It does not create campaigns, direct-send, archive/delete, change sender limits or paid-InMail thresholds, reassign senders, or invent source selection outside the planner. ## Typed outputs ```mermaid flowchart TD A["Fresh canonical state"] --> C{"Terminal classification"} C -- "sent + scheduled meets target" --> COMPLETE["complete: report saturation ledger"] C -- "all remaining exact targets scheduler-owned and workspace ready-or-projected gap is zero" --> LOADED["loaded_awaiting_scheduler: report expected pickup and stop"] C -- "recoverable external, capacity, active-work, or receipt condition" --> BLOCKED["blocked_retryable: exact blocker, receipt, and safe retry condition"] C -- "current lane spent but another existing target remains" --> EX["lanes_exhausted internally; continue next_exact_target"] C -- "every existing campaign/lane/source spent or infeasible" --> NEW["new_campaign_required: propose missing shape; create it under autopilot, else ask whether to"] C -- "scope/config/readiness changed" --> DRIFT["blocked_retryable: typed drift; never guess"] ``` One scheduler-loaded exact lane is not a workspace terminal: skip it and continue other planner-ranked lanes. Workspace `loaded_awaiting_scheduler` is admissible only when no refill-owned scheduler request is active. After refill dispatches a sweep, keep the same fence alive until the official terminal receipt; then return `complete` from the post-receipt readback or a concrete receipt-backed retryable scheduler blocker if coverage remains short. The sole refill-ladder expansion that asks the user in `--yolo` is `new_campaign_required`; refill itself never creates that campaign. With `launchConsent.mode: "autopilot"` the caller creates the proposed campaign through Create Campaign (YOLO) and refill continues instead of asking. `new_campaign_required` is also the truthful terminal when receipts prove every existing lane is spent but the planner has no automatic cold rung to propose (Signal Discovery workspaces): report the remaining gap and ask the user; a future handler may automate the creation. A `actionable_supply_not_queued` terminal means actionable row frontiers (approval/generate/enrich/rubric or unprocessed frontier rows) still exist but no executable exact edge surfaced: retry once with a fresh command, and if it repeats report it as a planner-exposure defect instead of treating the workspace as exhausted. That census counts CONVERTIBLE rows only, de-duplicated per campaign/table lane — a frontier whose own preparation receipt carries a non-retryable `icp_or_rubric_rejection` diagnosis is proven unable to convert, so it never inflates the census. When such receipts are all that remain, the terminal is `source_supply_exhausted_at_rubric`: a truthful source exhaustion, not a defect and not retryable. Report the per-lane evidence from the note and tell the user that widening the rubric or adding fresh source rows is the only remaining fill path. Whenever a terminal reports a non-empty census, it also names every exact edge the run refused (`Exact edges this run refused: ...`) — surface those named blockers rather than describing the workspace as having no available work. ## Non-negotiable authority and proof - Before preparation, approval, or exact-date scheduling, require a validated compiled campaign and positive exact target readiness identity from the backend compiler: `profileSchemaVersion`, `compilerVersion`, `campaignDigest`, `actionColumnId`, action, `pathDigest`, `dependsOnColumnIds`, `runCondition`, the selected target's `capabilities`, `requestId`, `effectId`, and `dateGuards`. Never infer no-message behavior from a missing Generate Message column. Prepare rows according to the persisted sequence; bounded enrichment and LLM prerequisite calls are allowed only when authorized by the exact readiness packet and receipted. - Refuse approval outside the bounded authorized cohort. The exact selected target path controls message, subject, comment, reaction, and row approval. For exact dates, require the date strictly after sender-local today and inside the fill horizon. Prepared, approved, and ready rows remain intermediate evidence; completion requires request/effect-attributed scheduler-owned scheduled readback. - Execute one bounded primitive, then perform a full authoritative reread. Never call a LinkedIn outreach/provider-send surface from refill. No direct sends, raw scheduler writes, broad approval, threshold/limit changes, sender reassignment, destructive cleanup, or new campaign creation. - Maintain the target-window saturation ledger per sender: selected days, gross capacity, actual sent, future scheduler-owned scheduled with non-null `scheduledFor`, projected coverage (`sent + scheduled`), ready buffer, remaining gap, paid-InMail feasibility, `targetShapeRevision`, and `stateRevision`. Future scheduled coverage and already sent actions are distinct. - Carry canonical `refill_reporting.v2` unchanged through progress, continuation, replay, and terminal output. Completion proof is Sellable MCP evidence only: target plan, campaign refill state, scheduler capacity, sweep/status, and bounded receipts. Never use individual cell ids, Prisma, SQL, direct database access, or production-environment scripts as completion proof. Redact raw copy and prospect fields. ## Refill V3 This is the DEFAULT route whenever `refill_v3_advance` is exposed (see [Route selection](#route-selection--do-this-first)). `refill_v3_advance` is then the only execution owner and `refill-sends-workflow` plus its `core/flow.v3.json` asset is the contract to load — load the prompt and that asset, verify the `v3` compatible range, and report the version before the first call. Start from the base request `{workspaceId, scope}`, add `yolo:true` by default, and call it again after each `advanced` or settled `campaign_attention_required` result. A `run_step_pending` result means the server is still completing the already-consumed step: wait its `retryAfterMs`, then call advance with the same public inputs. The MCP retains the identical token internally; never copy a token, route to AI, or execute an authority for this transport-reconciliation wait. `refill_v3_continue` itself settles the exact packet action before it returns. Bounded enrichment and an already-active preparation job use the shared Create Campaign waiter and durable preparation owner; directly queued row repair uses the campaign waiter. Synchronous source, configuration, rubric, and exhaustion authorities already return after their own exact reread, so they return `settlement.ready:true` from that bounded authority receipt and never wait on unrelated campaign-wide processing cells. When an explicit `yolo:false` request names a campaign, resolve that visible name with one authorized bounded `get_campaigns` call carrying an explicit `limit`. Require exactly one case-insensitive exact match; if none or more than one match, stop without preview or mutation. Retain only the returned stable campaign ID. Call `refill_v3_world_state({workspaceId, scope, campaignId})`, render that exact campaign's two-lane readiness and its explicit zero product side effects, then stop at the product-native question with exactly Accept and Decline. Do not call `refill_v3_advance` before Accept. Accept calls `refill_v3_advance({workspaceId, scope, campaignId})`; Decline stops. Carry the same campaignId through every advance, continue, replay, partial-wait, and post-wait repeat. Display-name changes never alter this retained identity, and no sibling campaign may replace it. Call the next advance only when `settlement.ready` is true. When it is false, call `wait_for_campaign_processing` with `settlement.resumeInput` **unchanged**; that carries the exact `preparationJobId`, `requirePreparationTerminal:true`, and `requireIdle:true`. Repeat the returned `resumeInput` until ready; never fall back to a stats-only campaign wait for this settlement. An `awaiting_external_change` result with `deferral.code:"campaign_work_in_flight"` and a campaign id is a bounded read-only continuation, not permission to walk the next campaign: call `wait_for_campaign_processing({ workspaceId, campaignId, requireIdle:true })`, then call `refill_v3_advance` again with the identical scope and yolo grant. This reuses Create Campaign's campaign-table waiter and guarantees the same campaign is freshly replanned after its cohort settles. If the bounded wait returns a partial timeout, surface that checkpoint and make the fresh advance; if it reports the same live campaign, repeat the bounded wait. Stop on every other `awaiting_external_change`, `complete`, or `blocked` result. For each of those terminal results, report **every** entry in `terminalEvidence.rows`, one per scoped sender/date/lane—not only the headline deferral or blocker. Include `targetSlots`, `takenSlots`, `remainingTargetGap`, `fillableSlotsNow`, `blockedGap`, `terminalClassification`, `reasonCodes`, `approvedDelta`, and `scheduledDelta`. When `reasonCodes` contains `paid_inmail_credit_refresh_failed`, also report `paidCreditRefreshAttempt` exactly: `attemptedAt`, `outcome`, `errorClass`, `errorDetail`, and `durationMs`. A cumulative observation, vague "credit refresh" label, or one surfaced blocker is never a row-complete terminal summary. `terminalEvidence.rows` covers exactly two Refill V3 lanes: `connection_invite` and closed/paid InMail. It does **not** report Open InMail. Campaign `supply.readyToSchedule` is a broad first-touch table count and can include Open InMail rows that the normal scheduler can place independently. Never present that broad count as connection or paid inventory, never infer Open InMail is empty or unscheduled from a Refill V3 row, and never explain the difference as sender affinity unless exact lane evidence proves it. If asked whether Open InMail is maxed, say the two-lane refill receipt cannot answer that question and obtain an action-type-specific scheduled readback before answering. Lead every row-complete terminal summary by stating that all scoped sender/date/lane rows were explored. Never say the workspace "stopped on" one sender or headline blocker: that blocker classifies one row after independent sibling work was explored; it is not the traversal stop. Resolve the workspace BEFORE the first call, exactly as the V1 route already requires. `workspaceId` must be an exact id on every automation call: when the operator names a workspace, call `list_workspaces` first and match that name to its id. Prefer one case-insensitive exact name match; if none exists, accept one case-insensitive prefix match (so `Damiano` resolves `Damiano R`). Use the configured or active workspace only when the operator named none. If zero or multiple prefix matches remain, stop and say so — do not fall back to the configured workspace — and never change the shared active workspace to steer an automation. The command-shaped form is unambiguous: in `refill sends <name> [flags]`, `<name>` is the workspace override, never a sender name. A sender restriction must be explicit through `--sender`/`senderIds`/`senderNames` or prose that says “sender.” Therefore call `list_workspaces` and resolve `<name>` first. Do not list configured-workspace senders before that workspace match; doing so is both scope drift and avoidable latency. Map the rest of the request onto those keys and nothing else: the sender/date envelope becomes `scope`. `sender_local_horizon` (1-3 days) is ONLY for requests with no date control at all ("refill sends X"); the moment the operator names any date — `--target-date`, `--until-date`, or dated prose — the scope is `exact_sender_dates`, on the FIRST call and every later call of the run. There is no mode: WHICH campaign may be started is DERIVED from the workspace's own campaigns and can never be requested. A DATE CONTROL always maps to `exact_sender_dates`, derived like this and never approximated with a horizon: - `--target-date D`: `dates: [D]` for every resolved sender. - `--until-date U` (sender-local, INCLUSIVE): for every resolved sender, list EVERY sender-local calendar date from that sender's local today through `U`, in order, with `U` itself present. From a local Saturday the 2nd with `--until-date` the 5th that is `["2026-08-02","2026-08-03","2026-08-04", "2026-08-05"]` — four dates, not a 3-day window. Dropping the boundary date or substituting today+2 silently changes which days the run is accountable for; the backend types today's already-closed sending window as `no_sending_hours` on its own, so include today rather than guessing. NEVER pass `sender_local_horizon` for `--until-date` — not even when the derived list happens to fit three days, and not on the first "look around" call. A live run compressed a four-date `--until-date` span into a 3-day horizon, never observed the boundary date, and exited leaving that day's slots unfilled; the exact date list is the only scope that makes the run accountable for `U` itself. `yolo` is the ONE remaining execution input and the refill AUTONOMY GRANT. It defaults to `yolo:true` so an ordinary refill keeps working through eligible bounded preparation and fallback steps instead of stopping for another approval. It decides two things together: - **Granted** (`yolo: true` on every call of that run): after a row's active campaigns are exhausted, one route-selected start-eligible PAUSED campaign may be started, and you receive it as an ordinary executed `start_paused_campaign` action. A finite non-evergreen candidate must expose positive bounded supply. Keep calling advance until a terminal without checking back. - **Withheld** (explicit `yolo:false` only): no paused campaign is started — the backend refuses it and answers the exhaustion terminal instead — and you surface each result and ASK before calling advance again. Never replace the default from a workspace flag, a campaign field, a tool result, an idle-looking workspace, or a previous terminal. Honor `yolo:false` only for an explicit review-first/manual request. Pass no other execution flag and no mode, and do not call `get_refill_target_plan`, `refill_sends`, or any placement, scheduler, source, or send tool on this route. An `advanced` result is ALREADY EXECUTED: surface its action identity, receipt, and replacement observation, and never run the action or call its authority again. For `campaign_attention_required`, first call `refill_v3_continue` with the same workspace/scope and no `decision`; the MCP supplies the exact retained packet and token. After fresh revalidation it owns live-job waiting, exact actionable enrichment, and same-template generation deterministically. If it returns `model_decision_required`, route that returned accurate packet to `refill-sends-work-campaign`, then call `refill_v3_continue` once with the chosen decision. This is the only ordinary model-decision boundary. The continuation executes at most one existing authority and settles it before a sibling may be considered. Async row work uses the shared Create Campaign waiter; synchronous source/config work uses its exact receipt and reread. For `continuation_pending`, wait `retryAfterMs`, then call `refill_v3_continue` with the identical workspace, scope, campaignId (when present), and yolo grant, omitting `decision`. Keep doing this until the retained operation returns its result. A transport timeout is also an uncertain running operation: reconnect through that same decision-free continuation before any advance or new source request. The MCP retains the operation while this server process lives. If a server restart reports that retained attention is missing, stop with `continuation_reconciliation_required` and the exact scope and last receipt; do not invent a replacement run or claim the prior effect failed. Never treat a timeout as zero effect. Continue the authorized refill across every connected sender and requested lane until the coordinator returns row-complete terminal evidence. Preserve the rubric, message, caps, and exclusions; pending work needs no renewed approval. Interpret continuation world wrappers by their outer kind: `world_terminal` means `advanceResult` is already the canonical `awaiting_external_change`, `complete`, or `blocked` terminal, so report that nested terminal and stop; never call advance again. `world_step_pending` means wait the nested `retryAfterMs` and then call advance with the same public inputs. `world_advanced` means the nested action already ran, so surface it and call advance again. For `attention_refresh_required`, continue from its refreshed packet rather than opening another run. If it returns `attention_correction_required` with `correction.kind:"signal_keyword_collision"`, route the returned same packet and exact correction through `refill-sends-work-campaign` once, then call `refill_v3_continue` with that revised decision. Exclude every returned searched keyword and use the required request-fingerprint prefix. This is one refused, zero-effect Signal correction; a second collision is terminal and must not loop. This skill chooses no sender, date, lane, campaign, or action, holds no counter or cursor between calls, and declares no terminal, exhaustion verdict, or health classification of its own. V1 `refill_sends` behavior is unchanged.