@sellable/mcp 0.1.38-rc.1 → 0.1.38-rc.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/package.json
CHANGED
|
@@ -773,439 +773,16 @@ Follow that prompt verbatim. It contains the full message-validation workflow, g
|
|
|
773
773
|
|
|
774
774
|
Do NOT proceed to Step 4 (message review gate) without loading and following the generate-message subskill — message-validation.md must prove the full generate-messages workflow ran, and that workflow is defined in the subskill.
|
|
775
775
|
|
|
776
|
-
## MANDATORY TOOL ORDER
|
|
776
|
+
## Tail (MANDATORY TOOL ORDER + Steps 13-16 + Threshold Trips + Hard Rules)
|
|
777
777
|
|
|
778
|
-
|
|
779
|
-
**review-batch cascade-driven**: you kick off Enrich Prospect only for
|
|
780
|
-
the imported review batch, and the workflow engine chains DNC Check →
|
|
781
|
-
ICP Score → Passes Rubric → Generate Message automatically. Your job is
|
|
782
|
-
to START the bounded cascade, WAIT for it to drain, OBSERVE the results,
|
|
783
|
-
and stop for review.
|
|
784
|
-
Do NOT manually run rubric-check, enrich, or message-generation
|
|
785
|
-
tools — the cascade already does them.
|
|
778
|
+
The full tail detail (~17k chars: MANDATORY TOOL ORDER, auto-execute-leads, validate-sample loop, auto-execute-messaging, awaiting-user-greenlight, threshold-trip logging, tail hard rules) lives in a dedicated on-demand subskill to keep this entry prompt fast to load.
|
|
786
779
|
|
|
787
|
-
|
|
788
|
-
Step 13 — import review batch + kick bounded cascade
|
|
789
|
-
import_leads(targetLeadCount=importLimit)
|
|
790
|
-
wait_for_lead_list_ready
|
|
791
|
-
confirm_lead_list
|
|
792
|
-
get_rows_minimal # capture enrichCellIds
|
|
793
|
-
queue_cells(cellIds=<review-batch Enrich Prospect cells only>) <-- starts bounded chain
|
|
794
|
-
wait_for_campaign_table_ready # review-batch cascade drains here
|
|
795
|
-
update_campaign(currentStep=validate-sample)
|
|
796
|
-
|
|
797
|
-
Step 14 — observe sample
|
|
798
|
-
get_rows_minimal # read passesRubric + message cell status per row
|
|
799
|
-
compute projectedPass
|
|
800
|
-
if OK: update_campaign(currentStep=auto-execute-messaging)
|
|
801
|
-
else: diagnose brief-vs-list; if brief: update_campaign_brief + re-queue + wait
|
|
802
|
-
(check_rubric / bulk_enrich_with_prospeo are NOT called here —
|
|
803
|
-
cascade already did them. wait_for_rubric_results is OK as a
|
|
804
|
-
read-only observation helper if you need to block on completion.)
|
|
805
|
-
|
|
806
|
-
Step 15 — observe messaging
|
|
807
|
-
get_rows_minimal # confirm passing rows have completed Generate Message cells
|
|
808
|
-
(rare) queue_cells on any pending Generate Message cells
|
|
809
|
-
token-contract spot check via get_rows
|
|
810
|
-
update_campaign(currentStep=awaiting-user-greenlight)
|
|
811
|
-
(generate_messages is NOT an MCP tool; messages come from the cascade)
|
|
812
|
-
|
|
813
|
-
Step 16 — awaiting-user-greenlight
|
|
814
|
-
attach_recommended_sequence({ campaignId }) # tier-aware: premium/SN -> If Open Profile->INMAIL_OPEN, else INVITE->accepted->DM
|
|
815
|
-
re-surface watchUrl + review-batch orientation
|
|
816
|
-
STOP. DO NOT call start_campaign. DO NOT auto-advance currentStep.
|
|
817
|
-
```
|
|
818
|
-
|
|
819
|
-
**Prefer `attach_recommended_sequence` over `attach_sequence` in the
|
|
820
|
-
autonomous tail.** The recommended tool takes only `campaignId` and
|
|
821
|
-
delegates template selection to the backend's tier-aware selector
|
|
822
|
-
(`selectTemplateForTiers`). `attach_sequence` requires you to
|
|
823
|
-
hand-author a `version: 2` template with nodes, branches, and
|
|
824
|
-
entryNodeId — that's error-prone mid-long-context (galley-off UAT
|
|
825
|
-
`20260420T195732Z` failed because Claude hit "Invalid node type" and
|
|
826
|
-
tried to debug via forbidden Bash/Glob calls). Use `attach_sequence`
|
|
827
|
-
only when the caller explicitly needs a custom non-recommended cadence.
|
|
828
|
-
|
|
829
|
-
Hard gates — if you find yourself about to violate any of these, stop
|
|
830
|
-
first:
|
|
831
|
-
|
|
832
|
-
- Do NOT use `update_cell` to write message bodies. The `Generate
|
|
833
|
-
Message` column's http_request writes those cells via the cascade.
|
|
834
|
-
`update_cell` remains reachable for legitimate operator overrides
|
|
835
|
-
AFTER the tail hands off.
|
|
836
|
-
- Do NOT call `check_rubric`, `enrich_with_prospeo`, or
|
|
837
|
-
`bulk_enrich_with_prospeo` in the tail. Those are direct-API
|
|
838
|
-
mutation tools that fetch data to the caller without writing to
|
|
839
|
-
the workflow table cells. Step 13's `queue_cells` already
|
|
840
|
-
triggers the column-level enrichment that populates those cells.
|
|
841
|
-
Running these tools in the tail produces duplicate cost with no
|
|
842
|
-
cell side effect.
|
|
843
|
-
- `wait_for_rubric_results` is read-only and OK to use as an
|
|
844
|
-
observation helper when you need to block on rubric completion —
|
|
845
|
-
it does not mutate cells. Prefer `get_rows_minimal` +
|
|
846
|
-
`wait_for_campaign_table_ready` for the primary cascade-drain
|
|
847
|
-
path, but use `wait_for_rubric_results({ targetCount: cohortSize })`
|
|
848
|
-
when the table-level wait returns before rubric cells finish.
|
|
849
|
-
- `start_campaign` is FORBIDDEN in the autonomous tail. It belongs only
|
|
850
|
-
in the Claude-greenlight path, AFTER the user signals "start". See
|
|
851
|
-
`references/final-handoff-contract.md`.
|
|
852
|
-
- You MAY NOT call `start_campaign` without a prior successful
|
|
853
|
-
`attach_recommended_sequence` (or `attach_sequence` if a custom
|
|
854
|
-
cadence is explicitly required) in the same run.
|
|
855
|
-
- You MAY NOT call `start_campaign` while ANY passing row has an
|
|
856
|
-
empty `Generate Message` cell. If you discover empty message cells in
|
|
857
|
-
the greenlight turn, ABORT greenlight and return to Step 15 first.
|
|
858
|
-
- You MAY NOT call `attach_sequence` while ANY passing row has an
|
|
859
|
-
empty `Generate Message` cell. Before `attach_sequence`, call
|
|
860
|
-
`get_rows_minimal` and confirm every row where `passesRubric=true`
|
|
861
|
-
has a `completed` Generate Message cell with non-empty `result`. If
|
|
862
|
-
any are pending, call `queue_cells` on those generateMessageCellIds
|
|
863
|
-
and wait. If rows truly won't message, ESCALATE.
|
|
864
|
-
- You MAY NOT advance past Step 13 without calling `queue_cells` on
|
|
865
|
-
the review-batch Enrich Prospect cells. Without it, every downstream
|
|
866
|
-
cell stays `pending` and the campaign ships empty.
|
|
867
|
-
- You MAY NOT queue enrichment for rows outside the configured review
|
|
868
|
-
batch before the user approves expansion. Full-list enrichment/message
|
|
869
|
-
generation is a credit-spend decision and must happen after the user
|
|
870
|
-
has reviewed the sample.
|
|
871
|
-
|
|
872
|
-
Load `core/auto-execute.yaml` exactly once at the start of Step 13. All
|
|
873
|
-
subsequent steps read the already-parsed config. Do not re-load mid-run.
|
|
874
|
-
Load each subskill prompt (`create-campaign-v2`, `research-sender`,
|
|
875
|
-
`generate-messages`) at most once per run. A multi-call chunk sequence counts
|
|
876
|
-
as one load. If a tool result already told you to load a prompt, load all
|
|
877
|
-
chunks once and remember; do not restart the same prompt from offset 0 later.
|
|
878
|
-
|
|
879
|
-
After every `update_campaign({ currentStep: ... })` in the tail, narrate
|
|
880
|
-
what changed and orient the user to what the watch link will show next —
|
|
881
|
-
reuse the v1 `create-campaign` watch-mode pattern verbatim.
|
|
882
|
-
|
|
883
|
-
## Step 13: auto-execute-leads
|
|
884
|
-
|
|
885
|
-
Entered on `CampaignOffer.currentStep === "auto-execute-leads"` (set by
|
|
886
|
-
atomic mint).
|
|
887
|
-
|
|
888
|
-
> Reminder: every provider search you run from this point forward — the
|
|
889
|
-
> review-batch source rerun in Step 13, any expansion search after the
|
|
890
|
-
> review batch, alternate-lane probes, account-based reruns, and operator
|
|
891
|
-
> follow-ups — MUST include `campaignOfferId`. This applies to
|
|
892
|
-
> `search_prospeo`, `search_sales_nav`, `search_apollo`,
|
|
893
|
-
> `search_signals`, and any other provider search tool added later.
|
|
894
|
-
> Campaignless searches after mint orphan results from the campaign UI.
|
|
895
|
-
>
|
|
896
|
-
> **Lead-list vs campaign-table identity rule (do not confuse them):**
|
|
897
|
-
> after the first `confirm_lead_list`, two workflow tables exist:
|
|
898
|
-
>
|
|
899
|
-
> - the **lead-list** table (the original `sourceLeadListId` returned by
|
|
900
|
-
> `import_leads`), and
|
|
901
|
-
> - the **campaign workflow table** (`CampaignOffer.workflowTableId`,
|
|
902
|
-
> also returned as `campaignTableId` from `confirm_lead_list`).
|
|
903
|
-
>
|
|
904
|
-
> `confirm_lead_list` overwrites `CampaignOffer.selectedLeadListId` with
|
|
905
|
-
> the campaign-table id. That means after the first confirm, the
|
|
906
|
-
> campaign-table id is NOT a valid `sourceLeadListId` for any future
|
|
907
|
-
> `import_leads` call.
|
|
908
|
-
>
|
|
909
|
-
> When you scale up the import, `mode: "add"` requires the ORIGINAL
|
|
910
|
-
> lead-list id (the one returned by the first `import_leads`), never the
|
|
911
|
-
> campaign-table id. Passing the campaign-table id silently writes
|
|
912
|
-
> straight into the campaign workflow table, bypassing the lead list
|
|
913
|
-
> entirely; the lead list stays at its original size and the
|
|
914
|
-
> campaign-builder `/leads` UI throws "Lead list not found" because it
|
|
915
|
-
> expects `selectedLeadListId` to resolve as a lead list.
|
|
916
|
-
>
|
|
917
|
-
> Capture the original `leadListId` from the FIRST `import_leads`
|
|
918
|
-
> response and reuse it for every scale-up `import_leads(mode: "add")`
|
|
919
|
-
> call on the same campaign. If the original id is unknown, query
|
|
920
|
-
> for the lead-list table by name or skip the `mode: "add"` path; do
|
|
921
|
-
> not pass the campaign-table id as `sourceLeadListId`.
|
|
922
|
-
|
|
923
|
-
1. Load `core/auto-execute.yaml`. Capture `import.importLimit`,
|
|
924
|
-
`sample.sampleSize`, `sample.minProjectedPass`,
|
|
925
|
-
`sample.maxRevisionRounds`, `messaging.tokenContract`,
|
|
926
|
-
`messaging.critique.enabled`, `handoff.autoStart`,
|
|
927
|
-
`handoff.orientation`, `retry.sameToolSameError`,
|
|
928
|
-
`logging.logEveryThresholdTrip`.
|
|
929
|
-
2. Resolve the approved source before importing. If there is no
|
|
930
|
-
`lead-source-intake.json`, keep the existing discovered-provider path:
|
|
931
|
-
`import_leads({ campaignOfferId, targetLeadCount: importLimit })`.
|
|
932
|
-
If a manifest exists, branch by `sourceType`:
|
|
933
|
-
- `supplied-linkedin-profiles`: revalidate file metadata and confirmation
|
|
934
|
-
token, confirm `load_csv_linkedin_leads` only after approval to batch the
|
|
935
|
-
supplied CSV into a Sellable lead list, persist the returned `leadListId`,
|
|
936
|
-
then call `confirm_lead_list({ sourceLeadListId: leadListId,
|
|
937
|
-
targetLeadCount: importLimit })`. Do not call `import_leads` for this
|
|
938
|
-
branch; the lead list is the source, and `confirm_lead_list` imports the
|
|
939
|
-
bounded review batch into the campaign table.
|
|
940
|
-
- `existing-lead-list`: revalidate that the lead list still exists in the
|
|
941
|
-
same workspace, reuse it as `sourceLeadListId`, then call
|
|
942
|
-
`confirm_lead_list({ sourceLeadListId, targetLeadCount: importLimit })`.
|
|
943
|
-
- `supplied-domains`: reuse or confirm `load_csv_domains`, preserving
|
|
944
|
-
`domainFilterId`; run a campaign-associated Prospeo people search with
|
|
945
|
-
`campaignOfferId`, provider prompt preflight, and that `domainFilterId`;
|
|
946
|
-
then import with `targetLeadCount: importLimit`.
|
|
947
|
-
Never exceed `importLimit`. This is a review batch, not a full
|
|
948
|
-
campaign-scale import. If a source search/list contains more rows than
|
|
949
|
-
the cap, import only the first `importLimit` rows and leave the rest for
|
|
950
|
-
a later expansion step.
|
|
951
|
-
Persist or recover materialized IDs on resume: `leadListId`,
|
|
952
|
-
`domainFilterId`, `searchId`, `campaignTableId`, imported row IDs, and queued
|
|
953
|
-
cell IDs. If the source file changed after preview, or an existing lead list
|
|
954
|
-
is missing/wrong-workspace, escalate before materialization instead of
|
|
955
|
-
silently applying stale preview data. Retrying Step 13 must not duplicate
|
|
956
|
-
lead lists, searches, campaign rows, or queued cells.
|
|
957
|
-
3. `wait_for_lead_list_ready` and `confirm_lead_list`.
|
|
958
|
-
4. **Verify the table is set up properly before queuing.** Call
|
|
959
|
-
`get_rows_minimal({ tableId })` and confirm:
|
|
960
|
-
- `Enrich Prospect` column exists on the table and every imported
|
|
961
|
-
row has a corresponding cell with `status === "pending"`.
|
|
962
|
-
- `DNC Check`, `ICP Score`, `Passes Rubric`, and `Generate Message`
|
|
963
|
-
columns all exist too (the cascade depends on all of them).
|
|
964
|
-
- If any are missing, or the cell is in a non-`pending` terminal
|
|
965
|
-
state from an earlier run, ESCALATE with an explicit "table not
|
|
966
|
-
configured for autonomous tail" diagnosis. Do NOT attempt to
|
|
967
|
-
patch the table shape from the tail.
|
|
968
|
-
5. **Kick off the bounded review-batch cascade.** `queue_cells({ tableId,
|
|
969
|
-
cellIds: <pending enrichCellIds for the imported review batch only> })`.
|
|
970
|
-
You queue ONLY the review-batch `Enrich Prospect` cells; the cascade
|
|
971
|
-
auto-dispatches `DNC Check`, `ICP Score`, `Passes Rubric`, and (for rows
|
|
972
|
-
where `Passes Rubric === true`) `Generate Message` as each upstream
|
|
973
|
-
completes. Do not queue cells for any rows beyond `importLimit` before
|
|
974
|
-
user expansion approval.
|
|
975
|
-
6. `wait_for_campaign_table_ready` — waits for the review-batch cascade to
|
|
976
|
-
drain.
|
|
977
|
-
If it times out with most cells still `pending`, ESCALATE: the
|
|
978
|
-
engine is blocked.
|
|
979
|
-
7. If the import returns zero usable leads, ESCALATE per
|
|
980
|
-
`references/escalation-ladder.md` (hard fail).
|
|
981
|
-
8. `update_campaign({ campaignId, currentStep: "validate-sample" })`.
|
|
982
|
-
9. Orient user on the watch link (same pattern as every other step
|
|
983
|
-
change).
|
|
984
|
-
|
|
985
|
-
**Do NOT call `check_rubric`, `wait_for_rubric_results`,
|
|
986
|
-
`enrich_with_prospeo`, or `bulk_enrich_with_prospeo` in Step 13.**
|
|
987
|
-
Those are direct-API tools that fetch enrichment/scoring data to the
|
|
988
|
-
caller; they do not write to the workflow table cells. The cascade
|
|
989
|
-
triggered by `queue_cells` runs the `Enrich Prospect` column's
|
|
990
|
-
http_request, which writes the enrichment into the cell, which
|
|
991
|
-
triggers the next column, and so on. Running the direct-API tools
|
|
992
|
-
is duplicate work with no cell-population side effect.
|
|
993
|
-
|
|
994
|
-
## Step 14: validate-sample (loop)
|
|
995
|
-
|
|
996
|
-
Entered on `CampaignOffer.currentStep === "validate-sample"`. Full
|
|
997
|
-
decision tree lives in `references/sample-validation-loop.md`.
|
|
998
|
-
|
|
999
|
-
**Step 14 is pure observation.** The bounded review-batch cascade that Step
|
|
1000
|
-
13 kicked off (via `queue_cells` on Enrich Prospect) has already written ICP
|
|
1001
|
-
scores and Passes Rubric values back into the workflow table. Step 14 reads
|
|
1002
|
-
those review-batch cell results — it does NOT call `check_rubric`,
|
|
1003
|
-
`wait_for_rubric_results`, `bulk_enrich_with_prospeo`, or any other
|
|
1004
|
-
direct enrichment/scoring tool. All of that already happened in the
|
|
1005
|
-
cascade; running them again is wasted work and can overwrite the
|
|
1006
|
-
real cell results with unrelated data.
|
|
1007
|
-
|
|
1008
|
-
Shape:
|
|
780
|
+
Before any auto-execute or validate-sample step, **load the dedicated tail subskill verbatim**:
|
|
1009
781
|
|
|
1010
|
-
```text
|
|
1011
|
-
get_rows_minimal({ tableId })
|
|
1012
|
-
passInSample = count of first sampleSize review-batch rows with passesRubric === true
|
|
1013
|
-
projectedPass = round(passInSample / sampleSize * importLimit)
|
|
1014
|
-
|
|
1015
|
-
if projectedPass >= minProjectedPass:
|
|
1016
|
-
advance to Step 15
|
|
1017
|
-
else:
|
|
1018
|
-
diagnose brief-vs-list per sample-validation-loop.md
|
|
1019
|
-
- brief: autonomous update_campaign_brief, then re-kick cascade
|
|
1020
|
-
(queue_cells on enrichCellIds) and wait for the new
|
|
1021
|
-
results
|
|
1022
|
-
- list: ESCALATE (no auto-revision of leads)
|
|
1023
|
-
- unknown: ESCALATE
|
|
1024
|
-
revisionRound += 1
|
|
1025
|
-
if revisionRound > maxRevisionRounds:
|
|
1026
|
-
ESCALATE
|
|
1027
782
|
```
|
|
783
|
+
mcp__sellable__get_subskill_prompt({ subskillName: "create-campaign-v2-tail" })
|
|
784
|
+
```
|
|
785
|
+
|
|
786
|
+
Follow that prompt verbatim. It contains the canonical MANDATORY TOOL ORDER (read this BEFORE any tail step), all Step 13-16 contracts, threshold-trip logging rules, and tail hard rules.
|
|
1028
787
|
|
|
1029
|
-
|
|
1030
|
-
Do NOT reset to 0 on resume — a stale resume must still respect the
|
|
1031
|
-
`maxRevisionRounds` cap.
|
|
1032
|
-
|
|
1033
|
-
A "list problem" diagnosis NEVER triggers autonomous
|
|
1034
|
-
`update_campaign_brief`. Escalate instead.
|
|
1035
|
-
|
|
1036
|
-
When the sample passes the projected-pass floor, call
|
|
1037
|
-
`update_campaign({ campaignId, currentStep: "auto-execute-messaging" })`
|
|
1038
|
-
and orient the user that messaging will complete for the review batch only.
|
|
1039
|
-
|
|
1040
|
-
## Step 15: auto-execute-messaging
|
|
1041
|
-
|
|
1042
|
-
Entered on `CampaignOffer.currentStep === "auto-execute-messaging"`.
|
|
1043
|
-
|
|
1044
|
-
**Messages are produced by the `Generate Message` column cascade, not
|
|
1045
|
-
by a separate tool.** Do NOT call a `generate_messages` MCP tool —
|
|
1046
|
-
that tool does not exist; `generate-messages` is a subskill prompt
|
|
1047
|
-
loaded during live messaging drafts. In the autonomous tail, messages
|
|
1048
|
-
flow through the column pipeline: `Enrich Prospect` →
|
|
1049
|
-
`DNC Check` → `ICP Score` → `Passes Rubric` → `Generate Message`.
|
|
1050
|
-
Each column's http_request auto-fires when its upstream dependency
|
|
1051
|
-
completes AND passes gate (e.g. `Passes Rubric === true` before
|
|
1052
|
-
`Generate Message`). Your job in Step 15 is to WAIT for the review-batch
|
|
1053
|
-
cascade to reach `Generate Message` for rows that passed ICP, and verify
|
|
1054
|
-
the output — not to generate messages manually.
|
|
1055
|
-
|
|
1056
|
-
**What Step 15 does:**
|
|
1057
|
-
|
|
1058
|
-
1. `get_rows_minimal` to read `passesRubric` + `generateMessageCellId`
|
|
1059
|
-
- the Generate Message cell's `status` and `result` per row.
|
|
1060
|
-
Before queueing or waiting on Generate Message cells, confirm the
|
|
1061
|
-
minted campaign brief still contains `{{...}}` in `## Approved Message
|
|
1062
|
-
Template`. If it does not, fail before the cascade runs. Do not repair
|
|
1063
|
-
after mint; the template must be present during mint so Step 15 never
|
|
1064
|
-
starts in freeform generation mode.
|
|
1065
|
-
2. For every row where `passesRubric === true`:
|
|
1066
|
-
- The Generate Message cell should be `running` or `completed`
|
|
1067
|
-
already (cascade auto-fired it). If it is still `pending`, queue
|
|
1068
|
-
it explicitly: `queue_cells({ tableId, cellIds:
|
|
1069
|
-
<generateMessageCellIds> })`.
|
|
1070
|
-
3. `wait_for_campaign_table_ready` (or poll) until every passing
|
|
1071
|
-
row's Generate Message cell is `completed`.
|
|
1072
|
-
4. Read the results back via `get_rows` (full) and sanity-check a
|
|
1073
|
-
sample against the Phase 84 token contract: no unresolved
|
|
1074
|
-
`{{tokens}}`, no invented proof, one sentence per line, etc.
|
|
1075
|
-
5. If the sample fails the token contract, diagnose brief-vs-list
|
|
1076
|
-
(same revision loop as Step 14) and escalate if over
|
|
1077
|
-
`maxRevisionRounds`.
|
|
1078
|
-
6. On success, `update_campaign({ campaignId, currentStep:
|
|
1079
|
-
"awaiting-user-greenlight" })`.
|
|
1080
|
-
|
|
1081
|
-
**Do NOT hand-write message bodies via `update_cell`.** `update_cell`
|
|
1082
|
-
is reachable for legitimate operator overrides AFTER the tail hands
|
|
1083
|
-
off, but during autonomous tail the cascade owns the writes. If you
|
|
1084
|
-
find yourself drafting a message body and about to push it through
|
|
1085
|
-
`update_cell`, you have lost state — stop, read the cells, and let
|
|
1086
|
-
the pipeline finish.
|
|
1087
|
-
|
|
1088
|
-
**What `attach_sequence` does (Step 16, not here):** binds the
|
|
1089
|
-
DM/InMail cadence template to the workflow table. Completely different
|
|
1090
|
-
job from messaging. Does NOT generate or write message text. A
|
|
1091
|
-
sequence with no `Generate Message` outputs would try to send empty
|
|
1092
|
-
strings, which is why Step 16 requires Step 15 to be complete.
|
|
1093
|
-
|
|
1094
|
-
1. Observe the review-batch messages on the same sample that passed
|
|
1095
|
-
validation.
|
|
1096
|
-
2. If `messaging.critique.enabled` is true (Plan 85-03), run the
|
|
1097
|
-
bounded critique pass on the sample output per
|
|
1098
|
-
`references/parallel-critique-protocol.md`. The pass runs on at
|
|
1099
|
-
most `messaging.critique.sampleSize` rows, sends each row through
|
|
1100
|
-
three parallel critics (targeting / copy / voice) that return
|
|
1101
|
-
structured JSON, and merges the voices in a synthesis step that
|
|
1102
|
-
enforces the Phase 84 token contract verbatim and re-runs the
|
|
1103
|
-
finalizer pass. Any rewrite that invents proof or uses an
|
|
1104
|
-
unsupported token falls back to the plain generated message. A
|
|
1105
|
-
trip of `messaging.critique.budgetUsdCap` HALTS critique for the
|
|
1106
|
-
remaining sample and continues the plain tail. Plan 85-02 reads
|
|
1107
|
-
the flag but never flips it; critique stays off until the flag is
|
|
1108
|
-
deliberately enabled.
|
|
1109
|
-
3. If `messaging.critique.opus.enabled` is also true, route the
|
|
1110
|
-
highest-value subset through the Opus / craft-message path per
|
|
1111
|
-
`references/thomas-variant-selection.md`. Opus is capped by
|
|
1112
|
-
`messaging.critique.opus.maxMessagesPerPass` and
|
|
1113
|
-
`messaging.critique.opus.budgetUsdCap`; tripping either cap keeps
|
|
1114
|
-
the remaining rows on the non-Opus synthesizer. Opus is bound by
|
|
1115
|
-
the same token contract as every other rewrite path.
|
|
1116
|
-
4. Check `messaging.tokenContract`. When `strict`, reject any sample
|
|
1117
|
-
message that contains unresolved or unsupported tokens (including
|
|
1118
|
-
any critique rewrite that tried to introduce one).
|
|
1119
|
-
5. If the sample passes the token contract (and critique when enabled),
|
|
1120
|
-
stop at the review-batch handoff. Do NOT scale to the full source list
|
|
1121
|
-
before explicit user expansion approval.
|
|
1122
|
-
6. If the sample fails the token contract or critique, diagnose +
|
|
1123
|
-
loop the same way Step 14 does (brief-vs-list), subject to the same
|
|
1124
|
-
`maxRevisionRounds` cap.
|
|
1125
|
-
7. On success, `update_campaign({ campaignId, currentStep:
|
|
1126
|
-
"awaiting-user-greenlight" })` and orient the user.
|
|
1127
|
-
|
|
1128
|
-
Critique failure modes NEVER escalate. A critic timeout, a total
|
|
1129
|
-
timeout, a budget trip, a fake-proof rejection, or an unsupported-
|
|
1130
|
-
token rejection all fall back to the plain generated message for that
|
|
1131
|
-
row. Step 15 does not stall on critique.
|
|
1132
|
-
|
|
1133
|
-
## Step 16: awaiting-user-greenlight
|
|
1134
|
-
|
|
1135
|
-
Entered on `CampaignOffer.currentStep === "awaiting-user-greenlight"`.
|
|
1136
|
-
Full contract lives in `references/final-handoff-contract.md`.
|
|
1137
|
-
|
|
1138
|
-
Shape:
|
|
1139
|
-
|
|
1140
|
-
1. `attach_sequence` (bind the committed sequence to the campaign).
|
|
1141
|
-
2. Re-surface the `watchUrl` using `references/watch-link-handoff.md`.
|
|
1142
|
-
3. Surface the `handoff.orientation` string from `auto-execute.yaml`
|
|
1143
|
-
alongside the link.
|
|
1144
|
-
4. STOP. Do NOT call `start_campaign`. Do NOT auto-advance
|
|
1145
|
-
`currentStep`. The autonomous tail ends here.
|
|
1146
|
-
5. Make the credit boundary explicit in the handoff: only the first review
|
|
1147
|
-
batch has been enriched/messaged; expanding to more leads requires a
|
|
1148
|
-
separate user instruction.
|
|
1149
|
-
|
|
1150
|
-
Two equally valid greenlight channels take Step 16 into a running
|
|
1151
|
-
campaign:
|
|
1152
|
-
|
|
1153
|
-
- **UI path** — user clicks "Approve all" and "Start Campaign" in the
|
|
1154
|
-
watch-link UI. The skill stays idle. On the next resume, it observes
|
|
1155
|
-
the running state and shifts to a "campaign is live" confirmation.
|
|
1156
|
-
- **Claude greenlight path** — user replies with an affirmative ("yeah
|
|
1157
|
-
start" / "looks good, start" / "ship it"). The skill then performs,
|
|
1158
|
-
in order: (a) bulk-approve queued messages via the EXISTING endpoint
|
|
1159
|
-
`POST /api/v3/workflow-tables/cells/approve-batch` (do NOT invent a
|
|
1160
|
-
new tool/endpoint), (b) `start_campaign({ campaignId })`, (c)
|
|
1161
|
-
`update_campaign({ campaignId, currentStep: "running" })`, (d)
|
|
1162
|
-
re-surface `watchUrl` plus a "campaign is live" confirmation.
|
|
1163
|
-
|
|
1164
|
-
A Claude greenlight on an already-running campaign is a no-op
|
|
1165
|
-
confirmation, NOT a duplicate start. Detect via `get_campaign` before
|
|
1166
|
-
calling any mutating tool; if the campaign is already running, skip
|
|
1167
|
-
approve-batch / start_campaign / update_campaign and just confirm.
|
|
1168
|
-
|
|
1169
|
-
Workspace/sender mismatch at greenlight time aborts the start with the
|
|
1170
|
-
same invariant Phase 84 enforces.
|
|
1171
|
-
|
|
1172
|
-
## Threshold Trips and Logging
|
|
1173
|
-
|
|
1174
|
-
When `logging.logEveryThresholdTrip === true` (default), log every
|
|
1175
|
-
threshold trip: `projectedPass` computation, retry cap, revision cap,
|
|
1176
|
-
hard-fail. These logs drive `auto-execute.yaml` tuning after 3-5 real
|
|
1177
|
-
runs.
|
|
1178
|
-
|
|
1179
|
-
## Tail Hard Rules
|
|
1180
|
-
|
|
1181
|
-
- Review-batch sourcing/import happens in Step 13, not during atomic mint.
|
|
1182
|
-
- Full-list expansion is not part of the default tail. It requires user
|
|
1183
|
-
approval after the review batch.
|
|
1184
|
-
Import must never exceed `importLimit`.
|
|
1185
|
-
- The tail NEVER calls `start_campaign` on its own.
|
|
1186
|
-
- The tail NEVER auto-revises leads. Brief revision is autonomous; lead
|
|
1187
|
-
revision is always operator-gated.
|
|
1188
|
-
- `revisionRound` persists across resume — no silent reset.
|
|
1189
|
-
- The bulk-approve action in the Claude greenlight path uses the
|
|
1190
|
-
existing `POST /api/v3/workflow-tables/cells/approve-batch` endpoint
|
|
1191
|
-
— do NOT create a new tool or endpoint for this.
|
|
1192
|
-
- Threshold trips are logged for calibration.
|
|
1193
|
-
|
|
1194
|
-
</autonomous_tail>
|
|
1195
|
-
|
|
1196
|
-
<references_index>
|
|
1197
|
-
|
|
1198
|
-
| File | Load when |
|
|
1199
|
-
| ------------------------------------------ | ---------------------------------------------------------------------- |
|
|
1200
|
-
| `references/approval-gate-framing.md` | Phase 85 commit gate, before showing the approval packet |
|
|
1201
|
-
| `references/watch-link-handoff.md` | Atomic mint + every tail resume, before surfacing watchUrl |
|
|
1202
|
-
| `references/sample-validation-loop.md` | Step 14, before enriching + scoring the sample |
|
|
1203
|
-
| `references/escalation-ladder.md` | Any tail step that needs to decide retry / revise / escalate |
|
|
1204
|
-
| `references/final-handoff-contract.md` | Step 16, and every Claude greenlight turn |
|
|
1205
|
-
| `references/parallel-critique-protocol.md` | Step 15 when `messaging.critique.enabled` is true |
|
|
1206
|
-
| `references/thomas-variant-selection.md` | Step 15 when `messaging.critique.opus.enabled` is true |
|
|
1207
|
-
| `references/thomas-revision-filters.md` | Any critique rewrite, before persisting |
|
|
1208
|
-
| `core/auto-execute.yaml` | Start of Step 13, load once; all tail steps read parsed values |
|
|
1209
|
-
| `core/auto-execute.README.md` | When tuning `auto-execute.yaml` knobs or reviewing threshold-trip logs |
|
|
1210
|
-
|
|
1211
|
-
</references_index>
|
|
788
|
+
Do NOT invoke any tail tool (auto_execute_leads, validate_sample, auto_execute_messaging, etc.) without first loading this subskill — the tool order and the threshold-trip checks live there.
|
|
@@ -0,0 +1,446 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: create-campaign-v2-tail
|
|
3
|
+
description: Tail steps (13-16) + MANDATORY TOOL ORDER + Threshold Trips + Tail Hard Rules for create-campaign-v2. Loaded on-demand via get_subskill_prompt before any auto-execute or validate-sample step. Internal subskill.
|
|
4
|
+
visibility: internal
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# create-campaign-v2 Tail (Steps 13-16 + Tool Ordering + Hard Rules)
|
|
8
|
+
|
|
9
|
+
This is the tail detail extracted from the main create-campaign-v2 SKILL.md to keep the entry prompt slim. The agent loads this on-demand BEFORE entering the auto-execute or validate-sample steps. Follow this verbatim.
|
|
10
|
+
|
|
11
|
+
## MANDATORY TOOL ORDER (read this BEFORE any tail step)
|
|
12
|
+
|
|
13
|
+
Every tail run MUST call these tools in this exact order. The tail is
|
|
14
|
+
**review-batch cascade-driven**: you kick off Enrich Prospect only for
|
|
15
|
+
the imported review batch, and the workflow engine chains DNC Check →
|
|
16
|
+
ICP Score → Passes Rubric → Generate Message automatically. Your job is
|
|
17
|
+
to START the bounded cascade, WAIT for it to drain, OBSERVE the results,
|
|
18
|
+
and stop for review.
|
|
19
|
+
Do NOT manually run rubric-check, enrich, or message-generation
|
|
20
|
+
tools — the cascade already does them.
|
|
21
|
+
|
|
22
|
+
```text
|
|
23
|
+
Step 13 — import review batch + kick bounded cascade
|
|
24
|
+
import_leads(targetLeadCount=importLimit)
|
|
25
|
+
wait_for_lead_list_ready
|
|
26
|
+
confirm_lead_list
|
|
27
|
+
get_rows_minimal # capture enrichCellIds
|
|
28
|
+
queue_cells(cellIds=<review-batch Enrich Prospect cells only>) <-- starts bounded chain
|
|
29
|
+
wait_for_campaign_table_ready # review-batch cascade drains here
|
|
30
|
+
update_campaign(currentStep=validate-sample)
|
|
31
|
+
|
|
32
|
+
Step 14 — observe sample
|
|
33
|
+
get_rows_minimal # read passesRubric + message cell status per row
|
|
34
|
+
compute projectedPass
|
|
35
|
+
if OK: update_campaign(currentStep=auto-execute-messaging)
|
|
36
|
+
else: diagnose brief-vs-list; if brief: update_campaign_brief + re-queue + wait
|
|
37
|
+
(check_rubric / bulk_enrich_with_prospeo are NOT called here —
|
|
38
|
+
cascade already did them. wait_for_rubric_results is OK as a
|
|
39
|
+
read-only observation helper if you need to block on completion.)
|
|
40
|
+
|
|
41
|
+
Step 15 — observe messaging
|
|
42
|
+
get_rows_minimal # confirm passing rows have completed Generate Message cells
|
|
43
|
+
(rare) queue_cells on any pending Generate Message cells
|
|
44
|
+
token-contract spot check via get_rows
|
|
45
|
+
update_campaign(currentStep=awaiting-user-greenlight)
|
|
46
|
+
(generate_messages is NOT an MCP tool; messages come from the cascade)
|
|
47
|
+
|
|
48
|
+
Step 16 — awaiting-user-greenlight
|
|
49
|
+
attach_recommended_sequence({ campaignId }) # tier-aware: premium/SN -> If Open Profile->INMAIL_OPEN, else INVITE->accepted->DM
|
|
50
|
+
re-surface watchUrl + review-batch orientation
|
|
51
|
+
STOP. DO NOT call start_campaign. DO NOT auto-advance currentStep.
|
|
52
|
+
```
|
|
53
|
+
|
|
54
|
+
**Prefer `attach_recommended_sequence` over `attach_sequence` in the
|
|
55
|
+
autonomous tail.** The recommended tool takes only `campaignId` and
|
|
56
|
+
delegates template selection to the backend's tier-aware selector
|
|
57
|
+
(`selectTemplateForTiers`). `attach_sequence` requires you to
|
|
58
|
+
hand-author a `version: 2` template with nodes, branches, and
|
|
59
|
+
entryNodeId — that's error-prone mid-long-context (galley-off UAT
|
|
60
|
+
`20260420T195732Z` failed because Claude hit "Invalid node type" and
|
|
61
|
+
tried to debug via forbidden Bash/Glob calls). Use `attach_sequence`
|
|
62
|
+
only when the caller explicitly needs a custom non-recommended cadence.
|
|
63
|
+
|
|
64
|
+
Hard gates — if you find yourself about to violate any of these, stop
|
|
65
|
+
first:
|
|
66
|
+
|
|
67
|
+
- Do NOT use `update_cell` to write message bodies. The `Generate
|
|
68
|
+
Message` column's http_request writes those cells via the cascade.
|
|
69
|
+
`update_cell` remains reachable for legitimate operator overrides
|
|
70
|
+
AFTER the tail hands off.
|
|
71
|
+
- Do NOT call `check_rubric`, `enrich_with_prospeo`, or
|
|
72
|
+
`bulk_enrich_with_prospeo` in the tail. Those are direct-API
|
|
73
|
+
mutation tools that fetch data to the caller without writing to
|
|
74
|
+
the workflow table cells. Step 13's `queue_cells` already
|
|
75
|
+
triggers the column-level enrichment that populates those cells.
|
|
76
|
+
Running these tools in the tail produces duplicate cost with no
|
|
77
|
+
cell side effect.
|
|
78
|
+
- `wait_for_rubric_results` is read-only and OK to use as an
|
|
79
|
+
observation helper when you need to block on rubric completion —
|
|
80
|
+
it does not mutate cells. Prefer `get_rows_minimal` +
|
|
81
|
+
`wait_for_campaign_table_ready` for the primary cascade-drain
|
|
82
|
+
path, but use `wait_for_rubric_results({ targetCount: cohortSize })`
|
|
83
|
+
when the table-level wait returns before rubric cells finish.
|
|
84
|
+
- `start_campaign` is FORBIDDEN in the autonomous tail. It belongs only
|
|
85
|
+
in the Claude-greenlight path, AFTER the user signals "start". See
|
|
86
|
+
`references/final-handoff-contract.md`.
|
|
87
|
+
- You MAY NOT call `start_campaign` without a prior successful
|
|
88
|
+
`attach_recommended_sequence` (or `attach_sequence` if a custom
|
|
89
|
+
cadence is explicitly required) in the same run.
|
|
90
|
+
- You MAY NOT call `start_campaign` while ANY passing row has an
|
|
91
|
+
empty `Generate Message` cell. If you discover empty message cells in
|
|
92
|
+
the greenlight turn, ABORT greenlight and return to Step 15 first.
|
|
93
|
+
- You MAY NOT call `attach_sequence` while ANY passing row has an
|
|
94
|
+
empty `Generate Message` cell. Before `attach_sequence`, call
|
|
95
|
+
`get_rows_minimal` and confirm every row where `passesRubric=true`
|
|
96
|
+
has a `completed` Generate Message cell with non-empty `result`. If
|
|
97
|
+
any are pending, call `queue_cells` on those generateMessageCellIds
|
|
98
|
+
and wait. If rows truly won't message, ESCALATE.
|
|
99
|
+
- You MAY NOT advance past Step 13 without calling `queue_cells` on
|
|
100
|
+
the review-batch Enrich Prospect cells. Without it, every downstream
|
|
101
|
+
cell stays `pending` and the campaign ships empty.
|
|
102
|
+
- You MAY NOT queue enrichment for rows outside the configured review
|
|
103
|
+
batch before the user approves expansion. Full-list enrichment/message
|
|
104
|
+
generation is a credit-spend decision and must happen after the user
|
|
105
|
+
has reviewed the sample.
|
|
106
|
+
|
|
107
|
+
Load `core/auto-execute.yaml` exactly once at the start of Step 13. All
|
|
108
|
+
subsequent steps read the already-parsed config. Do not re-load mid-run.
|
|
109
|
+
Load each subskill prompt (`create-campaign-v2`, `research-sender`,
|
|
110
|
+
`generate-messages`) at most once per run. A multi-call chunk sequence counts
|
|
111
|
+
as one load. If a tool result already told you to load a prompt, load all
|
|
112
|
+
chunks once and remember; do not restart the same prompt from offset 0 later.
|
|
113
|
+
|
|
114
|
+
After every `update_campaign({ currentStep: ... })` in the tail, narrate
|
|
115
|
+
what changed and orient the user to what the watch link will show next —
|
|
116
|
+
reuse the v1 `create-campaign` watch-mode pattern verbatim.
|
|
117
|
+
|
|
118
|
+
## Step 13: auto-execute-leads
|
|
119
|
+
|
|
120
|
+
Entered on `CampaignOffer.currentStep === "auto-execute-leads"` (set by
|
|
121
|
+
atomic mint).
|
|
122
|
+
|
|
123
|
+
> Reminder: every provider search you run from this point forward — the
|
|
124
|
+
> review-batch source rerun in Step 13, any expansion search after the
|
|
125
|
+
> review batch, alternate-lane probes, account-based reruns, and operator
|
|
126
|
+
> follow-ups — MUST include `campaignOfferId`. This applies to
|
|
127
|
+
> `search_prospeo`, `search_sales_nav`, `search_apollo`,
|
|
128
|
+
> `search_signals`, and any other provider search tool added later.
|
|
129
|
+
> Campaignless searches after mint orphan results from the campaign UI.
|
|
130
|
+
>
|
|
131
|
+
> **Lead-list vs campaign-table identity rule (do not confuse them):**
|
|
132
|
+
> after the first `confirm_lead_list`, two workflow tables exist:
|
|
133
|
+
>
|
|
134
|
+
> - the **lead-list** table (the original `sourceLeadListId` returned by
|
|
135
|
+
> `import_leads`), and
|
|
136
|
+
> - the **campaign workflow table** (`CampaignOffer.workflowTableId`,
|
|
137
|
+
> also returned as `campaignTableId` from `confirm_lead_list`).
|
|
138
|
+
>
|
|
139
|
+
> `confirm_lead_list` overwrites `CampaignOffer.selectedLeadListId` with
|
|
140
|
+
> the campaign-table id. That means after the first confirm, the
|
|
141
|
+
> campaign-table id is NOT a valid `sourceLeadListId` for any future
|
|
142
|
+
> `import_leads` call.
|
|
143
|
+
>
|
|
144
|
+
> When you scale up the import, `mode: "add"` requires the ORIGINAL
|
|
145
|
+
> lead-list id (the one returned by the first `import_leads`), never the
|
|
146
|
+
> campaign-table id. Passing the campaign-table id silently writes
|
|
147
|
+
> straight into the campaign workflow table, bypassing the lead list
|
|
148
|
+
> entirely; the lead list stays at its original size and the
|
|
149
|
+
> campaign-builder `/leads` UI throws "Lead list not found" because it
|
|
150
|
+
> expects `selectedLeadListId` to resolve as a lead list.
|
|
151
|
+
>
|
|
152
|
+
> Capture the original `leadListId` from the FIRST `import_leads`
|
|
153
|
+
> response and reuse it for every scale-up `import_leads(mode: "add")`
|
|
154
|
+
> call on the same campaign. If the original id is unknown, query
|
|
155
|
+
> for the lead-list table by name or skip the `mode: "add"` path; do
|
|
156
|
+
> not pass the campaign-table id as `sourceLeadListId`.
|
|
157
|
+
|
|
158
|
+
1. Load `core/auto-execute.yaml`. Capture `import.importLimit`,
|
|
159
|
+
`sample.sampleSize`, `sample.minProjectedPass`,
|
|
160
|
+
`sample.maxRevisionRounds`, `messaging.tokenContract`,
|
|
161
|
+
`messaging.critique.enabled`, `handoff.autoStart`,
|
|
162
|
+
`handoff.orientation`, `retry.sameToolSameError`,
|
|
163
|
+
`logging.logEveryThresholdTrip`.
|
|
164
|
+
2. Resolve the approved source before importing. If there is no
|
|
165
|
+
`lead-source-intake.json`, keep the existing discovered-provider path:
|
|
166
|
+
`import_leads({ campaignOfferId, targetLeadCount: importLimit })`.
|
|
167
|
+
If a manifest exists, branch by `sourceType`:
|
|
168
|
+
- `supplied-linkedin-profiles`: revalidate file metadata and confirmation
|
|
169
|
+
token, confirm `load_csv_linkedin_leads` only after approval to batch the
|
|
170
|
+
supplied CSV into a Sellable lead list, persist the returned `leadListId`,
|
|
171
|
+
then call `confirm_lead_list({ sourceLeadListId: leadListId,
|
|
172
|
+
targetLeadCount: importLimit })`. Do not call `import_leads` for this
|
|
173
|
+
branch; the lead list is the source, and `confirm_lead_list` imports the
|
|
174
|
+
bounded review batch into the campaign table.
|
|
175
|
+
- `existing-lead-list`: revalidate that the lead list still exists in the
|
|
176
|
+
same workspace, reuse it as `sourceLeadListId`, then call
|
|
177
|
+
`confirm_lead_list({ sourceLeadListId, targetLeadCount: importLimit })`.
|
|
178
|
+
- `supplied-domains`: reuse or confirm `load_csv_domains`, preserving
|
|
179
|
+
`domainFilterId`; run a campaign-associated Prospeo people search with
|
|
180
|
+
`campaignOfferId`, provider prompt preflight, and that `domainFilterId`;
|
|
181
|
+
then import with `targetLeadCount: importLimit`.
|
|
182
|
+
Never exceed `importLimit`. This is a review batch, not a full
|
|
183
|
+
campaign-scale import. If a source search/list contains more rows than
|
|
184
|
+
the cap, import only the first `importLimit` rows and leave the rest for
|
|
185
|
+
a later expansion step.
|
|
186
|
+
Persist or recover materialized IDs on resume: `leadListId`,
|
|
187
|
+
`domainFilterId`, `searchId`, `campaignTableId`, imported row IDs, and queued
|
|
188
|
+
cell IDs. If the source file changed after preview, or an existing lead list
|
|
189
|
+
is missing/wrong-workspace, escalate before materialization instead of
|
|
190
|
+
silently applying stale preview data. Retrying Step 13 must not duplicate
|
|
191
|
+
lead lists, searches, campaign rows, or queued cells.
|
|
192
|
+
3. `wait_for_lead_list_ready` and `confirm_lead_list`.
|
|
193
|
+
4. **Verify the table is set up properly before queuing.** Call
|
|
194
|
+
`get_rows_minimal({ tableId })` and confirm:
|
|
195
|
+
- `Enrich Prospect` column exists on the table and every imported
|
|
196
|
+
row has a corresponding cell with `status === "pending"`.
|
|
197
|
+
- `DNC Check`, `ICP Score`, `Passes Rubric`, and `Generate Message`
|
|
198
|
+
columns all exist too (the cascade depends on all of them).
|
|
199
|
+
- If any are missing, or the cell is in a non-`pending` terminal
|
|
200
|
+
state from an earlier run, ESCALATE with an explicit "table not
|
|
201
|
+
configured for autonomous tail" diagnosis. Do NOT attempt to
|
|
202
|
+
patch the table shape from the tail.
|
|
203
|
+
5. **Kick off the bounded review-batch cascade.** `queue_cells({ tableId,
|
|
204
|
+
cellIds: <pending enrichCellIds for the imported review batch only> })`.
|
|
205
|
+
You queue ONLY the review-batch `Enrich Prospect` cells; the cascade
|
|
206
|
+
auto-dispatches `DNC Check`, `ICP Score`, `Passes Rubric`, and (for rows
|
|
207
|
+
where `Passes Rubric === true`) `Generate Message` as each upstream
|
|
208
|
+
completes. Do not queue cells for any rows beyond `importLimit` before
|
|
209
|
+
user expansion approval.
|
|
210
|
+
6. `wait_for_campaign_table_ready` — waits for the review-batch cascade to
|
|
211
|
+
drain.
|
|
212
|
+
If it times out with most cells still `pending`, ESCALATE: the
|
|
213
|
+
engine is blocked.
|
|
214
|
+
7. If the import returns zero usable leads, ESCALATE per
|
|
215
|
+
`references/escalation-ladder.md` (hard fail).
|
|
216
|
+
8. `update_campaign({ campaignId, currentStep: "validate-sample" })`.
|
|
217
|
+
9. Orient user on the watch link (same pattern as every other step
|
|
218
|
+
change).
|
|
219
|
+
|
|
220
|
+
**Do NOT call `check_rubric`, `wait_for_rubric_results`,
|
|
221
|
+
`enrich_with_prospeo`, or `bulk_enrich_with_prospeo` in Step 13.**
|
|
222
|
+
Those are direct-API tools that fetch enrichment/scoring data to the
|
|
223
|
+
caller; they do not write to the workflow table cells. The cascade
|
|
224
|
+
triggered by `queue_cells` runs the `Enrich Prospect` column's
|
|
225
|
+
http_request, which writes the enrichment into the cell, which
|
|
226
|
+
triggers the next column, and so on. Running the direct-API tools
|
|
227
|
+
is duplicate work with no cell-population side effect.
|
|
228
|
+
|
|
229
|
+
## Step 14: validate-sample (loop)
|
|
230
|
+
|
|
231
|
+
Entered on `CampaignOffer.currentStep === "validate-sample"`. Full
|
|
232
|
+
decision tree lives in `references/sample-validation-loop.md`.
|
|
233
|
+
|
|
234
|
+
**Step 14 is pure observation.** The bounded review-batch cascade that Step
|
|
235
|
+
13 kicked off (via `queue_cells` on Enrich Prospect) has already written ICP
|
|
236
|
+
scores and Passes Rubric values back into the workflow table. Step 14 reads
|
|
237
|
+
those review-batch cell results — it does NOT call `check_rubric`,
|
|
238
|
+
`wait_for_rubric_results`, `bulk_enrich_with_prospeo`, or any other
|
|
239
|
+
direct enrichment/scoring tool. All of that already happened in the
|
|
240
|
+
cascade; running them again is wasted work and can overwrite the
|
|
241
|
+
real cell results with unrelated data.
|
|
242
|
+
|
|
243
|
+
Shape:
|
|
244
|
+
|
|
245
|
+
```text
|
|
246
|
+
get_rows_minimal({ tableId })
|
|
247
|
+
passInSample = count of first sampleSize review-batch rows with passesRubric === true
|
|
248
|
+
projectedPass = round(passInSample / sampleSize * importLimit)
|
|
249
|
+
|
|
250
|
+
if projectedPass >= minProjectedPass:
|
|
251
|
+
advance to Step 15
|
|
252
|
+
else:
|
|
253
|
+
diagnose brief-vs-list per sample-validation-loop.md
|
|
254
|
+
- brief: autonomous update_campaign_brief, then re-kick cascade
|
|
255
|
+
(queue_cells on enrichCellIds) and wait for the new
|
|
256
|
+
results
|
|
257
|
+
- list: ESCALATE (no auto-revision of leads)
|
|
258
|
+
- unknown: ESCALATE
|
|
259
|
+
revisionRound += 1
|
|
260
|
+
if revisionRound > maxRevisionRounds:
|
|
261
|
+
ESCALATE
|
|
262
|
+
```
|
|
263
|
+
|
|
264
|
+
Persisted counter rule: `revisionRound` persists across skill resume.
|
|
265
|
+
Do NOT reset to 0 on resume — a stale resume must still respect the
|
|
266
|
+
`maxRevisionRounds` cap.
|
|
267
|
+
|
|
268
|
+
A "list problem" diagnosis NEVER triggers autonomous
|
|
269
|
+
`update_campaign_brief`. Escalate instead.
|
|
270
|
+
|
|
271
|
+
When the sample passes the projected-pass floor, call
|
|
272
|
+
`update_campaign({ campaignId, currentStep: "auto-execute-messaging" })`
|
|
273
|
+
and orient the user that messaging will complete for the review batch only.
|
|
274
|
+
|
|
275
|
+
## Step 15: auto-execute-messaging
|
|
276
|
+
|
|
277
|
+
Entered on `CampaignOffer.currentStep === "auto-execute-messaging"`.
|
|
278
|
+
|
|
279
|
+
**Messages are produced by the `Generate Message` column cascade, not
|
|
280
|
+
by a separate tool.** Do NOT call a `generate_messages` MCP tool —
|
|
281
|
+
that tool does not exist; `generate-messages` is a subskill prompt
|
|
282
|
+
loaded during live messaging drafts. In the autonomous tail, messages
|
|
283
|
+
flow through the column pipeline: `Enrich Prospect` →
|
|
284
|
+
`DNC Check` → `ICP Score` → `Passes Rubric` → `Generate Message`.
|
|
285
|
+
Each column's http_request auto-fires when its upstream dependency
|
|
286
|
+
completes AND passes gate (e.g. `Passes Rubric === true` before
|
|
287
|
+
`Generate Message`). Your job in Step 15 is to WAIT for the review-batch
|
|
288
|
+
cascade to reach `Generate Message` for rows that passed ICP, and verify
|
|
289
|
+
the output — not to generate messages manually.
|
|
290
|
+
|
|
291
|
+
**What Step 15 does:**
|
|
292
|
+
|
|
293
|
+
1. `get_rows_minimal` to read `passesRubric` + `generateMessageCellId`
|
|
294
|
+
- the Generate Message cell's `status` and `result` per row.
|
|
295
|
+
Before queueing or waiting on Generate Message cells, confirm the
|
|
296
|
+
minted campaign brief still contains `{{...}}` in `## Approved Message
|
|
297
|
+
Template`. If it does not, fail before the cascade runs. Do not repair
|
|
298
|
+
after mint; the template must be present during mint so Step 15 never
|
|
299
|
+
starts in freeform generation mode.
|
|
300
|
+
2. For every row where `passesRubric === true`:
|
|
301
|
+
- The Generate Message cell should be `running` or `completed`
|
|
302
|
+
already (cascade auto-fired it). If it is still `pending`, queue
|
|
303
|
+
it explicitly: `queue_cells({ tableId, cellIds:
|
|
304
|
+
<generateMessageCellIds> })`.
|
|
305
|
+
3. `wait_for_campaign_table_ready` (or poll) until every passing
|
|
306
|
+
row's Generate Message cell is `completed`.
|
|
307
|
+
4. Read the results back via `get_rows` (full) and sanity-check a
|
|
308
|
+
sample against the Phase 84 token contract: no unresolved
|
|
309
|
+
`{{tokens}}`, no invented proof, one sentence per line, etc.
|
|
310
|
+
5. If the sample fails the token contract, diagnose brief-vs-list
|
|
311
|
+
(same revision loop as Step 14) and escalate if over
|
|
312
|
+
`maxRevisionRounds`.
|
|
313
|
+
6. On success, `update_campaign({ campaignId, currentStep:
|
|
314
|
+
"awaiting-user-greenlight" })`.
|
|
315
|
+
|
|
316
|
+
**Do NOT hand-write message bodies via `update_cell`.** `update_cell`
|
|
317
|
+
is reachable for legitimate operator overrides AFTER the tail hands
|
|
318
|
+
off, but during autonomous tail the cascade owns the writes. If you
|
|
319
|
+
find yourself drafting a message body and about to push it through
|
|
320
|
+
`update_cell`, you have lost state — stop, read the cells, and let
|
|
321
|
+
the pipeline finish.
|
|
322
|
+
|
|
323
|
+
**What `attach_sequence` does (Step 16, not here):** binds the
|
|
324
|
+
DM/InMail cadence template to the workflow table. Completely different
|
|
325
|
+
job from messaging. Does NOT generate or write message text. A
|
|
326
|
+
sequence with no `Generate Message` outputs would try to send empty
|
|
327
|
+
strings, which is why Step 16 requires Step 15 to be complete.
|
|
328
|
+
|
|
329
|
+
1. Observe the review-batch messages on the same sample that passed
|
|
330
|
+
validation.
|
|
331
|
+
2. If `messaging.critique.enabled` is true (Plan 85-03), run the
|
|
332
|
+
bounded critique pass on the sample output per
|
|
333
|
+
`references/parallel-critique-protocol.md`. The pass runs on at
|
|
334
|
+
most `messaging.critique.sampleSize` rows, sends each row through
|
|
335
|
+
three parallel critics (targeting / copy / voice) that return
|
|
336
|
+
structured JSON, and merges the voices in a synthesis step that
|
|
337
|
+
enforces the Phase 84 token contract verbatim and re-runs the
|
|
338
|
+
finalizer pass. Any rewrite that invents proof or uses an
|
|
339
|
+
unsupported token falls back to the plain generated message. A
|
|
340
|
+
trip of `messaging.critique.budgetUsdCap` HALTS critique for the
|
|
341
|
+
remaining sample and continues the plain tail. Plan 85-02 reads
|
|
342
|
+
the flag but never flips it; critique stays off until the flag is
|
|
343
|
+
deliberately enabled.
|
|
344
|
+
3. If `messaging.critique.opus.enabled` is also true, route the
|
|
345
|
+
highest-value subset through the Opus / craft-message path per
|
|
346
|
+
`references/thomas-variant-selection.md`. Opus is capped by
|
|
347
|
+
`messaging.critique.opus.maxMessagesPerPass` and
|
|
348
|
+
`messaging.critique.opus.budgetUsdCap`; tripping either cap keeps
|
|
349
|
+
the remaining rows on the non-Opus synthesizer. Opus is bound by
|
|
350
|
+
the same token contract as every other rewrite path.
|
|
351
|
+
4. Check `messaging.tokenContract`. When `strict`, reject any sample
|
|
352
|
+
message that contains unresolved or unsupported tokens (including
|
|
353
|
+
any critique rewrite that tried to introduce one).
|
|
354
|
+
5. If the sample passes the token contract (and critique when enabled),
|
|
355
|
+
stop at the review-batch handoff. Do NOT scale to the full source list
|
|
356
|
+
before explicit user expansion approval.
|
|
357
|
+
6. If the sample fails the token contract or critique, diagnose +
|
|
358
|
+
loop the same way Step 14 does (brief-vs-list), subject to the same
|
|
359
|
+
`maxRevisionRounds` cap.
|
|
360
|
+
7. On success, `update_campaign({ campaignId, currentStep:
|
|
361
|
+
"awaiting-user-greenlight" })` and orient the user.
|
|
362
|
+
|
|
363
|
+
Critique failure modes NEVER escalate. A critic timeout, a total
|
|
364
|
+
timeout, a budget trip, a fake-proof rejection, or an unsupported-
|
|
365
|
+
token rejection all fall back to the plain generated message for that
|
|
366
|
+
row. Step 15 does not stall on critique.
|
|
367
|
+
|
|
368
|
+
## Step 16: awaiting-user-greenlight
|
|
369
|
+
|
|
370
|
+
Entered on `CampaignOffer.currentStep === "awaiting-user-greenlight"`.
|
|
371
|
+
Full contract lives in `references/final-handoff-contract.md`.
|
|
372
|
+
|
|
373
|
+
Shape:
|
|
374
|
+
|
|
375
|
+
1. `attach_sequence` (bind the committed sequence to the campaign).
|
|
376
|
+
2. Re-surface the `watchUrl` using `references/watch-link-handoff.md`.
|
|
377
|
+
3. Surface the `handoff.orientation` string from `auto-execute.yaml`
|
|
378
|
+
alongside the link.
|
|
379
|
+
4. STOP. Do NOT call `start_campaign`. Do NOT auto-advance
|
|
380
|
+
`currentStep`. The autonomous tail ends here.
|
|
381
|
+
5. Make the credit boundary explicit in the handoff: only the first review
|
|
382
|
+
batch has been enriched/messaged; expanding to more leads requires a
|
|
383
|
+
separate user instruction.
|
|
384
|
+
|
|
385
|
+
Two equally valid greenlight channels take Step 16 into a running
|
|
386
|
+
campaign:
|
|
387
|
+
|
|
388
|
+
- **UI path** — user clicks "Approve all" and "Start Campaign" in the
|
|
389
|
+
watch-link UI. The skill stays idle. On the next resume, it observes
|
|
390
|
+
the running state and shifts to a "campaign is live" confirmation.
|
|
391
|
+
- **Claude greenlight path** — user replies with an affirmative ("yeah
|
|
392
|
+
start" / "looks good, start" / "ship it"). The skill then performs,
|
|
393
|
+
in order: (a) bulk-approve queued messages via the EXISTING endpoint
|
|
394
|
+
`POST /api/v3/workflow-tables/cells/approve-batch` (do NOT invent a
|
|
395
|
+
new tool/endpoint), (b) `start_campaign({ campaignId })`, (c)
|
|
396
|
+
`update_campaign({ campaignId, currentStep: "running" })`, (d)
|
|
397
|
+
re-surface `watchUrl` plus a "campaign is live" confirmation.
|
|
398
|
+
|
|
399
|
+
A Claude greenlight on an already-running campaign is a no-op
|
|
400
|
+
confirmation, NOT a duplicate start. Detect via `get_campaign` before
|
|
401
|
+
calling any mutating tool; if the campaign is already running, skip
|
|
402
|
+
approve-batch / start_campaign / update_campaign and just confirm.
|
|
403
|
+
|
|
404
|
+
Workspace/sender mismatch at greenlight time aborts the start with the
|
|
405
|
+
same invariant Phase 84 enforces.
|
|
406
|
+
|
|
407
|
+
## Threshold Trips and Logging
|
|
408
|
+
|
|
409
|
+
When `logging.logEveryThresholdTrip === true` (default), log every
|
|
410
|
+
threshold trip: `projectedPass` computation, retry cap, revision cap,
|
|
411
|
+
hard-fail. These logs drive `auto-execute.yaml` tuning after 3-5 real
|
|
412
|
+
runs.
|
|
413
|
+
|
|
414
|
+
## Tail Hard Rules
|
|
415
|
+
|
|
416
|
+
- Review-batch sourcing/import happens in Step 13, not during atomic mint.
|
|
417
|
+
- Full-list expansion is not part of the default tail. It requires user
|
|
418
|
+
approval after the review batch.
|
|
419
|
+
Import must never exceed `importLimit`.
|
|
420
|
+
- The tail NEVER calls `start_campaign` on its own.
|
|
421
|
+
- The tail NEVER auto-revises leads. Brief revision is autonomous; lead
|
|
422
|
+
revision is always operator-gated.
|
|
423
|
+
- `revisionRound` persists across resume — no silent reset.
|
|
424
|
+
- The bulk-approve action in the Claude greenlight path uses the
|
|
425
|
+
existing `POST /api/v3/workflow-tables/cells/approve-batch` endpoint
|
|
426
|
+
— do NOT create a new tool or endpoint for this.
|
|
427
|
+
- Threshold trips are logged for calibration.
|
|
428
|
+
|
|
429
|
+
</autonomous_tail>
|
|
430
|
+
|
|
431
|
+
<references_index>
|
|
432
|
+
|
|
433
|
+
| File | Load when |
|
|
434
|
+
| ------------------------------------------ | ---------------------------------------------------------------------- |
|
|
435
|
+
| `references/approval-gate-framing.md` | Phase 85 commit gate, before showing the approval packet |
|
|
436
|
+
| `references/watch-link-handoff.md` | Atomic mint + every tail resume, before surfacing watchUrl |
|
|
437
|
+
| `references/sample-validation-loop.md` | Step 14, before enriching + scoring the sample |
|
|
438
|
+
| `references/escalation-ladder.md` | Any tail step that needs to decide retry / revise / escalate |
|
|
439
|
+
| `references/final-handoff-contract.md` | Step 16, and every Claude greenlight turn |
|
|
440
|
+
| `references/parallel-critique-protocol.md` | Step 15 when `messaging.critique.enabled` is true |
|
|
441
|
+
| `references/thomas-variant-selection.md` | Step 15 when `messaging.critique.opus.enabled` is true |
|
|
442
|
+
| `references/thomas-revision-filters.md` | Any critique rewrite, before persisting |
|
|
443
|
+
| `core/auto-execute.yaml` | Start of Step 13, load once; all tail steps read parsed values |
|
|
444
|
+
| `core/auto-execute.README.md` | When tuning `auto-execute.yaml` knobs or reviewing threshold-trip logs |
|
|
445
|
+
|
|
446
|
+
</references_index>
|