@sellable/mcp 0.1.0
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/.claude-plugin/plugin.json +12 -0
- package/.mcp.json +9 -0
- package/README.md +355 -0
- package/dist/api.d.ts +21 -0
- package/dist/api.js +73 -0
- package/dist/auth.d.ts +60 -0
- package/dist/auth.js +246 -0
- package/dist/engage-memory.d.ts +63 -0
- package/dist/engage-memory.js +354 -0
- package/dist/index-dev.d.ts +2 -0
- package/dist/index-dev.js +17 -0
- package/dist/index.d.ts +7 -0
- package/dist/index.js +8 -0
- package/dist/server.d.ts +1 -0
- package/dist/server.js +499 -0
- package/dist/skills.d.ts +11 -0
- package/dist/skills.js +97 -0
- package/dist/tools/auth.d.ts +30 -0
- package/dist/tools/auth.js +124 -0
- package/dist/tools/blueprint-commit.d.ts +174 -0
- package/dist/tools/blueprint-commit.js +286 -0
- package/dist/tools/bootstrap.d.ts +64 -0
- package/dist/tools/bootstrap.js +246 -0
- package/dist/tools/campaigns.d.ts +589 -0
- package/dist/tools/campaigns.js +892 -0
- package/dist/tools/cells.d.ts +58 -0
- package/dist/tools/cells.js +48 -0
- package/dist/tools/context.d.ts +88 -0
- package/dist/tools/context.js +271 -0
- package/dist/tools/csv-domains.d.ts +73 -0
- package/dist/tools/csv-domains.js +464 -0
- package/dist/tools/csv-linkedin.d.ts +102 -0
- package/dist/tools/csv-linkedin.js +712 -0
- package/dist/tools/direct-campaigns.d.ts +240 -0
- package/dist/tools/direct-campaigns.js +250 -0
- package/dist/tools/engage-bootstrap.d.ts +94 -0
- package/dist/tools/engage-bootstrap.js +205 -0
- package/dist/tools/engage-discovery.d.ts +78 -0
- package/dist/tools/engage-discovery.js +150 -0
- package/dist/tools/engage-memory.d.ts +181 -0
- package/dist/tools/engage-memory.js +143 -0
- package/dist/tools/engage-state.d.ts +72 -0
- package/dist/tools/engage-state.js +62 -0
- package/dist/tools/enrichment.d.ts +167 -0
- package/dist/tools/enrichment.js +174 -0
- package/dist/tools/flow-preflight.d.ts +68 -0
- package/dist/tools/flow-preflight.js +138 -0
- package/dist/tools/framework.d.ts +44 -0
- package/dist/tools/framework.js +153 -0
- package/dist/tools/interaction-mode.d.ts +27 -0
- package/dist/tools/interaction-mode.js +102 -0
- package/dist/tools/leads.d.ts +2417 -0
- package/dist/tools/leads.js +2307 -0
- package/dist/tools/linkedin.d.ts +210 -0
- package/dist/tools/linkedin.js +229 -0
- package/dist/tools/navigation.d.ts +91 -0
- package/dist/tools/navigation.js +381 -0
- package/dist/tools/one-off.d.ts +229 -0
- package/dist/tools/one-off.js +273 -0
- package/dist/tools/processing.d.ts +70 -0
- package/dist/tools/processing.js +56 -0
- package/dist/tools/prompts.d.ts +211 -0
- package/dist/tools/prompts.js +210 -0
- package/dist/tools/provider-preflight.d.ts +21 -0
- package/dist/tools/provider-preflight.js +59 -0
- package/dist/tools/readiness.d.ts +261 -0
- package/dist/tools/readiness.js +510 -0
- package/dist/tools/rows.d.ts +126 -0
- package/dist/tools/rows.js +105 -0
- package/dist/tools/rubrics.d.ts +497 -0
- package/dist/tools/rubrics.js +681 -0
- package/dist/tools/senders.d.ts +44 -0
- package/dist/tools/senders.js +69 -0
- package/dist/tools/sequencer.d.ts +127 -0
- package/dist/tools/sequencer.js +194 -0
- package/dist/tools/tables.d.ts +35 -0
- package/dist/tools/tables.js +36 -0
- package/dist/tools/verify-row.d.ts +36 -0
- package/dist/tools/verify-row.js +38 -0
- package/dist/tools/workspaces.d.ts +140 -0
- package/dist/tools/workspaces.js +139 -0
- package/dist/utils/workspace-root.d.ts +1 -0
- package/dist/utils/workspace-root.js +39 -0
- package/package.json +46 -0
- package/skills/building-gtm-tables/SKILL.md +216 -0
- package/skills/building-gtm-tables/core/auto-execute.yaml +19 -0
- package/skills/building-gtm-tables/core/blueprint-schema.json +72 -0
- package/skills/building-gtm-tables/references/brief-to-blueprint.md +334 -0
- package/skills/building-gtm-tables/references/column-type-catalog.md +318 -0
- package/skills/building-gtm-tables/references/common-blueprints.fixtures.ts +199 -0
- package/skills/building-gtm-tables/references/common-blueprints.md +44 -0
- package/skills/building-gtm-tables/references/failure-taxonomy.md +197 -0
- package/skills/building-gtm-tables/references/uat-seed-prompts.md +37 -0
- package/skills/building-gtm-tables/references/verify-loop.md +74 -0
- package/skills/campaign-messages/SKILL.md +173 -0
- package/skills/campaign-messages/flow.v1.json +75 -0
- package/skills/craft-message/SKILL.md +401 -0
- package/skills/create-campaign/ARCHITECTURE.md +232 -0
- package/skills/create-campaign/DISCUSS.md +296 -0
- package/skills/create-campaign/FLOW_ASCII.md +240 -0
- package/skills/create-campaign/HOST-PARITY-CHECKLIST.md +49 -0
- package/skills/create-campaign/README.md +142 -0
- package/skills/create-campaign/SKILL.md +286 -0
- package/skills/create-campaign/context/README.md +67 -0
- package/skills/create-campaign/context/_TEMPLATE.md +12 -0
- package/skills/create-campaign/context/context.md +35 -0
- package/skills/create-campaign/context/learnings.md +16 -0
- package/skills/create-campaign/context/registry.json +19 -0
- package/skills/create-campaign/core/flow.v1.json +217 -0
- package/skills/create-campaign/core/policy.md +191 -0
- package/skills/create-campaign/core/providers/apollo.json +35 -0
- package/skills/create-campaign/core/providers/prospeo.json +34 -0
- package/skills/create-campaign/core/providers/registry.json +31 -0
- package/skills/create-campaign/core/providers/sales-nav.json +37 -0
- package/skills/create-campaign/core/providers/signal-discovery.json +42 -0
- package/skills/create-campaign/references/brief-template.md +64 -0
- package/skills/create-campaign/references/campaign-quality.md +84 -0
- package/skills/create-campaign/references/copy-calibration-examples.md +120 -0
- package/skills/create-campaign/references/offer-patterns.md +108 -0
- package/skills/create-campaign/references/provider-selection-strategy.md +212 -0
- package/skills/create-campaign/references/question-examples.md +167 -0
- package/skills/create-campaign/references/token-fill-examples.md +81 -0
- package/skills/create-campaign-brief/ARCHITECTURE.md +72 -0
- package/skills/create-campaign-brief/DISCUSS.md +64 -0
- package/skills/create-campaign-brief/README.md +176 -0
- package/skills/create-campaign-brief/SKILL.md +537 -0
- package/skills/create-campaign-brief/references/brief-synthesis-rules.md +100 -0
- package/skills/create-campaign-brief/references/brief-template.md +220 -0
- package/skills/create-campaign-brief/references/campaign-idea-options.md +30 -0
- package/skills/create-campaign-brief/references/copy-appendix-template.md +62 -0
- package/skills/create-campaign-brief/references/draft-lifecycle.md +23 -0
- package/skills/create-campaign-brief/references/examples/MANIFEST.json +89 -0
- package/skills/create-campaign-brief/references/examples/briefs/clover.md +223 -0
- package/skills/create-campaign-brief/references/examples/briefs/galley.md +222 -0
- package/skills/create-campaign-brief/references/examples/briefs/gelee.md +220 -0
- package/skills/create-campaign-brief/references/examples/briefs/hey-digital.md +234 -0
- package/skills/create-campaign-brief/references/examples/briefs/persona.md +231 -0
- package/skills/create-campaign-brief/references/examples/briefs/revvix.md +220 -0
- package/skills/create-campaign-brief/references/examples/briefs/sellable-dev.md +220 -0
- package/skills/create-campaign-brief/references/examples/briefs/superposition.md +233 -0
- package/skills/create-campaign-brief/references/examples/briefs/superpower.md +219 -0
- package/skills/create-campaign-brief/references/examples/briefs/westpark-villas.md +220 -0
- package/skills/create-campaign-brief/references/icp-lock-question-bank.md +43 -0
- package/skills/create-campaign-brief/references/messaging-inputs.md +58 -0
- package/skills/create-campaign-brief/references/output-acceptance-rubric.md +62 -0
- package/skills/create-campaign-brief/references/phase75-active-runtime-message-pack.md +248 -0
- package/skills/create-campaign-brief/references/phase75-canonical-brief-template.md +319 -0
- package/skills/create-campaign-brief/references/phase75-good-brief-and-messaging-examples.md +445 -0
- package/skills/create-campaign-brief/references/quick-research-protocol.md +39 -0
- package/skills/create-campaign-brief/references/reference-sheet-protocol.md +60 -0
- package/skills/create-campaign-brief/references/zero-shot-iteration-rules.md +66 -0
- package/skills/create-campaign-v2/SKILL.md +1619 -0
- package/skills/create-campaign-v2/core/auto-execute.README.md +219 -0
- package/skills/create-campaign-v2/core/auto-execute.yaml +121 -0
- package/skills/create-campaign-v2/core/flow.v2.json +1643 -0
- package/skills/create-campaign-v2/core/policy.md +82 -0
- package/skills/create-campaign-v2/references/ai-tells.md +253 -0
- package/skills/create-campaign-v2/references/approval-gate-framing.md +346 -0
- package/skills/create-campaign-v2/references/draft-lifecycle.md +110 -0
- package/skills/create-campaign-v2/references/escalation-ladder.md +119 -0
- package/skills/create-campaign-v2/references/filter-leads.md +495 -0
- package/skills/create-campaign-v2/references/final-handoff-contract.md +176 -0
- package/skills/create-campaign-v2/references/gold-standard-message-examples.md +394 -0
- package/skills/create-campaign-v2/references/gold-standard-message-patterns.md +314 -0
- package/skills/create-campaign-v2/references/gold-standard-message-validation-example.md +212 -0
- package/skills/create-campaign-v2/references/lead-validation-preview.md +172 -0
- package/skills/create-campaign-v2/references/parallel-critique-protocol.md +368 -0
- package/skills/create-campaign-v2/references/sample-validation-loop.md +289 -0
- package/skills/create-campaign-v2/references/step-13-import-leads.md +151 -0
- package/skills/create-campaign-v2/references/step-15-re-cascade.md +90 -0
- package/skills/create-campaign-v2/references/thomas-revision-filters.md +521 -0
- package/skills/create-campaign-v2/references/thomas-variant-selection.md +202 -0
- package/skills/create-campaign-v2/references/tier-routing-matrix.md +66 -0
- package/skills/create-campaign-v2/references/validation-criteria.md +367 -0
- package/skills/create-campaign-v2/references/watch-link-handoff.md +106 -0
- package/skills/create-campaign-v2-validation/SKILL.md +296 -0
- package/skills/create-post/SKILL.md +1308 -0
- package/skills/create-rubric/SKILL.md +251 -0
- package/skills/engage/SKILL.md +549 -0
- package/skills/engage/core/README.md +23 -0
- package/skills/engage/core/proven-searches.json +11 -0
- package/skills/engage/core/style-guide.template.md +47 -0
- package/skills/engage/core/tracked-people.json +10 -0
- package/skills/enrich-prospects/SKILL.md +97 -0
- package/skills/find-leads/SKILL.md +467 -0
- package/skills/generate-messages/SKILL.md +2361 -0
- package/skills/interview/SKILL.md +132 -0
- package/skills/interview/core/ENGAGE_STYLE_GUIDE.template.md +54 -0
- package/skills/interview/core/ICP.template.md +54 -0
- package/skills/interview/core/VOICE_PROFILE.template.md +101 -0
- package/skills/providers/apollo.md +520 -0
- package/skills/providers/prospeo.md +398 -0
- package/skills/providers/sales-nav.md +372 -0
- package/skills/providers/signal-discovery.md +495 -0
- package/skills/research/SKILL.md +258 -0
- package/skills/research/config.json +9 -0
- package/skills/research/override.md +13 -0
- package/skills/research-prospect/SKILL.md +99 -0
- package/skills/research-sender/SKILL.md +158 -0
- package/skills/workflow-sequences/SKILL.md +85 -0
|
@@ -0,0 +1,368 @@
|
|
|
1
|
+
# Parallel Critique Protocol
|
|
2
|
+
|
|
3
|
+
This reference governs the optional Step 15 critique pass introduced in
|
|
4
|
+
Plan 85-03. Load this file whenever `messaging.critique.enabled === true`
|
|
5
|
+
in `core/auto-execute.yaml`. When the flag is `false`, the plain Step 15
|
|
6
|
+
path runs unchanged and none of the protocol below applies.
|
|
7
|
+
|
|
8
|
+
## Principle
|
|
9
|
+
|
|
10
|
+
Step 15 (`auto-execute-messaging`) already generates messages under the
|
|
11
|
+
Phase 84 token contract. The critique pass is a bounded, opt-in quality
|
|
12
|
+
lens on top of that output. It is NOT a second drafting pass. It never
|
|
13
|
+
invents new tokens, new proof, or new personalization, and it never
|
|
14
|
+
ships a rewrite that violates the token contract.
|
|
15
|
+
|
|
16
|
+
The critique pass is:
|
|
17
|
+
|
|
18
|
+
- **Bounded.** It runs on `messaging.critique.sampleSize` rows, not
|
|
19
|
+
the full cohort. Cost is predictable whether the cohort is 50 or 500.
|
|
20
|
+
- **Parallel.** Three fixed critic groups (targeting, copy, voice)
|
|
21
|
+
run in parallel and return structured JSON.
|
|
22
|
+
- **Synthesized.** One synthesis step merges the three voices into a
|
|
23
|
+
single rewrite and re-runs the Phase 84 finalizer pass.
|
|
24
|
+
- **Budget-capped.** A trip on `budgetUsdCap` HALTS critique for the
|
|
25
|
+
remaining sample and continues the plain tail. Cost never explodes
|
|
26
|
+
silently.
|
|
27
|
+
- **Token-contract preserving.** Any rewrite that introduces an
|
|
28
|
+
unsupported token, an unresolved token, or invented proof is
|
|
29
|
+
rejected, and the plain generated message is persisted instead.
|
|
30
|
+
|
|
31
|
+
## When the Protocol Loads
|
|
32
|
+
|
|
33
|
+
Load this file at the start of Step 15 if and only if
|
|
34
|
+
`messaging.critique.enabled === true`. Do not load it during Step 13,
|
|
35
|
+
Step 14, or Step 16. Do not load it when the flag is `false` — the
|
|
36
|
+
plain Step 15 path runs unchanged.
|
|
37
|
+
|
|
38
|
+
If the flag is `true` but any critique knob is unparseable (missing
|
|
39
|
+
`sampleSize`, missing `critics`, missing `synthesis`), skip the pass
|
|
40
|
+
and continue on the plain Step 15 path. Log the config error for
|
|
41
|
+
calibration.
|
|
42
|
+
|
|
43
|
+
## Inputs
|
|
44
|
+
|
|
45
|
+
- The messages just drafted by the plain Generate Message cascade
|
|
46
|
+
call at the top of Step 15.
|
|
47
|
+
- `lead-sample.json`, `lead-filter.md`, `brief.md` (read-only — these
|
|
48
|
+
are the same artifacts the plain drafter used).
|
|
49
|
+
- `messaging.critique.*` config values from `auto-execute.yaml`.
|
|
50
|
+
- The Phase 84 token contract rules in
|
|
51
|
+
`references/thomas-revision-filters.md`.
|
|
52
|
+
|
|
53
|
+
## Step Shape
|
|
54
|
+
|
|
55
|
+
```text
|
|
56
|
+
if messaging.critique.enabled == false:
|
|
57
|
+
continue on the plain Step 15 path (no critique, no Opus)
|
|
58
|
+
return
|
|
59
|
+
|
|
60
|
+
loaded = load_auto_execute_config() # already parsed at Step 13
|
|
61
|
+
critique_cfg = loaded.messaging.critique
|
|
62
|
+
|
|
63
|
+
sample = first critique_cfg.sampleSize rows of the drafted messages
|
|
64
|
+
budget_used = 0.0
|
|
65
|
+
results = []
|
|
66
|
+
|
|
67
|
+
for row in sample:
|
|
68
|
+
if budget_used >= critique_cfg.budgetUsdCap:
|
|
69
|
+
log("budget_cap_tripped", row_id=row.id, budget_used=budget_used)
|
|
70
|
+
results.append(plain_message(row)) # fall back silently
|
|
71
|
+
continue
|
|
72
|
+
|
|
73
|
+
critics_out = run_parallel_critics(
|
|
74
|
+
row=row,
|
|
75
|
+
critics=critique_cfg.critics,
|
|
76
|
+
per_critic_timeout_s=critique_cfg.perCriticTimeoutSeconds,
|
|
77
|
+
total_timeout_s=critique_cfg.totalTimeoutSeconds,
|
|
78
|
+
)
|
|
79
|
+
# Timed-out critics are recorded as "no opinion" and skipped; the
|
|
80
|
+
# synthesis proceeds with the remaining voices. A total timeout
|
|
81
|
+
# falls back to the plain message for this row only.
|
|
82
|
+
|
|
83
|
+
rewrite = synthesize(
|
|
84
|
+
plain=row.plain_message,
|
|
85
|
+
critics=critics_out,
|
|
86
|
+
enforce_finalizer_pass=critique_cfg.synthesis.enforceFinalizerPass,
|
|
87
|
+
)
|
|
88
|
+
|
|
89
|
+
if rewrite.violates_token_contract:
|
|
90
|
+
log("rewrite_rejected_token_contract", row_id=row.id, reason=rewrite.reason)
|
|
91
|
+
results.append(plain_message(row)) # persist plain, not rewrite
|
|
92
|
+
continue
|
|
93
|
+
|
|
94
|
+
if critique_cfg.opus.enabled and is_highest_value(row, critics_out):
|
|
95
|
+
if opus_budget_left(critique_cfg.opus):
|
|
96
|
+
opus_rewrite = craft_message(row, rewrite, critics_out)
|
|
97
|
+
if not opus_rewrite.violates_token_contract:
|
|
98
|
+
rewrite = opus_rewrite
|
|
99
|
+
else:
|
|
100
|
+
log("opus_budget_cap_tripped", row_id=row.id)
|
|
101
|
+
|
|
102
|
+
results.append(rewrite)
|
|
103
|
+
budget_used += cost_of(critics_out, rewrite)
|
|
104
|
+
|
|
105
|
+
return results
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
`is_highest_value(...)` and Opus eligibility live in
|
|
109
|
+
`references/thomas-variant-selection.md`. The synthesis step's
|
|
110
|
+
finalizer pass is the same Phase 84 finalizer pass the plain
|
|
111
|
+
`generate-messages` skill already runs.
|
|
112
|
+
|
|
113
|
+
## Critic Groups
|
|
114
|
+
|
|
115
|
+
The three critic groups are fixed. Do NOT invent free-form critics.
|
|
116
|
+
Each critic runs independently, receives the same inputs, and returns
|
|
117
|
+
a strict JSON envelope.
|
|
118
|
+
|
|
119
|
+
### Targeting critic
|
|
120
|
+
|
|
121
|
+
- **Focus:** `signal-lead-fit-and-buyer-intent`
|
|
122
|
+
- **Question it answers:** Is the signal or personalization in this
|
|
123
|
+
message actually supported by the lead row and the validated filter?
|
|
124
|
+
Does the opener reference something the buyer will recognize as
|
|
125
|
+
theirs?
|
|
126
|
+
- **Never answers:** sentence-level copy questions, voice questions,
|
|
127
|
+
motion alignment.
|
|
128
|
+
|
|
129
|
+
### Copy critic
|
|
130
|
+
|
|
131
|
+
- **Focus:** `sentence-shape-proof-cta`
|
|
132
|
+
- **Question it answers:** Does the message follow the one-sentence-
|
|
133
|
+
per-line body shape? Is proof placed where the matched motion
|
|
134
|
+
expects it? Is the CTA proportional to the signal and useful, not a
|
|
135
|
+
naked meeting ask?
|
|
136
|
+
- **Never answers:** whether the lead is a good target, whether the
|
|
137
|
+
tone matches the motion.
|
|
138
|
+
|
|
139
|
+
### Voice critic
|
|
140
|
+
|
|
141
|
+
- **Focus:** `tone-motion-alignment`
|
|
142
|
+
- **Question it answers:** Does this read like the Primary Example
|
|
143
|
+
motion (casual-lowercase if Superpower, dense-credentialed if
|
|
144
|
+
Superposition)? Does any sentence introduce jargon a 5th-grade
|
|
145
|
+
reader wouldn't parse? Does the PS carry the right shape (thin
|
|
146
|
+
narrative, at most two beats)?
|
|
147
|
+
- **Never answers:** targeting, proof math, CTA heaviness.
|
|
148
|
+
|
|
149
|
+
## Critic JSON Envelope
|
|
150
|
+
|
|
151
|
+
Every critic returns exactly this envelope. Unknown fields are
|
|
152
|
+
rejected. Missing fields cause the critic to be dropped from synthesis
|
|
153
|
+
(treated as "no opinion") rather than blocking the tail.
|
|
154
|
+
|
|
155
|
+
```json
|
|
156
|
+
{
|
|
157
|
+
"criticId": "targeting" | "copy" | "voice",
|
|
158
|
+
"verdict": "keep" | "revise" | "reject",
|
|
159
|
+
"revisions": [
|
|
160
|
+
{
|
|
161
|
+
"line": "<original sentence or block>",
|
|
162
|
+
"suggestion": "<rewritten sentence or block>",
|
|
163
|
+
"reason": "<one-line rationale grounded in brief / lead-sample>"
|
|
164
|
+
}
|
|
165
|
+
],
|
|
166
|
+
"tokenNotes": [
|
|
167
|
+
{
|
|
168
|
+
"token": "{{example}}",
|
|
169
|
+
"status": "supported" | "unresolved" | "unsupported" | "proposed",
|
|
170
|
+
"evidence": "<field path in lead-sample.json or brief.md>"
|
|
171
|
+
}
|
|
172
|
+
],
|
|
173
|
+
"confidence": 0.0
|
|
174
|
+
}
|
|
175
|
+
```
|
|
176
|
+
|
|
177
|
+
Rules on the envelope:
|
|
178
|
+
|
|
179
|
+
- `verdict = reject` is reserved for rewrites the critic believes
|
|
180
|
+
cannot be saved (fake proof request, unsupported token the
|
|
181
|
+
synthesizer cannot drop, motion mismatch that needs brief revision).
|
|
182
|
+
A `reject` verdict short-circuits synthesis and falls back to the
|
|
183
|
+
plain message.
|
|
184
|
+
- `tokenNotes[*].status = "proposed"` is how a critic surfaces a NEW
|
|
185
|
+
token idea (e.g. a fresh field the brief doesn't yet document). The
|
|
186
|
+
synthesis step MUST NOT promote a proposed token into the persisted
|
|
187
|
+
rewrite. Proposed tokens appear in the optional operator review
|
|
188
|
+
surface only.
|
|
189
|
+
- `revisions[*].suggestion` must contain at most the same set of
|
|
190
|
+
tokens the plain message used, minus tokens the critic explicitly
|
|
191
|
+
asked to drop. It may never introduce a token that isn't supported
|
|
192
|
+
in `tokenNotes`.
|
|
193
|
+
|
|
194
|
+
## Synthesis
|
|
195
|
+
|
|
196
|
+
One synthesis step takes the plain message plus the three critic
|
|
197
|
+
envelopes and returns a single rewrite. It runs on the
|
|
198
|
+
`synthesis.model` tier configured in `auto-execute.yaml` (kept
|
|
199
|
+
intentionally vague here so the tier can be retuned without touching
|
|
200
|
+
the protocol file).
|
|
201
|
+
|
|
202
|
+
Rules:
|
|
203
|
+
|
|
204
|
+
1. **Token contract first.** The synthesizer enforces the Phase 84
|
|
205
|
+
token contract verbatim:
|
|
206
|
+
|
|
207
|
+
- Use only tokens documented in `brief.md` as supported fields.
|
|
208
|
+
- Reject any unresolved `{{token}}`.
|
|
209
|
+
- Reject any token flagged `unsupported` by any critic.
|
|
210
|
+
- Reject any rewrite that invents a metric, logo, customer,
|
|
211
|
+
funding detail, or product claim.
|
|
212
|
+
- A `proposed` token requires human approval and does NOT appear
|
|
213
|
+
in the persisted rewrite.
|
|
214
|
+
|
|
215
|
+
2. **One primary example.** Preserve the matched archived motion's
|
|
216
|
+
voice. Do not blend two motions into one rewrite.
|
|
217
|
+
|
|
218
|
+
3. **Finalizer pass last.** If `synthesis.enforceFinalizerPass` is
|
|
219
|
+
`true` (default), re-run the Phase 84 finalizer pass on the
|
|
220
|
+
synthesis output. The finalizer is the last guardrail against
|
|
221
|
+
critic-introduced token drift. A finalizer failure falls back to
|
|
222
|
+
the plain message for that row.
|
|
223
|
+
|
|
224
|
+
4. **PS earns its place.** If the synthesizer adds or moves a PS, the
|
|
225
|
+
PS must pass the revision-filter shape rules (thin narrative, at
|
|
226
|
+
most two beats, no resume list, no fake credential).
|
|
227
|
+
|
|
228
|
+
5. **Conservative merge.** When two critics disagree, prefer the
|
|
229
|
+
critic whose concern maps to the motion's weakest current link
|
|
230
|
+
(e.g. prefer the copy critic when the plain message is too dense;
|
|
231
|
+
prefer the voice critic when the plain message is tone-off).
|
|
232
|
+
Never synthesize toward a third option the critics didn't raise.
|
|
233
|
+
|
|
234
|
+
## Budget Control
|
|
235
|
+
|
|
236
|
+
Two independent caps:
|
|
237
|
+
|
|
238
|
+
- **`budgetUsdCap`** — total for critics + synthesis across the whole
|
|
239
|
+
pass. Tripping it halts critique for the remaining sample rows and
|
|
240
|
+
persists the plain message for those rows. The trip is logged as
|
|
241
|
+
`budget_cap_tripped` with the row id and the running cost. The
|
|
242
|
+
autonomous tail continues; Step 15 does NOT stall.
|
|
243
|
+
- **`opus.budgetUsdCap`** — separate cap for the Opus subset. Opus
|
|
244
|
+
trip halts Opus escalation for the remaining rows of the current
|
|
245
|
+
pass only; non-Opus critique continues normally. Also logged as
|
|
246
|
+
`opus_budget_cap_tripped`.
|
|
247
|
+
|
|
248
|
+
Neither cap causes an escalation (in the `escalation-ladder.md`
|
|
249
|
+
sense). Budget trips are planned early exits of the critique path,
|
|
250
|
+
not skill-level failures.
|
|
251
|
+
|
|
252
|
+
## Timeout Handling
|
|
253
|
+
|
|
254
|
+
- **`perCriticTimeoutSeconds`** (default 30) — a critic that misses
|
|
255
|
+
this cap is treated as "no opinion" for this row. Synthesis
|
|
256
|
+
proceeds with the remaining critic voices. Logged as
|
|
257
|
+
`critic_timeout` with `criticId` and `rowId`.
|
|
258
|
+
- **`totalTimeoutSeconds`** (default 120) — if the full parallel
|
|
259
|
+
group has not returned by this cap, the tail falls back to the
|
|
260
|
+
plain generated message for this row. Logged as
|
|
261
|
+
`critique_total_timeout` with `rowId`.
|
|
262
|
+
|
|
263
|
+
Timeouts never block the tail. A timed-out row is persisted as the
|
|
264
|
+
plain message and the loop continues to the next row.
|
|
265
|
+
|
|
266
|
+
## Fake Proof Rejection
|
|
267
|
+
|
|
268
|
+
`rejectOnFakeProof: true` (default) means: any rewrite that
|
|
269
|
+
introduces a metric, logo, customer name, funding detail, or product
|
|
270
|
+
claim that is not supported by `brief.md` is rejected. The rejection
|
|
271
|
+
logs `rewrite_rejected_fake_proof` with the offending claim and the
|
|
272
|
+
row id. The plain generated message is persisted for that row.
|
|
273
|
+
|
|
274
|
+
Critics may ask for stronger proof; the synthesizer may only
|
|
275
|
+
surface stronger proof that is already in the brief. If the brief
|
|
276
|
+
cannot support the critic's ask, the rewrite falls back.
|
|
277
|
+
|
|
278
|
+
## Unsupported Token Rejection
|
|
279
|
+
|
|
280
|
+
`rejectOnUnsupportedToken: true` (default) means: any rewrite that
|
|
281
|
+
introduces a `{{token}}` not declared in the brief is rejected. Two
|
|
282
|
+
sub-rules:
|
|
283
|
+
|
|
284
|
+
1. **Unresolved tokens** (tokens that expand to empty) are rejected.
|
|
285
|
+
The plain message's unresolved-token safety rules still apply.
|
|
286
|
+
2. **Proposed tokens** (new fields a critic believes should exist)
|
|
287
|
+
may surface for operator review but do NOT appear in the
|
|
288
|
+
persisted rewrite. The synthesizer strips them before finalization.
|
|
289
|
+
|
|
290
|
+
A rewrite that only differs from the plain message by an unsupported
|
|
291
|
+
token is a silent fallback to the plain message.
|
|
292
|
+
|
|
293
|
+
## Opus / Craft-Message Subset
|
|
294
|
+
|
|
295
|
+
Opus is reserved for the highest-value subset only. Default config
|
|
296
|
+
leaves `opus.enabled = false`. When enabled, the rules from
|
|
297
|
+
`references/thomas-variant-selection.md` decide which rows earn Opus.
|
|
298
|
+
Hard caps:
|
|
299
|
+
|
|
300
|
+
- `opus.maxMessagesPerPass` (default 2) — never escalate more rows
|
|
301
|
+
than this per critique pass.
|
|
302
|
+
- `opus.budgetUsdCap` (default 1.5 USD) — independent of the overall
|
|
303
|
+
critique budget.
|
|
304
|
+
|
|
305
|
+
An Opus rewrite runs the same token-contract enforcement as the
|
|
306
|
+
non-Opus synthesis output. A fake-proof or unsupported-token Opus
|
|
307
|
+
rewrite is rejected the same way.
|
|
308
|
+
|
|
309
|
+
## Fallback Behavior
|
|
310
|
+
|
|
311
|
+
Every critique-side failure mode falls back to the plain generated
|
|
312
|
+
message for that row. None of them escalate:
|
|
313
|
+
|
|
314
|
+
- Critique flag `false` → no critic calls, no Opus, plain Step 15
|
|
315
|
+
path runs.
|
|
316
|
+
- Config unparseable → no critic calls, plain Step 15 path runs.
|
|
317
|
+
- Per-critic timeout → synthesis proceeds without that critic.
|
|
318
|
+
- Total timeout → plain message persisted for that row.
|
|
319
|
+
- Budget cap tripped → plain message persisted for remaining rows.
|
|
320
|
+
- Opus budget tripped → rest of pass runs non-Opus.
|
|
321
|
+
- Token contract violation (fake proof, unsupported token, proposed
|
|
322
|
+
token leaked) → plain message persisted for that row.
|
|
323
|
+
- Synthesis finalizer pass failure → plain message persisted.
|
|
324
|
+
|
|
325
|
+
The tail continues. Step 15 does NOT stall on critique failures.
|
|
326
|
+
|
|
327
|
+
## Logging
|
|
328
|
+
|
|
329
|
+
When `logging.logEveryThresholdTrip === true` (default), log each of
|
|
330
|
+
these events with the row id (when applicable) and the relevant
|
|
331
|
+
figures:
|
|
332
|
+
|
|
333
|
+
- `critique_pass_start` (row count, sample cap)
|
|
334
|
+
- `critic_result` (criticId, verdict, confidence)
|
|
335
|
+
- `critic_timeout` (criticId, rowId)
|
|
336
|
+
- `critique_total_timeout` (rowId)
|
|
337
|
+
- `rewrite_rejected_token_contract` (rowId, reason)
|
|
338
|
+
- `rewrite_rejected_fake_proof` (rowId, claim)
|
|
339
|
+
- `budget_cap_tripped` (rowId, budgetUsed)
|
|
340
|
+
- `opus_selected` (rowId, reason)
|
|
341
|
+
- `opus_budget_cap_tripped` (rowId)
|
|
342
|
+
- `critique_pass_end` (rowsCritiqued, rowsFallback, totalCost)
|
|
343
|
+
|
|
344
|
+
These logs drive `auto-execute.yaml` tuning after 3-5 real critique
|
|
345
|
+
passes.
|
|
346
|
+
|
|
347
|
+
## Hard Rules
|
|
348
|
+
|
|
349
|
+
- Critique is OFF by default. Plan 85-02 never flips the flag.
|
|
350
|
+
- Sample size is bounded by `messaging.critique.sampleSize`. The
|
|
351
|
+
pass NEVER runs against the full cohort.
|
|
352
|
+
- Three critic groups only: targeting, copy, voice. Do NOT add a
|
|
353
|
+
fourth.
|
|
354
|
+
- Every critic returns the structured JSON envelope above. Free-form
|
|
355
|
+
critic output is discarded.
|
|
356
|
+
- The synthesizer enforces the Phase 84 token contract verbatim.
|
|
357
|
+
- The synthesis step MUST re-run the finalizer pass when
|
|
358
|
+
`enforceFinalizerPass === true` (default).
|
|
359
|
+
- Opus is reserved for the highest-value subset only and gated
|
|
360
|
+
independently from the main critique flag.
|
|
361
|
+
- Budget trips halt the pass gracefully and continue the tail on
|
|
362
|
+
plain messages.
|
|
363
|
+
- Fake proof and unsupported tokens ALWAYS fall back to the plain
|
|
364
|
+
message — never ship the rewrite.
|
|
365
|
+
- A proposed new token may surface for human approval but does NOT
|
|
366
|
+
appear in the persisted rewrite.
|
|
367
|
+
- No critique failure escalates in the `escalation-ladder.md` sense.
|
|
368
|
+
The plain path is always a valid fallback.
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
# Sample Validation Loop
|
|
2
|
+
|
|
3
|
+
This reference governs Step 14 (`validate-sample`) of the Plan 85-02
|
|
4
|
+
autonomous tail. Load this file before enriching + scoring the sample, and
|
|
5
|
+
on every revision round.
|
|
6
|
+
|
|
7
|
+
## Principle
|
|
8
|
+
|
|
9
|
+
We spend a bounded review batch (default 25 rows) to prove fit before the
|
|
10
|
+
user spends credits on hundreds more leads. The sample loop has one job:
|
|
11
|
+
answer the question "do we have enough real passing examples for the user to
|
|
12
|
+
judge this campaign?"
|
|
13
|
+
|
|
14
|
+
If the answer is yes, proceed to Step 15 messaging for the review batch. If
|
|
15
|
+
the answer is no, diagnose whether the brief is wrong or the list is wrong,
|
|
16
|
+
and either revise brief autonomously OR escalate to the user — never
|
|
17
|
+
auto-revise leads.
|
|
18
|
+
|
|
19
|
+
## Inputs
|
|
20
|
+
|
|
21
|
+
- `CampaignOffer.currentStep === "validate-sample"` (set at end of Step 13)
|
|
22
|
+
- Imported review batch from Step 13 (size: `importLimit`, default 25)
|
|
23
|
+
- Config from `auto-execute.yaml`: `sample.sampleSize`,
|
|
24
|
+
`sample.minProjectedPass`, `sample.maxRevisionRounds`
|
|
25
|
+
- Persisted counter `revisionRound` (starts at 0 on first entry; persists
|
|
26
|
+
across resume)
|
|
27
|
+
|
|
28
|
+
## Loop Shape
|
|
29
|
+
|
|
30
|
+
```text
|
|
31
|
+
1. pick first sampleSize rows from the imported review batch
|
|
32
|
+
|
|
33
|
+
2. paginate the campaign table to collect enrichCellIds where
|
|
34
|
+
enrichStatus = "pending" for the sample rows
|
|
35
|
+
- use get_rows_minimal (NOT get_rows); the default response embeds
|
|
36
|
+
full LinkedIn post content in carryData (~2KB/row)
|
|
37
|
+
- strip carryData from every row BEFORE retaining in tail context
|
|
38
|
+
(see §Known Tool Behaviors #4)
|
|
39
|
+
|
|
40
|
+
3. queue_cells(enrichCellIds, batchSize <= 100)
|
|
41
|
+
- confirm_lead_list does NOT auto-enqueue enrichment on imports
|
|
42
|
+
into an existing workflow table; Step 14 must enqueue manually
|
|
43
|
+
(see §Known Tool Behaviors #1)
|
|
44
|
+
|
|
45
|
+
4. wait for enrichment completion (poll via get_rows_minimal with
|
|
46
|
+
the enrichment batch ids; NOT by calling wait_for_rubric_results
|
|
47
|
+
before rubric was scheduled)
|
|
48
|
+
|
|
49
|
+
5. check_rubric(sample)
|
|
50
|
+
|
|
51
|
+
6. wait_for_rubric_results(sample, targetCount = <cohortSize>)
|
|
52
|
+
- cohortSize = stats.totalRows of the enrichment batch, or the
|
|
53
|
+
imported batch count
|
|
54
|
+
- default targetCount=25 matches the default review batch, but pass the
|
|
55
|
+
explicit batch count anyway so future larger expansion batches do not
|
|
56
|
+
accidentally stop early
|
|
57
|
+
(see §Known Tool Behaviors #3)
|
|
58
|
+
|
|
59
|
+
7. read the result via a subagent; extract ONLY:
|
|
60
|
+
- ready: boolean
|
|
61
|
+
- passRate.completed: number
|
|
62
|
+
- stats: object
|
|
63
|
+
Never retain the full rows payload in tail context.
|
|
64
|
+
- 25 rows ≈ 67KB of payload (full post bodies in carryData)
|
|
65
|
+
- 100 rows ≈ 268KB; 500 rows ≈ 1.3MB — blows context on Opus
|
|
66
|
+
(see §Known Tool Behaviors #2)
|
|
67
|
+
|
|
68
|
+
8. compute metrics:
|
|
69
|
+
passInSample = count of review-batch rows where rubric passed
|
|
70
|
+
projectedPass = round(passInSample / sampleSize * importLimit)
|
|
71
|
+
|
|
72
|
+
9. branch:
|
|
73
|
+
if projectedPass >= minProjectedPass:
|
|
74
|
+
proceed to Step 15 (auto-execute-messaging) with this review batch
|
|
75
|
+
else:
|
|
76
|
+
diagnose (see Brief-vs-List Diagnosis below)
|
|
77
|
+
revisionRound += 1
|
|
78
|
+
if revisionRound > maxRevisionRounds:
|
|
79
|
+
ESCALATE (see references/escalation-ladder.md)
|
|
80
|
+
else if diagnosis == "brief":
|
|
81
|
+
autonomous update_campaign_brief with diagnostic
|
|
82
|
+
rerun sample from step 1 of this loop
|
|
83
|
+
else if diagnosis == "list":
|
|
84
|
+
ESCALATE — do NOT auto-revise leads
|
|
85
|
+
else: # unknown
|
|
86
|
+
ESCALATE — do not guess
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
## Known Tool Behaviors (work around these)
|
|
90
|
+
|
|
91
|
+
These are observed tool behaviors that Step 14 must compensate for.
|
|
92
|
+
They are documented here so the tail does not accidentally assume
|
|
93
|
+
"enrichment auto-runs" or "rubric wait is context-cheap."
|
|
94
|
+
|
|
95
|
+
### 1. `confirm_lead_list` does NOT auto-enqueue enrichment
|
|
96
|
+
|
|
97
|
+
Observed: on imports into an existing workflow table (e.g. signal
|
|
98
|
+
discovery), `confirm_lead_list` returns `autoEnrichedCount=0`,
|
|
99
|
+
`enrichmentCellsCopied=0`, `enrichmentCellsPending=0`. Enrichment does
|
|
100
|
+
NOT kick off on its own.
|
|
101
|
+
|
|
102
|
+
Workaround: Step 14 must explicitly paginate the campaign table,
|
|
103
|
+
collect pending `enrichCellId`s, and call `queue_cells` before any
|
|
104
|
+
`wait_for_rubric_results` call. The plan text cannot assume
|
|
105
|
+
enrichment runs silently after import.
|
|
106
|
+
|
|
107
|
+
### 2. `wait_for_rubric_results` returns full rows payload
|
|
108
|
+
|
|
109
|
+
Observed: default response includes `carryData.Post Content` with
|
|
110
|
+
whole LinkedIn post bodies. ~67KB per 25 rows. ~268KB per 100 rows.
|
|
111
|
+
Retaining the full payload across tail turns blows Opus context.
|
|
112
|
+
|
|
113
|
+
Workaround (short-term): invoke `wait_for_rubric_results` inside a
|
|
114
|
+
subagent turn and extract ONLY `ready`, `passRate.completed`, and
|
|
115
|
+
`stats`. Never put the raw rows payload back into the tail's own
|
|
116
|
+
context. Prefer `get_rows_minimal` (not `get_rows`) when row-level
|
|
117
|
+
inspection is required, and strip `carryData` before retention.
|
|
118
|
+
|
|
119
|
+
Workaround (long-term): add a `includeRows=false` / stats-only mode
|
|
120
|
+
to `wait_for_rubric_results` at the MCP tool level. Tracked as a
|
|
121
|
+
backlog item; until then the subagent-read pattern is the contract.
|
|
122
|
+
|
|
123
|
+
### 3. `wait_for_rubric_results.targetCount` defaults to 25
|
|
124
|
+
|
|
125
|
+
Observed: default `targetCount=25` returns `ready=true` after the first 25
|
|
126
|
+
rubric completions. This matches the default review batch, but if a future
|
|
127
|
+
expansion batch is larger, pass the explicit batch size instead of relying on
|
|
128
|
+
the default. The completion semantic is `passRate.completed`, NOT "table
|
|
129
|
+
fully enriched and scored."
|
|
130
|
+
|
|
131
|
+
Workaround: always pass `targetCount = cohortSize` explicitly. Compute
|
|
132
|
+
`cohortSize` from `stats.totalRows` or the imported batch count.
|
|
133
|
+
Treat `needsEnrichCount > 0` after `ready=true` as "workers still
|
|
134
|
+
processing the queued batch," not "new cells need queuing" —
|
|
135
|
+
DO NOT re-call `queue_cells` in that case.
|
|
136
|
+
|
|
137
|
+
### 4. `get_rows_minimal.carryData` includes full post content
|
|
138
|
+
|
|
139
|
+
Observed: cell-discovery pagination during the enrich-queue step
|
|
140
|
+
returns rows whose `carryData` includes full LinkedIn post content
|
|
141
|
+
(~2KB/row). Iterating the full cohort with default options and
|
|
142
|
+
keeping the result in the tail turn blows context the same way (2).
|
|
143
|
+
|
|
144
|
+
Workaround: strip `carryData` from each row IMMEDIATELY after reading
|
|
145
|
+
and BEFORE retaining. Prefer a minimal projection (name, title,
|
|
146
|
+
company, enrichCellId, enrichStatus) over the default shape.
|
|
147
|
+
|
|
148
|
+
## Projected Pass Math
|
|
149
|
+
|
|
150
|
+
```text
|
|
151
|
+
projectedPass = round(passInSample / sampleSize * importLimit)
|
|
152
|
+
```
|
|
153
|
+
|
|
154
|
+
Worked examples with defaults (sampleSize=25, importLimit=25):
|
|
155
|
+
|
|
156
|
+
| passInSample | projectedPass | Handoff? (minProjectedPass=5) |
|
|
157
|
+
| ------------ | ------------- | ----------------------------- |
|
|
158
|
+
| 0 | 0 | No — escalate or revise |
|
|
159
|
+
| 4 | 4 | No — escalate or revise |
|
|
160
|
+
| 5 | 5 | Yes — exactly at floor |
|
|
161
|
+
| 10 | 10 | Yes |
|
|
162
|
+
| 15 | 15 | Yes |
|
|
163
|
+
| 25 | 25 | Yes |
|
|
164
|
+
|
|
165
|
+
When non-default importLimit/sampleSize are configured, the math scales
|
|
166
|
+
the same way. In the default review-batch mode, the sample size equals the
|
|
167
|
+
import limit, so the projection is the actual review-batch pass count.
|
|
168
|
+
|
|
169
|
+
## Brief-vs-List Diagnosis
|
|
170
|
+
|
|
171
|
+
Before blindly revising, classify the failure. Three buckets:
|
|
172
|
+
|
|
173
|
+
### Brief problem
|
|
174
|
+
|
|
175
|
+
Rubric fires cleanly but the passing sample fails voice/value-prop
|
|
176
|
+
alignment, OR messages repeatedly fail token contract, OR pass criteria
|
|
177
|
+
reflect a thesis the brief doesn't actually support.
|
|
178
|
+
|
|
179
|
+
Signal examples:
|
|
180
|
+
|
|
181
|
+
- Sample has ICP-matching leads, but drafted messages (if we drafted any)
|
|
182
|
+
fail token resolution because the brief's proof inventory is thin.
|
|
183
|
+
- Rubric pass rate is OK in absolute terms but the passing rows all fail
|
|
184
|
+
a downstream anchor (e.g. "mentions X pain point") that the brief
|
|
185
|
+
never supplied evidence for.
|
|
186
|
+
- Same token repeatedly can't resolve across multiple sample runs.
|
|
187
|
+
|
|
188
|
+
Action: autonomous `update_campaign_brief` with the specific diagnostic
|
|
189
|
+
(e.g. "add case-study proof for pain point Y"). Rerun sample. Increment
|
|
190
|
+
revisionRound.
|
|
191
|
+
|
|
192
|
+
#### Worked example — horizontal-SaaS seniority drift (Plan 95-03 Ambral)
|
|
193
|
+
|
|
194
|
+
Symptom observed during Plan 95-03 Ambral walkthrough: 14/30 (47%)
|
|
195
|
+
sample was FIT+MAYBE, but the MAYBE cluster was driven by sub-D-round
|
|
196
|
+
"VP Customer Success" titles. These rows match the title whitelist but
|
|
197
|
+
likely don't own budget at that stage.
|
|
198
|
+
|
|
199
|
+
Classification: **BRIEF problem**, not list.
|
|
200
|
+
|
|
201
|
+
Reasoning:
|
|
202
|
+
|
|
203
|
+
- Rubric fires cleanly (reasons match brief §5 ICP).
|
|
204
|
+
- Sample is NOT wrong-function or wrong-geo — titles are correct.
|
|
205
|
+
- The gap is in the brief's §5 seniority rule, which is too permissive
|
|
206
|
+
for horizontal-SaaS (where "VP CS" at a Series B can be a 2-person
|
|
207
|
+
team with no budget authority, vs "VP CS" at Series D which is a
|
|
208
|
+
proper budget owner).
|
|
209
|
+
|
|
210
|
+
Action: autonomous `update_campaign_brief` with diagnostic "tighten
|
|
211
|
+
§5 seniority to D-round and later; Series B-C VP CS titles do not
|
|
212
|
+
own expansion budget in horizontal-SaaS verticals." Rerun sample.
|
|
213
|
+
Expected: 2-3 rows graduate from MAYBE → FIT after the seniority
|
|
214
|
+
tightening + the corresponding rubric tightening. revisionRound = 1.
|
|
215
|
+
|
|
216
|
+
Do NOT classify this as a list problem. The list is sourced correctly;
|
|
217
|
+
the brief's buyer-specificity is under-tuned.
|
|
218
|
+
|
|
219
|
+
### List problem
|
|
220
|
+
|
|
221
|
+
Rubric fires cleanly, but the sample is overwhelmingly outside ICP, OR
|
|
222
|
+
enrichment yields blanks (name/title/company missing), OR rubric pass
|
|
223
|
+
rate is zero with no pattern that brief editing could fix.
|
|
224
|
+
|
|
225
|
+
Signal examples:
|
|
226
|
+
|
|
227
|
+
- 10/10 rows are wrong function / wrong seniority / wrong geo.
|
|
228
|
+
- 8/10 rows enrich to empty strings for the tokens the brief needs.
|
|
229
|
+
- Rubric passes 0 rows and the failure reasons are all "not ICP."
|
|
230
|
+
- **Marketplace supply-side contamination:** >20% of sample rows match
|
|
231
|
+
the brief §14 forbidden side (e.g. Skillsync sample contains
|
|
232
|
+
supply-side GitHub contributors; the brief explicitly forbids
|
|
233
|
+
outbound to that side). This is a sourcing contamination, NOT a
|
|
234
|
+
brief tightening. Escalate for operator re-source. See
|
|
235
|
+
`references/filter-leads.md` §Marketplace safety rail.
|
|
236
|
+
|
|
237
|
+
Action: STOP. Escalate with the explicit diagnostic "the sourced list
|
|
238
|
+
looks bad — revise leads or start over." Do NOT call
|
|
239
|
+
`update_campaign_brief`. Do NOT silently rerun `import_leads`.
|
|
240
|
+
|
|
241
|
+
### Unknown
|
|
242
|
+
|
|
243
|
+
Neither bucket is clean. Signals are mixed, or the rubric result is
|
|
244
|
+
uninterpretable (e.g. `save_rubrics` was bypassed upstream and the
|
|
245
|
+
rubric never actually fired).
|
|
246
|
+
|
|
247
|
+
Action: STOP. Escalate. Do not guess.
|
|
248
|
+
|
|
249
|
+
## Token Contract Check
|
|
250
|
+
|
|
251
|
+
When `messaging.tokenContract === "strict"` (default), the sample
|
|
252
|
+
validation loop ALSO dry-checks token resolution on the enriched sample
|
|
253
|
+
before declaring success. If tokens fail to resolve on the sample, that's
|
|
254
|
+
a brief problem (the brief is asking for tokens the enrichment can't
|
|
255
|
+
provide), not a list problem — unless enrichment is returning blanks,
|
|
256
|
+
which is a list problem.
|
|
257
|
+
|
|
258
|
+
Prefer the brief diagnosis when a specific token is missing and the brief
|
|
259
|
+
could be edited to stop asking for it. Prefer the list diagnosis when
|
|
260
|
+
enrichment itself is incomplete.
|
|
261
|
+
|
|
262
|
+
## Revision Round Counter
|
|
263
|
+
|
|
264
|
+
- Increments only on a failed round (projectedPass < minProjectedPass +
|
|
265
|
+
diagnosis acted on).
|
|
266
|
+
- Persists with the committed campaign metadata; does NOT live in
|
|
267
|
+
session memory.
|
|
268
|
+
- On skill resume, reads the persisted counter. Does NOT reset to 0.
|
|
269
|
+
- Trip: `revisionRound > maxRevisionRounds` ⇒ escalate.
|
|
270
|
+
- Logged on every increment so `auto-execute.yaml` tuning has data.
|
|
271
|
+
|
|
272
|
+
## Logging
|
|
273
|
+
|
|
274
|
+
When `logging.logEveryThresholdTrip === true` (default), log:
|
|
275
|
+
|
|
276
|
+
- Every `projectedPass` computation (with inputs + result).
|
|
277
|
+
- Every diagnosis decision (brief / list / unknown) and the reason.
|
|
278
|
+
- Every `revisionRound` increment.
|
|
279
|
+
- Every escalation trigger (which condition fired).
|
|
280
|
+
|
|
281
|
+
## Hard Rules
|
|
282
|
+
|
|
283
|
+
- A list problem NEVER triggers an autonomous `update_campaign_brief`.
|
|
284
|
+
- `projectedPass` math uses rounding, not floor/ceil.
|
|
285
|
+
- The counter persists across resume.
|
|
286
|
+
- The loop never retries the same sample silently — a rerun always goes
|
|
287
|
+
through diagnosis + brief revision first.
|
|
288
|
+
- The loop never calls `import_leads` again. Full-cohort import is a
|
|
289
|
+
Step 13 action, not a recovery action for a bad sample.
|