Read one Outreach Hub campaign's test numbers against its own kill and scale rules and recommend scale, keep testing or kill, or say there is not enough data yet, in one sentence that quotes the numbers behind it. Also says which rule lines were crossed and which could not be judged. Use each morning and whenever a test's numbers change. Recommends only: never changes a stage, sends, spends or makes the call, which is always a person's.
campaign-verdict/SKILL.md12.7 KB314 lines
When asked which version of this skill is active, report metadata.version exactly.
Answer only with JSON that matches schemas/verdict.schema.json.
You read one campaign's numbers and its kill and scale rules, and you recommend what a person should do next. The desk shows your answer on the campaign as Claude's call; a person decides. Never write as if anything has happened or will happen because of your answer.
What you are given
A JSON object matching schemas/verdict-input.schema.json:
campaign:nameandchannels(email,callor both).rules: this campaign's own rules, as campaign-planner wrote them. Rates are fractions:0.015is 1.5%. They are usually the house rules in campaign-planner's SKILL.md, but always judge by the ones you are given.bounce_min_sends: sends before the bounce line is judged. It is the same floor the campaign card uses before it flags a bounce rate.numbers:sent,bounced,positive_replies,dials,connects,meetings.
Null is unknown, never zero
A count is null when the desk has no source for it yet or has not synced
it. Never read null as 0. A line that needs a null number is unknown,
and an unknown line can never cross a bar. "No positive replies yet" and
"positive replies not counted yet" are different sentences; only the first
may come from a 0.
Smartlead's reply count is not in the input on purpose: it includes out-of-office and no-thanks replies. Never estimate positive replies from anything else you are given.
The lines
Judge only the lines for the campaign's channels. Set the others to null
in checks. meetings_scale always applies.
| Check | Channel | Sample | Rate | Crossed when |
|---|---|---|---|---|
positive_kill | sent of at least minSends | positive_replies / sent | rate is under positiveRate.kill | |
positive_scale | sent of at least minSends | positive_replies / sent | rate is over positiveRate.scale | |
connect_kill | call | dials of at least minDials | connects / dials | rate is under connectRate.kill |
connect_scale | call | dials of at least minDials | connects / dials | rate is over connectRate.scale |
bounce_kill | sent of at least bounce_min_sends | bounced / sent | rate is over maxBounceRate | |
meetings_scale | any | none | none | meetings is at least scaleMeetings |
"Under" and "over" are strict: a rate exactly on a line has not crossed it. Compare the exact fraction, never a rounded one.
Give each line a status, deciding in this order:
unknownwhen the sample count (sentordials) is null.under_minimumwhen the sample count is below the line's minimum.unknownwhen the other count the line needs is null.crossedornot_crossed.
observed is what the line was judged on, as a person writes it:
"0.4% after 612 sends", "3.9% after 155 dials", "312 of 400 sends",
"1 meeting". It is null only when the status is unknown.
The verdict
Take the first that applies:
- Bounce kill beats everything.
bounce_killcrossed:kill, even when replies or meetings would scale. A list that bounces burns the sending domains for every campaign. - Meetings clear the scale bar on their own.
meetings_scalecrossed:scale, whatever the sample sizes, and even if a channel crossed its kill line. Meetings are what the test is for. - A channel that scales wins.
positive_scaleorconnect_scalecrossed:scale. When the other channel crossed its kill line, stillscale, and the reason says which channel to scale and which to stop. The audience answered; the failing channel is the thing to drop. - Kill only when every channel says kill. Every channel the campaign
uses has crossed its kill line (
positive_killfor email,connect_killfor calls):kill. A kill on one channel while the other is still under its minimum or unknown iskeep_testing, so an unfinished channel is never killed for the other's result. - A reply or connect line was judged. Any of
positive_kill,positive_scale,connect_killorconnect_scaleiscrossedornot_crossed:keep_testing. - Otherwise
not_enough_data. A bounce rate under its line, or fewer meetings than the bar, does not say whether the audience answers, so neither makes a test judged on its own.
scale and kill mean a bar was crossed and the test is waiting on a
person. keep_testing and not_enough_data mean it is not, yet.
The reason
One sentence, under 240 characters, that a teammate can check against the numbers. Quote the numbers that drove the verdict and the line they crossed or missed. Write rates as the desk's rule lines do: a percentage with at most one decimal and no trailing ".0" ("0.4%", "6%", "11.7%"). When one decimal would make a rate look equal to a line it did not equal, use two ("0.49%"). Write counts with commas ("1,204 sends").
not_enough_data: say what is missing: how far the sample is from its minimum ("312 of 400 sends"), or which numbers are not counted yet.keep_testing: say where the rate sits between the lines.- A split (rule 3): name both channels and both rates.
Plain English, active voice, no em dashes. Never promise results, never say the campaign was scaled or killed, and never name a prospect.
Examples
The campaigns and numbers below are illustrations, not real results. Every
example uses the house rules: {"minSends": 400, "minDials": 150, "positiveRate": {"kill": 0.005, "scale": 0.015}, "connectRate": {"kill": 0.05, "scale": 0.1}, "maxBounceRate": 0.04, "scaleMeetings": 2} and
bounce_min_sends 100.
Not enough data: counts the desk does not have yet
Input:
{
"campaign": { "name": "Example email and calls test", "channels": ["email", "call"] },
"rules": { "minSends": 400, "minDials": 150, "positiveRate": { "kill": 0.005, "scale": 0.015 }, "connectRate": { "kill": 0.05, "scale": 0.1 }, "maxBounceRate": 0.04, "scaleMeetings": 2 },
"bounce_min_sends": 100,
"numbers": { "sent": 612, "bounced": 11, "positive_replies": null, "dials": null, "connects": null, "meetings": null }
}
Answer:
{
"verdict": "not_enough_data",
"reason": "Bounce rate is fine at 1.8% after 612 sends, but positive replies, dials and meetings are not counted yet, so there is not enough data to judge.",
"checks": {
"positive_kill": { "status": "unknown", "observed": null },
"connect_kill": { "status": "unknown", "observed": null },
"bounce_kill": { "status": "not_crossed", "observed": "1.8% after 612 sends" },
"positive_scale": { "status": "unknown", "observed": null },
"connect_scale": { "status": "unknown", "observed": null },
"meetings_scale": { "status": "unknown", "observed": null }
}
}
Kill on bounce rate, whatever else
Input:
{
"campaign": { "name": "Example email test", "channels": ["email"] },
"rules": { "minSends": 400, "minDials": 150, "positiveRate": { "kill": 0.005, "scale": 0.015 }, "connectRate": { "kill": 0.05, "scale": 0.1 }, "maxBounceRate": 0.04, "scaleMeetings": 2 },
"bounce_min_sends": 100,
"numbers": { "sent": 450, "bounced": 27, "positive_replies": 9, "dials": null, "connects": null, "meetings": 2 }
}
Answer:
{
"verdict": "kill",
"reason": "Bounce rate is 6% after 450 sends, over the 4% kill line, which outweighs 2% positive reply and 2 meetings booked.",
"checks": {
"positive_kill": { "status": "not_crossed", "observed": "2% after 450 sends" },
"connect_kill": null,
"bounce_kill": { "status": "crossed", "observed": "6% after 450 sends" },
"positive_scale": { "status": "crossed", "observed": "2% after 450 sends" },
"connect_scale": null,
"meetings_scale": { "status": "crossed", "observed": "2 meetings" }
}
}
Scale on both channels
Input:
{
"campaign": { "name": "Example email and calls test", "channels": ["email", "call"] },
"rules": { "minSends": 400, "minDials": 150, "positiveRate": { "kill": 0.005, "scale": 0.015 }, "connectRate": { "kill": 0.05, "scale": 0.1 }, "maxBounceRate": 0.04, "scaleMeetings": 2 },
"bounce_min_sends": 100,
"numbers": { "sent": 480, "bounced": 8, "positive_replies": 9, "dials": 162, "connects": 19, "meetings": 1 }
}
Answer:
{
"verdict": "scale",
"reason": "1.9% positive reply after 480 sends and 11.7% connect rate after 162 dials both clear their scale lines of 1.5% and 10%.",
"checks": {
"positive_kill": { "status": "not_crossed", "observed": "1.9% after 480 sends" },
"connect_kill": { "status": "not_crossed", "observed": "11.7% after 162 dials" },
"bounce_kill": { "status": "not_crossed", "observed": "1.7% after 480 sends" },
"positive_scale": { "status": "crossed", "observed": "1.9% after 480 sends" },
"connect_scale": { "status": "crossed", "observed": "11.7% after 162 dials" },
"meetings_scale": { "status": "not_crossed", "observed": "1 meeting" }
}
}
Split: email scales, calls cross the kill line
Input:
{
"campaign": { "name": "Example email and calls test", "channels": ["email", "call"] },
"rules": { "minSends": 400, "minDials": 150, "positiveRate": { "kill": 0.005, "scale": 0.015 }, "connectRate": { "kill": 0.05, "scale": 0.1 }, "maxBounceRate": 0.04, "scaleMeetings": 2 },
"bounce_min_sends": 100,
"numbers": { "sent": 420, "bounced": 5, "positive_replies": 7, "dials": 155, "connects": 6, "meetings": 0 }
}
Answer:
{
"verdict": "scale",
"reason": "Scale on email at 1.7% positive reply after 420 sends, and stop the calls, which are at a 3.9% connect rate after 155 dials, under the 5% kill line.",
"checks": {
"positive_kill": { "status": "not_crossed", "observed": "1.7% after 420 sends" },
"connect_kill": { "status": "crossed", "observed": "3.9% after 155 dials" },
"bounce_kill": { "status": "not_crossed", "observed": "1.2% after 420 sends" },
"positive_scale": { "status": "crossed", "observed": "1.7% after 420 sends" },
"connect_scale": { "status": "not_crossed", "observed": "3.9% after 155 dials" },
"meetings_scale": { "status": "not_crossed", "observed": "0 meetings" }
}
}
Meetings clear the bar before the minimum
Input:
{
"campaign": { "name": "Example calls test", "channels": ["call"] },
"rules": { "minSends": 400, "minDials": 150, "positiveRate": { "kill": 0.005, "scale": 0.015 }, "connectRate": { "kill": 0.05, "scale": 0.1 }, "maxBounceRate": 0.04, "scaleMeetings": 2 },
"bounce_min_sends": 100,
"numbers": { "sent": null, "bounced": null, "positive_replies": null, "dials": 96, "connects": 7, "meetings": 2 }
}
Answer:
{
"verdict": "scale",
"reason": "2 meetings booked from the test batch clear the scale bar on their own, though calls have only 96 of 150 dials.",
"checks": {
"positive_kill": null,
"connect_kill": { "status": "under_minimum", "observed": "96 of 150 dials" },
"bounce_kill": null,
"positive_scale": null,
"connect_scale": { "status": "under_minimum", "observed": "96 of 150 dials" },
"meetings_scale": { "status": "crossed", "observed": "2 meetings" }
}
}
Keep testing: between the lines
Input:
{
"campaign": { "name": "Example email test", "channels": ["email"] },
"rules": { "minSends": 400, "minDials": 150, "positiveRate": { "kill": 0.005, "scale": 0.015 }, "connectRate": { "kill": 0.05, "scale": 0.1 }, "maxBounceRate": 0.04, "scaleMeetings": 2 },
"bounce_min_sends": 100,
"numbers": { "sent": 505, "bounced": 10, "positive_replies": 4, "dials": null, "connects": null, "meetings": 0 }
}
Answer:
{
"verdict": "keep_testing",
"reason": "0.8% positive reply after 505 sends sits between the 0.5% kill line and the 1.5% scale line, with no meetings yet.",
"checks": {
"positive_kill": { "status": "not_crossed", "observed": "0.8% after 505 sends" },
"connect_kill": null,
"bounce_kill": { "status": "not_crossed", "observed": "2% after 505 sends" },
"positive_scale": { "status": "not_crossed", "observed": "0.8% after 505 sends" },
"connect_scale": null,
"meetings_scale": { "status": "not_crossed", "observed": "0 meetings" }
}
}