@sellable/install 0.1.388 → 0.1.390-uat.refill-zero-shot.20260721230009
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/container/Dockerfile +6 -6
- package/container/README.md +2 -2
- package/lib/sellable-agent/external-runtime-builder.mjs +1 -1
- package/lib/sellable-agent/host-bootstrap.mjs +2 -2
- package/lib/sellable-agent/host-worker.mjs +2 -2
- package/lib/sellable-agent/profile-materializer.mjs +2 -2
- package/lib/sellable-agent/provisioning-adapter.mjs +5 -5
- package/package.json +1 -1
- package/skill-templates/refill-sends.md +423 -635
package/container/Dockerfile
CHANGED
|
@@ -3,13 +3,13 @@ FROM ${HERMES_IMAGE}
|
|
|
3
3
|
|
|
4
4
|
USER root
|
|
5
5
|
|
|
6
|
-
ARG INSTALLER_PACKAGE=@sellable/install@0.1.
|
|
7
|
-
ARG MCP_PACKAGE=@sellable/mcp@0.1.
|
|
6
|
+
ARG INSTALLER_PACKAGE=@sellable/install@0.1.389
|
|
7
|
+
ARG MCP_PACKAGE=@sellable/mcp@0.1.644
|
|
8
8
|
ARG INSTALLER_INTEGRITY
|
|
9
9
|
ARG MCP_INTEGRITY=sha512-b7N9CnZXBoO1JPc6YeM3S96FnsJQI8BjMtb1Vp0moKkSy3YQ/jd9VQDwFxUGgC7nXx0BNlvNJH9QocmarC52yA==
|
|
10
10
|
|
|
11
|
-
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.
|
|
12
|
-
&& test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.
|
|
11
|
+
RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.389" \
|
|
12
|
+
&& test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.644" \
|
|
13
13
|
&& test -n "${INSTALLER_INTEGRITY}" \
|
|
14
14
|
&& test "$(npm view "${INSTALLER_PACKAGE}" dist.integrity)" = "${INSTALLER_INTEGRITY}" \
|
|
15
15
|
&& test "$(npm view "${MCP_PACKAGE}" dist.integrity)" = "${MCP_INTEGRITY}" \
|
|
@@ -23,9 +23,9 @@ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.388" \
|
|
|
23
23
|
&& mkdir -p /opt/sellable-agent/install-root /opt/sellable-agent/mcp-root \
|
|
24
24
|
&& npm install --prefix /opt/sellable-agent/install-root --omit=dev --ignore-scripts --no-audit --no-fund "${INSTALLER_PACKAGE}" \
|
|
25
25
|
&& npm install --prefix /opt/sellable-agent/mcp-root --omit=dev --ignore-scripts --no-audit --no-fund "${MCP_PACKAGE}" \
|
|
26
|
-
&& node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.
|
|
26
|
+
&& node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.389' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.644') throw new Error('package identity rejected');" \
|
|
27
27
|
&& node --input-type=module -e "import { buildExternalRuntimeClosure } from '/opt/sellable-agent/install-root/node_modules/@sellable/install/lib/sellable-agent/external-runtime-builder.mjs'; const built = buildExternalRuntimeClosure({ mcpPackageRoot: '/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp', mcpNodeModulesRoot: '/opt/sellable-agent/mcp-root/node_modules', hermesSourceRoot: '/opt/hermes', ownerUid: 0, ownerGid: 0 }); if (!built.closureDigest) throw new Error('runtime closure rejected');" \
|
|
28
|
-
&& node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.
|
|
28
|
+
&& node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.389', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.644', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
|
|
29
29
|
&& npm cache clean --force
|
|
30
30
|
|
|
31
31
|
COPY entrypoint.sh /usr/local/bin/sellable-agent-container-entrypoint
|
package/container/README.md
CHANGED
|
@@ -2,12 +2,12 @@
|
|
|
2
2
|
|
|
3
3
|
This image is a dedicated Agent worker/runtime boundary beside an existing Hermes dashboard. It never replaces or reconfigures the dashboard container.
|
|
4
4
|
|
|
5
|
-
Build from this directory after `@sellable/install@0.1.
|
|
5
|
+
Build from this directory after `@sellable/install@0.1.389` is published:
|
|
6
6
|
|
|
7
7
|
```sh
|
|
8
8
|
docker build --platform linux/amd64 \
|
|
9
9
|
--build-arg INSTALLER_INTEGRITY='sha512-…' \
|
|
10
|
-
--tag sellable-agent-host:0.1.
|
|
10
|
+
--tag sellable-agent-host:0.1.389 .
|
|
11
11
|
```
|
|
12
12
|
|
|
13
13
|
Create a private `bootstrap/` directory owned by root with mode `0700`. It must contain `bootstrap.json`, `provider-auth`, and `slack-app`, each owned by root with mode `0600`. `bootstrap.json` contains only non-secret identities, hashes, package integrities, and the control-plane URL. The other two files contain the provider JSON and global Slack app token whose hashes are pinned by `bootstrap.json`.
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
|
|
29
29
|
const MANIFEST_VERSION = "sellable-agent-external-runtime-closure/v1";
|
|
30
30
|
const MCP_PACKAGE = "@sellable/mcp";
|
|
31
|
-
const MCP_VERSION = "0.1.
|
|
31
|
+
const MCP_VERSION = "0.1.644";
|
|
32
32
|
const SAFE_COMPONENT = /^[A-Za-z0-9@._+-]+$/;
|
|
33
33
|
const HERMES_EXCLUDES = new Set([".git", ".playwright", "node_modules"]);
|
|
34
34
|
|
|
@@ -38,8 +38,8 @@ import { installAgentHostServices } from "./service-installer.mjs";
|
|
|
38
38
|
const PACKAGE_ROOT = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
39
39
|
const VERSION = "sellable-agent-host-bootstrap/v1";
|
|
40
40
|
const RECEIPT_VERSION = "sellable-agent-host-registration/v1";
|
|
41
|
-
const INSTALLER_PACKAGE = "@sellable/install@0.1.
|
|
42
|
-
const MCP_PACKAGE = "@sellable/mcp@0.1.
|
|
41
|
+
const INSTALLER_PACKAGE = "@sellable/install@0.1.389";
|
|
42
|
+
const MCP_PACKAGE = "@sellable/mcp@0.1.644";
|
|
43
43
|
const HERMES_VERSION = "0.18.0";
|
|
44
44
|
const SHA256 = /^[a-f0-9]{64}$/;
|
|
45
45
|
const SHA256_16 = /^[a-f0-9]{16}$/;
|
|
@@ -1058,8 +1058,8 @@ export function compileClaimToProfileDesired(activeClaim, config) {
|
|
|
1058
1058
|
]) ||
|
|
1059
1059
|
!exactObject(runtimeService, ["principalId", "policyHash", "credentialGeneration", "credentialFingerprint"]) ||
|
|
1060
1060
|
pinned.hermesCli !== "hermes" || pinned.hermesVersion !== "0.18.0" ||
|
|
1061
|
-
pinned.installerPackage !== "@sellable/install@0.1.
|
|
1062
|
-
pinned.mcpPackage !== "@sellable/mcp@0.1.
|
|
1061
|
+
pinned.installerPackage !== "@sellable/install@0.1.389" ||
|
|
1062
|
+
pinned.mcpPackage !== "@sellable/mcp@0.1.644" ||
|
|
1063
1063
|
!Array.isArray(policy.toolInclude) || policy.toolInclude.length === 0 ||
|
|
1064
1064
|
!Number.isSafeInteger(slack.generation) || slack.generation < 1 ||
|
|
1065
1065
|
!/^A[A-Z0-9]{8,20}$/.test(slack.appId ?? "") ||
|
|
@@ -131,8 +131,8 @@ function validateDesired(desired) {
|
|
|
131
131
|
!Number.isSafeInteger(desired.slackGeneration) || desired.slackGeneration < 1 ||
|
|
132
132
|
!Number.isSafeInteger(desired.serviceCredentialGeneration) || desired.serviceCredentialGeneration < 1 ||
|
|
133
133
|
desired.hermesCli !== "hermes" || desired.hermesVersion !== "0.18.0" ||
|
|
134
|
-
desired.installerPackage !== "@sellable/install@0.1.
|
|
135
|
-
desired.mcpPackage !== "@sellable/mcp@0.1.
|
|
134
|
+
desired.installerPackage !== "@sellable/install@0.1.389" ||
|
|
135
|
+
desired.mcpPackage !== "@sellable/mcp@0.1.644" ||
|
|
136
136
|
!Array.isArray(desired.toolInclude) || desired.toolInclude.length === 0 ||
|
|
137
137
|
desired.toolInclude.some((tool) => !/^[a-z][a-z0-9_]{0,127}$/.test(tool)) ||
|
|
138
138
|
new Set(desired.toolInclude).size !== desired.toolInclude.length ||
|
|
@@ -19,8 +19,8 @@ import { fileURLToPath } from "node:url";
|
|
|
19
19
|
import { deriveContainedProfileId } from "./profile-materializer.mjs";
|
|
20
20
|
|
|
21
21
|
export const PROVISIONING_ACTION = "PROVISION_HERMES_PROFILE";
|
|
22
|
-
export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.
|
|
23
|
-
export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.
|
|
22
|
+
export const PINNED_INSTALL_PACKAGE = "@sellable/install@0.1.389";
|
|
23
|
+
export const PINNED_MCP_PACKAGE = "@sellable/mcp@0.1.644";
|
|
24
24
|
|
|
25
25
|
export function deriveAgentProfileId(workspaceId, agentId, hostId) {
|
|
26
26
|
return deriveContainedProfileId(workspaceId, agentId, hostId);
|
|
@@ -576,7 +576,7 @@ function inspectProvisionedProfile({
|
|
|
576
576
|
!matchesReadback(manifest, request) ||
|
|
577
577
|
manifest.serviceCredentialReference !== serviceCredentialReference ||
|
|
578
578
|
manifest.installerPackage !== PINNED_INSTALL_PACKAGE ||
|
|
579
|
-
manifest.installerVersion !== "0.1.
|
|
579
|
+
manifest.installerVersion !== "0.1.389" ||
|
|
580
580
|
manifest.mcpPackage !== PINNED_MCP_PACKAGE ||
|
|
581
581
|
manifest.credentialKeyVersion !== credentialKeyVersion ||
|
|
582
582
|
!/^[a-f0-9]{16}$/.test(manifest.credentialFingerprint) ||
|
|
@@ -781,7 +781,7 @@ function successReceipt({
|
|
|
781
781
|
subject: `agent-profile:${observed.profileId}`,
|
|
782
782
|
cli: {
|
|
783
783
|
installPackage: PINNED_INSTALL_PACKAGE,
|
|
784
|
-
installVersion: "0.1.
|
|
784
|
+
installVersion: "0.1.389",
|
|
785
785
|
mcpPackage: PINNED_MCP_PACKAGE,
|
|
786
786
|
command: "hermes profile bootstrap",
|
|
787
787
|
},
|
|
@@ -821,7 +821,7 @@ function failure(code, stages, extra = {}, request = null) {
|
|
|
821
821
|
subject: safeIdentity ? `agent-profile:${safeIdentity}` : "agent-profile:unbound",
|
|
822
822
|
cli: {
|
|
823
823
|
installPackage: PINNED_INSTALL_PACKAGE,
|
|
824
|
-
installVersion: "0.1.
|
|
824
|
+
installVersion: "0.1.389",
|
|
825
825
|
mcpPackage: PINNED_MCP_PACKAGE,
|
|
826
826
|
command: "hermes profile bootstrap",
|
|
827
827
|
},
|
package/package.json
CHANGED
|
@@ -1,676 +1,464 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: refill-sends
|
|
3
|
-
description:
|
|
3
|
+
description: Refill projected sends across a workspace or selected senders through the canonical fenced coordinator.
|
|
4
4
|
visibility: public
|
|
5
5
|
allowed-tools:
|
|
6
6
|
- mcp__sellable__refill_sends
|
|
7
7
|
- mcp__sellable__get_refill_target_plan
|
|
8
8
|
- mcp__sellable__get_scheduler_fill_capacity
|
|
9
|
-
- mcp__sellable__run_scheduler_sweep
|
|
10
|
-
- mcp__sellable__refresh_paid_inmail_credits
|
|
11
9
|
- mcp__sellable__get_subskill_asset
|
|
12
10
|
- mcp__sellable__get_auth_status
|
|
13
11
|
- mcp__sellable__start_cli_login
|
|
14
12
|
- mcp__sellable__wait_for_cli_login
|
|
15
13
|
- mcp__sellable__get_active_workspace
|
|
16
14
|
- mcp__sellable__list_workspaces
|
|
17
|
-
- mcp__sellable__set_active_workspace
|
|
18
15
|
- mcp__sellable__get_subskill_prompt
|
|
19
16
|
- mcp__sellable__search_subskill_prompts
|
|
20
17
|
- mcp__sellable__list_senders
|
|
21
|
-
- mcp__sellable__get_sender_routing
|
|
22
|
-
- mcp__sellable__get_campaign_waterfall
|
|
23
|
-
- mcp__sellable__resolve_campaign_fill_route
|
|
24
|
-
- mcp__sellable__get_campaign_refill_state
|
|
25
|
-
- mcp__sellable__fill_campaign_horizon
|
|
26
|
-
- mcp__sellable__get_campaign
|
|
27
|
-
- mcp__sellable__get_campaign_context
|
|
28
|
-
- mcp__sellable__update_campaign
|
|
29
|
-
- mcp__sellable__get_provider_prompt
|
|
30
|
-
- mcp__sellable__get_campaign_message_preparation_status
|
|
31
|
-
- mcp__sellable__start_campaign_message_preparation
|
|
32
|
-
- mcp__sellable__start_campaign
|
|
33
|
-
- mcp__sellable__cancel_campaign_message_preparation
|
|
34
|
-
- mcp__sellable__import_leads
|
|
35
|
-
- mcp__sellable__wait_for_lead_list_ready
|
|
36
|
-
- mcp__sellable__confirm_lead_list
|
|
37
|
-
- mcp__sellable__search_signals
|
|
38
|
-
- mcp__sellable__select_promising_posts
|
|
39
|
-
- mcp__sellable__fetch_post_engagers
|
|
40
|
-
- mcp__sellable__search_sales_nav
|
|
41
|
-
- mcp__sellable__lookup_sales_nav_filter
|
|
42
|
-
- mcp__sellable__search_prospeo
|
|
43
|
-
- mcp__sellable__search_prospeo_companies
|
|
44
|
-
- mcp__sellable__confirm_prospeo_company_accounts
|
|
45
|
-
- mcp__sellable__load_csv_linkedin_leads
|
|
46
|
-
- mcp__sellable__load_csv_domains
|
|
47
|
-
- mcp__sellable__list_dnc_entries
|
|
48
|
-
- mcp__sellable__load_csv_dnc_entries
|
|
49
|
-
- mcp__sellable__get_rows
|
|
50
|
-
- mcp__sellable__get_rows_minimal
|
|
51
|
-
- mcp__sellable__get_table_rows
|
|
52
|
-
- mcp__sellable__get_campaign_table_schema
|
|
53
18
|
- mcp__sellable__select_campaign_cells
|
|
54
|
-
- mcp__sellable__queue_campaign_cells
|
|
55
|
-
- mcp__sellable__wait_for_campaign_processing
|
|
56
|
-
- mcp__sellable__get_sender
|
|
57
19
|
---
|
|
58
20
|
|
|
59
21
|
# Refill Sends
|
|
60
22
|
|
|
61
|
-
Use this
|
|
62
|
-
|
|
23
|
+
Use this skill for “fill”, “refill sends”, “max out sends”, “load everyone up”,
|
|
24
|
+
or “fill horizon sends”. `refill_sends` is the only execution owner. The host
|
|
25
|
+
resolves the request, invokes the coordinator, and follows its exact
|
|
26
|
+
continuation; it never reconstructs the refill ladder from low-level tools.
|
|
63
27
|
|
|
64
|
-
Host
|
|
28
|
+
Host commands:
|
|
65
29
|
|
|
66
30
|
- Claude Code: `/sellable:refill-sends`
|
|
67
31
|
- Codex: `$sellable:refill-sends`
|
|
68
32
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
senders.
|
|
78
|
-
- `--until <YYYY-MM-DD>`: fill through that sender-local date, inclusive,
|
|
79
|
-
instead of the default scheduler-forward 48-hour target window.
|
|
80
|
-
- `--target-date <YYYY-MM-DD>`: fill only that sender-local date. This means
|
|
81
|
-
prepare rows for scheduler-fillable slots on that date, not every remaining
|
|
82
|
-
daily-limit slot and not an inclusive through-date.
|
|
83
|
-
- `senderIds: <id>, <id>` or `senderNames: <name>, <name>`: explicit selector
|
|
84
|
-
alternatives when the host preserves natural-language arguments better than
|
|
85
|
-
shell-style flags.
|
|
86
|
-
- `actionTypes: send_invite, send_inmail_closed`: optional lane selector. Omit
|
|
87
|
-
`actionTypes` to preserve the target planner's lane inference.
|
|
88
|
-
- `untilDate: YYYY-MM-DD`: explicit date selector alternative when the host
|
|
89
|
-
preserves natural-language arguments better than shell-style flags.
|
|
90
|
-
- `targetDate: YYYY-MM-DD`: exact-date selector alternative when the host
|
|
91
|
-
preserves natural-language arguments better than shell-style flags.
|
|
92
|
-
|
|
93
|
-
When the host can call typed MCP tools, start with:
|
|
33
|
+
## Entry and exact scope
|
|
34
|
+
|
|
35
|
+
Accepted request fields are `--yolo`, `workspaceId`, `--sender`/`senderIds`/
|
|
36
|
+
`senderNames`, `actionTypes`, `--until`/`untilDate`, `--target-date`/
|
|
37
|
+
`targetDate`, and compatibility `horizonSendDays`. Omitted dates mean the
|
|
38
|
+
scheduler-forward 48-hour window. `untilDate` is sender-local and inclusive;
|
|
39
|
+
`targetDate` is one sender-local scheduler-fillable date. Skip no-send days and
|
|
40
|
+
never extend beyond the requested date. Finish the full D1 reread before D2.
|
|
94
41
|
|
|
95
42
|
```text
|
|
96
43
|
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 })
|
|
97
44
|
```
|
|
98
45
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
`targetConfig` and the server-issued `runHandle` are separate values and must
|
|
123
|
-
survive each continuation unchanged except for a server-issued takeover fence.
|
|
124
|
-
Only a literal `messageTemplateRevision` with `source:"user_approved"`, the
|
|
125
|
-
approved `templateMarkdown`, prior/new authority digests, 1–500 exact cohort
|
|
126
|
-
row IDs, and request/effect/revision-operation IDs can authorize
|
|
127
|
-
`revise_message_template_and_rerun`. No envelope means no revision. A
|
|
128
|
-
`forceRerun:true` queue call cannot infer or authorize template mutation.
|
|
129
|
-
When the user supplied a complete revision except for the current prior digest,
|
|
130
|
-
acquire the exact run first and reread with its unchanged `targetConfig` and
|
|
131
|
-
`runHandle`. Use only `reportingContext.source.templateAuthorityDigest` from
|
|
132
|
-
that fenced reread as `priorTemplateAuthorityDigest`, then immediately continue
|
|
133
|
-
the same run with the complete literal revision—even when the ordinary refill
|
|
134
|
-
queue is empty or the sender is otherwise ineligible. An empty ordinary queue
|
|
135
|
-
must not suppress an explicit revision. If the fenced reread returns no digest,
|
|
136
|
-
stop with `template_authority_missing` or the returned
|
|
137
|
-
`templateAuthorityBlocker`; never guess the digest.
|
|
138
|
-
After the revision receipt is committed and dispatched, the next mutation must
|
|
139
|
-
be the planner-provided revision-derived `approve_messages` action. Preserve
|
|
140
|
-
its exact `rowSelector:{type:"rowIds",rowIds:[...]}` cohort,
|
|
141
|
-
`approvalMode:"approve"`, compiled readiness authority, and
|
|
142
|
-
`reportingContext`, even if the ordinary sender lane is ineligible. Never use
|
|
143
|
-
`queue_campaign_cells` to flip Approved cells: that path cannot produce the
|
|
144
|
-
authoritative approval/lane-scope receipt. Require the bounded preparation
|
|
145
|
-
receipt's `laneScope` before considering the selected first-touch lane
|
|
146
|
-
approved. Zero sender capacity remains a scheduler blocker and never authorizes
|
|
147
|
-
reconnection, campaign start, or direct send.
|
|
148
|
-
|
|
149
|
-
Before that approval mutation, perform an exact authoritative copy audit with
|
|
150
|
-
`select_campaign_cells({ columnRole:"generateMessage",
|
|
151
|
-
rowSelector:{type:"rowIds",rowIds:[...]},
|
|
152
|
-
includeGeneratedMessageReview:true })` over the complete revision cohort in
|
|
153
|
-
batches of at most 20. Require every
|
|
154
|
-
`authoritativeGeneratedMessageReview.complete:true`, every row status to be
|
|
155
|
-
completed-like, the exact user-approved rendered subject/body, the expected
|
|
156
|
-
`templateAuthorityDigest`, and `matchesCurrentTemplateAuthority:true`. This
|
|
157
|
-
projection is the only MCP copy-review source of truth. Never use formula
|
|
158
|
-
Message/Subject columns, campaign message previews, table previews, or CSV
|
|
159
|
-
exports to validate revised Generate Message results. If any row is missing,
|
|
160
|
-
non-current, or text/digest mismatched, stop before approval and report
|
|
161
|
-
`regenerated_copy_integrity_failed`; never regenerate again implicitly or
|
|
162
|
-
approve the cohort on counts/revision hashes alone.
|
|
163
|
-
|
|
164
|
-
Safe primitives are paid-credit refresh,
|
|
165
|
-
existing-row message preparation, generated-message approval, receipt-proven
|
|
166
|
-
same-source row copy, one bounded Signal Discovery search derived only from
|
|
167
|
-
the campaign's already-approved positive ICP criteria when no reusable source
|
|
168
|
-
rows or tab keywords remain, one bounded SAME-provider-source add for a cold
|
|
169
|
-
campaign whose eligible/prepared rows are receipt-proven exhausted (Sales Nav
|
|
170
|
-
or Prospeo, same saved search — never a source-family switch), the start of a
|
|
171
|
-
route-resolver start-eligible PAUSED campaign-backed sequence campaign selected
|
|
172
|
-
by the packet, a request-scoped exact-date product scheduler sweep, and
|
|
173
|
-
read-only wait rereads. Each source add and campaign start is one bounded
|
|
174
|
-
primitive followed by a full replan.
|
|
175
|
-
Approved-criteria keywords and the attempt/exhaustion marker are scoped to the
|
|
176
|
-
current fenced refill run: archive temporary derived search tabs after the
|
|
177
|
-
source import is materialized, keep normal source-list/import receipts, and
|
|
178
|
-
retry from the campaign's current approved rubric in every new refill run.
|
|
179
|
-
Same-source copy, same-provider-source add, and source
|
|
180
|
-
fallback are safe only after receipt-proven exhaustion:
|
|
181
|
-
`hasMoreFrontierRows:false`, zero `approvalCandidates`, no `stuckActiveCells`,
|
|
182
|
-
and no non-terminal `approvedNotDispatched` work. It does not run unbounded
|
|
183
|
-
approval, lower paid-InMail thresholds/floors, switch an existing campaign's
|
|
184
|
-
source family, create NEW campaigns, archive/delete, launch, or send directly;
|
|
185
|
-
it never raw-writes scheduler fields.
|
|
186
|
-
Continue with the workflow below for route selection, state rereads, approval
|
|
187
|
-
gating, source import, preparation, and bounded approval.
|
|
188
|
-
|
|
189
|
-
The approval scope is the exact `workspaceId`, target date/window, sender ids,
|
|
190
|
-
action types, original planner lane chains, and refill side-effect classes.
|
|
191
|
-
Fresh planner-selected campaign/table/source ids, readiness authority,
|
|
192
|
-
`actionKey`, `targetShapeRevision`, `stateRevision`, and sent/scheduled/ready
|
|
193
|
-
counts are per-rung progress inside that envelope. Execute only
|
|
194
|
-
`target.globalActionQueue[0]`, then perform a full authoritative reread before
|
|
195
|
-
deciding any next action.
|
|
196
|
-
|
|
197
|
-
Sequence readiness is a positive authority contract. Before any preparation,
|
|
198
|
-
approval, or exact-date sweep, require a validated compiled campaign and a
|
|
199
|
-
positive exact target readiness identity from the backend compiler. Preserve
|
|
200
|
-
`profileSchemaVersion`, `compilerVersion`, `campaignDigest`,
|
|
201
|
-
`actionColumnId`, action, `pathDigest`, `dependsOnColumnIds`,
|
|
202
|
-
`runCondition`, the selected target's `capabilities`, `requestId`,
|
|
203
|
-
`effectId`, and `dateGuards` unchanged. If any field is missing, unknown,
|
|
204
|
-
blocked, or disagrees with the fresh target plan, stop with the typed readiness
|
|
205
|
-
repair blocker; prompts never compile, repair, or reinterpret the sequence.
|
|
206
|
-
|
|
207
|
-
Always prepare rows according to the persisted sequence. Never infer no-message
|
|
208
|
-
behavior from a missing Generate Message column; use only the selected target's
|
|
209
|
-
`capabilities`. Do not choose campaign-union behavior: mixed INVITE -> DM
|
|
210
|
-
sequences keep distinct target paths, and the exact selected target path decides
|
|
211
|
-
whether message, subject, comment, reaction, or row approval is required.
|
|
212
|
-
Refuse approval outside the bounded authorized cohort; bounded enrichment and LLM prerequisite calls
|
|
213
|
-
are allowed only when authorized by the exact readiness
|
|
214
|
-
packet and returned with a receipt. Never call a LinkedIn
|
|
215
|
-
outreach/provider-send surface from refill; product scheduling remains the only
|
|
216
|
-
send path.
|
|
217
|
-
|
|
218
|
-
Always execute one bounded primitive, then perform a full authoritative reread
|
|
219
|
-
and continue the same `--yolo` invocation when the fresh action remains inside
|
|
220
|
-
the original sender/action/date/lane-chain/side-effect envelope. For an
|
|
221
|
-
exact-date sweep, immediately re-attest `dateGuards`: the target date must be
|
|
222
|
-
strictly after sender-local today and inside the fill horizon. Completion
|
|
223
|
-
requires exact request/effect-attributed campaign, table, sender,
|
|
224
|
-
`actionColumnId`, action, and date scheduler-owned scheduled readback.
|
|
225
|
-
Prepared, approved, and ready rows remain intermediate evidence.
|
|
226
|
-
|
|
227
|
-
## Workspace Contract
|
|
228
|
-
|
|
229
|
-
First resolve the target workspace id from the user's request, automation config,
|
|
230
|
-
or install-time workspace mapping. Pass `workspaceId` on every scheduled or
|
|
231
|
-
`--yolo` refill tool call, including setup/read calls such as
|
|
232
|
-
`refill_sends`, `get_refill_target_plan`, `list_senders`,
|
|
233
|
-
`get_sender_routing`, `resolve_campaign_fill_route`,
|
|
234
|
-
`get_campaign_refill_state`, `get_scheduler_fill_capacity`,
|
|
235
|
-
`run_scheduler_sweep`, and any later refill mutation covered by the packet.
|
|
236
|
-
Missing `workspaceId` in scheduled or `--yolo` mode is a blocker; stop with
|
|
237
|
-
`WORKSPACE_REQUIRED` instead of running against an implicit or guessed
|
|
238
|
-
workspace.
|
|
239
|
-
|
|
240
|
-
Do not solve scheduled or `--yolo` workspace uncertainty by changing the shared
|
|
241
|
-
active workspace. Manual interactive workspace switching remains a separate
|
|
242
|
-
diagnostic/setup flow, outside automation.
|
|
243
|
-
|
|
244
|
-
Examples:
|
|
46
|
+
```mermaid
|
|
47
|
+
flowchart TD
|
|
48
|
+
A["Parse the operator request"] --> AUTH{"Sellable auth valid?"}
|
|
49
|
+
AUTH -- No --> LOGIN["Run the returned login flow and wait"]
|
|
50
|
+
LOGIN --> AUTH
|
|
51
|
+
AUTH -- Yes --> WS["Resolve exact workspace by ID, exact name, or install mapping"]
|
|
52
|
+
WS --> WSG{"One accessible workspace match?"}
|
|
53
|
+
WSG -- No --> WB["Output: blocked_retryable / WORKSPACE_REQUIRED or workspace_ambiguous"]
|
|
54
|
+
WSG -- Yes --> SENDERS["Resolve each sender inside that workspace by ID, exact name, or unambiguous prefix"]
|
|
55
|
+
SENDERS --> SG{"Every selector resolves exactly?"}
|
|
56
|
+
SG -- No --> SB["Output: blocked_retryable / sender_selector_unresolved with candidates"]
|
|
57
|
+
SG -- Yes --> DATE["Normalize 48h, exact targetDate, or inclusive untilDate in sender-local time"]
|
|
58
|
+
DATE --> PLAN["Call refill_sends with the exact workspace and scope"]
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
Scheduled and `--yolo` execution require an explicit `workspaceId` on every
|
|
62
|
+
call. Never change the shared active workspace to control an automation, never
|
|
63
|
+
fall back to its full fleet, and never mutate the workspace that merely happens
|
|
64
|
+
to be active. If the native tool is unavailable, use only the host’s exact
|
|
65
|
+
configured Sellable MCP transport; otherwise stop with `sellable_mcp_unavailable`
|
|
66
|
+
instead of manually reproducing the workflow.
|
|
67
|
+
|
|
68
|
+
Before product work, load the closed workflow asset completely and verify it:
|
|
245
69
|
|
|
246
70
|
```text
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
list_senders({ workspaceId:"cmlq1v8ms0000jx04ang4hi7e" })
|
|
71
|
+
get_subskill_prompt({ subskillName: "refill-sends-workflow" })
|
|
72
|
+
get_subskill_asset({ subskillName: "refill-sends-workflow", assetPath: "core/flow.v1.json" })
|
|
250
73
|
```
|
|
251
74
|
|
|
252
|
-
|
|
75
|
+
Continue chunks until `hasMore:false`; require
|
|
76
|
+
`workflow:"refill-sends-workflow"` and a `v1` version. Route intent is immutable:
|
|
77
|
+
workspace-wide `--yolo` uses `intent:"auto"` from first plan through terminal.
|
|
78
|
+
|
|
79
|
+
## Coordinator lifecycle
|
|
80
|
+
|
|
81
|
+
```mermaid
|
|
82
|
+
flowchart TD
|
|
83
|
+
A["Fresh workspace refill plan"] --> FULL{"Projected coverage sent + scheduled is full?"}
|
|
84
|
+
FULL -- Yes --> COMPLETE["Terminal: complete"]
|
|
85
|
+
FULL -- No --> ACTIVE{"Active exact fenced run exists?"}
|
|
86
|
+
ACTIVE -- Yes --> RESUME["Resume only issued runId, fence, targetConfig, reportingContext"]
|
|
87
|
+
ACTIVE -- No --> PICK["Select globalActionQueue 0"]
|
|
88
|
+
PICK --> PREFLIGHT["Exact campaign, table, sender, action, lane preflight"]
|
|
89
|
+
PREFLIGHT --> ACT["Execute one bounded planner action"]
|
|
90
|
+
RESUME --> ACT
|
|
91
|
+
ACT --> OUTCOME{"Canonical outcome?"}
|
|
92
|
+
OUTCOME -- "Committed or canonical no-op" --> A
|
|
93
|
+
OUTCOME -- "Still running" --> WAIT["Bounded read-only wait or receipt reconciliation"]
|
|
94
|
+
WAIT --> A
|
|
95
|
+
OUTCOME -- "Source exhausted" --> NEXT{"Another planner-ranked existing target?"}
|
|
96
|
+
NEXT -- Yes --> HANDOFF["Terminalize exact fence as next_exact_target"]
|
|
97
|
+
HANDOFF --> A
|
|
98
|
+
NEXT -- No --> NC["Terminal: new_campaign_required; ask whether to create it"]
|
|
99
|
+
OUTCOME -- "Provider unavailable after one retry" --> RETRY["Terminal: blocked_retryable; universeExhausted false"]
|
|
100
|
+
OUTCOME -- "Scope, config, readiness, or receipt drift" --> BLOCK["Terminal: blocked_retryable with exact blocker"]
|
|
101
|
+
OUTCOME -- "Planner marker done" --> DONE["Terminalize marker; never dispatch it"]
|
|
102
|
+
```
|
|
253
103
|
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
104
|
+
If `status:"in_progress"`, invoke only the returned
|
|
105
|
+
`continuation.arguments`. `targetConfig` and `runHandle` are separate and remain
|
|
106
|
+
unchanged except for a server-issued takeover fence. `active_exact_run` resumes
|
|
107
|
+
that fence; `next_exact_target` carries no stale run fence. Never open a second
|
|
108
|
+
or overlapping run, ask the user to type “continue”, redispatch a control marker, or
|
|
109
|
+
turn `done`, `next_campaign`, or a continuation object into a product action.
|
|
110
|
+
|
|
111
|
+
## Per-sender campaign and lane waterfall
|
|
112
|
+
|
|
113
|
+
Use managed evergreen order when healthy. Otherwise build the same waterfall
|
|
114
|
+
on the fly from existing eligible campaigns the sender is already attached to.
|
|
115
|
+
This makes regular and evergreen campaigns differ only in where ordering comes
|
|
116
|
+
from, not in how refill proceeds.
|
|
117
|
+
|
|
118
|
+
```mermaid
|
|
119
|
+
flowchart TD
|
|
120
|
+
A["All existing campaign-backed sequences attached to one sender"] --> FILTER["Exclude wrong workspace/sender, archived, completed, direct, disconnected, or sequence-invalid targets"]
|
|
121
|
+
FILTER --> STALE{"Managed waterfall complete and live?"}
|
|
122
|
+
STALE -- Yes --> MANAGED["Use configured priority order"]
|
|
123
|
+
STALE -- No --> DERIVE["Derive deterministic order from dashboard-active campaigns"]
|
|
124
|
+
DERIVE --> P1["Prefer canonical source/lane priority"]
|
|
125
|
+
P1 --> P2["Then future scheduled and ready inventory"]
|
|
126
|
+
P2 --> P3["Then recent successful sends and source health"]
|
|
127
|
+
P3 --> P4["Then active before exact start-eligible paused, recency, stable ID"]
|
|
128
|
+
MANAGED --> PIN["Pin exact per-sender and per-action lane chain for this run"]
|
|
129
|
+
P4 --> PIN
|
|
130
|
+
PIN --> LANE["Select highest-priority non-exhausted lane"]
|
|
131
|
+
LANE --> REFILL["Run row lifecycle"]
|
|
132
|
+
REFILL --> EX{"Lane structurally exhausted?"}
|
|
133
|
+
EX -- No --> REFILL
|
|
134
|
+
EX -- Yes --> MORE{"Another pinned existing lane or source family?"}
|
|
135
|
+
MORE -- Yes --> LANE
|
|
136
|
+
MORE -- No --> END["Output: lanes_exhausted, then new_campaign_required if coverage still short"]
|
|
257
137
|
```
|
|
258
138
|
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
this command is already running inside an active Codex goal, keep that goal open
|
|
291
|
-
until every selected sender lane is filled by projected coverage
|
|
292
|
-
(`sent + scheduled`) for the scheduler-forward target window, Christian
|
|
293
|
-
explicitly stops/statuses the run, or a concrete non-scheduler blocker appears.
|
|
294
|
-
Do not call the goal complete or blocked only because the current state is
|
|
295
|
-
`awaiting_scheduler_after_ready_buffer`; treat it as loaded, awaiting scheduler.
|
|
296
|
-
|
|
297
|
-
Load the internal workflow prompt and deterministic flow asset before taking
|
|
298
|
-
any operational step:
|
|
139
|
+
Campaign enrollment/removal, missing pinned targets, or loss of eligibility is
|
|
140
|
+
typed scope drift; a fresh planner may rederive, but the host cannot silently
|
|
141
|
+
substitute a target. A current dashboard-active `PAUSED` campaign is eligible
|
|
142
|
+
only when the planner names that exact campaign as start-eligible. Starting it
|
|
143
|
+
may let the product scheduler schedule/send approved sequence actions and must
|
|
144
|
+
be reported. DMs are follow-ups, not refill horizon targets. Mixed sequences
|
|
145
|
+
keep distinct exact target paths; do not choose campaign-union behavior.
|
|
146
|
+
|
|
147
|
+
## Connection and InMail capacity overlay
|
|
148
|
+
|
|
149
|
+
```mermaid
|
|
150
|
+
flowchart TD
|
|
151
|
+
S["Sender capacity and exact selected days"] --> C{"Connection slots available now or later in rolling week?"}
|
|
152
|
+
S --> F{"Paid-InMail credit facts fresh?"}
|
|
153
|
+
F -- No --> RF["Refresh exact selected sender once per run, then replan"]
|
|
154
|
+
RF --> P{"Credits meet existing threshold, normally 5?"}
|
|
155
|
+
F -- Yes --> P
|
|
156
|
+
C -- Yes --> INV["Enable existing invite lanes"]
|
|
157
|
+
C -- No --> INVCAP["Cap invite lane with timing evidence"]
|
|
158
|
+
P -- Yes --> MAIL["Enable existing paid-InMail or cascade lane"]
|
|
159
|
+
P -- No --> MAILCAP["Skip paid lane; never lower threshold"]
|
|
160
|
+
INV --> UNION["Rank enabled existing lanes per sender"]
|
|
161
|
+
MAIL --> UNION
|
|
162
|
+
INVCAP --> ALT{"Paid lane enabled?"}
|
|
163
|
+
MAILCAP --> ALT2{"Invite or same-campaign connection fallback enabled?"}
|
|
164
|
+
ALT -- Yes --> UNION
|
|
165
|
+
ALT -- No --> CAP["Output: blocked_retryable / capacity or window"]
|
|
166
|
+
ALT2 -- Yes --> UNION
|
|
167
|
+
ALT2 -- No --> CAP
|
|
168
|
+
UNION --> POOL["Pool shared-campaign prepared inventory; never double-enrich"]
|
|
169
|
+
```
|
|
299
170
|
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
171
|
+
`rollingWeeklyInvite.capacityFreedDuringWindow:true` means later capacity is
|
|
172
|
+
schedulable; use its timing fields rather than treating the opening gate as a
|
|
173
|
+
full-day blocker. Credit freshness precedes scheduler wait. Fresh facts below
|
|
174
|
+
threshold authorize only an already-eligible existing connection fallback or
|
|
175
|
+
another existing campaign—not a threshold change or new campaign.
|
|
176
|
+
|
|
177
|
+
## Row lifecycle
|
|
178
|
+
|
|
179
|
+
For every selected lane, use the smallest action that can reduce its gap.
|
|
180
|
+
|
|
181
|
+
```mermaid
|
|
182
|
+
flowchart TD
|
|
183
|
+
A["Lane still has projected gap"] --> ACTIVE{"Active import, preparation, or stuck work?"}
|
|
184
|
+
ACTIVE -- "Healthy active" --> WAIT["Bounded read-only wait; honor wait.deadlineAt"]
|
|
185
|
+
WAIT --> A
|
|
186
|
+
ACTIVE -- "Stuck or anomalous" --> REPAIR["Run only planner-bounded repairable cells or return blocker"]
|
|
187
|
+
REPAIR --> A
|
|
188
|
+
ACTIVE -- No --> APPROVE{"Generated rows can be approved?"}
|
|
189
|
+
APPROVE -- Yes --> AP["Approve exact bounded cohort with readiness authority"]
|
|
190
|
+
AP --> REPLAN["Record receipt and full authoritative replan"]
|
|
191
|
+
APPROVE -- No --> ENRICH{"Existing rows can be enriched or prepared?"}
|
|
192
|
+
ENRICH -- Yes --> PREP["Enrich, generate, or rerun exact bounded cohort"]
|
|
193
|
+
PREP --> REPLAN
|
|
194
|
+
ENRICH -- No --> SOURCE{"Selected source can add qualified rows?"}
|
|
195
|
+
SOURCE -- Yes --> ADD["Copy/import bounded rows from exact selected source"]
|
|
196
|
+
ADD --> REPLAN
|
|
197
|
+
SOURCE -- No --> EXPAND["Run source-family acquisition flow"]
|
|
198
|
+
EXPAND --> REPLAN
|
|
199
|
+
REPLAN --> A
|
|
200
|
+
```
|
|
201
|
+
|
|
202
|
+
Structural exhaustion requires receipt-proven
|
|
203
|
+
`hasMoreFrontierRows:false`, zero `approvalCandidates`, no fresh active prep,
|
|
204
|
+
no `stuckActiveCells`, and no non-terminal `approvedNotDispatched` work. A dry
|
|
205
|
+
preparation receipt is run-scoped and prevents the identical no-op from
|
|
206
|
+
repeating. `USER_ADDED_ROWS_LIMIT_EXCEEDED` may use a bounded same-source split
|
|
207
|
+
through `load_csv_linkedin_leads` into the exact review batch; a true campaign
|
|
208
|
+
table hard cap returns a capacity blocker and never deletes rows.
|
|
209
|
+
|
|
210
|
+
## Sender Post Engagers acquisition
|
|
211
|
+
|
|
212
|
+
```mermaid
|
|
213
|
+
flowchart TD
|
|
214
|
+
A["Sender Post Engagers lane needs rows"] --> UNUSED{"Unused selected post/engager rows exist?"}
|
|
215
|
+
UNUSED -- Yes --> USE["Import/copy bounded unused rows"]
|
|
216
|
+
UNUSED -- No --> REFRESH["Refresh this sender's latest recent authored posts"]
|
|
217
|
+
REFRESH --> SELECT["Select new unprocessed relevant high-engagement posts"]
|
|
218
|
+
SELECT --> FETCH["Fetch/import their engagers and dedupe"]
|
|
219
|
+
FETCH --> YIELD{"Usable qualified yield?"}
|
|
220
|
+
YIELD -- Yes --> USE
|
|
221
|
+
YIELD -- No --> EX["Exhaust only this Post Engagers lane"]
|
|
222
|
+
EX --> NEXT["Advance to Shared Signal Discovery or next pinned existing lane"]
|
|
223
|
+
```
|
|
224
|
+
|
|
225
|
+
The campaign must be sender-owned: exactly that sender, authored posts only.
|
|
226
|
+
Never invent keywords, search third-party posts, or run general Signal Discovery
|
|
227
|
+
against the Post Engagers campaign.
|
|
228
|
+
|
|
229
|
+
## Shared Signal Discovery acquisition
|
|
230
|
+
|
|
231
|
+
```mermaid
|
|
232
|
+
flowchart TD
|
|
233
|
+
A["Shared Signal lane needs rows"] --> UNUSED{"Unused selected rows or unprocessed posts exist?"}
|
|
234
|
+
UNUSED -- Yes --> USE["Import/copy bounded rows"]
|
|
235
|
+
UNUSED -- No --> STALE["Re-search stale existing topics for newly published posts"]
|
|
236
|
+
STALE --> SY{"Projected qualified yield meets min remaining gap or 100?"}
|
|
237
|
+
SY -- Yes --> USE
|
|
238
|
+
SY -- No --> DERIVE["Derive up to five concise positive-ICP topics from approved campaign intent"]
|
|
239
|
+
DERIVE --> DY{"Projected qualified yield meets floor?"}
|
|
240
|
+
DY -- Yes --> USE
|
|
241
|
+
DY -- No --> AGENT["Request 3-5 new agent-supplied topics through the same fenced continuation"]
|
|
242
|
+
AGENT --> SEARCH["Search once, excluding searchedKeywords, then project actual returned candidates"]
|
|
243
|
+
SEARCH --> PROVIDER{"All LinkedIn providers failed?"}
|
|
244
|
+
PROVIDER -- Yes --> RETRY["Retry the same bounded search once"]
|
|
245
|
+
RETRY --> RECOVER{"Provider recovered?"}
|
|
246
|
+
RECOVER -- No --> BLOCK["Output: blocked_retryable / provider_unavailable; universeExhausted false"]
|
|
247
|
+
RECOVER -- Yes --> AY{"Actual qualified yield meets floor?"}
|
|
248
|
+
PROVIDER -- No --> AY
|
|
249
|
+
AY -- Yes --> USE
|
|
250
|
+
AY -- No --> EX["Receipt-proven signal_yield_below_floor"]
|
|
251
|
+
EX --> NEXT["Advance to next pinned existing lane"]
|
|
252
|
+
```
|
|
253
|
+
|
|
254
|
+
Provider outage never consumes a keyword tier or proves exhaustion. Temporary
|
|
255
|
+
derived search state is run-scoped; later runs may rediscover new posts. Agent
|
|
256
|
+
keywords are supplied only through the exact run continuation, never by user
|
|
257
|
+
steering or a second run.
|
|
258
|
+
|
|
259
|
+
## Sales Nav and Prospeo acquisition
|
|
260
|
+
|
|
261
|
+
```mermaid
|
|
262
|
+
flowchart TD
|
|
263
|
+
A["Cold-provider lane needs rows"] --> UNUSED{"Unused exact-source rows exist?"}
|
|
264
|
+
UNUSED -- Yes --> USE["Import/copy bounded rows"]
|
|
265
|
+
UNUSED -- No --> DEEP["Continue latest/unfetched pages of exact saved Sales Nav or Prospeo search"]
|
|
266
|
+
DEEP --> DY{"Qualified yield?"}
|
|
267
|
+
DY -- Yes --> USE
|
|
268
|
+
DY -- No --> OPTIONAL{"Known activity/recency filter exists?"}
|
|
269
|
+
OPTIONAL -- No --> NEXT["Advance to next pinned existing lane"]
|
|
270
|
+
OPTIONAL -- Yes --> CLONE["Clone exact approved search; remove one optional activity/recency filter"]
|
|
271
|
+
CLONE --> PROOF{"Original unchanged and every hard-fit filter fingerprint preserved?"}
|
|
272
|
+
PROOF -- No --> BLOCK["Output: blocked_retryable / provider_search_drift"]
|
|
273
|
+
PROOF -- Yes --> TRIAL["Import bounded trial candidates into existing gated source"]
|
|
274
|
+
TRIAL --> GATE["Run current ICP, exclusion, DNC, persona, geography, and company-fit gates"]
|
|
275
|
+
GATE --> GY{"Qualified yield?"}
|
|
276
|
+
GY -- Yes --> USE
|
|
277
|
+
GY -- No --> QUARANTINE["Rejects remain unsendable; advance lane"]
|
|
278
|
+
QUARANTINE --> NEXT
|
|
279
|
+
```
|
|
280
|
+
|
|
281
|
+
The original saved search is immutable. An automatic trial may remove only a
|
|
282
|
+
known activity/recency signal; role, seniority, geography, industry, company
|
|
283
|
+
size, domain, include/exclude, DNC, and every other hard-fit constraint remain
|
|
284
|
+
byte-for-byte. Imported candidates are not approvals: current campaign gates
|
|
285
|
+
quarantine bad fits. If rubric rejects dominate, enter the bounded sample-quality
|
|
286
|
+
trial below before another source add. Do not repeat a spent trial or switch
|
|
287
|
+
outside planner order.
|
|
288
|
+
|
|
289
|
+
## Rubric sample-quality trial
|
|
290
|
+
|
|
291
|
+
Rubric rejection may mean the source is poor or that one or several required
|
|
292
|
+
checks are unnecessarily narrow. Decide from an exact sample, not pass-rate alone.
|
|
293
|
+
|
|
294
|
+
```mermaid
|
|
295
|
+
flowchart TD
|
|
296
|
+
A["Rows fail ICP/rubric gate"] --> STALE{"Score stale, errored, or computed under mismatched authority?"}
|
|
297
|
+
STALE -- Yes --> RESCORE["Rerun exact bounded cohort under unchanged current rubric"]
|
|
298
|
+
RESCORE --> RESULT{"Now qualified?"}
|
|
299
|
+
RESULT -- Yes --> CONTINUE["Continue row lifecycle"]
|
|
300
|
+
RESULT -- No --> SAMPLE
|
|
301
|
+
STALE -- No --> SAMPLE["Take exact representative rejected sample from prep receipt"]
|
|
302
|
+
SAMPLE --> SNAP["Snapshot complete rubric and prior digest"]
|
|
303
|
+
SNAP --> PROPOSE["Propose smallest 1-3 required-to-advisory bundle that creates useful new passes"]
|
|
304
|
+
PROPOSE --> HARD{"Touches employment, DNC/opt-out, legal/compliance, or explicit exclusion?"}
|
|
305
|
+
HARD -- Yes --> REJECT["Reject proposal; preserve complete prior rubric"]
|
|
306
|
+
HARD -- No --> SIM["Simulate bundle from recorded per-prospect evaluations; no mutation"]
|
|
307
|
+
SIM --> DELTA{"Any newly passing prospects?"}
|
|
308
|
+
DELTA -- No --> REJECT
|
|
309
|
+
DELTA -- Yes --> QUALITY["Review only newly passing profiles in campaign and customer context"]
|
|
310
|
+
QUALITY --> REPLY{"If these prospects replied, would the customer be comfortable and consider the replies relevant?"}
|
|
311
|
+
REPLY -- No --> REJECT
|
|
312
|
+
REPLY -- Yes --> DRIFT{"Live rubric digest still equals prior digest?"}
|
|
313
|
+
DRIFT -- No --> BLOCK["Output: blocked_retryable / rubric config drift"]
|
|
314
|
+
DRIFT -- Yes --> APPLY["Guarded apply entire required-to-advisory bundle"]
|
|
315
|
+
APPLY --> EXACT["Force exact bounded ICP rescore for accepted sample"]
|
|
316
|
+
EXACT --> VERIFY{"Dispatch and accepted quality receipt valid?"}
|
|
317
|
+
VERIFY -- Yes --> KEEP["Keep bundle; receipt before/after digests, changed checks, sample counts, and verdict"]
|
|
318
|
+
VERIFY -- No --> ROLLBACK["Restore complete prior snapshot and receipt rollback"]
|
|
319
|
+
REJECT --> BETTER["Keep rows unsendable; continue better supply or next existing lane"]
|
|
320
|
+
ROLLBACK --> BETTER
|
|
321
|
+
```
|
|
322
|
+
|
|
323
|
+
`--yolo` may change more than one required rubric at a time only through this
|
|
324
|
+
single bounded reversible bundle. The change demotes selected required checks to
|
|
325
|
+
advisory scoring; it does not erase their evidence. The acceptance test is the
|
|
326
|
+
newly passing delta: if any reviewed prospect would make the customer unhappy,
|
|
327
|
+
surprised, or consider the reply irrelevant, keep the complete prior rubric and
|
|
328
|
+
continue the source waterfall. Never expose raw prospect profiles in the public
|
|
329
|
+
receipt.
|
|
330
|
+
|
|
331
|
+
## Scheduler and uncertain-receipt recovery
|
|
332
|
+
|
|
333
|
+
```mermaid
|
|
334
|
+
flowchart TD
|
|
335
|
+
A["Ready buffer covers remaining projected gap"] --> CREDIT{"Every selected paid lane has fresh credit facts?"}
|
|
336
|
+
CREDIT -- No --> RF["Refresh each exact selected sender once, then full replan"]
|
|
337
|
+
RF --> A
|
|
338
|
+
CREDIT -- Yes --> DATE{"Exact targetDate?"}
|
|
339
|
+
DATE -- Yes --> SWEEP["Dispatch one request-scoped product scheduler sweep"]
|
|
340
|
+
DATE -- No --> WAIT["Enter coordinator-owned bounded scheduler wait"]
|
|
341
|
+
SWEEP --> RECEIPT{"Receipt state?"}
|
|
342
|
+
RECEIPT -- Terminal --> READ["Canonical target-plan and request/effect readback"]
|
|
343
|
+
RECEIPT -- Active --> POLL["Poll only issued continuation until wait.deadlineAt"]
|
|
344
|
+
RECEIPT -- uncertain_outcome --> RECON["Reconcile same request key read-only; never redispatch"]
|
|
345
|
+
POLL --> RECEIPT
|
|
346
|
+
RECON --> TERM["Terminal: blocked_retryable with request identity intact"]
|
|
347
|
+
WAIT --> READ
|
|
348
|
+
READ --> FULL{"Projected coverage full?"}
|
|
349
|
+
FULL -- Yes --> COMPLETE["Terminal: complete"]
|
|
350
|
+
FULL -- No --> READY{"Ready buffer still covers gap?"}
|
|
351
|
+
READY -- Yes --> LOADED["Terminal: loaded_awaiting_scheduler"]
|
|
352
|
+
READY -- No --> PREP["Return to row lifecycle"]
|
|
353
|
+
```
|
|
354
|
+
|
|
355
|
+
A scheduler sweep is a visible workspace-wide scheduling side effect: existing
|
|
356
|
+
product gates may schedule unrelated eligible cells in that workspace/date.
|
|
357
|
+
It never sends directly or raw-writes scheduler fields. `cellsConsidered is
|
|
358
|
+
allocation-attempt count`; `readyCellsFound` is prefilter inventory. Inspect
|
|
359
|
+
`campaignScopeSummary`, `prefiltered`, `skipped`, and `deferred`. For ready
|
|
360
|
+
closed-InMail cells with stale paid-credit evidence, use
|
|
361
|
+
`refresh_paid_inmail_credits_then_rerun`. `wait_for_capacity_or_window` means do
|
|
362
|
+
not source or prep more rows; `no_ready_cells_continue_refill_prep` returns to
|
|
363
|
+
the row lifecycle. `cellsScheduled:0` alone is not failure.
|
|
364
|
+
|
|
365
|
+
## Explicit message-template revision
|
|
366
|
+
|
|
367
|
+
Ordinary refill never changes copy. Only a literal user-approved revision
|
|
368
|
+
envelope may enter this side path.
|
|
369
|
+
|
|
370
|
+
```mermaid
|
|
371
|
+
flowchart TD
|
|
372
|
+
A["Literal messageTemplateRevision with approved markdown, digests, 1-500 row IDs, request/effect IDs"] --> DIGEST{"Current prior authority digest present?"}
|
|
373
|
+
DIGEST -- No --> FENCE["Acquire exact run and read reportingContext source templateAuthorityDigest"]
|
|
374
|
+
FENCE --> DG{"Digest returned?"}
|
|
375
|
+
DG -- No --> BLOCK["Terminal: blocked_retryable / template_authority_missing"]
|
|
376
|
+
DG -- Yes --> REVISE["Apply revision to exact cohort"]
|
|
377
|
+
DIGEST -- Yes --> REVISE
|
|
378
|
+
REVISE --> AUDIT["select_campaign_cells in batches <=20 with includeGeneratedMessageReview:true"]
|
|
379
|
+
AUDIT --> MATCH{"Every authoritativeGeneratedMessageReview complete, exact text/digest, matchesCurrentTemplateAuthority true?"}
|
|
380
|
+
MATCH -- No --> FAIL["Stop before approval: regenerated_copy_integrity_failed"]
|
|
381
|
+
MATCH -- Yes --> APPROVE["Approve only exact rowIds using compiled readiness authority"]
|
|
382
|
+
APPROVE --> RECEIPT{"Bounded preparation receipt includes laneScope?"}
|
|
383
|
+
RECEIPT -- No --> FAIL
|
|
384
|
+
RECEIPT -- Yes --> PLAN["Return to canonical refill plan"]
|
|
385
|
+
```
|
|
386
|
+
|
|
387
|
+
Formula Message/Subject columns, previews, exports, and counts are not copy
|
|
388
|
+
authority. `forceRerun:true` never implies a revision or approval.
|
|
389
|
+
|
|
390
|
+
## Manual approval and `--yolo`
|
|
391
|
+
|
|
392
|
+
```mermaid
|
|
393
|
+
flowchart TD
|
|
394
|
+
A["Render fresh bounded packet in normal chat as Markdown"] --> MODE{"--yolo present?"}
|
|
395
|
+
MODE -- Yes --> AUTO["Auto-accept all planner-ranked transitions among existing campaigns, lanes, sources, and safe refill primitives"]
|
|
396
|
+
MODE -- No --> ASK["Open host-native structured question with exactly Accept and Decline"]
|
|
397
|
+
ASK --> DECISION{"Operator choice?"}
|
|
398
|
+
DECISION -- Accept --> RUN["Execute only rendered envelope"]
|
|
399
|
+
DECISION -- Decline --> STOP["Stop with no mutation"]
|
|
400
|
+
AUTO --> RUN
|
|
401
|
+
```
|
|
402
|
+
|
|
403
|
+
Use `request_user_input` in Codex and `AskUserQuestion` in Claude Code. The
|
|
404
|
+
full packet belongs in the normal chat message immediately before the question:
|
|
405
|
+
workspace, sender scope, campaign-by-campaign plan table, action, target/cap,
|
|
406
|
+
source, blocker/skip reason, exact IDs, expected side effects, forbidden actions,
|
|
407
|
+
and stop condition. The question body must be compact and refer back to the
|
|
408
|
+
posted packet instead of duplicating it. `--yolo` may switch among every fresh
|
|
409
|
+
planner-ranked existing campaign/lane/source family and start the exact selected
|
|
410
|
+
`PAUSED` campaign. It does not create campaigns, direct-send, archive/delete,
|
|
411
|
+
change sender limits or paid-InMail thresholds, reassign senders, or invent
|
|
412
|
+
source selection outside the planner.
|
|
413
|
+
|
|
414
|
+
## Typed outputs
|
|
415
|
+
|
|
416
|
+
```mermaid
|
|
417
|
+
flowchart TD
|
|
418
|
+
A["Fresh canonical state"] --> C{"Terminal classification"}
|
|
419
|
+
C -- "sent + scheduled meets target" --> COMPLETE["complete: report saturation ledger"]
|
|
420
|
+
C -- "ready buffer covers gap after bounded scheduler wait" --> LOADED["loaded_awaiting_scheduler: report expected pickup and stop"]
|
|
421
|
+
C -- "recoverable external, capacity, active-work, or receipt condition" --> BLOCKED["blocked_retryable: exact blocker, receipt, and safe retry condition"]
|
|
422
|
+
C -- "current lane spent but another existing target remains" --> EX["lanes_exhausted internally; continue next_exact_target"]
|
|
423
|
+
C -- "every existing campaign/lane/source spent or infeasible" --> NEW["new_campaign_required: propose missing shape and ask whether to create it"]
|
|
424
|
+
C -- "scope/config/readiness changed" --> DRIFT["blocked_retryable: typed drift; never guess"]
|
|
303
425
|
```
|
|
304
426
|
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
and
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
`
|
|
317
|
-
|
|
318
|
-
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
- `target.eligibleSenderLedger` is the public sender eligibility ledger.
|
|
345
|
-
- `target.senderRefillPlans[]` is the canonical sender-level packet; read and
|
|
346
|
-
display it before mutation.
|
|
347
|
-
- Each sender packet includes `campaignRanking.options`, `sourcePlan`,
|
|
348
|
-
`refillReceipt`, `nextActions`, and `manualAlternates`.
|
|
349
|
-
- `refillReceipt` is the public ladder receipt. It carries the selected
|
|
350
|
-
campaign/sender/lane summary, skipped rungs, existing-row frontier proof, and
|
|
351
|
-
any absolute `wait.deadlineAt`.
|
|
352
|
-
- Preserve these coverage labels exactly: `Need to prepare`, `Goal`,
|
|
353
|
-
`Already sent`, `Scheduled`, `Ready and waiting to be scheduled`,
|
|
354
|
-
`Still need to schedule`, and `Still need to prepare`. Never render an
|
|
355
|
-
unlabeled `Still need` value.
|
|
356
|
-
- `target.globalActionQueue` is the only cross-sender yolo execution queue.
|
|
357
|
-
Execute only the globally ranked primitive at
|
|
358
|
-
`target.globalActionQueue[0]`, then rerun `get_refill_target_plan` with the
|
|
359
|
-
same `workspaceId` before choosing another action. In `--yolo`, repeat this
|
|
360
|
-
through exact `continuation.arguments`: resume active targets with their
|
|
361
|
-
`targetConfig`/`runId`/`fence`, and start each next planner-preflighted target
|
|
362
|
-
with its `targetConfig` and a new fence, until workspace projected coverage
|
|
363
|
-
fills, the coordinator returns the same-target `loaded_awaiting_scheduler`
|
|
364
|
-
terminal, or a concrete blocker appears.
|
|
365
|
-
- When that primitive is `run_scheduler_sweep`, show `workspaceWide:true` and
|
|
366
|
-
its reason in the approval/yolo packet so the operator sees that the sweep
|
|
367
|
-
may schedule other eligible workspace cells on the approved date.
|
|
368
|
-
- `manualAlternates` are not yolo actions. Threshold/floor lowering, NEW
|
|
369
|
-
campaign creation, source-family switches, and the capSaturated
|
|
370
|
-
`manual_source_replenishment` dead-end (needs human table cleanup) are manual
|
|
371
|
-
continuations only. Same-provider-source continuation
|
|
372
|
-
(`continue_sales_nav_source`, `continue_prospeo_source`) and
|
|
373
|
-
`start_paused_campaign` for a start-eligible campaign are `--yolo` primitives,
|
|
374
|
-
not manual alternates.
|
|
375
|
-
|
|
376
|
-
Refill action ladder: approve generated rows only when an explicit bounded
|
|
377
|
-
approval gate exists, process all existing same-campaign unenriched/unprepared
|
|
378
|
-
rows in bounded batches before any source work, then copy bounded net-new rows
|
|
379
|
-
from the selected source (`selectedLeadListId`, provider, and source
|
|
380
|
-
fingerprint preserved), then use provider-aligned same-source-more against the
|
|
381
|
-
campaign's OWN saved provider search (Sales Nav or Prospeo for cold campaigns).
|
|
382
|
-
Same-provider-source-more is a bounded `--yolo` add. A DIFFERENT source or a
|
|
383
|
-
source-family switch changes the reply-rate baseline and stays a manual
|
|
384
|
-
alternate, not a `--yolo` side effect.
|
|
385
|
-
For Signal Discovery, consume bounded net-new eligible source rows first. If
|
|
386
|
-
none remain and the campaign has no reusable tab keywords or unselected posts,
|
|
387
|
-
derive at most five search keywords from its saved positive ICP criteria without
|
|
388
|
-
changing rubrics or thresholds. Broadening rounds follow a strict keyword-source
|
|
389
|
-
ORDER because the post universe refreshes over time — a keyword searched days or
|
|
390
|
-
weeks ago yields NEW posts today: (1) `keywordSource:"stale_researched"` FIRST
|
|
391
|
-
re-searches the campaign's EXISTING keywords whose last search is stale (tab
|
|
392
|
-
timestamp before the current run day, or no stored timestamp), deduping to posts
|
|
393
|
-
not already scraped/selected; (2) `"derived"` then derives fresh keywords from the
|
|
394
|
-
campaign's stored search intent; (3) `"agent_supplied"` last. When a
|
|
395
|
-
`broaden_signal_search` action returns `keywordSource:"agent_supplied"`, both
|
|
396
|
-
earlier tiers are receipt-proven spent this run but the 45-day universe is not
|
|
397
|
-
proven dry: read the campaign brief, ICP, and persona and invent 3-5 concrete NEW
|
|
398
|
-
GTM-topic keywords that are NOT in the action's `searchedKeywords` negative list —
|
|
399
|
-
still without changing rubrics or thresholds, and never re-issuing a spent search.
|
|
400
|
-
When a run stops asking for agent-supplied keywords (blocker
|
|
401
|
-
`signal_agent_keywords_invalid`, or a pending `agent_supplied` broaden with no
|
|
402
|
-
keywords), re-invoke the SAME `refill_sends` command with the run's `runId` and
|
|
403
|
-
`fence` plus `agentKeywords: [your 3-5 keywords]`; the fenced run validates them
|
|
404
|
-
against the negative list, consumes them once into the pending broaden round, and
|
|
405
|
-
resumes automatically. An out-of-envelope payload is typed-refused so you can
|
|
406
|
-
correct and re-invoke. Signal exhaustion is a
|
|
407
|
-
quantified yield projection, not a fuzzy engagement read: every tier projects
|
|
408
|
-
new-lead yield as the sum of scrapable engager counts across the NEW recent (<=45
|
|
409
|
-
day) candidate posts times the campaign's observed headline-ICP pass rate, and the
|
|
410
|
-
floor `min(remaining prep gap, 100)` is only ever evaluated on the ACTUAL
|
|
411
|
-
candidates a search round returned — never as a pre-search short-circuit. A
|
|
412
|
-
below-floor round at the stale or derived tier consumes that tier and escalates to
|
|
413
|
-
the next one; the agent tier is the always-available terminal tier, so only an
|
|
414
|
-
agent-supplied round that actually searched and yielded below floor — zero new
|
|
415
|
-
posts being the projection-zero special case — is receipt-proven lane exhaustion
|
|
416
|
-
(`signal_yield_below_floor`, recording the keyword-source tier, candidate post
|
|
417
|
-
count, engager sum, observed pass rate, and threshold): update run state, replan,
|
|
418
|
-
and continue with the exact next ranked campaign in the original sender lane chain
|
|
419
|
-
during the same run. Do not persist derived keywords or lane exhaustion into
|
|
420
|
-
campaign configuration; a later refill run rereads the current approved rubric and
|
|
421
|
-
may re-search refreshed keywords or discover new posts and engagers.
|
|
422
|
-
|
|
423
|
-
Sender-owned Post Engagers is the structural exception to general Signal
|
|
424
|
-
Discovery broadening. A campaign named `<sender> - Post Engagers` with exactly
|
|
425
|
-
one attached sender may select only that sender's authored posts; third-party
|
|
426
|
-
keyword results can never refill it. Once its bounded sender-owned continuation
|
|
427
|
-
has no reusable post/frontier, treat `sender_owned_post_required` as exhaustion
|
|
428
|
-
of that campaign lane only, record it on the sender cursor, and immediately
|
|
429
|
-
replan to the exact next ranked waterfall lane—normally Shared Signal
|
|
430
|
-
Discovery—inside the same `--yolo` run. Do not request agent keywords, load the
|
|
431
|
-
campaign brief for keyword invention, or dispatch a general Signal search against
|
|
432
|
-
the Post Engagers campaign. General stale/derived/agent keyword broadening resumes
|
|
433
|
-
normally only after the planner selects the shared Signal Discovery campaign.
|
|
434
|
-
Source/copy/fallback requires receipt-proven exhaustion of earlier rungs:
|
|
435
|
-
`existingRowFrontier.hasMoreFrontierRows:false`, zero `approvalCandidates`, no
|
|
436
|
-
fresh active prep, no `stuckActiveCells`, and no non-terminal
|
|
437
|
-
`approvedNotDispatched` rows. Treat anomalies, `stuckActiveCells`, and
|
|
438
|
-
non-terminal `approvedNotDispatched` as diagnose-and-report gates, not
|
|
439
|
-
exhaustion. Terminal `approvedNotDispatched` blockers may be reported, then the
|
|
440
|
-
ladder can proceed.
|
|
441
|
-
|
|
442
|
-
Repairable draft fallback: when a run ends `lanes_exhausted` (or the route
|
|
443
|
-
reports no schedulable campaign) and `resolve_campaign_fill_route`
|
|
444
|
-
skippedTargets contains a non-archived DRAFT campaign blocked only by
|
|
445
|
-
`campaign_sequence_missing` whose table already has prospect rows and a
|
|
446
|
-
completed builder pipeline (brief plus Enrich/ICP/Generate Message/Approved
|
|
447
|
-
columns), `--yolo` may finish that campaign's setup with existing product
|
|
448
|
-
tools and continue the waterfall into it: (1) `update_campaign` to attach
|
|
449
|
-
exactly the workspace's already-eligible refill senders from the current
|
|
450
|
-
eligible sender ledger — never invent or reassign other senders; (2)
|
|
451
|
-
`attach_recommended_sequence` WITHOUT a currentStep override so the backend
|
|
452
|
-
selects the tier-correct cascade while the approved-message gate stays
|
|
453
|
-
intact; (3) invoke `refill_sends --yolo` again with the same
|
|
454
|
-
workspace scope so the fresh route targets the now start-eligible campaign and
|
|
455
|
-
the normal bounded prep/approve/start/schedule ladder fills it. This repairs
|
|
456
|
-
only existing campaigns already holding imported prospects; it never creates
|
|
457
|
-
campaigns, never touches archived campaigns, and messaging still flows through
|
|
458
|
-
the campaign's own brief, rubric, and bounded approval gates.
|
|
459
|
-
|
|
460
|
-
Run-local paid-credit guard: in `--yolo`, the `refill_sends` MCP command
|
|
461
|
-
automatically maintains a checkpointed `refreshedPaidInmailSenderIds` set for
|
|
462
|
-
the entire fenced run across resume calls. If its first target plan has stale/missing paid-InMail credit
|
|
463
|
-
facts, it refreshes each selected sender at most once, reruns
|
|
464
|
-
`get_refill_target_plan`, and returns the post-refresh `targetPlan` before
|
|
465
|
-
choosing the next prep/source-copy/bounded-approval/read-only wait action. If fresh facts are still below
|
|
466
|
-
threshold, below-threshold paid-InMail facts fall back to an existing connection
|
|
467
|
-
lane, the same Sales Nav cascade campaign's connection branch, or a manual
|
|
468
|
-
continuation.
|
|
469
|
-
|
|
470
|
-
Freshness gate precedes scheduler wait: if any selected
|
|
471
|
-
`target.senderRefillPlans[].paidInmail.status` is `missing_credit_facts` or
|
|
472
|
-
`stale_credit_facts`, or the target plan contains a
|
|
473
|
-
`refresh_paid_inmail_credits` candidate, do not enter `wait_for_scheduler` even
|
|
474
|
-
when `remainingReadyOrProjectedGap:0`. Refresh the exact selected sender credit
|
|
475
|
-
facts once, rerun `get_refill_target_plan`, and only then decide whether
|
|
476
|
-
scheduler wait is the next safe action. If facts remain missing/stale after the
|
|
477
|
-
single refresh attempt, stop with a paid-InMail freshness blocker instead of
|
|
478
|
-
waiting on scheduler pickup.
|
|
479
|
-
The standalone MCP refresh surface is the scoped route
|
|
480
|
-
`/api/v3/mcp/senders/:senderId/refresh-inmail-credits` with explicit
|
|
481
|
-
`workspaceId`; do not use active-workspace mutation as the automation control
|
|
482
|
-
path.
|
|
483
|
-
|
|
484
|
-
Compact refill lessons: sender-level target plan is final truth; trust
|
|
485
|
-
`schedulerGate.sendable` and scheduler gate blockers, not raw
|
|
486
|
-
`unipileAccountStatus` labels alone; use compact prep status checks for
|
|
487
|
-
long-running jobs; reread target plans after prep or cancel; treat ready rows as
|
|
488
|
-
intermediate; avoid huge parallel target-plan reads when output is large. If a
|
|
489
|
-
campaign produces prepared/ready rows but sender-level projected coverage does
|
|
490
|
-
not move after one bounded settle loop, pivot to compact prep status or a
|
|
491
|
-
scheduler-proven lane. Do not keep waiting on campaign-level ready counts.
|
|
492
|
-
|
|
493
|
-
For exact-date requests, pass `targetDate` to `get_refill_target_plan`. If you
|
|
494
|
-
need raw proof, call the read-only `get_scheduler_fill_capacity` query for the
|
|
495
|
-
same sender/action/date; it tells the MCP how many cells the product scheduler
|
|
496
|
-
will try to place and does not import, approve, schedule, refresh credits, or
|
|
497
|
-
mutate.
|
|
498
|
-
Treat `rollingWeeklyInvite.capacityFreedDuringWindow:true` as proof that
|
|
499
|
-
rolling-weekly capacity frees later in the send window and remains schedulable;
|
|
500
|
-
use its timing fields instead of treating the window-start gate as a permanent
|
|
501
|
-
full-day blocker.
|
|
502
|
-
When the refill loop has ready rows and needs scheduler pickup now, use
|
|
503
|
-
`run_scheduler_sweep` with the same explicit `workspaceId`; it can place cells
|
|
504
|
-
within existing scheduler gates and returns the receipt, but it never sends or
|
|
505
|
-
bypasses limits.
|
|
506
|
-
A scheduler sweep is a visible workspace-wide scheduling side effect. The
|
|
507
|
-
request packet limits the expected target changes to its exact date, senders,
|
|
508
|
-
action types, campaigns, tables, and stable sources, but the product scheduler
|
|
509
|
-
may also place unrelated eligible workspace work; disclose that possibility in
|
|
510
|
-
the approval packet and receipt. For an exact-date sweep action, execute it
|
|
511
|
-
once, then reread the full target plan before taking or presenting another
|
|
512
|
-
action.
|
|
513
|
-
The exact-date `run_scheduler_sweep` may schedule eligible workspace cells
|
|
514
|
-
through existing product gates; it never sends directly or raw-writes scheduler
|
|
515
|
-
fields. Only non-exact or no-sweep scheduler waits are read-only.
|
|
516
|
-
On resume, reconcile the current target plan and matching sweep status/receipt
|
|
517
|
-
before retrying. A matching same-key terminal receipt replays; an
|
|
518
|
-
`uncertain_outcome` stops for reconciliation and must not schedule again.
|
|
519
|
-
Scheduler-run receipt interpretation: `cellsConsidered is allocation-attempt
|
|
520
|
-
count`, not total ready supply, while `readyCellsFound` is ready inventory found
|
|
521
|
-
before prefilters. Inspect `campaignScopeSummary` before assuming the selected
|
|
522
|
-
refill campaign/table was included; if absent, do not infer that target was
|
|
523
|
-
ready-but-blocked. Interpret `prefiltered` as ready cells removed before
|
|
524
|
-
allocation, `skipped` as considered cells blocked by scheduler gates, and
|
|
525
|
-
`deferred` as considered cells waiting on windows/capacity/cooldown. For ready
|
|
526
|
-
closed-InMail cells with stale paid-credit prefilter/defer reasons, refresh
|
|
527
|
-
paid-InMail credits once through existing tools, then rerun `run_scheduler_sweep`
|
|
528
|
-
or read `action:"status"`; `refresh_paid_inmail_credits_then_rerun` is that
|
|
529
|
-
path. `wait_for_capacity_or_window` means report loaded/capped/waiting and do
|
|
530
|
-
not source or prep more rows; `no_ready_cells_continue_refill_prep` means return
|
|
531
|
-
to the refill/prep ladder. Do not treat `cellsScheduled:0` alone as failure.
|
|
532
|
-
If the target plan is complete by projected coverage, report that the selected
|
|
533
|
-
target is already filled and no-op without asking for approval. If the ready
|
|
534
|
-
buffer covers the projected gap, paid InMail credit facts are fresh for every
|
|
535
|
-
selected paid-InMail lane, but scheduled coverage is still short, keep the run
|
|
536
|
-
open in a persistent read-only scheduler wait loop. Honor the emitted wait
|
|
537
|
-
metadata: reread `get_refill_target_plan` after about 30 seconds by default (or
|
|
538
|
-
the action's explicit `rereadAfterMs`), never use a blind hard-coded sleep, and
|
|
539
|
-
stop at the named bound using `receipt_deadline > unchanged_poll_bound >
|
|
540
|
-
run_budget` precedence. A matching active/uncertain request key is never
|
|
541
|
-
redispatched. Continue until projected coverage fills the target, a concrete
|
|
542
|
-
non-scheduler blocker appears, a named bound stops the run, or Christian
|
|
543
|
-
explicitly asks to stop or only receive a status report. Treat
|
|
544
|
-
`awaiting_scheduler_after_ready_buffer` as an in-progress wait state, not a
|
|
545
|
-
close-out condition.
|
|
546
|
-
Wait actions are gates, not competing goals. When `wait_for_active_work` or
|
|
547
|
-
`wait_for_scheduler` includes receipt `wait.deadlineAt`, honor that absolute
|
|
548
|
-
deadline; if it is expired on this call, escalate to diagnostics with the
|
|
549
|
-
receipt evidence instead of issuing another blind wait.
|
|
550
|
-
If paid InMail credit facts are stale or missing and the first target plan
|
|
551
|
-
contains `refresh_paid_inmail_credits`, do not present that as the operator's
|
|
552
|
-
next action in `--yolo`. Do not present paid-credit refresh as the next operator
|
|
553
|
-
action after `refill_sends` returns `autoPaidInmailRefresh` and the
|
|
554
|
-
post-refresh `targetPlan`. Trust `refill_sends.autoPaidInmailRefresh`: it should
|
|
555
|
-
show the exact sender ids refreshed once, sender-credit-cache write receipts,
|
|
556
|
-
and a returned post-refresh `targetPlan`. Continue from that post-refresh packet.
|
|
557
|
-
If paid InMail is below threshold after the fresh credit read, report the exact
|
|
558
|
-
campaign/table/column threshold action or same-campaign connection fallback;
|
|
559
|
-
`--yolo` does not lower paid-InMail thresholds or create campaigns.
|
|
560
|
-
|
|
561
|
-
If the plain route's managed waterfall targets are stale, for example skipped
|
|
562
|
-
targets show archived/completed shared slots or the returned targets do not cover
|
|
563
|
-
the named sender, immediately run the active route refetch before declaring a
|
|
564
|
-
sender blocked. Current dashboard-active `PAUSED` campaign-backed sequence
|
|
565
|
-
campaigns are start-eligible refill candidates; read refill state before deciding
|
|
566
|
-
whether to prep, approve, start, or skip. Do not treat them as archived inventory.
|
|
567
|
-
|
|
568
|
-
For interactive Codex or Claude Code sessions, that approval must use the
|
|
569
|
-
host-native structured question gate, not plain chat:
|
|
570
|
-
|
|
571
|
-
- Codex: use `request_user_input`.
|
|
572
|
-
- Claude Code: use `AskUserQuestion`.
|
|
573
|
-
|
|
574
|
-
The approval question must present the final refill step with exactly two
|
|
575
|
-
choices: `Accept` and `Decline`. Render the full operator output packet in
|
|
576
|
-
normal chat immediately before opening the structured approval question so it
|
|
577
|
-
can be displayed as Markdown. The chat packet must include workspace, sender
|
|
578
|
-
scope, a campaign-by-campaign table with campaign name, sender names, action,
|
|
579
|
-
target count/cap, source/list, and blocker/skip reason, then exact ids,
|
|
580
|
-
expected side effects, forbidden actions, and the stop condition. The structured
|
|
581
|
-
question body must be compact and refer back to the posted packet instead of
|
|
582
|
-
duplicating it. Treat `Accept` as permission for only the rendered target, caps,
|
|
583
|
-
mode, and side effects. Treat `Decline` as a hard stop with no mutation.
|
|
584
|
-
|
|
585
|
-
If Christian includes `--yolo` in the same refill request, treat that flag as
|
|
586
|
-
auto-accept for the rendered bounded refill packet after the required fresh state
|
|
587
|
-
reread. For sender-scoped language with no named senders, `--yolo` means all
|
|
588
|
-
eligible healthy senders enrolled in active campaign-backed sequence campaigns in
|
|
589
|
-
the requested `workspaceId`. Without `--yolo`, if Christian did not name senders, ask
|
|
590
|
-
which eligible enrolled senders to refill before choosing campaigns or mutating.
|
|
591
|
-
|
|
592
|
-
`--yolo` covers the exact sender set, action/date target, caps, original
|
|
593
|
-
planner-emitted lane chains, approval mode, and refill side effects. It may
|
|
594
|
-
authorize `start_campaign` only when the fresh packet's selected evergreen lane,
|
|
595
|
-
selected campaign ranking, and original lane chain name the same `PAUSED`,
|
|
596
|
-
dashboard-active, campaign-backed sequence refill target. Starting a paused
|
|
597
|
-
campaign can let the product scheduler schedule/send
|
|
598
|
-
approved eligible sequence actions, so include that expected side effect in the
|
|
599
|
-
packet. It does not authorize starting unrelated, archived, completed, draft, or
|
|
600
|
-
direct campaigns, separate launch/send actions, archive or delete cleanup,
|
|
601
|
-
direct scheduler writes, sender reassignment, paid-InMail threshold changes,
|
|
602
|
-
connection campaign creation, paid-InMail credit refreshes outside the exact
|
|
603
|
-
rendered sender packet, or campaigns outside those
|
|
604
|
-
selected for the eligible sender set. Refuse if a fresh selection leaves the
|
|
605
|
-
original lane chain or changes the sender/action/date/cap/side-effect envelope.
|
|
606
|
-
Campaign/table/action-key/readiness revisions and sent/scheduled counts may
|
|
607
|
-
change as the fresh planner advances the approved lane chain; those changes do
|
|
608
|
-
not require a second approval. Treat leaving that stable envelope as scope
|
|
609
|
-
drift and refuse it.
|
|
610
|
-
|
|
611
|
-
Short form: start only the exact selected `PAUSED` campaign authorized by all
|
|
612
|
-
three fresh planner proofs; leave every unselected paused fallback unchanged.
|
|
613
|
-
|
|
614
|
-
In `--yolo`, continue until projected coverage fills or a concrete blocker is
|
|
615
|
-
proven. After each
|
|
616
|
-
apply/prep/source-copy/bounded-approval/read-only wait result, reread state, settle processing when needed, and move to
|
|
617
|
-
the next selected sender or start-eligible same-packet campaign instead of
|
|
618
|
-
stopping after the first partial result. Receipt-proven source exhaustion must
|
|
619
|
-
advance the planner run-state cursor before choosing the next lane. If no
|
|
620
|
-
in-envelope safe action remains,
|
|
621
|
-
return concrete continuation options with campaign names, exact ids, which option
|
|
622
|
-
is still covered by the current packet, and which option needs a new approval
|
|
623
|
-
packet.
|
|
624
|
-
|
|
625
|
-
For `--yolo` fill/schedule requests, completion means projected saturation
|
|
626
|
-
(`sent + scheduled`) for the scheduler-forward target window, not merely
|
|
627
|
-
prepared/approved/ready rows. Maintain a target-window saturation ledger per
|
|
628
|
-
selected sender: selected send days, gross capacity, actual sent cells, future
|
|
629
|
-
scheduler-owned scheduled cells with non-null `scheduledFor`, projected count,
|
|
630
|
-
ready-to-schedule buffer, remaining projected gap, paid-InMail feasibility,
|
|
631
|
-
`targetShapeRevision`, `stateRevision`, and the next MCP primitive that can
|
|
632
|
-
reduce the gap. After every apply/prep/source-copy/bounded-approval/read-only wait result, wait for processing, reread
|
|
633
|
-
the target plan/refill state, recompute the ledger, then keep applying safe
|
|
634
|
-
bounded actions until projected coverage fills the target window or a concrete
|
|
635
|
-
non-scheduler blocker is proven.
|
|
636
|
-
If ready rows cover the projected gap but the scheduler has not picked them up
|
|
637
|
-
yet, report loaded, awaiting scheduler and run the persistent read-only
|
|
638
|
-
scheduler wait loop. Do not finish the refill goal, mark it complete, or mark it
|
|
639
|
-
blocked only because it is still `awaiting_scheduler_after_ready_buffer`; keep
|
|
640
|
-
waiting unless Christian stops the run or the host cannot continue.
|
|
641
|
-
|
|
642
|
-
Carry the canonical `refill_reporting.v2` DTO through progress, continuation,
|
|
643
|
-
replay, and terminal reporting without recomputing counts or losing live epoch,
|
|
644
|
-
snapshot sequence, reset reason, watermark, blockers, compatibility, or
|
|
645
|
-
terminal outcome. Redact raw copy and prospect fields.
|
|
646
|
-
|
|
647
|
-
Future scheduled coverage and already sent actions are distinct; only
|
|
648
|
-
scheduler-owned future scheduled actions count as scheduled coverage. For
|
|
649
|
-
multiple target dates, finish D1 execution and the full D1 reread before
|
|
650
|
-
planning D2. Completion proof is product-native Sellable MCP evidence only:
|
|
651
|
-
target plan, campaign refill state, scheduler capacity, sweep/status, and
|
|
652
|
-
bounded receipts. Never use individual cell ids, Prisma, SQL, direct database
|
|
653
|
-
access, or production-environment scripts as completion proof.
|
|
654
|
-
|
|
655
|
-
In `--yolo`, the default fill target is the scheduler-forward 48-hour window
|
|
656
|
-
unless `--target-date`/`targetDate`, `--until`/`untilDate`, or an explicit
|
|
657
|
-
compatibility `horizonSendDays` is provided. If a target date is provided,
|
|
658
|
-
compute bounded gaps for only that sender-local date using scheduler-fillable
|
|
659
|
-
slots. If an until date is provided, compute bounded gaps through that
|
|
660
|
-
sender-local date inclusive, skipping no-send days and never extending beyond
|
|
661
|
-
that date without a new packet. For each target sender, compute the bounded gap
|
|
662
|
-
from that sender's healthy daily capacity, existing future scheduler-owned
|
|
663
|
-
scheduled sends, and rows already ready to schedule across active campaigns
|
|
664
|
-
enrolled with that sender. Then pick the best same-sender campaign to fill the
|
|
665
|
-
gap: prefer recent/future scheduler-owned sends for that sender, then strongest
|
|
666
|
-
recent result evidence, then source health. Do not stop
|
|
667
|
-
after filling only one sender when the request was sender-scoped. If a
|
|
668
|
-
same-source copy hits the campaign-table row cap, split the current source into
|
|
669
|
-
a bounded LinkedIn profile source list, confirm only that smaller list into the
|
|
670
|
-
same campaign, and operate on the copied review batch. Do not fall back to
|
|
671
|
-
on-demand campaigns or unrelated active-campaign fills.
|
|
672
|
-
|
|
673
|
-
Public concepts are regular campaign and evergreen campaign. Internal direct
|
|
674
|
-
campaign types are unsupported refill targets. `fill_campaign_horizon` is only a
|
|
675
|
-
legacy evergreen-only lower-level primitive after route and refill-state
|
|
676
|
-
evidence proves an evergreen target.
|
|
427
|
+
Do not keep polling after a terminal. `loaded_awaiting_scheduler` is a valid
|
|
428
|
+
terminal, not permission for a host-owned loop. The sole refill-ladder expansion
|
|
429
|
+
that asks the user in `--yolo` is `new_campaign_required`; refill itself never
|
|
430
|
+
creates that campaign.
|
|
431
|
+
|
|
432
|
+
## Non-negotiable authority and proof
|
|
433
|
+
|
|
434
|
+
- Before preparation, approval, or exact-date scheduling, require a validated
|
|
435
|
+
compiled campaign and positive exact target readiness identity from the
|
|
436
|
+
backend compiler: `profileSchemaVersion`, `compilerVersion`,
|
|
437
|
+
`campaignDigest`, `actionColumnId`, action, `pathDigest`,
|
|
438
|
+
`dependsOnColumnIds`, `runCondition`, the selected target's `capabilities`,
|
|
439
|
+
`requestId`, `effectId`, and `dateGuards`. Never infer no-message behavior
|
|
440
|
+
from a missing Generate Message column. Prepare rows according to the
|
|
441
|
+
persisted sequence; bounded enrichment and LLM prerequisite calls are
|
|
442
|
+
allowed only when authorized by the exact readiness packet and receipted.
|
|
443
|
+
- Refuse approval outside the bounded authorized cohort. The exact selected
|
|
444
|
+
target path controls message, subject, comment, reaction, and row approval.
|
|
445
|
+
For exact dates, require the date strictly after sender-local today and inside
|
|
446
|
+
the fill horizon. Prepared, approved, and ready rows remain intermediate
|
|
447
|
+
evidence; completion requires request/effect-attributed scheduler-owned
|
|
448
|
+
scheduled readback.
|
|
449
|
+
- Execute one bounded primitive, then perform a full authoritative reread.
|
|
450
|
+
Never call a LinkedIn outreach/provider-send surface from refill. No direct
|
|
451
|
+
sends, raw scheduler writes, broad approval, threshold/limit changes,
|
|
452
|
+
sender reassignment, destructive cleanup, or new campaign creation.
|
|
453
|
+
- Maintain the target-window saturation ledger per sender: selected days,
|
|
454
|
+
gross capacity, actual sent, future scheduler-owned scheduled with non-null
|
|
455
|
+
`scheduledFor`, projected coverage (`sent + scheduled`), ready buffer,
|
|
456
|
+
remaining gap, paid-InMail feasibility, `targetShapeRevision`, and
|
|
457
|
+
`stateRevision`. Future scheduled coverage and already sent actions are
|
|
458
|
+
distinct.
|
|
459
|
+
- Carry canonical `refill_reporting.v2` unchanged through progress,
|
|
460
|
+
continuation, replay, and terminal output. Completion proof is Sellable MCP
|
|
461
|
+
evidence only: target plan, campaign refill state, scheduler capacity,
|
|
462
|
+
sweep/status, and bounded receipts. Never use individual cell ids, Prisma,
|
|
463
|
+
SQL, direct database access, or production-environment scripts as completion
|
|
464
|
+
proof. Redact raw copy and prospect fields.
|